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/listbook.h>
35#include <wx/clipbrd.h>
36#include <wx/aui/aui.h>
37#include <wx/progdlg.h>
38
39// #if defined(__OCPN__ANDROID__)
40// #include <GLES2/gl2.h>
41// #elif defined(__WXQT__) || defined(__WXGTK__)
42// #include <GL/glew.h>
43// #endif
44
45#include "config.h"
46
47#include <wx/listimpl.cpp>
48
49#include "chcanv.h"
50#include "TCWin.h"
51#include "geodesic.h"
52#include "styles.h"
53#include "routeman.h"
54#include "piano.h"
55#include "navutil.h"
56#include "navutil_base.h"
57#include "kml.h"
58#include "concanv.h"
59#include "thumbwin.h"
60#include "chartdb.h"
61#include "chartimg.h"
62#include "cutil.h"
63#include "MarkInfo.h"
64#include "nav_object_database.h"
65#include "RoutePropDlgImpl.h"
66#include "TrackPropDlg.h"
67#include "tcmgr.h"
68#include "own_ship.h"
69#include "routemanagerdialog.h"
70#include "route_point_gui.h"
71#include "pluginmanager.h"
72#include "ocpn_pixel.h"
73#include "ocpndc.h"
74#include "undo.h"
75#include "toolbar.h"
76#include "multiplexer.h"
77#include "timers.h"
78#include "tide_time.h"
79#include "glTextureDescriptor.h"
80#include "ChInfoWin.h"
81#include "Quilt.h"
82#include "select_item.h"
83#include "select.h"
84#include "SystemCmdSound.h"
85#include "FontMgr.h"
86#include "ais_decoder.h"
87#include "ais_target_data.h"
88#include "AISTargetAlertDialog.h"
89#include "SendToGpsDlg.h"
90#include "compass.h"
91#include "OCPNRegion.h"
92#include "gshhs.h"
93#include "canvasMenu.h"
94#include "wx28compat.h"
95#include "track.h"
96#include "track_gui.h"
97#include "route.h"
98#include "OCPN_AUIManager.h"
99#include "MUIBar.h"
100#include "CanvasConfig.h"
101#include "CanvasOptions.h"
102#include "mbtiles.h"
103#include "ocpn_frame.h"
104#include "idents.h"
105#include "conn_params.h"
106#include "route_gui.h"
107#include "line_clip.h"
108
109#ifdef __OCPN__ANDROID__
110#include "androidUTIL.h"
111#endif
112
113#ifdef ocpnUSE_GL
114#include "glChartCanvas.h"
115#endif
116
117#include "cm93.h" // for chart outline draw
118#include "s57chart.h" // for ArrayOfS57Obj
119#include "s52plib.h"
120#include "s52utils.h"
121
122#include "ais.h"
123
124#ifdef __MSVC__
125#define _CRTDBG_MAP_ALLOC
126#include <stdlib.h>
127#include <crtdbg.h>
128#define DEBUG_NEW new (_NORMAL_BLOCK, __FILE__, __LINE__)
129#define new DEBUG_NEW
130#endif
131
132#ifndef __WXMSW__
133#include <signal.h>
134#include <setjmp.h>
135
136#endif
137
138extern float g_ChartScaleFactorExp;
139extern float g_ShipScaleFactorExp;
140extern double g_mouse_zoom_sensitivity;
141
142#include <vector>
143//#include <wx-3.0/wx/aui/auibar.h>
144
145#if defined(__MSVC__) && (_MSC_VER < 1700)
146#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
147#endif
148
149// Define to enable the invocation of a temporary menubar by pressing the Alt
150// key. Not implemented for Windows XP, as it interferes with Alt-Tab
151// processing.
152#define OCPN_ALT_MENUBAR 1
153
154// Profiling support
155//#include "/usr/include/valgrind/callgrind.h"
156
157// ----------------------------------------------------------------------------
158// Useful Prototypes
159// ----------------------------------------------------------------------------
160extern bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
161extern void catch_signals(int signo);
162
163extern void AlphaBlending(ocpnDC &dc, int x, int y, int size_x, int size_y,
164 float radius, wxColour color,
165 unsigned char transparency);
166
167extern double g_ChartNotRenderScaleFactor;
168extern double gLat, gLon, gCog, gSog, gHdt;
169// extern double vLat, vLon;
170extern ChartDB *ChartData;
171extern bool bDBUpdateInProgress;
172extern ColorScheme global_color_scheme;
173extern int g_nbrightness;
174
175extern ConsoleCanvas *console;
176extern OCPNPlatform *g_Platform;
177
178extern RouteList *pRouteList;
179extern std::vector<Track*> g_TrackList;
180extern MyConfig *pConfig;
181extern Select *pSelect;
182extern Routeman *g_pRouteMan;
183extern ThumbWin *pthumbwin;
184extern TCMgr *ptcmgr;
185extern Select *pSelectTC;
186extern Select *pSelectAIS;
187extern WayPointman *pWayPointMan;
188extern MarkInfoDlg *g_pMarkInfoDialog;
189extern RoutePropDlgImpl *pRoutePropDialog;
190extern TrackPropDlg *pTrackPropDialog;
191extern ActiveTrack *g_pActiveTrack;
192
193extern RoutePoint *pAnchorWatchPoint1;
194extern RoutePoint *pAnchorWatchPoint2;
195extern double AnchorPointMinDist;
196extern bool AnchorAlertOn1;
197extern bool AnchorAlertOn2;
198extern int g_nAWMax;
199extern int g_iDistanceFormat;
200
201extern RouteManagerDialog *pRouteManagerDialog;
202extern GoToPositionDialog *pGoToPositionDialog;
203extern wxString GetLayerName(int id);
204extern wxString g_uploadConnection;
205extern bool g_bsimplifiedScalebar;
206
207extern bool bDrawCurrentValues;
208
209extern s52plib *ps52plib;
210
211extern bool bGPSValid;
212extern bool g_bTempShowMenuBar;
213extern bool g_bShowMenuBar;
214extern bool g_bShowCompassWin;
215
216extern AisDecoder *g_pAIS;
217extern bool g_bShowAreaNotices;
218extern int g_Show_Target_Name_Scale;
219extern bool g_bCPAWarn;
220extern bool g_bTCPA_Max;
221
222extern MyFrame *gFrame;
223
224extern int g_iNavAidRadarRingsNumberVisible;
225extern float g_fNavAidRadarRingsStep;
226extern int g_pNavAidRadarRingsStepUnits;
227extern bool g_bWayPointPreventDragging;
228extern bool g_bEnableZoomToCursor;
229extern bool g_bShowChartBar;
230extern bool g_bInlandEcdis;
231extern int g_ENCSoundingScaleFactor;
232extern int g_ENCTextScaleFactor;
233extern int g_maxzoomin;
234
235extern float g_GLMinSymbolLineWidth;
236bool g_bAllowShipToActive;
237bool g_bShowShipToActive;
238int g_shipToActiveStyle;
239int g_shipToActiveColor;
240
241extern AISTargetQueryDialog *g_pais_query_dialog_active;
242extern int g_ais_query_dialog_x, g_ais_query_dialog_y;
243
244extern int g_S57_dialog_sx, g_S57_dialog_sy;
245
246extern PopUpDSlide *pPopupDetailSlider;
247extern int g_detailslider_dialog_x, g_detailslider_dialog_y;
248
249extern bool g_b_overzoom_x; // Allow high overzoom
250extern double g_plus_minus_zoom_factor;
251
252extern int g_OwnShipIconType;
253extern double g_n_ownship_length_meters;
254extern double g_n_ownship_beam_meters;
255extern double g_n_gps_antenna_offset_y;
256extern double g_n_gps_antenna_offset_x;
257extern int g_n_ownship_min_mm;
258
259extern double g_COGAvg; // only needed for debug....
260
261extern int g_click_stop;
262extern double g_ownship_predictor_minutes;
263extern double g_ownship_HDTpredictor_miles;
264
265extern bool g_bquiting;
266extern AISTargetListDialog *g_pAISTargetList;
267extern wxString g_sAIS_Alert_Sound_File;
268extern wxString g_anchorwatch_sound_file;
269
270extern PlugInManager *g_pi_manager;
271
272extern OCPN_AUIManager *g_pauimgr;
273
274extern bool g_bopengl;
275extern bool g_bdisable_opengl;
276
277extern bool g_bFullScreenQuilt;
278
279extern bool g_bsmoothpanzoom;
280
281bool g_bDebugOGL;
282
283extern bool g_b_assume_azerty;
284
285extern ChartGroupArray *g_pGroupArray;
286extern wxString g_default_routepoint_icon;
287
288extern S57QueryDialog *g_pObjectQueryDialog;
289extern ocpnStyle::StyleManager *g_StyleManager;
290
291extern OcpnSound *g_anchorwatch_sound;
292
293extern bool g_bShowTrue, g_bShowMag;
294extern bool g_btouch;
295extern bool g_bresponsive;
296extern int g_chart_zoom_modifier_raster;
297extern int g_chart_zoom_modifier_vector;
298extern int g_ChartScaleFactor;
299
300
301#ifdef ocpnUSE_GL
302#endif
303
304extern bool g_bShowFPS;
305extern double g_gl_ms_per_frame;
306extern bool g_benable_rotate;
307extern bool g_bRollover;
308
309extern bool g_bSpaceDropMark;
310extern bool g_bAutoHideToolbar;
311extern int g_nAutoHideToolbar;
312extern bool g_bDeferredInitDone;
313
314extern wxString g_CmdSoundString;
315extern bool g_boptionsactive;
316
317// TODO why are these static?
318static int mouse_x;
319static int mouse_y;
320static bool mouse_leftisdown;
321
322bool g_brouteCreating;
323
324bool g_bShowTrackPointTime;
325
326int r_gamma_mult;
327int g_gamma_mult;
328int b_gamma_mult;
329int gamma_state;
330bool g_brightness_init;
331int last_brightness;
332
333int g_cog_predictor_width;
334extern double g_display_size_mm;
335
336// extern bool g_bshowToolbar;
337extern ocpnFloatingToolbarDialog *g_MainToolbar;
338extern wxColour g_colourOwnshipRangeRingsColour;
339
340// LIVE ETA OPTION
341bool g_bShowLiveETA;
342double g_defaultBoatSpeed;
343double g_defaultBoatSpeedUserUnit;
344
345extern int g_nAIS_activity_timer;
346extern bool g_bskew_comp;
347extern float g_compass_scalefactor;
348extern int g_COGAvgSec; // COG average period (sec.) for Course Up Mode
349
350wxGLContext *g_pGLcontext; // shared common context
351
352extern bool g_useMUI;
353extern unsigned int g_canvasConfig;
354extern wxString g_lastPluginMessage;
355
356extern ChartCanvas *g_focusCanvas;
357extern ChartCanvas *g_overlayCanvas;
358
359extern float g_toolbar_scalefactor;
360extern SENCThreadManager *g_SencThreadManager;
361
362wxString g_ObjQFileExt;
363
364// "Curtain" mode parameters
365wxDialog *g_pcurtain;
366extern double gLat, gLat;
367
368extern int g_GUIScaleFactor;
369// Win DPI scale factor
370double g_scaler;
371
372#define MIN_BRIGHT 10
373#define MAX_BRIGHT 100
374
375//------------------------------------------------------------------------------
376// ChartCanvas Implementation
377//------------------------------------------------------------------------------
378BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
379EVT_PAINT(ChartCanvas::OnPaint)
380EVT_ACTIVATE(ChartCanvas::OnActivate)
381EVT_SIZE(ChartCanvas::OnSize)
382#ifndef HAVE_WX_GESTURE_EVENTS
383EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
384#endif
385EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
386EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
387EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
388EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
389EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
390EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
391EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
392EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
393EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
394EVT_KEY_UP(ChartCanvas::OnKeyUp)
395EVT_CHAR(ChartCanvas::OnKeyChar)
396EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
397EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
398EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
399EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
400EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
401
402END_EVENT_TABLE()
403
404// Define a constructor for my canvas
405ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex)
406 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER) {
407 parent_frame = (MyFrame *)frame; // save a pointer to parent
408 m_canvasIndex = canvasIndex;
409
410 pscratch_bm = NULL;
411
412 SetBackgroundColour(wxColour(0, 0, 0));
413 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
414 // color scheme change
415
416 m_groupIndex = 0;
417 m_bDrawingRoute = false;
418 m_bRouteEditing = false;
419 m_bMarkEditing = false;
420 m_bRoutePoinDragging = false;
421 m_bIsInRadius = false;
422 m_bMayToggleMenuBar = true;
423
424 m_bFollow = false;
425 m_bShowNavobjects = true;
426 m_bTCupdate = false;
427 m_bAppendingRoute = false; // was true in MSW, why??
428 pThumbDIBShow = NULL;
429 m_bShowCurrent = false;
430 m_bShowTide = false;
431 bShowingCurrent = false;
432 pCwin = NULL;
433 warp_flag = false;
434 m_bzooming = false;
435 m_b_paint_enable = true;
436 m_routeState = 0;
437
438 pss_overlay_bmp = NULL;
439 pss_overlay_mask = NULL;
440 m_bChartDragging = false;
441 m_bMeasure_Active = false;
442 m_bMeasure_DistCircle = false;
443 m_pMeasureRoute = NULL;
444 m_pTrackRolloverWin = NULL;
445 m_pRouteRolloverWin = NULL;
446 m_pAISRolloverWin = NULL;
447 m_bedge_pan = false;
448 m_disable_edge_pan = false;
449 m_dragoffsetSet = false;
450 m_bautofind = false;
451 m_bFirstAuto = true;
452 m_groupIndex = 0;
453 m_singleChart = NULL;
454 m_upMode = NORTH_UP_MODE;
455 m_bShowAIS = true;
456 m_bShowAISScaled = false;
457
458 m_vLat = 0.;
459 m_vLon = 0.;
460
461 m_pCIWin = NULL;
462
463 m_pSelectedRoute = NULL;
464 m_pSelectedTrack = NULL;
465 m_pRoutePointEditTarget = NULL;
466 m_pFoundPoint = NULL;
467 m_pMouseRoute = NULL;
468 m_prev_pMousePoint = NULL;
469 m_pEditRouteArray = NULL;
470 m_pFoundRoutePoint = NULL;
471 m_FinishRouteOnKillFocus = true;
472
473 m_pRolloverRouteSeg = NULL;
474 m_pRolloverTrackSeg = NULL;
475 m_bsectors_shown = false;
476
477 m_bbrightdir = false;
478 r_gamma_mult = 1;
479 g_gamma_mult = 1;
480 b_gamma_mult = 1;
481
482 m_pos_image_user_day = NULL;
483 m_pos_image_user_dusk = NULL;
484 m_pos_image_user_night = NULL;
485 m_pos_image_user_grey_day = NULL;
486 m_pos_image_user_grey_dusk = NULL;
487 m_pos_image_user_grey_night = NULL;
488
489 m_zoom_factor = 1;
490 m_rotation_speed = 0;
491 m_mustmove = 0;
492
493 m_OSoffsetx = 0.;
494 m_OSoffsety = 0.;
495
496 m_pos_image_user_yellow_day = NULL;
497 m_pos_image_user_yellow_dusk = NULL;
498 m_pos_image_user_yellow_night = NULL;
499
500 SetOwnShipState(SHIP_INVALID);
501
502 undo = new Undo(this);
503
504 VPoint.Invalidate();
505
506 m_glcc = NULL;
507
508 m_toolBar = NULL;
509 m_toolbar_scalefactor = 1.0;
510 m_toolbarOrientation = wxTB_HORIZONTAL;
511 m_focus_indicator_pix = 1;
512
513 m_pCurrentStack = NULL;
514 m_bpersistent_quilt = false;
515 m_piano_ctx_menu = NULL;
516 m_Compass = NULL;
517
518 g_ChartNotRenderScaleFactor = 2.0;
519 m_bShowScaleInStatusBar = true;
520
521 m_muiBar = NULL;
522 m_bShowScaleInStatusBar = false;
523
524 m_bShowOutlines = false;
525 m_bDisplayGrid = false;
526 m_bShowDepthUnits = true;
527 m_encDisplayCategory = (int)STANDARD;
528
529 m_encShowLights = true;
530 m_encShowAnchor = true;
531 m_encShowDataQual = false;
532 m_bShowGPS = true;
533 m_pQuilt = new Quilt(this);
534 SetQuiltMode(true);
535 SetAlertString(_T(""));
536 m_sector_glat = 0;
537 m_sector_glon = 0;
538
539#ifdef HAVE_WX_GESTURE_EVENTS
540 m_oldVPSScale = -1.0;
541 m_popupWanted = false;
542 m_leftdown = false;
543#endif /* HAVE_WX_GESTURE_EVENTS */
544
545 SetupGlCanvas();
546 /*
547 #ifdef ocpnUSE_GL
548 if ( !g_bdisable_opengl )
549 {
550 if(g_bopengl){
551 wxLogMessage( _T("Creating glChartCanvas") );
552 m_glcc = new glChartCanvas(this);
553
554 // We use one context for all GL windows, so that textures etc will be
555 automatically shared if(IsPrimaryCanvas()){ wxGLContext *pctx = new
556 wxGLContext(m_glcc); m_glcc->SetContext(pctx); g_pGLcontext = pctx; // Save a
557 copy of the common context
558 }
559 else{
560 #ifdef __WXOSX__
561 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
562 #else
563 m_glcc->SetContext(g_pGLcontext); // If not primary canvas,
564 use the saved common context #endif
565 }
566 }
567 }
568 #endif
569 */
570 singleClickEventIsValid = false;
571
572 // Build the cursors
573
574 pCursorLeft = NULL;
575 pCursorRight = NULL;
576 pCursorUp = NULL;
577 pCursorDown = NULL;
578 pCursorArrow = NULL;
579 pCursorPencil = NULL;
580 pCursorCross = NULL;
581
582 RebuildCursors();
583
584 SetCursor(*pCursorArrow);
585
586 pPanTimer = new wxTimer(this, m_MouseDragging);
587 pPanTimer->Stop();
588
589 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
590 pMovementTimer->Stop();
591
592 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
593 pMovementStopTimer->Stop();
594
595 pRotDefTimer = new wxTimer(this, ROT_TIMER);
596 pRotDefTimer->Stop();
597
598 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
599 m_DoubleClickTimer->Stop();
600
601 m_panx = m_pany = 0;
602 m_panspeed = 0;
603
604 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
605 pCurTrackTimer->Stop();
606 m_curtrack_timer_msec = 10;
607
608 m_wheelzoom_stop_oneshot = 0;
609 m_last_wheel_dir = 0;
610
611 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
612
613 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
614
615 m_rollover_popup_timer_msec = 20;
616
617 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
618
619 m_b_rot_hidef = true;
620
621 proute_bm = NULL;
622 m_prot_bm = NULL;
623
624 m_upMode = NORTH_UP_MODE;
625 m_bLookAhead = false;
626 m_VPRotate = 0;
627
628 // Set some benign initial values
629
630 m_cs = GLOBAL_COLOR_SCHEME_DAY;
631 VPoint.clat = 0;
632 VPoint.clon = 0;
633 VPoint.view_scale_ppm = 1;
634 VPoint.Invalidate();
635
636 m_canvas_scale_factor = 1.;
637
638 m_canvas_width = 1000;
639
640 m_overzoomTextWidth = 0;
641 m_overzoomTextHeight = 0;
642
643 // Create the default world chart
644 pWorldBackgroundChart = new GSHHSChart;
645
646 // Create the default depth unit emboss maps
647 m_pEM_Feet = NULL;
648 m_pEM_Meters = NULL;
649 m_pEM_Fathoms = NULL;
650
651 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
652
653 m_pEM_OverZoom = NULL;
654 SetOverzoomFont();
655 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
656
657 // Build icons for tide/current points
658 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
659 m_bmTideDay = style->GetIconScaled(_T("tidesml"), 1. / g_Platform->GetDisplayDIPMult(this));
660
661 // Dusk
662 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
663
664 // Night
665 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
666
667 // Build Dusk/Night ownship icons
668 double factor_dusk = 0.5;
669 double factor_night = 0.25;
670
671 // Red
672 m_os_image_red_day = style->GetIcon(_T("ship-red")).ConvertToImage();
673
674 int rimg_width = m_os_image_red_day.GetWidth();
675 int rimg_height = m_os_image_red_day.GetHeight();
676
677 m_os_image_red_dusk = m_os_image_red_day.Copy();
678 m_os_image_red_night = m_os_image_red_day.Copy();
679
680 for (int iy = 0; iy < rimg_height; iy++) {
681 for (int ix = 0; ix < rimg_width; ix++) {
682 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
683 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
684 m_os_image_red_day.GetGreen(ix, iy),
685 m_os_image_red_day.GetBlue(ix, iy));
686 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
687 hsv.value = hsv.value * factor_dusk;
688 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
689 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
690
691 hsv = wxImage::RGBtoHSV(rgb);
692 hsv.value = hsv.value * factor_night;
693 nrgb = wxImage::HSVtoRGB(hsv);
694 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
695 }
696 }
697 }
698
699 // Grey
700 m_os_image_grey_day =
701 style->GetIcon(_T("ship-red")).ConvertToImage().ConvertToGreyscale();
702
703 int gimg_width = m_os_image_grey_day.GetWidth();
704 int gimg_height = m_os_image_grey_day.GetHeight();
705
706 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
707 m_os_image_grey_night = m_os_image_grey_day.Copy();
708
709 for (int iy = 0; iy < gimg_height; iy++) {
710 for (int ix = 0; ix < gimg_width; ix++) {
711 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
712 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
713 m_os_image_grey_day.GetGreen(ix, iy),
714 m_os_image_grey_day.GetBlue(ix, iy));
715 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
716 hsv.value = hsv.value * factor_dusk;
717 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
718 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
719
720 hsv = wxImage::RGBtoHSV(rgb);
721 hsv.value = hsv.value * factor_night;
722 nrgb = wxImage::HSVtoRGB(hsv);
723 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
724 }
725 }
726 }
727
728 // Yellow
729 m_os_image_yellow_day = m_os_image_red_day.Copy();
730
731 gimg_width = m_os_image_yellow_day.GetWidth();
732 gimg_height = m_os_image_yellow_day.GetHeight();
733
734 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
735 m_os_image_yellow_night = m_os_image_red_day.Copy();
736
737 for (int iy = 0; iy < gimg_height; iy++) {
738 for (int ix = 0; ix < gimg_width; ix++) {
739 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
740 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
741 m_os_image_yellow_day.GetGreen(ix, iy),
742 m_os_image_yellow_day.GetBlue(ix, iy));
743 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
744 hsv.hue += 60. / 360.; // shift to yellow
745 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
746 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
747
748 hsv = wxImage::RGBtoHSV(rgb);
749 hsv.value = hsv.value * factor_dusk;
750 hsv.hue += 60. / 360.; // shift to yellow
751 nrgb = wxImage::HSVtoRGB(hsv);
752 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
753
754 hsv = wxImage::RGBtoHSV(rgb);
755 hsv.hue += 60. / 360.; // shift to yellow
756 hsv.value = hsv.value * factor_night;
757 nrgb = wxImage::HSVtoRGB(hsv);
758 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
759 }
760 }
761 }
762
763 // Set initial pointers to ownship images
764 m_pos_image_red = &m_os_image_red_day;
765 m_pos_image_yellow = &m_os_image_yellow_day;
766 m_pos_image_grey = &m_os_image_grey_day;
767
768 SetUserOwnship();
769
770 m_pBrightPopup = NULL;
771
772#ifdef ocpnUSE_GL
773 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
774#endif
775
776 int gridFontSize = 8;
777#if defined(__WXOSX__) || defined(__WXGTK3__)
778 // Support scaled HDPI displays.
779 gridFontSize *= GetContentScaleFactor();
780#endif
781
782 m_pgridFont = FontMgr::Get().FindOrCreateFont(
783 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, FALSE,
784 wxString(_T ( "Arial" )));
785
786 m_Piano = new Piano(this);
787
788 m_bShowCompassWin = g_bShowCompassWin;
789
790 m_Compass = new ocpnCompass(this);
791 m_Compass->SetScaleFactor(g_compass_scalefactor);
792 m_Compass->Show(m_bShowCompassWin);
793
794 m_bToolbarEnable = false;
795 m_pianoFrozen = false;
796
797 SetMinSize(wxSize(200, 200));
798
799 m_displayScale = 1.0;
800#if defined(__WXOSX__) || defined(__WXGTK3__)
801 // Support scaled HDPI displays.
802 m_displayScale = GetContentScaleFactor();
803#endif
804
805
806#ifdef HAVE_WX_GESTURE_EVENTS
807 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE |
808 wxTOUCH_PRESS_GESTURES)) {
809 wxLogError("Failed to enable touch events");
810 }
811
812 Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
813
814 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
815 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
816
817 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
818 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
819
820 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
821 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
822
823 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
824 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
825#endif
826}
827
828ChartCanvas::~ChartCanvas() {
829 delete pThumbDIBShow;
830
831 // Delete Cursors
832 delete pCursorLeft;
833 delete pCursorRight;
834 delete pCursorUp;
835 delete pCursorDown;
836 delete pCursorArrow;
837 delete pCursorPencil;
838 delete pCursorCross;
839
840 delete pPanTimer;
841 delete pMovementTimer;
842 delete pMovementStopTimer;
843 delete pCurTrackTimer;
844 delete pRotDefTimer;
845 delete m_DoubleClickTimer;
846
847 delete m_pTrackRolloverWin;
848 delete m_pRouteRolloverWin;
849 delete m_pAISRolloverWin;
850 delete m_pBrightPopup;
851
852 delete m_pCIWin;
853
854 delete pscratch_bm;
855
856 m_dc_route.SelectObject(wxNullBitmap);
857 delete proute_bm;
858
859 delete pWorldBackgroundChart;
860 delete pss_overlay_bmp;
861
862 delete m_pEM_Feet;
863 delete m_pEM_Meters;
864 delete m_pEM_Fathoms;
865
866 delete m_pEM_OverZoom;
867 // delete m_pEM_CM93Offset;
868
869 delete m_prot_bm;
870
871 delete m_pos_image_user_day;
872 delete m_pos_image_user_dusk;
873 delete m_pos_image_user_night;
874 delete m_pos_image_user_grey_day;
875 delete m_pos_image_user_grey_dusk;
876 delete m_pos_image_user_grey_night;
877 delete m_pos_image_user_yellow_day;
878 delete m_pos_image_user_yellow_dusk;
879 delete m_pos_image_user_yellow_night;
880
881 delete undo;
882#ifdef ocpnUSE_GL
883 if (!g_bdisable_opengl) {
884 delete m_glcc;
885
886#if wxCHECK_VERSION(2, 9, 0)
887 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
888#endif
889 }
890#endif
891
892 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
893 // wx tries to deliver events to this canvas during destroy.
894 MUIBar *muiBar = m_muiBar;
895 m_muiBar = 0;
896 delete muiBar;
897 delete m_pQuilt;
898}
899
900void ChartCanvas::RebuildCursors() {
901 delete pCursorLeft;
902 delete pCursorRight;
903 delete pCursorUp;
904 delete pCursorDown;
905 delete pCursorArrow;
906 delete pCursorPencil;
907 delete pCursorCross;
908
909 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
910 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
911
912 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
913
914 wxImage ICursorLeft = style->GetIcon(_T("left")).ConvertToImage();
915 wxImage ICursorRight = style->GetIcon(_T("right")).ConvertToImage();
916 wxImage ICursorUp = style->GetIcon(_T("up")).ConvertToImage();
917 wxImage ICursorDown = style->GetIcon(_T("down")).ConvertToImage();
918 wxImage ICursorPencil = style->GetIconScaled(_T("pencil"), pencilScale).ConvertToImage();
919 wxImage ICursorCross = style->GetIcon(_T("cross")).ConvertToImage();
920
921#if !defined(__WXMSW__) && !defined(__WXQT__)
922 ICursorLeft.ConvertAlphaToMask(128);
923 ICursorRight.ConvertAlphaToMask(128);
924 ICursorUp.ConvertAlphaToMask(128);
925 ICursorDown.ConvertAlphaToMask(128);
926 ICursorPencil.ConvertAlphaToMask(10);
927 ICursorCross.ConvertAlphaToMask(10);
928#endif
929
930 if (ICursorLeft.Ok()) {
931 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
932 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
933 pCursorLeft = new wxCursor(ICursorLeft);
934 } else
935 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
936
937 if (ICursorRight.Ok()) {
938 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
939 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
940 pCursorRight = new wxCursor(ICursorRight);
941 } else
942 pCursorRight = new wxCursor(wxCURSOR_ARROW);
943
944 if (ICursorUp.Ok()) {
945 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
946 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
947 pCursorUp = new wxCursor(ICursorUp);
948 } else
949 pCursorUp = new wxCursor(wxCURSOR_ARROW);
950
951 if (ICursorDown.Ok()) {
952 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
953 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
954 pCursorDown = new wxCursor(ICursorDown);
955 } else
956 pCursorDown = new wxCursor(wxCURSOR_ARROW);
957
958 if (ICursorPencil.Ok()) {
959 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
960 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
961 pCursorPencil = new wxCursor(ICursorPencil);
962 } else
963 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
964
965 if (ICursorCross.Ok()) {
966 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
967 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
968 pCursorCross = new wxCursor(ICursorCross);
969 } else
970 pCursorCross = new wxCursor(wxCURSOR_ARROW);
971
972 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
973 pPlugIn_Cursor = NULL;
974}
975
976void ChartCanvas::CanvasApplyLocale() {
977 CreateDepthUnitEmbossMaps(m_cs);
978 CreateOZEmbossMapData(m_cs);
979}
980
981void ChartCanvas::SetupGlCanvas() {
982#ifndef __OCPN__ANDROID__
983#ifdef ocpnUSE_GL
984 if (!g_bdisable_opengl) {
985 if (g_bopengl) {
986 wxLogMessage(_T("Creating glChartCanvas"));
987 m_glcc = new glChartCanvas(this);
988
989 // We use one context for all GL windows, so that textures etc will be
990 // automatically shared
991 if (IsPrimaryCanvas()) {
992 // qDebug() << "Creating Primary Context";
993
994 // wxGLContextAttrs ctxAttr;
995 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
996 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
997 // NULL, &ctxAttr);
998 wxGLContext *pctx = new wxGLContext(m_glcc);
999 m_glcc->SetContext(pctx);
1000 g_pGLcontext = pctx; // Save a copy of the common context
1001 } else {
1002#ifdef __WXOSX__
1003 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
1004#else
1005 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
1006 // saved common context
1007#endif
1008 }
1009 }
1010 }
1011#endif
1012#endif
1013
1014#ifdef __OCPN__ANDROID__ // ocpnUSE_GL
1015 if (!g_bdisable_opengl) {
1016 if (g_bopengl) {
1017 // qDebug() << "SetupGlCanvas";
1018 wxLogMessage(_T("Creating glChartCanvas"));
1019
1020 // We use one context for all GL windows, so that textures etc will be
1021 // automatically shared
1022 if (IsPrimaryCanvas()) {
1023 qDebug() << "Creating Primary glChartCanvas";
1024
1025 // wxGLContextAttrs ctxAttr;
1026 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1027 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1028 // NULL, &ctxAttr);
1029 m_glcc = new glChartCanvas(this);
1030
1031 wxGLContext *pctx = new wxGLContext(m_glcc);
1032 m_glcc->SetContext(pctx);
1033 g_pGLcontext = pctx; // Save a copy of the common context
1034 m_glcc->m_pParentCanvas = this;
1035 // m_glcc->Reparent(this);
1036 } else {
1037 qDebug() << "Creating Secondary glChartCanvas";
1038 // QGLContext *pctx =
1039 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
1040 // << "pctx: " << pctx;
1041
1042 m_glcc = new glChartCanvas(
1043 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
1044 // m_glcc = new glChartCanvas(this, pctx); //Shared
1045 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
1046 wxGLContext *pwxctx = new wxGLContext(m_glcc);
1047 m_glcc->SetContext(pwxctx);
1048 m_glcc->m_pParentCanvas = this;
1049 // m_glcc->Reparent(this);
1050 }
1051 }
1052 }
1053#endif
1054}
1055
1056void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
1057 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1058
1059 // On Android, we get a KillFocus on just about every keystroke.
1060 // Why?
1061#ifdef __OCPN__ANDROID__
1062 return;
1063#endif
1064
1065 // Special logic:
1066 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
1067 // canvas focus. Why??? Who knows... So, we provide for this case by
1068 // starting a timer if required to actually Finish() a route on a legitimate
1069 // focus change, but not if the focus is quickly regained ( <20 msec.) on
1070 // this canvas.
1071#ifdef __WXOSX__
1072 if (m_routeState && m_FinishRouteOnKillFocus)
1073 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
1074#else
1075 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1076#endif
1077}
1078
1079void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
1080 m_routeFinishTimer.Stop();
1081
1082 // Try to keep the global top-line menubar selections up to date with the
1083 // current "focus" canvas
1084 gFrame->UpdateGlobalMenuItems(this);
1085
1086 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1087}
1088
1089void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
1090 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1091}
1092
1093#ifdef HAVE_WX_GESTURE_EVENTS
1094void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
1095 /* we defer the popup menu call upon the leftup event
1096 else the menu disappears immediately,
1097 (see
1098 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
1099 */
1100 m_popupWanted = true;
1101}
1102
1103void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
1104 // not implemented yet
1105}
1106
1107void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1108
1109void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1110
1111void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1112 wxPoint pos = event.GetPosition();
1113
1114 m_leftdown = false;
1115
1116 if (!m_popupWanted) {
1117 wxMouseEvent ev(wxEVT_LEFT_UP);
1118 ev.m_x = pos.x;
1119 ev.m_y = pos.y;
1120 MouseEvent(ev);
1121 return;
1122 }
1123
1124 m_popupWanted = false;
1125
1126 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1127 ev.m_x = pos.x;
1128 ev.m_y = pos.y;
1129
1130 MouseEvent(ev);
1131}
1132
1133void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1134 m_leftdown = true;
1135
1136 wxPoint pos = event.GetPosition();
1137 MouseEvent(event);
1138}
1139
1140void ChartCanvas::OnMotion(wxMouseEvent &event) {
1141 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1142 dragging, upon simple click, and without the OnLeftDown event before Thus,
1143 this consists in skiping it, and setting the leftdown bit according to a
1144 status that we trust */
1145 event.m_leftDown = m_leftdown;
1146 MouseEvent(event);
1147}
1148
1149void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1150 /* there are spurious end zoom events upon right-click */
1151 if (event.IsGestureEnd()) return;
1152
1153 double factor = event.GetZoomFactor();
1154
1155 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1156 m_oldVPSScale = GetVPScale();
1157 }
1158
1159 double current_vps = GetVPScale();
1160 double wanted_factor = m_oldVPSScale / current_vps * factor;
1161
1162 ZoomCanvas(wanted_factor, true, false);
1163
1164 // Allow combined zoom/pan operation
1165 if (event.IsGestureStart()) {
1166 m_zoomStartPoint = event.GetPosition();
1167 } else {
1168 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1169 PanCanvas(-delta.x, -delta.y);
1170 m_zoomStartPoint = event.GetPosition();
1171 }
1172}
1173
1174void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1175
1176void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1177 DoRotateCanvas(0.0);
1178}
1179#endif /* HAVE_WX_GESTURE_EVENTS */
1180
1181void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1182 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1183 m_vLat = pcc->iLat;
1184 m_vLon = pcc->iLon;
1185
1186 m_restore_dbindex = pcc->DBindex;
1187 m_bFollow = pcc->bFollow;
1188 if (pcc->GroupID < 0) pcc->GroupID = 0;
1189
1190 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1191 m_groupIndex = 0;
1192 else
1193 m_groupIndex = pcc->GroupID;
1194
1195 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1196
1197 ShowTides(pcc->bShowTides);
1198 ShowCurrents(pcc->bShowCurrents);
1199
1200 SetShowDepthUnits(pcc->bShowDepthUnits);
1201 SetShowGrid(pcc->bShowGrid);
1202 SetShowOutlines(pcc->bShowOutlines);
1203
1204 SetShowAIS(pcc->bShowAIS);
1205 SetAttenAIS(pcc->bAttenAIS);
1206
1207 // ENC options
1208 SetShowENCText(pcc->bShowENCText);
1209 m_encDisplayCategory = pcc->nENCDisplayCategory;
1210 m_encShowDepth = pcc->bShowENCDepths;
1211 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1212 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1213 m_encShowLights = pcc->bShowENCLights;
1214 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1215 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1216
1217 bool courseUp = pcc->bCourseUp;
1218 bool headUp = pcc->bHeadUp;
1219 m_upMode = NORTH_UP_MODE;
1220 if (courseUp)
1221 m_upMode = COURSE_UP_MODE;
1222 else if (headUp)
1223 m_upMode = HEAD_UP_MODE;
1224
1225 m_bLookAhead = pcc->bLookahead;
1226
1227 m_singleChart = NULL;
1228}
1229
1230void ChartCanvas::ApplyGlobalSettings() {
1231 // GPS compas window
1232 m_bShowCompassWin = g_bShowCompassWin;
1233 if (m_Compass) {
1234 m_Compass->Show(m_bShowCompassWin);
1235 if (m_bShowCompassWin) m_Compass->UpdateStatus();
1236 }
1237}
1238
1239void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1240 bool groupOK = CheckGroup(m_groupIndex);
1241
1242 if (!groupOK) {
1243 SetGroupIndex(m_groupIndex, true);
1244 }
1245}
1246
1247void ChartCanvas::SetShowGPS(bool bshow) {
1248 if (m_bShowGPS != bshow) {
1249 delete m_Compass;
1250 m_Compass = new ocpnCompass(this, bshow);
1251 m_Compass->SetScaleFactor(g_compass_scalefactor);
1252 m_Compass->Show(m_bShowCompassWin);
1253 }
1254 m_bShowGPS = bshow;
1255}
1256
1257void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1258 if (m_Compass) {
1259 m_Compass->Show(m_bShowCompassWin);
1260 if (m_bShowCompassWin) m_Compass->UpdateStatus();
1261 }
1262}
1263
1264void ChartCanvas::SetToolbarEnable(bool bShow) {
1265 return;
1266
1267 // if(GetToolbarEnable() != bShow)
1268 {
1269 m_bToolbarEnable = bShow;
1270 // if(!m_toolBar)
1271 // RequestNewCanvasToolbar( true );
1272 if (GetToolbar()) GetToolbar()->Show(bShow);
1273 }
1274}
1275
1276int ChartCanvas::GetPianoHeight() {
1277 int height = 0;
1278 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1279
1280 return height;
1281}
1282
1283void ChartCanvas::ConfigureChartBar() {
1284 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1285
1286 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
1287 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
1288
1289 if (GetQuiltMode()) {
1290 m_Piano->SetRoundedRectangles(true);
1291 }
1292 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
1293 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon(_T("polyprj"))));
1294 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
1295}
1296
1297void ChartCanvas::ShowTides(bool bShow) {
1298 gFrame->LoadHarmonics();
1299
1300 if (ptcmgr->IsReady()) {
1301 SetbShowTide(bShow);
1302 if (m_toolBar) m_toolBar->GetToolbar()->ToggleTool(ID_TIDE, bShow);
1303 wxString tip = _("Show Tides");
1304 if (bShow) tip = _("Hide Tides");
1305 if (m_toolBar) m_toolBar->SetToolShortHelp(ID_TIDE, tip);
1306
1307 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1308 } else {
1309 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1310 SetbShowTide(false);
1311 if (m_toolBar) m_toolBar->GetToolbar()->ToggleTool(ID_TIDE, false);
1312 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1313 }
1314
1315 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1316 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1317
1318 // TODO
1319 // if( GetbShowTide() ) {
1320 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1321 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1322 // update
1323 // } else
1324 // FrameTCTimer.Stop();
1325}
1326
1327void ChartCanvas::ShowCurrents(bool bShow) {
1328 gFrame->LoadHarmonics();
1329
1330 if (ptcmgr->IsReady()) {
1331 SetbShowCurrent(bShow);
1332 if (m_toolBar) m_toolBar->GetToolbar()->ToggleTool(ID_CURRENT, bShow);
1333 wxString tip = _("Show Currents");
1334 if (bShow) tip = _("Hide Currents");
1335 if (m_toolBar) m_toolBar->SetToolShortHelp(ID_CURRENT, tip);
1336
1337 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1338 } else {
1339 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1340 SetbShowCurrent(false);
1341 if (m_toolBar) m_toolBar->GetToolbar()->ToggleTool(ID_CURRENT, false);
1342 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1343 }
1344
1345 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1346 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1347
1348 // TODO
1349 // if( GetbShowCurrent() ) {
1350 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1351 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1352 // update
1353 // } else
1354 // FrameTCTimer.Stop();
1355}
1356
1357// TODO
1358extern bool g_bPreserveScaleOnX;
1359extern ChartDummy *pDummyChart;
1360extern int g_sticky_chart;
1361
1362void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1363
1364void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1365 SetAlertString(_T(""));
1366
1367 int new_index = index;
1368 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1369
1370 bool bgroup_override = false;
1371 int old_group_index = new_index;
1372
1373 if (!CheckGroup(new_index)) {
1374 new_index = 0;
1375 bgroup_override = true;
1376 }
1377
1378 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1379 new_index = index;
1380
1381 // Get the currently displayed chart native scale, and the current ViewPort
1382 int current_chart_native_scale = GetCanvasChartNativeScale();
1383 ViewPort vp = GetVP();
1384
1385 m_groupIndex = new_index;
1386
1387 // Are there ENCs in this group
1388 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1389
1390 // Update the MUIBar for ENC availability
1391 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1392
1393 // Allow the chart database to pre-calculate the MBTile inclusion test
1394 // boolean...
1395 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1396
1397 // Invalidate the "sticky" chart on group change, since it might not be in
1398 // the new group
1399 g_sticky_chart = -1;
1400
1401 // We need a chartstack and quilt to figure out which chart to open in the
1402 // new group
1403 UpdateCanvasOnGroupChange();
1404
1405 int dbi_now = -1;
1406 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1407
1408 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1409
1410 // If a new reference chart is indicated, set a good scale for it.
1411 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1412 double best_scale = GetBestStartScale(dbi_hint, vp);
1413 SetVPScale(best_scale);
1414 }
1415
1416 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1417
1418 // Refresh the canvas, selecting the "best" chart,
1419 // applying the prior ViewPort exactly
1420 canvasChartsRefresh(dbi_hint);
1421
1422 if (!autoSwitch && bgroup_override) {
1423 // show a short timed message box
1424 wxString msg(_("Group \""));
1425
1426 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1427 msg += pGroup->m_group_name;
1428
1429 msg += _("\" is empty.");
1430
1431 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1432
1433 return;
1434 }
1435
1436 // Message box is deferred so that canvas refresh occurs properly before
1437 // dialog
1438 if (bgroup_override) {
1439 wxString msg(_("Group \""));
1440
1441 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1442 msg += pGroup->m_group_name;
1443
1444 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1445
1446 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1447 }
1448}
1449
1450bool ChartCanvas::CheckGroup(int igroup) {
1451 if (!ChartData) return true; // Not known yet...
1452
1453 if (igroup == 0) return true; // "all charts" is always OK
1454
1455 if (igroup < 0) // negative group is an error
1456 return false;
1457
1458 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1459
1460 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1461 // and auto-shift to group 0
1462 return false;
1463
1464 for (const auto &elem : pGroup->m_element_array) {
1465 for (unsigned int ic = 0;
1466 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1467 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1468 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1469
1470 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1471 }
1472 }
1473
1474 // If necessary, check for GSHHS
1475 for (const auto &elem : pGroup->m_element_array) {
1476 const wxString &element_root = elem.m_element_name;
1477 wxString test_string = _T("GSHH");
1478 if (element_root.Upper().Contains(test_string)) return true;
1479 }
1480
1481 return false;
1482}
1483
1484void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1485 if (!ChartData) return;
1486
1487 OCPNPlatform::ShowBusySpinner();
1488
1489 double old_scale = GetVPScale();
1490 InvalidateQuilt();
1491 SetQuiltRefChart(-1);
1492
1493 m_singleChart = NULL;
1494
1495 // delete m_pCurrentStack;
1496 // m_pCurrentStack = NULL;
1497
1498 // Build a new ChartStack
1499 if (!m_pCurrentStack) {
1500 m_pCurrentStack = new ChartStack;
1501 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1502 }
1503
1504 if (-1 != dbi_hint) {
1505 if (GetQuiltMode()) {
1506 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1507 SetQuiltRefChart(dbi_hint);
1508 } else {
1509 // Open the saved chart
1510 ChartBase *pTentative_Chart;
1511 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1512
1513 if (pTentative_Chart) {
1514 /* m_singleChart is always NULL here, (set above) should this go before
1515 * that? */
1516 if (m_singleChart) m_singleChart->Deactivate();
1517
1518 m_singleChart = pTentative_Chart;
1519 m_singleChart->Activate();
1520
1521 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1522 GetpCurrentStack(), m_singleChart->GetFullPath());
1523 }
1524 }
1525
1526 // refresh_Piano();
1527 } else {
1528 // Select reference chart from the stack, as though clicked by user
1529 // Make it the smallest scale chart on the stack
1530 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1531 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1532 SetQuiltRefChart(selected_index);
1533 }
1534
1535 // Validate the correct single chart, or set the quilt mode as appropriate
1536 SetupCanvasQuiltMode();
1537 if (!GetQuiltMode() && m_singleChart == 0) {
1538 // use a dummy like in DoChartUpdate
1539 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1540 m_singleChart = pDummyChart;
1541 SetVPScale(old_scale);
1542 }
1543
1544 ReloadVP();
1545
1546 UpdateCanvasControlBar();
1547 UpdateGPSCompassStatusBox(true);
1548
1549 SetCursor(wxCURSOR_ARROW);
1550
1551 OCPNPlatform::HideBusySpinner();
1552}
1553
1554bool ChartCanvas::DoCanvasUpdate(void) {
1555 double tLat, tLon; // Chart Stack location
1556 double vpLat, vpLon; // ViewPort location
1557
1558 bool bNewChart = false;
1559 bool bNewView = false;
1560 bool bCanvasChartAutoOpen = true; // debugging
1561
1562 bool bNewPiano = false;
1563 bool bOpenSpecified;
1564 ChartStack LastStack;
1565 ChartBase *pLast_Ch;
1566
1567 ChartStack WorkStack;
1568
1569 if (bDBUpdateInProgress) return false;
1570 if (!ChartData) return false;
1571
1572 if (ChartData->IsBusy()) return false;
1573
1574 // Startup case:
1575 // Quilting is enabled, but the last chart seen was not quiltable
1576 // In this case, drop to single chart mode, set persistence flag,
1577 // And open the specified chart
1578 // TODO implement this
1579 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1580 // if( GetQuiltMode() ) {
1581 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1582 // gFrame->ToggleQuiltMode();
1583 // m_bpersistent_quilt = true;
1584 // m_singleChart = NULL;
1585 // }
1586 // }
1587 // }
1588
1589 // If in auto-follow mode, use the current glat,glon to build chart
1590 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1591 // other means
1592
1593 if (m_bFollow) {
1594 tLat = gLat;
1595 tLon = gLon;
1596
1597 // Set the ViewPort center based on the OWNSHIP offset
1598 double dx = m_OSoffsetx;
1599 double dy = m_OSoffsety;
1600 double d_east = dx / GetVP().view_scale_ppm;
1601 double d_north = dy / GetVP().view_scale_ppm;
1602
1603 if (GetUpMode() == NORTH_UP_MODE) {
1604 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1605 } else {
1606 double offset_angle = atan2(d_north, d_east);
1607 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1608 double chart_angle = GetVPRotation();
1609 double target_angle = chart_angle + offset_angle;
1610 double d_east_mod = offset_distance * cos(target_angle);
1611 double d_north_mod = offset_distance * sin(target_angle);
1612 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1613 }
1614
1615 // on lookahead mode, adjust the vp center point
1616 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1617 double angle = g_COGAvg + (GetVPRotation() * 180. / PI);
1618
1619 double pixel_deltay =
1620 fabs(cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1621 double pixel_deltax = fabs(sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1622
1623 double pixel_delta_tent =
1624 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1625
1626 double pixel_delta = 0;
1627
1628 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1629 // avoid jumping of the vp center point during slow maneuvering, or at
1630 // anchor....
1631 if (!std::isnan(gSog)) {
1632 if (gSog < 1.0)
1633 pixel_delta = 0.;
1634 else if (gSog >= 3.0)
1635 pixel_delta = pixel_delta_tent;
1636 else
1637 pixel_delta = pixel_delta_tent * (gSog - 1.0) / 2.0;
1638 }
1639
1640 double meters_to_shift =
1641 cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1642
1643 double dir_to_shift = g_COGAvg;
1644
1645 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1646 &vpLon);
1647 } else if (m_bLookAhead && !bGPSValid) {
1648 m_OSoffsetx = 0; // center ownship on loss of GPS
1649 m_OSoffsety = 0;
1650 vpLat = gLat;
1651 vpLon = gLon;
1652 }
1653
1654 } else {
1655 tLat = m_vLat;
1656 tLon = m_vLon;
1657 vpLat = m_vLat;
1658 vpLon = m_vLon;
1659 }
1660
1661 if (GetQuiltMode()) {
1662 int current_db_index = -1;
1663 if (m_pCurrentStack)
1664 current_db_index =
1665 m_pCurrentStack
1666 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1667 // chart dbIndex
1668 else
1669 m_pCurrentStack = new ChartStack;
1670
1671 // This logic added to enable opening a chart when there is no
1672 // previous chart indication, either from inital startup, or from adding
1673 // new chart directory
1674 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1675 m_pCurrentStack) {
1676 if (m_pCurrentStack->nEntry) {
1677 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1678 1); // smallest scale
1679 SelectQuiltRefdbChart(new_dbIndex, true);
1680 m_bautofind = false;
1681 }
1682 }
1683
1684 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1685 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1686
1687 if (m_bFirstAuto) {
1688 // Allow the chart database to pre-calculate the MBTile inclusion test
1689 // boolean...
1690 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1691
1692 double proposed_scale_onscreen =
1693 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1694
1695 int initial_db_index = m_restore_dbindex;
1696 if (initial_db_index < 0) {
1697 if (m_pCurrentStack->nEntry) {
1698 initial_db_index =
1699 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1700 } else
1701 m_bautofind = true; // initial_db_index = 0;
1702 }
1703
1704 if (m_pCurrentStack->nEntry) {
1705 int initial_type = ChartData->GetDBChartType(initial_db_index);
1706
1707 // Check to see if the target new chart is quiltable as a reference
1708 // chart
1709
1710 if (!IsChartQuiltableRef(initial_db_index)) {
1711 // If it is not quiltable, then walk the stack up looking for a
1712 // satisfactory chart i.e. one that is quiltable and of the same type
1713 // XXX if there's none?
1714 int stack_index = 0;
1715
1716 if (stack_index >= 0) {
1717 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1718 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1719 if (IsChartQuiltableRef(test_db_index) &&
1720 (initial_type ==
1721 ChartData->GetDBChartType(initial_db_index))) {
1722 initial_db_index = test_db_index;
1723 break;
1724 }
1725 stack_index++;
1726 }
1727 }
1728 }
1729
1730 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1731 if (pc) {
1732 SetQuiltRefChart(initial_db_index);
1733 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1734 }
1735
1736 // Check proposed scale, see how much underzoom results
1737 // Adjust as necessary to prevent slow loading on initial startup
1738 // For MBTILES we skip this test because they are always shown in
1739 // reasonable range of scale
1740 if (pc) {
1741 if (pc->GetChartType() != CHART_TYPE_MBTILES)
1742 proposed_scale_onscreen =
1743 wxMin(proposed_scale_onscreen, 4.0 * pc->GetNativeScale());
1744 else
1745 proposed_scale_onscreen =
1746 wxMin(proposed_scale_onscreen, 32.0 * pc->GetNativeScale());
1747 }
1748 }
1749
1750 bNewView |= SetViewPoint(vpLat, vpLon,
1751 GetCanvasScaleFactor() / proposed_scale_onscreen,
1752 0, GetVPRotation());
1753 }
1754 // else
1755 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1756
1757 goto update_finish;
1758 }
1759
1760 // Single Chart Mode from here....
1761 pLast_Ch = m_singleChart;
1762 ChartTypeEnum new_open_type;
1763 ChartFamilyEnum new_open_family;
1764 if (pLast_Ch) {
1765 new_open_type = pLast_Ch->GetChartType();
1766 new_open_family = pLast_Ch->GetChartFamily();
1767 } else {
1768 new_open_type = CHART_TYPE_KAP;
1769 new_open_family = CHART_FAMILY_RASTER;
1770 }
1771
1772 bOpenSpecified = m_bFirstAuto;
1773
1774 // Make sure the target stack is valid
1775 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1776
1777 // Build a chart stack based on tLat, tLon
1778 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1779 m_groupIndex)) { // Bogus Lat, Lon?
1780 if (NULL == pDummyChart) {
1781 pDummyChart = new ChartDummy;
1782 bNewChart = true;
1783 }
1784
1785 if (m_singleChart)
1786 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1787
1788 m_singleChart = pDummyChart;
1789
1790 // If the current viewpoint is invalid, set the default scale to
1791 // something reasonable.
1792 double set_scale = GetVPScale();
1793 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1794
1795 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1796
1797 // If the chart stack has just changed, there is new status
1798 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1799 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1800 bNewPiano = true;
1801 bNewChart = true;
1802 }
1803 }
1804
1805 // Copy the new (by definition empty) stack into the target stack
1806 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1807
1808 goto update_finish;
1809 }
1810
1811 // Check to see if Chart Stack has changed
1812 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1813 // New chart stack, so...
1814 bNewPiano = true;
1815
1816 // Save a copy of the current stack
1817 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1818
1819 // Copy the new stack into the target stack
1820 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1821
1822 // Is Current Chart in new stack?
1823
1824 int tEntry = -1;
1825 if (NULL != m_singleChart) // this handles startup case
1826 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1827 m_singleChart->GetFullPath());
1828
1829 if (tEntry != -1) { // m_singleChart is in the new stack
1830 m_pCurrentStack->CurrentStackEntry = tEntry;
1831 bNewChart = false;
1832 }
1833
1834 else // m_singleChart is NOT in new stack
1835 { // So, need to open a new chart
1836 // Find the largest scale raster chart that opens OK
1837
1838 ChartBase *pProposed = NULL;
1839
1840 if (bCanvasChartAutoOpen) {
1841 bool search_direction =
1842 false; // default is to search from lowest to highest
1843 int start_index = 0;
1844
1845 // A special case: If panning at high scale, open largest scale
1846 // chart first
1847 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1848 (LastStack.nEntry == 0)) {
1849 search_direction = true;
1850 start_index = m_pCurrentStack->nEntry - 1;
1851 }
1852
1853 // Another special case, open specified index on program start
1854 if (bOpenSpecified) {
1855 search_direction = false;
1856 start_index = 0;
1857 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1858 start_index = 0;
1859
1860 new_open_type = CHART_TYPE_DONTCARE;
1861 }
1862
1863 pProposed = ChartData->OpenStackChartConditional(
1864 m_pCurrentStack, start_index, search_direction, new_open_type,
1865 new_open_family);
1866
1867 // Try to open other types/families of chart in some priority
1868 if (NULL == pProposed)
1869 pProposed = ChartData->OpenStackChartConditional(
1870 m_pCurrentStack, start_index, search_direction,
1871 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1872
1873 if (NULL == pProposed)
1874 pProposed = ChartData->OpenStackChartConditional(
1875 m_pCurrentStack, start_index, search_direction,
1876 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1877
1878 bNewChart = true;
1879
1880 } // bCanvasChartAutoOpen
1881
1882 else
1883 pProposed = NULL;
1884
1885 // If no go, then
1886 // Open a Dummy Chart
1887 if (NULL == pProposed) {
1888 if (NULL == pDummyChart) {
1889 pDummyChart = new ChartDummy;
1890 bNewChart = true;
1891 }
1892
1893 if (pLast_Ch)
1894 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1895
1896 pProposed = pDummyChart;
1897 }
1898
1899 // Arriving here, pProposed points to an opened chart, or NULL.
1900 if (m_singleChart) m_singleChart->Deactivate();
1901 m_singleChart = pProposed;
1902
1903 if (m_singleChart) {
1904 m_singleChart->Activate();
1905 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1906 m_pCurrentStack, m_singleChart->GetFullPath());
1907 }
1908 } // need new chart
1909
1910 // Arriving here, m_singleChart is opened and OK, or NULL
1911 if (NULL != m_singleChart) {
1912 // Setup the view using the current scale
1913 double set_scale = GetVPScale();
1914
1915 // If the current viewpoint is invalid, set the default scale to
1916 // something reasonable.
1917 if (!GetVP().IsValid())
1918 set_scale = 1. / 20000.;
1919 else { // otherwise, match scale if elected.
1920 double proposed_scale_onscreen;
1921
1922 if (m_bFollow) { // autoset the scale only if in autofollow
1923 double new_scale_ppm =
1924 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1925 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1926 } else
1927 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1928
1929 // This logic will bring a new chart onscreen at roughly twice the true
1930 // paper scale equivalent. Note that first chart opened on application
1931 // startup (bOpenSpecified = true) will open at the config saved scale
1932 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1933 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1934 double equivalent_vp_scale =
1935 GetCanvasScaleFactor() / proposed_scale_onscreen;
1936 double new_scale_ppm =
1937 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1938 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1939 }
1940
1941 if (m_bFollow) { // bounds-check the scale only if in autofollow
1942 proposed_scale_onscreen =
1943 wxMin(proposed_scale_onscreen,
1944 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1945 GetCanvasWidth()));
1946 proposed_scale_onscreen =
1947 wxMax(proposed_scale_onscreen,
1948 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1949 g_b_overzoom_x));
1950 }
1951
1952 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1953 }
1954
1955 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1956 m_singleChart->GetChartSkew() * PI / 180.,
1957 GetVPRotation());
1958 }
1959 } // new stack
1960
1961 else // No change in Chart Stack
1962 {
1963 if ((m_bFollow) && m_singleChart)
1964 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1965 m_singleChart->GetChartSkew() * PI / 180.,
1966 GetVPRotation());
1967 }
1968
1969update_finish:
1970
1971 // TODO
1972 // if( bNewPiano ) UpdateControlBar();
1973
1974 // Update the ownship position on thumbnail chart, if shown
1975 if (pthumbwin && pthumbwin->IsShown()) {
1976 if (pthumbwin->pThumbChart) {
1977 if (pthumbwin->pThumbChart->UpdateThumbData(gLat, gLon))
1978 pthumbwin->Refresh(TRUE);
1979 }
1980 }
1981
1982 m_bFirstAuto = false; // Auto open on program start
1983
1984 // If we need a Refresh(), do it here...
1985 // But don't duplicate a Refresh() done by SetViewPoint()
1986 if (bNewChart && !bNewView) Refresh(false);
1987
1988#ifdef ocpnUSE_GL
1989 // If a new chart, need to invalidate gl viewport for refresh
1990 // so the fbo gets flushed
1991 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1992#endif
1993
1994 return bNewChart | bNewView;
1995}
1996
1997void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1998 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1999
2000 SetQuiltRefChart(db_index);
2001 if (ChartData) {
2002 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
2003 if (pc) {
2004 if (b_autoscale) {
2005 double best_scale_ppm = GetBestVPScale(pc);
2006 SetVPScale(best_scale_ppm);
2007 }
2008 } else
2009 SetQuiltRefChart(-1);
2010 } else
2011 SetQuiltRefChart(-1);
2012}
2013
2014void ChartCanvas::SelectQuiltRefChart(int selected_index) {
2015 std::vector<int> piano_chart_index_array =
2016 GetQuiltExtendedStackdbIndexArray();
2017 int current_db_index = piano_chart_index_array[selected_index];
2018
2019 SelectQuiltRefdbChart(current_db_index);
2020}
2021
2022double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
2023 if (pchart) {
2024 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
2025
2026 if ((g_bPreserveScaleOnX) ||
2027 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2028 double new_scale_ppm = GetVPScale();
2029 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2030 } else {
2031 // This logic will bring the new chart onscreen at roughly twice the true
2032 // paper scale equivalent.
2033 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2034 double equivalent_vp_scale =
2035 GetCanvasScaleFactor() / proposed_scale_onscreen;
2036 double new_scale_ppm =
2037 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2038 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2039 }
2040
2041 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2042 // set. Otherwise, we get severe performance problems on all platforms
2043
2044 double max_underzoom_multiplier = 2.0;
2045 if (GetVP().b_quilt) {
2046 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2047 pchart->GetChartType(),
2048 pchart->GetChartFamily());
2049 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2050 }
2051
2052 proposed_scale_onscreen = wxMin(
2053 proposed_scale_onscreen,
2054 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2055 max_underzoom_multiplier);
2056
2057 // And, do not allow excessive overzoom either
2058 proposed_scale_onscreen =
2059 wxMax(proposed_scale_onscreen,
2060 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2061
2062 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2063 } else
2064 return 1.0;
2065}
2066
2067void ChartCanvas::SetupCanvasQuiltMode(void) {
2068 if (GetQuiltMode()) // going to quilt mode
2069 {
2070 ChartData->LockCache();
2071
2072 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2073
2074 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2075
2076 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2077 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2078 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2079 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2080
2081 m_Piano->SetRoundedRectangles(true);
2082
2083 // Select the proper Ref chart
2084 int target_new_dbindex = -1;
2085 if (m_pCurrentStack) {
2086 target_new_dbindex =
2087 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2088
2089 if (-1 != target_new_dbindex) {
2090 if (!IsChartQuiltableRef(target_new_dbindex)) {
2091 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2092 int type = ChartData->GetDBChartType(target_new_dbindex);
2093
2094 // walk the stack up looking for a satisfactory chart
2095 int stack_index = m_pCurrentStack->CurrentStackEntry;
2096
2097 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2098 (stack_index >= 0)) {
2099 int proj_tent = ChartData->GetDBChartProj(
2100 m_pCurrentStack->GetDBIndex(stack_index));
2101 int type_tent = ChartData->GetDBChartType(
2102 m_pCurrentStack->GetDBIndex(stack_index));
2103
2104 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2105 if ((proj == proj_tent) && (type_tent == type)) {
2106 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2107 break;
2108 }
2109 }
2110 stack_index++;
2111 }
2112 }
2113 }
2114 }
2115
2116 if (IsChartQuiltableRef(target_new_dbindex))
2117 SelectQuiltRefdbChart(target_new_dbindex,
2118 false); // Try not to allow a scale change
2119 else
2120 SelectQuiltRefdbChart(-1, false);
2121
2122 m_singleChart = NULL; // Bye....
2123
2124 // Re-qualify the quilt reference chart selection
2125 AdjustQuiltRefChart();
2126
2127 // Restore projection type saved on last quilt mode toggle
2128 // TODO
2129 // if(g_sticky_projection != -1)
2130 // GetVP().SetProjectionType(g_sticky_projection);
2131 // else
2132 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2133 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2134
2135 } else // going to SC Mode
2136 {
2137 std::vector<int> empty_array;
2138 m_Piano->SetActiveKeyArray(empty_array);
2139 m_Piano->SetNoshowIndexArray(empty_array);
2140 m_Piano->SetEclipsedIndexArray(empty_array);
2141
2142 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2143 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2144 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2145 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2146 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2147
2148 m_Piano->SetRoundedRectangles(false);
2149 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2150 }
2151
2152 // When shifting from quilt to single chart mode, select the "best" single
2153 // chart to show
2154 if (!GetQuiltMode()) {
2155 if (ChartData && ChartData->IsValid()) {
2156 UnlockQuilt();
2157
2158 double tLat, tLon;
2159 if (m_bFollow == true) {
2160 tLat = gLat;
2161 tLon = gLon;
2162 } else {
2163 tLat = m_vLat;
2164 tLon = m_vLon;
2165 }
2166
2167 if (!m_singleChart) {
2168 // Build a temporary chart stack based on tLat, tLon
2169 ChartStack TempStack;
2170 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2171 m_groupIndex);
2172
2173 // Iterate over the quilt charts actually shown, looking for the
2174 // largest scale chart that will be in the new chartstack.... This
2175 // will (almost?) always be the reference chart....
2176
2177 ChartBase *Candidate_Chart = NULL;
2178 int cur_max_scale = (int)1e8;
2179
2180 ChartBase *pChart = GetFirstQuiltChart();
2181 while (pChart) {
2182 // Is this pChart in new stack?
2183 int tEntry =
2184 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2185 if (tEntry != -1) {
2186 if (pChart->GetNativeScale() < cur_max_scale) {
2187 Candidate_Chart = pChart;
2188 cur_max_scale = pChart->GetNativeScale();
2189 }
2190 }
2191 pChart = GetNextQuiltChart();
2192 }
2193
2194 m_singleChart = Candidate_Chart;
2195
2196 // If the quilt is empty, there is no "best" chart.
2197 // So, open the smallest scale chart in the current stack
2198 if (NULL == m_singleChart) {
2199 m_singleChart = ChartData->OpenStackChartConditional(
2200 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2201 CHART_FAMILY_DONTCARE);
2202 }
2203 }
2204
2205 // Invalidate all the charts in the quilt,
2206 // as any cached data may be region based and not have fullscreen coverage
2207 InvalidateAllQuiltPatchs();
2208
2209 if (m_singleChart) {
2210 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2211 std::vector<int> one_array;
2212 one_array.push_back(dbi);
2213 m_Piano->SetActiveKeyArray(one_array);
2214 }
2215
2216 if (m_singleChart) {
2217 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2218 }
2219 }
2220 // Invalidate the current stack so that it will be rebuilt on next tick
2221 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2222 }
2223}
2224
2225bool ChartCanvas::IsTempMenuBarEnabled() {
2226#ifdef __WXMSW__
2227 int major;
2228 wxGetOsVersion(&major);
2229 return (major >
2230 5); // For Windows, function is only available on Vista and above
2231#else
2232 return true;
2233#endif
2234}
2235
2236double ChartCanvas::GetCanvasRangeMeters() {
2237 int width, height;
2238 GetSize(&width, &height);
2239 int minDimension = wxMin(width, height);
2240
2241 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2242 range *= cos(GetVP().clat * PI / 180.);
2243 return range;
2244}
2245
2246void ChartCanvas::SetCanvasRangeMeters(double range) {
2247 int width, height;
2248 GetSize(&width, &height);
2249 int minDimension = wxMin(width, height);
2250
2251 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2252 SetVPScale(scale_ppm / 2);
2253}
2254
2255bool ChartCanvas::SetUserOwnship() {
2256 // Look for user defined ownship image
2257 // This may be found in the shared data location along with other user
2258 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2259 if (pWayPointMan && pWayPointMan->DoesIconExist(_T("ownship"))) {
2260 double factor_dusk = 0.5;
2261 double factor_night = 0.25;
2262
2263 wxBitmap *pbmp = pWayPointMan->GetIconBitmap(_T("ownship"));
2264 m_pos_image_user_day = new wxImage;
2265 *m_pos_image_user_day = pbmp->ConvertToImage();
2266 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2267
2268 int gimg_width = m_pos_image_user_day->GetWidth();
2269 int gimg_height = m_pos_image_user_day->GetHeight();
2270
2271 // Make dusk and night images
2272 m_pos_image_user_dusk = new wxImage;
2273 m_pos_image_user_night = new wxImage;
2274
2275 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2276 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2277
2278 for (int iy = 0; iy < gimg_height; iy++) {
2279 for (int ix = 0; ix < gimg_width; ix++) {
2280 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2281 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2282 m_pos_image_user_day->GetGreen(ix, iy),
2283 m_pos_image_user_day->GetBlue(ix, iy));
2284 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2285 hsv.value = hsv.value * factor_dusk;
2286 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2287 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2288 nrgb.blue);
2289
2290 hsv = wxImage::RGBtoHSV(rgb);
2291 hsv.value = hsv.value * factor_night;
2292 nrgb = wxImage::HSVtoRGB(hsv);
2293 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2294 nrgb.blue);
2295 }
2296 }
2297 }
2298
2299 // Make some alternate greyed out day/dusk/night images
2300 m_pos_image_user_grey_day = new wxImage;
2301 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2302
2303 m_pos_image_user_grey_dusk = new wxImage;
2304 m_pos_image_user_grey_night = new wxImage;
2305
2306 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2307 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2308
2309 for (int iy = 0; iy < gimg_height; iy++) {
2310 for (int ix = 0; ix < gimg_width; ix++) {
2311 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2312 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2313 m_pos_image_user_grey_day->GetGreen(ix, iy),
2314 m_pos_image_user_grey_day->GetBlue(ix, iy));
2315 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2316 hsv.value = hsv.value * factor_dusk;
2317 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2318 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2319 nrgb.blue);
2320
2321 hsv = wxImage::RGBtoHSV(rgb);
2322 hsv.value = hsv.value * factor_night;
2323 nrgb = wxImage::HSVtoRGB(hsv);
2324 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2325 nrgb.blue);
2326 }
2327 }
2328 }
2329
2330 // Make a yellow image for rendering under low accuracy chart conditions
2331 m_pos_image_user_yellow_day = new wxImage;
2332 m_pos_image_user_yellow_dusk = new wxImage;
2333 m_pos_image_user_yellow_night = new wxImage;
2334
2335 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2336 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2337 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2338
2339 for (int iy = 0; iy < gimg_height; iy++) {
2340 for (int ix = 0; ix < gimg_width; ix++) {
2341 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2342 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2343 m_pos_image_user_grey_day->GetGreen(ix, iy),
2344 m_pos_image_user_grey_day->GetBlue(ix, iy));
2345
2346 // Simply remove all "blue" from the greyscaled image...
2347 // so, what is not black becomes yellow.
2348 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2349 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2350 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2351
2352 hsv = wxImage::RGBtoHSV(rgb);
2353 hsv.value = hsv.value * factor_dusk;
2354 nrgb = wxImage::HSVtoRGB(hsv);
2355 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2356
2357 hsv = wxImage::RGBtoHSV(rgb);
2358 hsv.value = hsv.value * factor_night;
2359 nrgb = wxImage::HSVtoRGB(hsv);
2360 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2361 0);
2362 }
2363 }
2364 }
2365
2366 return true;
2367 } else
2368 return false;
2369}
2370
2371void ChartCanvas::SetDisplaySizeMM(double size) {
2372 m_display_size_mm = size;
2373
2374 // int sx, sy;
2375 // wxDisplaySize( &sx, &sy );
2376
2377 // Calculate pixels per mm for later reference
2378 wxSize sd = g_Platform->getDisplaySize();
2379 double max_physical = wxMax(sd.x, sd.y);
2380 // Set DPI (Win) scale factor
2381 g_scaler = g_Platform->GetDisplayDIPMult(this);
2382
2383 m_pix_per_mm = (max_physical) / ((double)m_display_size_mm);
2384 m_canvas_scale_factor = (max_physical) / (m_display_size_mm / 1000.);
2385
2386 if (ps52plib) ps52plib->SetPPMM(m_pix_per_mm);
2387
2388 wxString msg;
2389 msg.Printf(
2390 _T("Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): ")
2391 _T("%d:%d "),
2392 m_display_size_mm, sd.x, sd.y);
2393 wxLogMessage(msg);
2394
2395 int ssx, ssy;
2396 ::wxDisplaySize(&ssx, &ssy);
2397 msg.Printf(_T("wxDisplaySize(): %d %d"), ssx, ssy);
2398 wxLogMessage(msg);
2399
2400 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2401}
2402#if 0
2403void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2404{
2405 wxString msg(event.m_string.c_str(), wxConvUTF8);
2406 // if cpus are removed between runs
2407 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2408 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2409 }
2410
2411 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2412 {
2413 compress_msg_array.RemoveAt(event.thread);
2414 compress_msg_array.Insert( msg, event.thread);
2415 }
2416 else
2417 compress_msg_array.Add(msg);
2418
2419
2420 wxString combined_msg;
2421 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2422 combined_msg += compress_msg_array[i];
2423 combined_msg += _T("\n");
2424 }
2425
2426 bool skip = false;
2427 pprog->Update(pprog_count, combined_msg, &skip );
2428 pprog->SetSize(pprog_size);
2429 if(skip)
2430 b_skipout = skip;
2431}
2432#endif
2433void ChartCanvas::InvalidateGL() {
2434 if (!m_glcc) return;
2435#ifdef ocpnUSE_GL
2436 if (g_bopengl) m_glcc->Invalidate();
2437#endif
2438 if (m_Compass) m_Compass->UpdateStatus(true);
2439}
2440
2441int ChartCanvas::GetCanvasChartNativeScale() {
2442 int ret = 1;
2443 if (!VPoint.b_quilt) {
2444 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2445 } else
2446 ret = (int)m_pQuilt->GetRefNativeScale();
2447
2448 return ret;
2449}
2450
2451ChartBase *ChartCanvas::GetChartAtCursor() {
2452 ChartBase *target_chart;
2453 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2454 target_chart = m_singleChart;
2455 else if (VPoint.b_quilt)
2456 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2457 else
2458 target_chart = NULL;
2459 return target_chart;
2460}
2461
2462ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2463 ChartBase *target_chart;
2464 if (VPoint.b_quilt)
2465 target_chart =
2466 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2467 else
2468 target_chart = NULL;
2469 return target_chart;
2470}
2471
2472int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2473 int new_dbIndex = -1;
2474 if (!VPoint.b_quilt) {
2475 if (m_pCurrentStack) {
2476 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2477 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2478 if (sc >= scale) {
2479 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2480 break;
2481 }
2482 }
2483 }
2484 } else {
2485 // Using the current quilt, select a useable reference chart
2486 // Said chart will be in the extended (possibly full-screen) stack,
2487 // And will have a scale equal to or just greater than the stipulated
2488 // value
2489 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2490 if (im > 0) {
2491 for (unsigned int is = 0; is < im; is++) {
2492 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2493 m_pQuilt->GetExtendedStackIndexArray()[is]);
2494 if ((m.Scale_ge(
2495 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2496 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2497 break;
2498 }
2499 }
2500 }
2501 }
2502
2503 return new_dbIndex;
2504}
2505
2506void ChartCanvas::EnablePaint(bool b_enable) {
2507 m_b_paint_enable = b_enable;
2508#ifdef ocpnUSE_GL
2509 if (m_glcc) m_glcc->EnablePaint(b_enable);
2510#endif
2511}
2512
2513bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2514
2515void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2516
2517std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2518 return m_pQuilt->GetQuiltIndexArray();
2519 ;
2520}
2521
2522void ChartCanvas::SetQuiltMode(bool b_quilt) {
2523 VPoint.b_quilt = b_quilt;
2524 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2525}
2526
2527bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2528
2529int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2530 return m_pQuilt->GetRefChartdbIndex();
2531}
2532
2533void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2534 m_pQuilt->InvalidateAllQuiltPatchs();
2535}
2536
2537ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2538 return m_pQuilt->GetLargestScaleChart();
2539}
2540
2541ChartBase *ChartCanvas::GetFirstQuiltChart() {
2542 return m_pQuilt->GetFirstChart();
2543}
2544
2545ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2546
2547int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2548
2549void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2550 m_pQuilt->SetHiliteIndex(dbIndex);
2551}
2552
2553std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2554 bool flag2) {
2555 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2556}
2557
2558int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2559 return m_pQuilt->GetRefChartdbIndex();
2560}
2561
2562std::vector<int> ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2563 return m_pQuilt->GetExtendedStackIndexArray();
2564}
2565
2566std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2567 return m_pQuilt->GetEclipsedStackIndexArray();
2568}
2569
2570void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2571
2572double ChartCanvas::GetQuiltMaxErrorFactor() {
2573 return m_pQuilt->GetMaxErrorFactor();
2574}
2575
2576bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2577 return m_pQuilt->IsChartQuiltableRef(db_index);
2578}
2579
2580bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2581 double chartMaxScale =
2582 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2583 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2584}
2585
2586void ChartCanvas::StartMeasureRoute() {
2587 if (!m_routeState) { // no measure tool if currently creating route
2588 if (m_bMeasure_Active) {
2589 g_pRouteMan->DeleteRoute(m_pMeasureRoute,
2590 NavObjectChanges::getInstance());
2591 m_pMeasureRoute = NULL;
2592 }
2593
2594 m_bMeasure_Active = true;
2595 m_nMeasureState = 1;
2596 m_bDrawingRoute = false;
2597
2598 SetCursor(*pCursorPencil);
2599 Refresh();
2600 }
2601}
2602
2603void ChartCanvas::CancelMeasureRoute() {
2604 m_bMeasure_Active = false;
2605 m_nMeasureState = 0;
2606 m_bDrawingRoute = false;
2607
2608 g_pRouteMan->DeleteRoute(m_pMeasureRoute, NavObjectChanges::getInstance());
2609 m_pMeasureRoute = NULL;
2610
2611 SetCursor(*pCursorArrow);
2612}
2613
2614ViewPort &ChartCanvas::GetVP() { return VPoint; }
2615
2616void ChartCanvas::SetVP(ViewPort &vp) { VPoint = vp; }
2617
2618// void ChartCanvas::SetFocus()
2619// {
2620// printf("set %d\n", m_canvasIndex);
2621// //wxWindow:SetFocus();
2622// }
2623
2624void ChartCanvas::TriggerDeferredFocus() {
2625 //#if defined(__WXGTK__) || defined(__WXOSX__)
2626
2627 m_deferredFocusTimer.Start(20, true);
2628
2629#if defined(__WXGTK__) || defined(__WXOSX__)
2630 gFrame->Raise();
2631#endif
2632
2633 // gFrame->Raise();
2634 //#else
2635 // SetFocus();
2636 // Refresh(true);
2637 //#endif
2638}
2639
2640void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2641 SetFocus();
2642 Refresh(true);
2643}
2644
2645void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2646 if (g_pi_manager)
2647 if (g_pi_manager->SendKeyEventToPlugins(event))
2648 return; // PlugIn did something, and does not want the canvas to do
2649 // anything else
2650
2651 int key_char = event.GetKeyCode();
2652
2653 if (g_benable_rotate) {
2654 switch (key_char) {
2655 case ']':
2656 RotateCanvas(1);
2657 Refresh();
2658 break;
2659
2660 case '[':
2661 RotateCanvas(-1);
2662 Refresh();
2663 break;
2664
2665 case '\\':
2666 DoRotateCanvas(0);
2667 break;
2668 }
2669 }
2670
2671 event.Skip();
2672}
2673
2674void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2675 if (g_pi_manager)
2676 if (g_pi_manager->SendKeyEventToPlugins(event))
2677 return; // PlugIn did something, and does not want the canvas to do
2678 // anything else
2679
2680 bool b_handled = false;
2681
2682 m_modkeys = event.GetModifiers();
2683
2684 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2685
2686#ifdef OCPN_ALT_MENUBAR
2687#ifndef __WXOSX__
2688 // If the permanent menubar is disabled, we show it temporarily when Alt is
2689 // pressed or when Alt + a letter is presssed (for the top-menu-level
2690 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2691 // some special cases.
2692 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2693 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2694 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2695 if (!g_bTempShowMenuBar) {
2696 g_bTempShowMenuBar = true;
2697 parent_frame->ApplyGlobalSettings(false);
2698 }
2699 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2700 event.Skip();
2701 return;
2702 }
2703 // If another key is pressed while Alt is down, do NOT toggle the menus when
2704 // Alt is released
2705 if (event.GetKeyCode() != WXK_ALT) {
2706 m_bMayToggleMenuBar = false;
2707 }
2708 }
2709#endif
2710#endif
2711
2712 // HOTKEYS
2713 switch (event.GetKeyCode()) {
2714 case WXK_TAB:
2715 // parent_frame->SwitchKBFocus( this );
2716 break;
2717
2718 case WXK_MENU:
2719 int x, y;
2720 event.GetPosition(&x, &y);
2721 m_FinishRouteOnKillFocus = false;
2722 CallPopupMenu(x, y);
2723 m_FinishRouteOnKillFocus = true;
2724 break;
2725
2726 case WXK_ALT:
2727 m_modkeys |= wxMOD_ALT;
2728 break;
2729
2730 case WXK_CONTROL:
2731 m_modkeys |= wxMOD_CONTROL;
2732 break;
2733
2734 case WXK_LEFT:
2735 if (m_modkeys == wxMOD_CONTROL)
2736 parent_frame->DoStackDown(this);
2737 else if (g_bsmoothpanzoom) {
2738 StartTimedMovement();
2739 m_panx = -1;
2740 } else {
2741 PanCanvas(-panspeed, 0);
2742 }
2743 b_handled = true;
2744 break;
2745
2746 case WXK_UP:
2747 if (g_bsmoothpanzoom) {
2748 StartTimedMovement();
2749 m_pany = -1;
2750 } else
2751 PanCanvas(0, -panspeed);
2752 b_handled = true;
2753 break;
2754
2755 case WXK_RIGHT:
2756 if (m_modkeys == wxMOD_CONTROL)
2757 parent_frame->DoStackUp(this);
2758 else if (g_bsmoothpanzoom) {
2759 StartTimedMovement();
2760 m_panx = 1;
2761 } else
2762 PanCanvas(panspeed, 0);
2763 b_handled = true;
2764
2765 break;
2766
2767 case WXK_DOWN:
2768 if (g_bsmoothpanzoom) {
2769 StartTimedMovement();
2770 m_pany = 1;
2771 } else
2772 PanCanvas(0, panspeed);
2773 b_handled = true;
2774 break;
2775
2776 case WXK_F2:
2777 TogglebFollow();
2778 break;
2779
2780 case WXK_F3: {
2781 SetShowENCText(!GetShowENCText());
2782 Refresh(true);
2783 InvalidateGL();
2784 break;
2785 }
2786 case WXK_F4:
2787 if (!m_bMeasure_Active) {
2788 if (event.ShiftDown())
2789 m_bMeasure_DistCircle = true;
2790 else
2791 m_bMeasure_DistCircle = false;
2792
2793 StartMeasureRoute();
2794 } else {
2795 CancelMeasureRoute();
2796
2797 SetCursor(*pCursorArrow);
2798
2799 SurfaceToolbar();
2800 InvalidateGL();
2801 Refresh(false);
2802 }
2803
2804 break;
2805
2806 case WXK_F5:
2807 parent_frame->ToggleColorScheme();
2808 gFrame->Raise();
2809 TriggerDeferredFocus();
2810 break;
2811
2812 case WXK_F6: {
2813 int mod = m_modkeys & wxMOD_SHIFT;
2814 if (mod != m_brightmod) {
2815 m_brightmod = mod;
2816 m_bbrightdir = !m_bbrightdir;
2817 }
2818
2819 if (!m_bbrightdir) {
2820 g_nbrightness -= 10;
2821 if (g_nbrightness <= MIN_BRIGHT) {
2822 g_nbrightness = MIN_BRIGHT;
2823 m_bbrightdir = true;
2824 }
2825 } else {
2826 g_nbrightness += 10;
2827 if (g_nbrightness >= MAX_BRIGHT) {
2828 g_nbrightness = MAX_BRIGHT;
2829 m_bbrightdir = false;
2830 }
2831 }
2832
2833 SetScreenBrightness(g_nbrightness);
2834 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2835
2836 SetFocus(); // just in case the external program steals it....
2837 gFrame->Raise(); // And reactivate the application main
2838
2839 break;
2840 }
2841
2842 case WXK_F7:
2843 parent_frame->DoStackDown(this);
2844 break;
2845
2846 case WXK_F8:
2847 parent_frame->DoStackUp(this);
2848 break;
2849
2850#ifndef __WXOSX__
2851 case WXK_F9:
2852 ToggleCanvasQuiltMode();
2853 break;
2854#endif
2855
2856 case WXK_F11:
2857 parent_frame->ToggleFullScreen();
2858 b_handled = true;
2859 break;
2860
2861 case WXK_F12: {
2862 if (m_modkeys == wxMOD_ALT)
2863 m_nMeasureState = *(volatile int *)(0); // generate a fault for testing
2864
2865 ToggleChartOutlines();
2866 break;
2867 }
2868
2869 case WXK_PAUSE: // Drop MOB
2870 parent_frame->ActivateMOB();
2871 break;
2872
2873 // NUMERIC PAD
2874 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2875 case WXK_PAGEUP: {
2876 ZoomCanvas(g_plus_minus_zoom_factor, false);
2877 break;
2878 }
2879 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2880 case WXK_PAGEDOWN: {
2881 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2882 break;
2883 }
2884 case WXK_DELETE:
2885 case WXK_BACK:
2886 if (m_bMeasure_Active) {
2887 if (m_nMeasureState > 2) {
2888 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2889 m_pMeasureRoute->m_lastMousePointIndex =
2890 m_pMeasureRoute->GetnPoints();
2891 m_nMeasureState--;
2892 gFrame->RefreshAllCanvas();
2893 } else {
2894 CancelMeasureRoute();
2895 StartMeasureRoute();
2896 }
2897 }
2898 break;
2899 default:
2900 break;
2901 }
2902
2903 if (event.GetKeyCode() < 128) // ascii
2904 {
2905 int key_char = event.GetKeyCode();
2906
2907 // Handle both QWERTY and AZERTY keyboard separately for a few control
2908 // codes
2909 if (!g_b_assume_azerty) {
2910 switch (key_char) {
2911 case '+':
2912 case '=':
2913 ZoomCanvas(g_plus_minus_zoom_factor, false);
2914 break;
2915
2916 case '-':
2917 case '_':
2918 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2919 break;
2920 }
2921
2922#ifdef __WXMAC__
2923 if (g_benable_rotate) {
2924 switch (key_char) {
2925 // On other platforms these are handled in OnKeyChar, which
2926 // (apparently) works better in some locales. On OS X it is better
2927 // to handle them here, since pressing Alt (which should change the
2928 // rotation speed) changes the key char and so prevents the keys
2929 // from working.
2930 case ']':
2931 RotateCanvas(1);
2932 break;
2933
2934 case '[':
2935 RotateCanvas(-1);
2936 break;
2937
2938 case '\\':
2939 DoRotateCanvas(0);
2940 break;
2941 }
2942 }
2943#endif
2944 } else { // AZERTY
2945 switch (key_char) {
2946 case 43:
2947 ZoomCanvas(g_plus_minus_zoom_factor, false);
2948 break;
2949
2950 case 54: // '-' alpha/num pad
2951 // case 56: // '_' alpha/num pad
2952 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2953 break;
2954 }
2955 }
2956
2957 if (event.ControlDown()) key_char -= 64;
2958
2959 if (key_char >= '0' && key_char <= '9')
2960 SetGroupIndex(key_char - '0');
2961 else
2962
2963 switch (key_char) {
2964 case 'A':
2965 SetShowENCAnchor(!GetShowENCAnchor());
2966 ReloadVP();
2967
2968 break;
2969
2970 case 'C':
2971 parent_frame->ToggleColorScheme();
2972 break;
2973
2974 case 'D': {
2975 int x, y;
2976 event.GetPosition(&x, &y);
2977 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
2978 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
2979 // First find out what kind of chart is being used
2980 if (!pPopupDetailSlider) {
2981 if (VPoint.b_quilt) {
2982 if (m_pQuilt) {
2983 if (m_pQuilt->GetChartAtPix(
2984 VPoint,
2985 wxPoint(
2986 x, y))) // = null if no chart loaded for this point
2987 {
2988 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2989 ->GetChartType();
2990 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2991 ->GetChartFamily();
2992 }
2993 }
2994 } else {
2995 if (m_singleChart) {
2996 ChartType = m_singleChart->GetChartType();
2997 ChartFam = m_singleChart->GetChartFamily();
2998 }
2999 }
3000 // If a charttype is found show the popupslider
3001 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3002 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3003 pPopupDetailSlider = new PopUpDSlide(
3004 this, -1, ChartType, ChartFam,
3005 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3006 wxDefaultSize, wxSIMPLE_BORDER, _T(""));
3007 if (pPopupDetailSlider) pPopupDetailSlider->Show();
3008 }
3009 } else //( !pPopupDetailSlider ) close popupslider
3010 {
3011 if (pPopupDetailSlider) pPopupDetailSlider->Close();
3012 pPopupDetailSlider = NULL;
3013 }
3014 break;
3015 }
3016
3017 case 'L':
3018 SetShowENCLights(!GetShowENCLights());
3019 ReloadVP();
3020
3021 break;
3022
3023 case 'M':
3024 if (event.ShiftDown())
3025 m_bMeasure_DistCircle = true;
3026 else
3027 m_bMeasure_DistCircle = false;
3028
3029 StartMeasureRoute();
3030 break;
3031
3032 case 'N':
3033 if (g_bInlandEcdis && ps52plib) {
3034 SetENCDisplayCategory((_DisCat)STANDARD);
3035 }
3036 break;
3037
3038 case 'O':
3039 ToggleChartOutlines();
3040 break;
3041
3042 case 'Q':
3043 ToggleCanvasQuiltMode();
3044 break;
3045
3046 case 'P':
3047 parent_frame->ToggleTestPause();
3048 break;
3049#if 0
3050 case 'R':
3051 parent_frame->ToggleRocks();
3052 break;
3053#endif
3054 case 'S':
3055 SetShowENCDepth(!m_encShowDepth);
3056 ReloadVP();
3057 break;
3058
3059 case 'T':
3060 SetShowENCText(!GetShowENCText());
3061 ReloadVP();
3062 break;
3063
3064 case 'U':
3065 SetShowENCDataQual(!GetShowENCDataQual());
3066 ReloadVP();
3067 break;
3068
3069 case 'V':
3070 m_bShowNavobjects = !m_bShowNavobjects;
3071 Refresh(true);
3072 break;
3073
3074 case 'W': // W Toggle CPA alarm
3075 ToggleCPAWarn();
3076
3077 break;
3078
3079 case 1: // Ctrl A
3080 TogglebFollow();
3081
3082 break;
3083
3084 case 2: // Ctrl B
3085 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3086 break;
3087
3088 case 13: // Ctrl M // Drop Marker at cursor
3089 {
3090 if (event.ControlDown()) gFrame->DropMarker(false);
3091 break;
3092 }
3093
3094 case 14: // Ctrl N - Activate next waypoint in a route
3095 {
3096 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3097 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3098 if ((indexActive + 1) <= r->GetnPoints()) {
3099 g_pRouteMan->ActivateNextPoint(r, true);
3100 InvalidateGL();
3101 Refresh(false);
3102 }
3103 }
3104 break;
3105 }
3106
3107 case 15: // Ctrl O - Drop Marker at boat's position
3108 {
3109 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3110 break;
3111 }
3112
3113 case 32: // Special needs use space bar
3114 {
3115 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3116 break;
3117 }
3118
3119 case -32: // Ctrl Space // Drop MOB
3120 {
3121 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3122
3123 break;
3124 }
3125
3126 case -20: // Ctrl ,
3127 {
3128 parent_frame->DoSettings();
3129 break;
3130 }
3131 case 17: // Ctrl Q
3132 parent_frame->Close();
3133 return;
3134
3135 case 18: // Ctrl R
3136 StartRoute();
3137 return;
3138
3139 case 20: // Ctrl T
3140 if (NULL == pGoToPositionDialog) // There is one global instance of
3141 // the Go To Position Dialog
3142 pGoToPositionDialog = new GoToPositionDialog(this);
3143 pGoToPositionDialog->SetCanvas(this);
3144 pGoToPositionDialog->Show();
3145 break;
3146
3147 case 25: // Ctrl Y
3148 if (undo->AnythingToRedo()) {
3149 undo->RedoNextAction();
3150 InvalidateGL();
3151 Refresh(false);
3152 }
3153 break;
3154
3155 case 26:
3156 if (event.ShiftDown()) { // Shift-Ctrl-Z
3157 if (undo->AnythingToRedo()) {
3158 undo->RedoNextAction();
3159 InvalidateGL();
3160 Refresh(false);
3161 }
3162 } else { // Ctrl Z
3163 if (undo->AnythingToUndo()) {
3164 undo->UndoLastAction();
3165 InvalidateGL();
3166 Refresh(false);
3167 }
3168 }
3169 break;
3170
3171 case 27:
3172 // Generic break
3173 if (m_bMeasure_Active) {
3174 CancelMeasureRoute();
3175
3176 SetCursor(*pCursorArrow);
3177
3178 SurfaceToolbar();
3179 gFrame->RefreshAllCanvas();
3180 }
3181
3182 if (m_routeState) // creating route?
3183 {
3184 FinishRoute();
3185 SurfaceToolbar();
3186 InvalidateGL();
3187 Refresh(false);
3188 }
3189
3190 break;
3191
3192 case 7: // Ctrl G
3193 switch (gamma_state) {
3194 case (0):
3195 r_gamma_mult = 0;
3196 g_gamma_mult = 1;
3197 b_gamma_mult = 0;
3198 gamma_state = 1;
3199 break;
3200 case (1):
3201 r_gamma_mult = 1;
3202 g_gamma_mult = 0;
3203 b_gamma_mult = 0;
3204 gamma_state = 2;
3205 break;
3206 case (2):
3207 r_gamma_mult = 1;
3208 g_gamma_mult = 1;
3209 b_gamma_mult = 1;
3210 gamma_state = 0;
3211 break;
3212 }
3213 SetScreenBrightness(g_nbrightness);
3214
3215 break;
3216
3217 case 9: // Ctrl I
3218 if (event.ControlDown()) {
3219 m_bShowCompassWin = !m_bShowCompassWin;
3220 SetShowGPSCompassWindow(m_bShowCompassWin);
3221 Refresh(false);
3222 }
3223 break;
3224
3225 default:
3226 break;
3227
3228 } // switch
3229 }
3230
3231#ifndef __WXMAC__
3232 // Allow OnKeyChar to catch the key events too.
3233 // On OS X this is unnecessary since we handle all key events here.
3234 if (!b_handled) event.Skip();
3235#endif
3236}
3237
3238void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3239 if (g_pi_manager)
3240 if (g_pi_manager->SendKeyEventToPlugins(event))
3241 return; // PlugIn did something, and does not want the canvas to do
3242 // anything else
3243
3244 switch (event.GetKeyCode()) {
3245 case WXK_TAB:
3246 parent_frame->SwitchKBFocus(this);
3247 break;
3248
3249 case WXK_LEFT:
3250 case WXK_RIGHT:
3251 m_panx = 0;
3252 if (!m_pany) m_panspeed = 0;
3253 break;
3254
3255 case WXK_UP:
3256 case WXK_DOWN:
3257 m_pany = 0;
3258 if (!m_panx) m_panspeed = 0;
3259 break;
3260
3261 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3262 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3263 case WXK_PAGEUP:
3264 case WXK_PAGEDOWN:
3265 if (m_mustmove) DoMovement(m_mustmove);
3266
3267 m_zoom_factor = 1;
3268 break;
3269
3270 case WXK_ALT:
3271 m_modkeys &= ~wxMOD_ALT;
3272#ifdef OCPN_ALT_MENUBAR
3273#ifndef __WXOSX__
3274 // If the permanent menu bar is disabled, and we are not in the middle of
3275 // another key combo, then show the menu bar temporarily when Alt is
3276 // released (or hide it if already visible).
3277 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3278 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3279 parent_frame->ApplyGlobalSettings(false);
3280 }
3281 m_bMayToggleMenuBar = true;
3282#endif
3283#endif
3284 break;
3285
3286 case WXK_CONTROL:
3287 m_modkeys &= ~wxMOD_CONTROL;
3288 break;
3289 }
3290
3291 if (event.GetKeyCode() < 128) // ascii
3292 {
3293 int key_char = event.GetKeyCode();
3294
3295 // Handle both QWERTY and AZERTY keyboard separately for a few control
3296 // codes
3297 if (!g_b_assume_azerty) {
3298 switch (key_char) {
3299 case '+':
3300 case '=':
3301 case '-':
3302 case '_':
3303 case 54:
3304 case 56: // '_' alpha/num pad
3305 DoMovement(m_mustmove);
3306
3307 // m_zoom_factor = 1;
3308 break;
3309 case '[':
3310 case ']':
3311 DoMovement(m_mustmove);
3312 m_rotation_speed = 0;
3313 break;
3314 }
3315 } else {
3316 switch (key_char) {
3317 case 43:
3318 case 54: // '-' alpha/num pad
3319 case 56: // '_' alpha/num pad
3320 DoMovement(m_mustmove);
3321
3322 m_zoom_factor = 1;
3323 break;
3324 }
3325 }
3326 }
3327 event.Skip();
3328}
3329
3330void ChartCanvas::ToggleChartOutlines(void) {
3331 m_bShowOutlines = !m_bShowOutlines;
3332
3333 Refresh(false);
3334
3335#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3336 // needs a full refresh
3337 if (g_bopengl) InvalidateGL();
3338#endif
3339}
3340
3341void ChartCanvas::ToggleLookahead() {
3342 m_bLookAhead = !m_bLookAhead;
3343 m_OSoffsetx = 0; // center ownship
3344 m_OSoffsety = 0;
3345}
3346
3347void ChartCanvas::SetUpMode(int mode) {
3348 m_upMode = mode;
3349
3350 if (mode != NORTH_UP_MODE) {
3351 // Stuff the COGAvg table in case COGUp is selected
3352 double stuff = 0;
3353 if (!std::isnan(gCog)) stuff = gCog;
3354
3355 if (g_COGAvgSec > 0) {
3356 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3357 }
3358 g_COGAvg = stuff;
3359 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3360 } else {
3361 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3362 SetVPRotation(GetVPSkew());
3363 else
3364 SetVPRotation(0); /* reset to north up */
3365 }
3366
3367 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3368 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3369
3370 UpdateGPSCompassStatusBox(true);
3371 gFrame->DoChartUpdate();
3372}
3373
3374bool ChartCanvas::DoCanvasCOGSet(void) {
3375 if (GetUpMode() == NORTH_UP_MODE) return false;
3376
3377 if (std::isnan(g_COGAvg)) return true;
3378
3379 double old_VPRotate = m_VPRotate;
3380
3381 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3382 m_VPRotate = -gHdt * PI / 180.;
3383 } else if (GetUpMode() == COURSE_UP_MODE)
3384 m_VPRotate = -g_COGAvg * PI / 180.;
3385
3386 SetVPRotation(m_VPRotate);
3387 bool bnew_chart = DoCanvasUpdate();
3388
3389 if ((bnew_chart) || (old_VPRotate != m_VPRotate)) ReloadVP();
3390
3391 return true;
3392}
3393
3394void ChartCanvas::StopMovement() {
3395 m_panx = m_pany = 0;
3396 m_panspeed = 0;
3397 m_zoom_factor = 1;
3398 m_rotation_speed = 0;
3399 m_mustmove = 0;
3400#if 0
3401#if !defined(__WXGTK__) && !defined(__WXQT__)
3402 SetFocus();
3403 gFrame->Raise();
3404#endif
3405#endif
3406}
3407
3408/* instead of integrating in timer callbacks
3409 (which do not always get called fast enough)
3410 we can perform the integration of movement
3411 at each render frame based on the time change */
3412bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3413 // Start/restart the stop movement timer
3414 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3415
3416 if (!pMovementTimer->IsRunning()) {
3417 // printf("timer not running, starting\n");
3418 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3419 }
3420
3421 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3422 // already moving, gets called again because of key-repeat event
3423 return false;
3424 }
3425
3426 m_last_movement_time = wxDateTime::UNow();
3427
3428 /* jumpstart because paint gets called right away, if we want first frame to
3429 * move */
3430 // m_last_movement_time -= wxTimeSpan::Milliseconds(100);
3431
3432 // Refresh( false );
3433
3434 return true;
3435}
3436
3437void ChartCanvas::DoTimedMovement() {
3438 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3439 !m_rotation_speed)
3440 return; /* not moving */
3441
3442 wxDateTime now = wxDateTime::UNow();
3443 long dt = 0;
3444 if (m_last_movement_time.IsValid())
3445 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3446
3447 m_last_movement_time = now;
3448
3449 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3450 dt = 500;
3451
3452 DoMovement(dt);
3453}
3454
3455void ChartCanvas::DoMovement(long dt) {
3456 /* if we get here quickly assume 1ms so that some movement occurs */
3457 if (dt == 0) dt = 1;
3458
3459 m_mustmove -= dt;
3460 if (m_mustmove < 0) m_mustmove = 0;
3461
3462 if (m_pan_drag.x || m_pan_drag.y) {
3463 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3464 m_pan_drag.x = m_pan_drag.y = 0;
3465 }
3466
3467 if (m_panx || m_pany) {
3468 const double slowpan = .1, maxpan = 2;
3469 if (m_modkeys == wxMOD_ALT)
3470 m_panspeed = slowpan;
3471 else {
3472 m_panspeed += (double)dt / 500; /* apply acceleration */
3473 m_panspeed = wxMin(maxpan, m_panspeed);
3474 }
3475 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3476 }
3477
3478 if (m_zoom_factor != 1) {
3479 double alpha = 400, beta = 1.5;
3480 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3481
3482 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3483
3484 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3485
3486 // Try to hit the zoom target exactly.
3487 // if(m_wheelzoom_stop_oneshot > 0)
3488 {
3489 if (zoom_factor > 1) {
3490 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3491 zoom_factor = VPoint.chart_scale / m_zoom_target;
3492 }
3493
3494 else if (zoom_factor < 1) {
3495 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3496 zoom_factor = VPoint.chart_scale / m_zoom_target;
3497 }
3498 }
3499
3500 if (fabs(zoom_factor - 1) > 1e-4)
3501 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3502
3503 if (m_wheelzoom_stop_oneshot > 0) {
3504 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3505 m_wheelzoom_stop_oneshot = 0;
3506 StopMovement();
3507 }
3508
3509 // Don't overshoot the zoom target.
3510 if (zoom_factor > 1) {
3511 if (VPoint.chart_scale <= m_zoom_target) {
3512 m_wheelzoom_stop_oneshot = 0;
3513 StopMovement();
3514 }
3515 } else if (zoom_factor < 1) {
3516 if (VPoint.chart_scale >= m_zoom_target) {
3517 m_wheelzoom_stop_oneshot = 0;
3518 StopMovement();
3519 }
3520 }
3521 }
3522 }
3523
3524 if (m_rotation_speed) { /* in degrees per second */
3525 double speed = m_rotation_speed;
3526 if (m_modkeys == wxMOD_ALT) speed /= 10;
3527 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3528 }
3529}
3530
3531void ChartCanvas::SetColorScheme(ColorScheme cs) {
3532 SetAlertString(_T(""));
3533
3534 // Setup ownship image pointers
3535 switch (cs) {
3536 case GLOBAL_COLOR_SCHEME_DAY:
3537 m_pos_image_red = &m_os_image_red_day;
3538 m_pos_image_grey = &m_os_image_grey_day;
3539 m_pos_image_yellow = &m_os_image_yellow_day;
3540 m_pos_image_user = m_pos_image_user_day;
3541 m_pos_image_user_grey = m_pos_image_user_grey_day;
3542 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3543 m_cTideBitmap = m_bmTideDay;
3544 m_cCurrentBitmap = m_bmCurrentDay;
3545
3546 break;
3547 case GLOBAL_COLOR_SCHEME_DUSK:
3548 m_pos_image_red = &m_os_image_red_dusk;
3549 m_pos_image_grey = &m_os_image_grey_dusk;
3550 m_pos_image_yellow = &m_os_image_yellow_dusk;
3551 m_pos_image_user = m_pos_image_user_dusk;
3552 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3553 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3554 m_cTideBitmap = m_bmTideDusk;
3555 m_cCurrentBitmap = m_bmCurrentDusk;
3556 break;
3557 case GLOBAL_COLOR_SCHEME_NIGHT:
3558 m_pos_image_red = &m_os_image_red_night;
3559 m_pos_image_grey = &m_os_image_grey_night;
3560 m_pos_image_yellow = &m_os_image_yellow_night;
3561 m_pos_image_user = m_pos_image_user_night;
3562 m_pos_image_user_grey = m_pos_image_user_grey_night;
3563 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3564 m_cTideBitmap = m_bmTideNight;
3565 m_cCurrentBitmap = m_bmCurrentNight;
3566 break;
3567 default:
3568 m_pos_image_red = &m_os_image_red_day;
3569 m_pos_image_grey = &m_os_image_grey_day;
3570 m_pos_image_yellow = &m_os_image_yellow_day;
3571 m_pos_image_user = m_pos_image_user_day;
3572 m_pos_image_user_grey = m_pos_image_user_grey_day;
3573 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3574 m_cTideBitmap = m_bmTideDay;
3575 m_cCurrentBitmap = m_bmCurrentDay;
3576 break;
3577 }
3578
3579 CreateDepthUnitEmbossMaps(cs);
3580 CreateOZEmbossMapData(cs);
3581
3582 // Set up fog effect base color
3583 m_fog_color = wxColor(
3584 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3585 float dim = 1.0;
3586 switch (cs) {
3587 case GLOBAL_COLOR_SCHEME_DUSK:
3588 dim = 0.5;
3589 break;
3590 case GLOBAL_COLOR_SCHEME_NIGHT:
3591 dim = 0.25;
3592 break;
3593 default:
3594 break;
3595 }
3596 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3597 m_fog_color.Blue() * dim);
3598
3599 // Really dark
3600#if 0
3601 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3602 SetBackgroundColour( wxColour(0,0,0) );
3603
3604 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3605 }
3606 else{
3607 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3608#ifndef __WXMAC__
3609 SetBackgroundColour( wxNullColour );
3610#endif
3611 }
3612#endif
3613
3614 UpdateToolbarColorScheme(cs);
3615
3616 m_Piano->SetColorScheme(cs);
3617
3618 m_Compass->SetColorScheme(cs);
3619
3620 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3621
3622 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3623#ifdef ocpnUSE_GL
3624 if (g_bopengl && m_glcc) {
3625 m_glcc->SetColorScheme(cs);
3626 g_glTextureManager->ClearAllRasterTextures();
3627 // m_glcc->FlushFBO();
3628 }
3629#endif
3630 SetbTCUpdate(true); // force re-render of tide/current locators
3631 m_brepaint_piano = true;
3632
3633 ReloadVP();
3634
3635 m_cs = cs;
3636}
3637
3638wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3639 wxImage img = Bitmap.ConvertToImage();
3640 int sx = img.GetWidth();
3641 int sy = img.GetHeight();
3642
3643 wxImage new_img(img);
3644
3645 for (int i = 0; i < sx; i++) {
3646 for (int j = 0; j < sy; j++) {
3647 if (!img.IsTransparent(i, j)) {
3648 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3649 (unsigned char)(img.GetGreen(i, j) * factor),
3650 (unsigned char)(img.GetBlue(i, j) * factor));
3651 }
3652 }
3653 }
3654
3655 wxBitmap ret = wxBitmap(new_img);
3656
3657 return ret;
3658}
3659
3660void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3661 int max) {
3662 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3663 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3664
3665 if (!m_pBrightPopup) {
3666 // Calculate size
3667 int x, y;
3668 GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3669
3670 m_pBrightPopup = new TimedPopupWin(this, 3);
3671
3672 m_pBrightPopup->SetSize(x, y);
3673 m_pBrightPopup->Move(120, 120);
3674 }
3675
3676 int bmpsx = m_pBrightPopup->GetSize().x;
3677 int bmpsy = m_pBrightPopup->GetSize().y;
3678
3679 wxBitmap bmp(bmpsx, bmpsx);
3680 wxMemoryDC mdc(bmp);
3681
3682 mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3683 mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3684 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3685 mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3686 mdc.Clear();
3687
3688 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3689
3690 mdc.SetFont(*pfont);
3691 wxString val;
3692
3693 if (brightness == max)
3694 val = _T("MAX");
3695 else if (brightness == min)
3696 val = _T("MIN");
3697 else
3698 val.Printf(_T("%3d"), brightness);
3699
3700 mdc.DrawText(val, 0, 0);
3701
3702 mdc.SelectObject(wxNullBitmap);
3703
3704 m_pBrightPopup->SetBitmap(bmp);
3705 m_pBrightPopup->Show();
3706 m_pBrightPopup->Refresh();
3707}
3708
3709void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3710 m_b_rot_hidef = true;
3711 ReloadVP();
3712}
3713
3714void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3715 if (!g_bRollover) return;
3716
3717 bool b_need_refresh = false;
3718
3719 wxSize win_size = GetSize() * m_displayScale;
3720 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3721
3722 // Handle the AIS Rollover Window first
3723 bool showAISRollover = false;
3724 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3725 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
3726 SelectItem *pFind = pSelectAIS->FindSelection(
3727 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3728 if (pFind) {
3729 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3730 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3731
3732 if (ptarget) {
3733 showAISRollover = true;
3734
3735 if (NULL == m_pAISRolloverWin) {
3736 m_pAISRolloverWin = new RolloverWin(this);
3737 m_pAISRolloverWin->IsActive(false);
3738 b_need_refresh = true;
3739 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3740 m_AISRollover_MMSI != FoundAIS_MMSI) {
3741 // Sometimes the mouse moves fast enough to get over a new AIS
3742 // target before the one-shot has fired to remove the old target.
3743 // Result: wrong target data is shown.
3744 // Detect this case,close the existing rollover ASAP, and restart
3745 // the timer.
3746 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3747 m_pAISRolloverWin->IsActive(false);
3748 m_AISRollover_MMSI = 0;
3749 Refresh();
3750 return;
3751 }
3752
3753 m_AISRollover_MMSI = FoundAIS_MMSI;
3754
3755 if (!m_pAISRolloverWin->IsActive()) {
3756 wxString s = ptarget->GetRolloverString();
3757 m_pAISRolloverWin->SetString(s);
3758
3759 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3760 AIS_ROLLOVER, win_size);
3761 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3762 m_pAISRolloverWin->IsActive(true);
3763 b_need_refresh = true;
3764 }
3765 }
3766 } else {
3767 m_AISRollover_MMSI = 0;
3768 showAISRollover = false;
3769 }
3770 }
3771
3772 // Maybe turn the rollover off
3773 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3774 m_pAISRolloverWin->IsActive(false);
3775 m_AISRollover_MMSI = 0;
3776 b_need_refresh = true;
3777 }
3778
3779 // Now the Route info rollover
3780 // Show the route segment info
3781 bool showRouteRollover = false;
3782
3783 if (NULL == m_pRolloverRouteSeg) {
3784 // Get a list of all selectable sgements, and search for the first
3785 // visible segment as the rollover target.
3786
3787 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
3788 SelectableItemList SelList = pSelect->FindSelectionList(
3789 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3790 wxSelectableItemListNode *node = SelList.GetFirst();
3791 while (node) {
3792 SelectItem *pFindSel = node->GetData();
3793
3794 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3795
3796 if (pr && pr->IsVisible()) {
3797 m_pRolloverRouteSeg = pFindSel;
3798 showRouteRollover = true;
3799
3800 if (NULL == m_pRouteRolloverWin) {
3801 m_pRouteRolloverWin = new RolloverWin(this, 10);
3802 m_pRouteRolloverWin->IsActive(false);
3803 }
3804
3805 if (!m_pRouteRolloverWin->IsActive()) {
3806 wxString s;
3807 RoutePoint *segShow_point_a =
3808 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3809 RoutePoint *segShow_point_b =
3810 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3811
3812 double brg, dist;
3813 DistanceBearingMercator(
3814 segShow_point_b->m_lat, segShow_point_b->m_lon,
3815 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3816
3817 if (!pr->m_bIsInLayer)
3818 s.Append(_("Route") + _T(": "));
3819 else
3820 s.Append(_("Layer Route: "));
3821
3822 if (pr->m_RouteNameString.IsEmpty())
3823 s.Append(_("(unnamed)"));
3824 else
3825 s.Append(pr->m_RouteNameString);
3826
3827 s << _T("\n") << _("Total Length: ")
3828 << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
3829 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
3830 << segShow_point_b->GetName() << _T("\n");
3831
3832 if (g_bShowTrue)
3833 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
3834 0x00B0);
3835 if (g_bShowMag) {
3836 double latAverage =
3837 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
3838 double lonAverage =
3839 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
3840 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
3841
3842 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
3843 0x00B0);
3844 }
3845
3846 s << FormatDistanceAdaptive(dist);
3847
3848 // Compute and display cumulative distance from route start point to
3849 // current leg end point and RNG,TTG,ETA from ship to current leg end
3850 // point for active route
3851 double shiptoEndLeg = 0.;
3852 bool validActive = false;
3853 if (pr->IsActive() &&
3854 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
3855 validActive = true;
3856
3857 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
3858 wxRoutePointListNode *node =
3859 (pr->pRoutePointList)->GetFirst()->GetNext();
3860 RoutePoint *prp;
3861 float dist_to_endleg = 0;
3862 wxString t;
3863
3864 while (node) {
3865 prp = node->GetData();
3866 if (validActive)
3867 shiptoEndLeg += prp->m_seg_len;
3868 else if (prp->m_bIsActive)
3869 validActive = true;
3870 dist_to_endleg += prp->m_seg_len;
3871 if (prp->IsSame(segShow_point_a)) break;
3872 node = node->GetNext();
3873 }
3874 s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
3875 }
3876 // write from ship to end selected leg point data if the route is
3877 // active
3878 if (validActive) {
3879 s << _T("\n") << _("From Ship To") << _T(" ")
3880 << segShow_point_b->GetName() << _T("\n");
3881 shiptoEndLeg +=
3882 g_pRouteMan
3883 ->GetCurrentRngToActivePoint(); // add distance from ship
3884 // to active point
3885 shiptoEndLeg +=
3886 segShow_point_b
3887 ->m_seg_len; // add the lenght of the selected leg
3888 s << FormatDistanceAdaptive(shiptoEndLeg);
3889 // ensure sog/cog are valid and vmg is positive to keep data
3890 // coherent
3891 double vmg = 0.;
3892 if (!std::isnan(gCog) && !std::isnan(gSog))
3893 vmg = gSog *
3894 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
3895 PI / 180.);
3896 if (vmg > 0.) {
3897 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
3898 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
3899 s << _T(" - ")
3900 << wxString(ttg_sec > SECONDS_PER_DAY
3901 ? ttg_span.Format(_("%Dd %H:%M"))
3902 : ttg_span.Format(_("%H:%M")));
3903 wxDateTime dtnow, eta;
3904 eta = dtnow.SetToCurrent().Add(ttg_span);
3905 s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
3906 << eta.Format(_T(" %d %H:%M"));
3907 } else
3908 s << _T(" ---- ----");
3909 }
3910 m_pRouteRolloverWin->SetString(s);
3911
3912 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3913 LEG_ROLLOVER, win_size);
3914 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
3915 m_pRouteRolloverWin->IsActive(true);
3916 b_need_refresh = true;
3917 showRouteRollover = true;
3918 break;
3919 }
3920 } else
3921 node = node->GetNext();
3922 }
3923 } else {
3924 // Is the cursor still in select radius, and not timed out?
3925 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
3926 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
3927 m_pRolloverRouteSeg))
3928 showRouteRollover = false;
3929 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
3930 showRouteRollover = false;
3931 else
3932 showRouteRollover = true;
3933 }
3934
3935 // If currently creating a route, do not show this rollover window
3936 if (m_routeState) showRouteRollover = false;
3937
3938 // Similar for AIS target rollover window
3939 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
3940 showRouteRollover = false;
3941
3942 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
3943 !showRouteRollover) {
3944 m_pRouteRolloverWin->IsActive(false);
3945 m_pRolloverRouteSeg = NULL;
3946 m_pRouteRolloverWin->Destroy();
3947 m_pRouteRolloverWin = NULL;
3948 b_need_refresh = true;
3949 } else if (m_pRouteRolloverWin && showRouteRollover) {
3950 m_pRouteRolloverWin->IsActive(true);
3951 b_need_refresh = true;
3952 }
3953
3954 // Now the Track info rollover
3955 // Show the track segment info
3956 bool showTrackRollover = false;
3957
3958 if (NULL == m_pRolloverTrackSeg) {
3959 // Get a list of all selectable sgements, and search for the first
3960 // visible segment as the rollover target.
3961
3962 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
3963 SelectableItemList SelList = pSelect->FindSelectionList(
3964 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
3965 wxSelectableItemListNode *node = SelList.GetFirst();
3966 while (node) {
3967 SelectItem *pFindSel = node->GetData();
3968
3969 Track *pt = (Track *)pFindSel->m_pData3; // candidate
3970
3971 if (pt && pt->IsVisible()) {
3972 m_pRolloverTrackSeg = pFindSel;
3973 showTrackRollover = true;
3974
3975 if (NULL == m_pTrackRolloverWin) {
3976 m_pTrackRolloverWin = new RolloverWin(this, 10);
3977 m_pTrackRolloverWin->IsActive(false);
3978 }
3979
3980 if (!m_pTrackRolloverWin->IsActive()) {
3981 wxString s;
3982 TrackPoint *segShow_point_a =
3983 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
3984 TrackPoint *segShow_point_b =
3985 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
3986
3987 double brg, dist;
3988 DistanceBearingMercator(
3989 segShow_point_b->m_lat, segShow_point_b->m_lon,
3990 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3991
3992 if (!pt->m_bIsInLayer)
3993 s.Append(_("Track") + _T(": "));
3994 else
3995 s.Append(_("Layer Track: "));
3996
3997 if (pt->GetName().IsEmpty())
3998 s.Append(_("(unnamed)"));
3999 else
4000 s.Append(pt->GetName());
4001 double tlenght = pt->Length();
4002 s << _T("\n") << _("Total Track: ")
4003 << FormatDistanceAdaptive(tlenght);
4004 if (pt->GetLastPoint()->GetTimeString() &&
4005 pt->GetPoint(0)->GetTimeString()) {
4006 wxTimeSpan ttime = pt->GetLastPoint()->GetCreateTime() -
4007 pt->GetPoint(0)->GetCreateTime();
4008 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4009 s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
4010 << getUsrSpeedUnit();
4011 s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
4012 : ttime.Format(_T(" %H:%M")));
4013 }
4014 if (g_bShowTrackPointTime && segShow_point_b->GetTimeString())
4015 s << _T("\n") << _("Segment Created: ")
4016 << segShow_point_b->GetTimeString();
4017
4018 s << _T("\n");
4019 if (g_bShowTrue)
4020 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4021 0x00B0);
4022
4023 if (g_bShowMag) {
4024 double latAverage =
4025 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4026 double lonAverage =
4027 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4028 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4029
4030 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4031 0x00B0);
4032 }
4033
4034 s << FormatDistanceAdaptive(dist);
4035
4036 if (segShow_point_a->GetTimeString() &&
4037 segShow_point_b->GetTimeString()) {
4038 double segmentSpeed =
4039 toUsrSpeed(dist / ((segShow_point_b->GetCreateTime() -
4040 segShow_point_a->GetCreateTime())
4041 .GetSeconds()
4042 .ToDouble() /
4043 3600.));
4044 s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4045 << getUsrSpeedUnit();
4046 }
4047
4048 m_pTrackRolloverWin->SetString(s);
4049
4050 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4051 LEG_ROLLOVER, win_size);
4052 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4053 m_pTrackRolloverWin->IsActive(true);
4054 b_need_refresh = true;
4055 showTrackRollover = true;
4056 break;
4057 }
4058 } else
4059 node = node->GetNext();
4060 }
4061 } else {
4062 // Is the cursor still in select radius, and not timed out?
4063 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
4064 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4065 m_pRolloverTrackSeg))
4066 showTrackRollover = false;
4067 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4068 showTrackRollover = false;
4069 else
4070 showTrackRollover = true;
4071 }
4072
4073 // Similar for AIS target rollover window
4074 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4075 showTrackRollover = false;
4076
4077 // Similar for route rollover window
4078 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4079 showTrackRollover = false;
4080
4081 // TODO We onlt show tracks on primary canvas....
4082 // if(!IsPrimaryCanvas())
4083 // showTrackRollover = false;
4084
4085 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4086 !showTrackRollover) {
4087 m_pTrackRolloverWin->IsActive(false);
4088 m_pRolloverTrackSeg = NULL;
4089 m_pTrackRolloverWin->Destroy();
4090 m_pTrackRolloverWin = NULL;
4091 b_need_refresh = true;
4092 } else if (m_pTrackRolloverWin && showTrackRollover) {
4093 m_pTrackRolloverWin->IsActive(true);
4094 b_need_refresh = true;
4095 }
4096
4097 if (b_need_refresh) Refresh();
4098}
4099
4100void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4101 if (s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4102 extendedSectorLegs)) {
4103 if (!m_bsectors_shown) {
4104 ReloadVP(false);
4105 m_bsectors_shown = true;
4106 }
4107 } else {
4108 if (m_bsectors_shown) {
4109 ReloadVP(false);
4110 m_bsectors_shown = false;
4111 }
4112 }
4113
4114// This is here because GTK status window update is expensive..
4115// cairo using pango rebuilds the font every time so is very
4116// inefficient
4117// Anyway, only update the status bar when this timer expires
4118#if defined(__WXGTK__) || defined(__WXQT__)
4119 {
4120 // Check the absolute range of the cursor position
4121 // There could be a window wherein the chart geoereferencing is not
4122 // valid....
4123 double cursor_lat, cursor_lon;
4124 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4125
4126 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4127 while (cursor_lon < -180.) cursor_lon += 360.;
4128
4129 while (cursor_lon > 180.) cursor_lon -= 360.;
4130
4131 SetCursorStatus(cursor_lat, cursor_lon);
4132 }
4133 }
4134#endif
4135}
4136
4137void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4138 if (!parent_frame->m_pStatusBar) return;
4139
4140 wxString s1;
4141 s1 += _T(" ");
4142 s1 += toSDMM(1, cursor_lat);
4143 s1 += _T(" ");
4144 s1 += toSDMM(2, cursor_lon);
4145
4146 if (STAT_FIELD_CURSOR_LL >= 0)
4147 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4148
4149 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4150
4151 double brg, dist;
4152 wxString s;
4153 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4154 if (g_bShowMag)
4155 s.Printf("%03d%c(M) ", (int)gFrame->GetMag(brg), 0x00B0);
4156 else
4157 s.Printf("%03d%c ", (int)brg, 0x00B0);
4158
4159 s << FormatDistanceAdaptive(dist);
4160
4161 // CUSTOMIZATION - LIVE ETA OPTION
4162 // -------------------------------------------------------
4163 // Calculate an "live" ETA based on route starting from the current
4164 // position of the boat and goes to the cursor of the mouse.
4165 // In any case, an standard ETA will be calculated with a default speed
4166 // of the boat to give an estimation of the route (in particular if GPS
4167 // is off).
4168
4169 // Display only if option "live ETA" is selected in Settings > Display >
4170 // General.
4171 if (g_bShowLiveETA) {
4172 float realTimeETA;
4173 float boatSpeed;
4174 float boatSpeedDefault = g_defaultBoatSpeed;
4175
4176 // Calculate Estimate Time to Arrival (ETA) in minutes
4177 // Check before is value not closed to zero (it will make an very big
4178 // number...)
4179 if (!std::isnan(gSog)) {
4180 boatSpeed = gSog;
4181 if (boatSpeed < 0.5) {
4182 realTimeETA = 0;
4183 } else {
4184 realTimeETA = dist / boatSpeed * 60;
4185 }
4186 } else {
4187 realTimeETA = 0;
4188 }
4189
4190 // Add space after distance display
4191 s << " ";
4192 // Display ETA
4193 s << minutesToHoursDays(realTimeETA);
4194
4195 // In any case, display also an ETA with default speed at 6knts
4196
4197 s << " [@";
4198 s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4199 s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4200 s << " ";
4201 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4202 s << "]";
4203 }
4204 // END OF - LIVE ETA OPTION
4205
4206 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4207}
4208
4209// CUSTOMIZATION - FORMAT MINUTES
4210// -------------------------------------------------------
4211// New function to format minutes into a more readable format:
4212// * Hours + minutes, or
4213// * Days + hours.
4214wxString minutesToHoursDays(float timeInMinutes) {
4215 wxString s;
4216
4217 if (timeInMinutes == 0) {
4218 s << "--min";
4219 }
4220
4221 // Less than 60min, keep time in minutes
4222 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4223 s << wxString::Format(_T("%d"), (int)timeInMinutes);
4224 s << "min";
4225 }
4226
4227 // Between 1h and less than 24h, display time in hours, minutes
4228 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4229 int hours;
4230 int min;
4231 hours = (int)timeInMinutes / 60;
4232 min = (int)timeInMinutes % 60;
4233
4234 if (min == 0) {
4235 s << wxString::Format(_T("%d"), hours);
4236 s << "h";
4237 } else {
4238 s << wxString::Format(_T("%d"), hours);
4239 s << "h";
4240 s << wxString::Format(_T("%d"), min);
4241 s << "min";
4242 }
4243
4244 }
4245
4246 // More than 24h, display time in days, hours
4247 else if (timeInMinutes > 24 * 60) {
4248 int days;
4249 int hours;
4250 days = (int)(timeInMinutes / 60) / 24;
4251 hours = (int)(timeInMinutes / 60) % 24;
4252
4253 if (hours == 0) {
4254 s << wxString::Format(_T("%d"), days);
4255 s << "d";
4256 } else {
4257 s << wxString::Format(_T("%d"), days);
4258 s << "d";
4259 s << wxString::Format(_T("%d"), hours);
4260 s << "h";
4261 }
4262 }
4263
4264 return s;
4265}
4266
4267// END OF CUSTOMIZATION - FORMAT MINUTES
4268// Thanks open source code ;-)
4269// -------------------------------------------------------
4270
4271void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4272 double clat, clon;
4273 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4274 *lat = clat;
4275 *lon = clon;
4276}
4277
4278void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4279 wxPoint2DDouble *r) {
4280 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4281}
4282
4283void ChartCanvas::GetDoubleCanvasPointPixVP(ViewPort &vp, double rlat,
4284 double rlon, wxPoint2DDouble *r) {
4285 // If the Current Chart is a raster chart, and the
4286 // requested lat/long is within the boundaries of the chart,
4287 // and the VP is not rotated,
4288 // then use the embedded BSB chart georeferencing algorithm
4289 // for greater accuracy
4290 // Additionally, use chart embedded georef if the projection is TMERC
4291 // i.e. NOT MERCATOR and NOT POLYCONIC
4292
4293 // If for some reason the chart rejects the request by returning an error,
4294 // then fall back to Viewport Projection estimate from canvas parameters
4295 if (!g_bopengl && m_singleChart &&
4296 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4297 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4298 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4299 (m_singleChart->GetChartProjectionType() !=
4300 PROJECTION_TRANSVERSE_MERCATOR) &&
4301 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4302 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4303 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4304 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4305 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4306 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4307 // Cur_BSB_Ch->GetCOVRTablenPoints
4308 // ( 0 ), rlon,
4309 // rlat );
4310 // bInside = true;
4311 // if ( bInside )
4312 if (Cur_BSB_Ch) {
4313 // This is a Raster chart....
4314 // If the VP is changing, the raster chart parameters may not yet be
4315 // setup So do that before accessing the chart's embedded
4316 // georeferencing
4317 Cur_BSB_Ch->SetVPRasterParms(vp);
4318 double rpixxd, rpixyd;
4319 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4320 r->m_x = rpixxd;
4321 r->m_y = rpixyd;
4322 return;
4323 }
4324 }
4325 }
4326
4327 // if needed, use the VPoint scaling estimator,
4328 *r = vp.GetDoublePixFromLL(rlat, rlon);
4329}
4330
4331// This routine might be deleted and all of the rendering improved
4332// to have floating point accuracy
4333bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4334 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4335}
4336
4337bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4338 wxPoint *r) {
4339 wxPoint2DDouble p;
4340 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4341
4342 // some projections give nan values when invisible values (other side of
4343 // world) are requested we should stop using integer coordinates or return
4344 // false here (and test it everywhere)
4345 if (std::isnan(p.m_x)) {
4346 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4347 return false;
4348 }
4349
4350 if( (abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6) )
4351 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4352 else
4353 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4354
4355 return true;
4356}
4357
4358void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4359 double &lon) {
4360 // If the Current Chart is a raster chart, and the
4361 // requested x,y is within the boundaries of the chart,
4362 // and the VP is not rotated,
4363 // then use the embedded BSB chart georeferencing algorithm
4364 // for greater accuracy
4365 // Additionally, use chart embedded georef if the projection is TMERC
4366 // i.e. NOT MERCATOR and NOT POLYCONIC
4367
4368 // If for some reason the chart rejects the request by returning an error,
4369 // then fall back to Viewport Projection estimate from canvas parameters
4370 bool bUseVP = true;
4371
4372 if (!g_bopengl && m_singleChart &&
4373 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4374 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4375 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4376 (m_singleChart->GetChartProjectionType() !=
4377 PROJECTION_TRANSVERSE_MERCATOR) &&
4378 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4379 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4380 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4381 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4382
4383 // TODO maybe need iterative process to validate bInside
4384 // first pass is mercator, then check chart boundaries
4385
4386 if (Cur_BSB_Ch) {
4387 // This is a Raster chart....
4388 // If the VP is changing, the raster chart parameters may not yet be
4389 // setup So do that before accessing the chart's embedded
4390 // georeferencing
4391 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4392
4393 double slat, slon;
4394 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4395 lat = slat;
4396
4397 if (slon < -180.)
4398 slon += 360.;
4399 else if (slon > 180.)
4400 slon -= 360.;
4401
4402 lon = slon;
4403 bUseVP = false;
4404 }
4405 }
4406 }
4407
4408 // if needed, use the VPoint scaling estimator
4409 if (bUseVP) {
4410 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4411 }
4412}
4413
4414void ChartCanvas::ZoomCanvasSimple(double factor) {
4415 DoZoomCanvas(factor, false);
4416 extendedSectorLegs.clear();
4417}
4418
4419void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4420 bool stoptimer) {
4421 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4422
4423 if (g_bsmoothpanzoom) {
4424 if (StartTimedMovement(stoptimer)) {
4425 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4426 m_zoom_factor = factor;
4427 }
4428
4429 m_zoom_target = VPoint.chart_scale / factor;
4430 } else {
4431 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4432
4433 DoZoomCanvas(factor, can_zoom_to_cursor);
4434 }
4435
4436 extendedSectorLegs.clear();
4437}
4438
4439void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4440 // possible on startup
4441 if (!ChartData) return;
4442 if (!m_pCurrentStack) return;
4443
4444 if (g_bShowCompassWin) {
4445 m_bShowCompassWin = true;
4446 SetShowGPSCompassWindow(true); // Cancel effects of Ctrl-I
4447 }
4448
4449 /* TODO: queue the quilted loading code to a background thread
4450 so yield is never called from here, and also rendering is not delayed */
4451
4452 // Cannot allow Yield() re-entrancy here
4453 if (m_bzooming) return;
4454 m_bzooming = true;
4455
4456 double old_ppm = GetVP().view_scale_ppm;
4457
4458 // Capture current cursor position for zoom to cursor
4459 double zlat = m_cursor_lat;
4460 double zlon = m_cursor_lon;
4461
4462 double proposed_scale_onscreen =
4463 GetVP().chart_scale /
4464 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4465 bool b_do_zoom = false;
4466
4467 if (factor > 1) {
4468 b_do_zoom = true;
4469
4470 // double zoom_factor = factor;
4471
4472 ChartBase *pc = NULL;
4473
4474 if (!VPoint.b_quilt) {
4475 pc = m_singleChart;
4476 } else {
4477 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4478 if (new_db_index >= 0)
4479 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4480
4481 if (m_pCurrentStack)
4482 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4483 new_db_index); // highlite the correct bar entry
4484 }
4485
4486 if (pc) {
4487 // double target_scale_ppm = GetVPScale() * zoom_factor;
4488 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4489 // target_scale_ppm;
4490
4491 // Query the chart to determine the appropriate zoom range
4492 double min_allowed_scale =
4493 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4494
4495 if (proposed_scale_onscreen < min_allowed_scale) {
4496 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4497 m_zoom_factor = 1; /* stop zooming */
4498 b_do_zoom = false;
4499 } else
4500 proposed_scale_onscreen = min_allowed_scale;
4501 }
4502
4503 } else {
4504 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4505 }
4506
4507 } else if (factor < 1) {
4508 b_do_zoom = true;
4509
4510 ChartBase *pc = NULL;
4511
4512 bool b_smallest = false;
4513
4514 if (!VPoint.b_quilt) { // not quilted
4515 pc = m_singleChart;
4516
4517 if (pc) {
4518 // If m_singleChart is not on the screen, unbound the zoomout
4519 LLBBox viewbox = VPoint.GetBBox();
4520 // BoundingBox chart_box;
4521 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4522 double max_allowed_scale;
4523
4524 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4525
4526 // We can allow essentially unbounded zoomout in single chart mode
4527 // if( ChartData->GetDBBoundingBox( current_index,
4528 // &chart_box ) &&
4529 // !viewbox.IntersectOut( chart_box ) )
4530 // // Clamp the minimum scale zoom-out to the value
4531 // specified by the chart max_allowed_scale =
4532 // wxMin(max_allowed_scale, 4.0 *
4533 // pc->GetNormalScaleMax(
4534 // GetCanvasScaleFactor(),
4535 // GetCanvasWidth() ) );
4536 if (proposed_scale_onscreen > max_allowed_scale) {
4537 m_zoom_factor = 1; /* stop zooming */
4538 proposed_scale_onscreen = max_allowed_scale;
4539 }
4540 }
4541
4542 } else {
4543 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4544 if (new_db_index >= 0)
4545 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4546
4547 if (m_pCurrentStack)
4548 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4549 new_db_index); // highlite the correct bar entry
4550
4551 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4552
4553 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4554 proposed_scale_onscreen =
4555 wxMin(proposed_scale_onscreen,
4556 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4557 }
4558
4559 // set a minimum scale
4560 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4561 m_absolute_min_scale_ppm)
4562 b_do_zoom = false;
4563 }
4564
4565 double new_scale =
4566 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4567
4568 if (b_do_zoom) {
4569 if (can_zoom_to_cursor && g_bEnableZoomToCursor) {
4570 // Arrange to combine the zoom and pan into one operation for smoother
4571 // appearance
4572 SetVPScale(new_scale, false); // adjust, but deferred refresh
4573
4574 wxPoint r;
4575 GetCanvasPointPix(zlat, zlon, &r);
4576 PanCanvas(r.x - mouse_x, r.y - mouse_y); // this will give the Refresh()
4577 } else {
4578 SetVPScale(new_scale);
4579
4580 if (m_bFollow) DoCanvasUpdate();
4581 }
4582 }
4583
4584 m_bzooming = false;
4585}
4586int rot;
4587void ChartCanvas::RotateCanvas(double dir) {
4588 //SetUpMode(NORTH_UP_MODE);
4589
4590 if (g_bsmoothpanzoom) {
4591 if (StartTimedMovement()) {
4592 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4593 m_rotation_speed = dir * 60;
4594 }
4595 } else {
4596 double speed = dir * 10;
4597 if (m_modkeys == wxMOD_ALT) speed /= 20;
4598 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4599 }
4600}
4601
4602void ChartCanvas::DoRotateCanvas(double rotation) {
4603 while (rotation < 0) rotation += 2 * PI;
4604 while (rotation > 2 * PI) rotation -= 2 * PI;
4605
4606 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4607
4608 SetVPRotation(rotation);
4609 parent_frame->UpdateRotationState(VPoint.rotation);
4610}
4611
4612void ChartCanvas::DoTiltCanvas(double tilt) {
4613 while (tilt < 0) tilt = 0;
4614 while (tilt > .95) tilt = .95;
4615
4616 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4617
4618 VPoint.tilt = tilt;
4619 Refresh(false);
4620}
4621
4622void ChartCanvas::TogglebFollow(void) {
4623 if (!m_bFollow)
4624 SetbFollow();
4625 else
4626 ClearbFollow();
4627}
4628
4629void ChartCanvas::ClearbFollow(void) {
4630 m_bFollow = false; // update the follow flag
4631
4632 if (m_toolBar) m_toolBar->GetToolbar()->ToggleTool(ID_FOLLOW, false);
4633 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4634
4635 UpdateFollowButtonState();
4636
4637 DoCanvasUpdate();
4638 ReloadVP();
4639 parent_frame->SetChartUpdatePeriod();
4640}
4641
4642void ChartCanvas::SetbFollow(void) {
4643 JumpToPosition(gLat, gLon, GetVPScale());
4644 m_bFollow = true;
4645
4646 if (m_toolBar) m_toolBar->GetToolbar()->ToggleTool(ID_FOLLOW, true);
4647 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4648
4649 UpdateFollowButtonState();
4650
4651 // Is the OWNSHIP on-screen?
4652 // If not, then reset the OWNSHIP offset to 0 (center screen)
4653 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4654 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4655 m_OSoffsetx = 0;
4656 m_OSoffsety = 0;
4657 }
4658
4659 DoCanvasUpdate();
4660 ReloadVP();
4661 parent_frame->SetChartUpdatePeriod();
4662}
4663
4664void ChartCanvas::UpdateFollowButtonState(void) {
4665 if (m_muiBar) {
4666 if (!m_bFollow)
4667 m_muiBar->SetFollowButtonState(0);
4668 else {
4669 if (m_bLookAhead)
4670 m_muiBar->SetFollowButtonState(2);
4671 else
4672 m_muiBar->SetFollowButtonState(1);
4673 }
4674 }
4675
4676#ifdef __OCPN__ANDROID__
4677 if (!m_bFollow)
4678 androidSetFollowTool(0);
4679 else {
4680 if (m_bLookAhead)
4681 androidSetFollowTool(2);
4682 else
4683 androidSetFollowTool(1);
4684 }
4685#endif
4686}
4687
4688void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4689 if (lon > 180.0) lon -= 360.0;
4690 m_vLat = lat;
4691 m_vLon = lon;
4692 StopMovement();
4693 m_bFollow = false;
4694
4695 if (!GetQuiltMode()) {
4696 double skew = 0;
4697 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4698 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4699 } else {
4700 if (scale_ppm != GetVPScale()) {
4701 // XXX should be done in SetViewPoint
4702 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4703 AdjustQuiltRefChart();
4704 }
4705 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4706 }
4707
4708 ReloadVP();
4709
4710 if (m_toolBar) m_toolBar->GetToolbar()->ToggleTool(ID_FOLLOW, false);
4711
4712 UpdateFollowButtonState();
4713
4714 // TODO
4715 // if( g_pi_manager ) {
4716 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4717 // }
4718}
4719
4720bool ChartCanvas::PanCanvas(double dx, double dy) {
4721 if (!ChartData) return false;
4722
4723 extendedSectorLegs.clear();
4724
4725 // double clat = VPoint.clat, clon = VPoint.clon;
4726 double dlat, dlon;
4727 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
4728
4729 int iters = 0;
4730 for (;;) {
4731 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
4732
4733 if (iters++ > 5) return false;
4734 if (!std::isnan(dlat)) break;
4735
4736 dx *= .5, dy *= .5;
4737 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
4738 }
4739
4740 // avoid overshooting the poles
4741 if (dlat > 90)
4742 dlat = 90;
4743 else if (dlat < -90)
4744 dlat = -90;
4745
4746 if (dlon > 360.) dlon -= 360.;
4747 if (dlon < -360.) dlon += 360.;
4748
4749 // This should not really be necessary, but round-trip georef on some
4750 // charts is not perfect, So we can get creep on repeated unidimensional
4751 // pans, and corrupt chart cacheing.......
4752
4753 // But this only works on north-up projections
4754 // TODO: can we remove this now?
4755 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
4756 // .001 ) ) {
4757 //
4758 // if( dx == 0 ) dlon = clon;
4759 // if( dy == 0 ) dlat = clat;
4760 // }
4761
4762 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4763
4764 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
4765
4766 if (VPoint.b_quilt) {
4767 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4768 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
4769 // Tweak the scale slightly for a new ref chart
4770 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
4771 if (pc) {
4772 double tweak_scale_ppm =
4773 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
4774 SetVPScale(tweak_scale_ppm);
4775 }
4776 }
4777 }
4778
4779 // Turn off bFollow only if the ownship has left the screen
4780 double offx, offy;
4781 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
4782
4783 double offset_angle = atan2(offy, offx);
4784 double offset_distance = sqrt((offy * offy) + (offx * offx));
4785 double chart_angle = GetVPRotation();
4786 double target_angle = chart_angle - offset_angle;
4787 double d_east_mod = offset_distance * cos(target_angle);
4788 double d_north_mod = offset_distance * sin(target_angle);
4789
4790 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
4791 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
4792
4793 // m_OSoffsetx = offx * VPoint.view_scale_ppm;
4794 // m_OSoffsety = offy * VPoint.view_scale_ppm;
4795
4796 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4797 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
4798 m_bFollow = false; // update the follow flag
4799 if (m_toolBar) m_toolBar->GetToolbar()->ToggleTool(ID_FOLLOW, false);
4800
4801 UpdateFollowButtonState();
4802 }
4803
4804 Refresh(false);
4805
4806 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
4807
4808 return true;
4809}
4810
4811void ChartCanvas::ReloadVP(bool b_adjust) {
4812 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
4813
4814 LoadVP(VPoint, b_adjust);
4815}
4816
4817void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
4818#ifdef ocpnUSE_GL
4819 if (g_bopengl && m_glcc) {
4820 m_glcc->Invalidate();
4821 if (m_glcc->GetSize() != GetSize()) {
4822 m_glcc->SetSize(GetSize());
4823 }
4824 } else
4825#endif
4826 {
4827 m_cache_vp.Invalidate();
4828 m_bm_cache_vp.Invalidate();
4829 }
4830
4831 VPoint.Invalidate();
4832
4833 if (m_pQuilt) m_pQuilt->Invalidate();
4834
4835 // Make sure that the Selected Group is sensible...
4836 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
4837 // m_groupIndex = 0;
4838 // if( !CheckGroup( m_groupIndex ) )
4839 // m_groupIndex = 0;
4840
4841 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
4842 vp.m_projection_type, b_adjust);
4843
4844}
4845
4846void ChartCanvas::SetQuiltRefChart(int dbIndex) {
4847 m_pQuilt->SetReferenceChart(dbIndex);
4848 VPoint.Invalidate();
4849 m_pQuilt->Invalidate();
4850}
4851
4852double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
4853 if (m_pQuilt)
4854 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
4855 else
4856 return vp.view_scale_ppm;
4857}
4858
4859// Verify and adjust the current reference chart,
4860// so that it will not lead to excessive overzoom or underzoom onscreen
4861int ChartCanvas::AdjustQuiltRefChart() {
4862 int ret = -1;
4863 if (m_pQuilt) {
4864 wxASSERT(ChartData);
4865 ChartBase *pc =
4866 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
4867 if (pc) {
4868 double min_ref_scale =
4869 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
4870 double max_ref_scale =
4871 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
4872
4873 if (VPoint.chart_scale < min_ref_scale) {
4874 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
4875 } else if (VPoint.chart_scale > max_ref_scale) {
4876 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
4877 } else {
4878 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
4879
4880 int ref_family = pc->GetChartFamily();
4881
4882 if (!brender_ok) {
4883 unsigned int target_stack_index = 0;
4884 int target_stack_index_check =
4885 m_pQuilt->GetExtendedStackIndexArray()
4886 [m_pQuilt->GetRefChartdbIndex()]; // Lookup
4887
4888 if (wxNOT_FOUND != target_stack_index_check)
4889 target_stack_index = target_stack_index_check;
4890
4891 int extended_array_count =
4892 m_pQuilt->GetExtendedStackIndexArray().size();
4893 while ((!brender_ok) &&
4894 ((int)target_stack_index < (extended_array_count - 1))) {
4895 target_stack_index++;
4896 int test_db_index =
4897 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
4898
4899 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
4900 IsChartQuiltableRef(test_db_index)) {
4901 // open the target, and check the min_scale
4902 ChartBase *ptest_chart =
4903 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
4904 if (ptest_chart) {
4905 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
4906 }
4907 }
4908 }
4909
4910 if (brender_ok) { // found a better reference chart
4911 int new_db_index =
4912 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
4913 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
4914 IsChartQuiltableRef(new_db_index)) {
4915 m_pQuilt->SetReferenceChart(new_db_index);
4916 ret = new_db_index;
4917 } else
4918 ret = m_pQuilt->GetRefChartdbIndex();
4919 } else
4920 ret = m_pQuilt->GetRefChartdbIndex();
4921
4922 } else
4923 ret = m_pQuilt->GetRefChartdbIndex();
4924 }
4925 } else
4926 ret = -1;
4927 }
4928
4929 return ret;
4930}
4931
4932void ChartCanvas::UpdateCanvasOnGroupChange(void) {
4933 delete m_pCurrentStack;
4934 m_pCurrentStack = NULL;
4935 m_pCurrentStack = new ChartStack;
4936 wxASSERT(ChartData);
4937 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
4938 m_groupIndex);
4939
4940 if (m_pQuilt) {
4941 m_pQuilt->Compose(VPoint);
4942 SetFocus();
4943 }
4944}
4945
4946bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
4947 double latNE, double lonNE) {
4948 // Center Point
4949 double latc = (latSW + latNE) / 2.0;
4950 double lonc = (lonSW + lonNE) / 2.0;
4951
4952 // Get scale in ppm (latitude)
4953 double ne_easting, ne_northing;
4954 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
4955
4956 double sw_easting, sw_northing;
4957 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
4958
4959 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
4960
4961 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
4962}
4963
4964bool ChartCanvas::SetVPScale(double scale, bool refresh) {
4965 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
4966 VPoint.rotation, VPoint.m_projection_type, true, refresh);
4967}
4968
4969bool ChartCanvas::SetVPProjection(int projection) {
4970 if (!g_bopengl) // alternative projections require opengl
4971 return false;
4972
4973 // the view scale varies depending on geographic location and projection
4974 // rescale to keep the relative scale on the screen the same
4975 double prev_true_scale_ppm = m_true_scale_ppm;
4976 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
4977 VPoint.skew, VPoint.rotation, projection) &&
4978 SetVPScale(wxMax(
4979 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
4980 m_absolute_min_scale_ppm));
4981}
4982
4983bool ChartCanvas::SetViewPoint(double lat, double lon) {
4984 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
4985 VPoint.rotation);
4986}
4987
4988bool ChartCanvas::SetVPRotation(double angle) {
4989 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm, VPoint.skew,
4990 angle);
4991}
4992
4993bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
4994 double skew, double rotation, int projection,
4995 bool b_adjust, bool b_refresh) {
4996 bool b_ret = false;
4997
4998 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
4999 skew -= 2 * PI;
5000
5001 // Any sensible change?
5002 if (VPoint.IsValid()) {
5003 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5004 (fabs(VPoint.skew - skew) < 1e-9) &&
5005 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5006 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5007 (VPoint.m_projection_type == projection ||
5008 projection == PROJECTION_UNKNOWN))
5009 return false;
5010 }
5011
5012 if (VPoint.m_projection_type != projection)
5013 VPoint.InvalidateTransformCache(); // invalidate
5014
5015 // Take a local copy of the last viewport
5016 ViewPort last_vp = VPoint;
5017
5018 VPoint.skew = skew;
5019 VPoint.clat = lat;
5020 VPoint.clon = lon;
5021 VPoint.rotation = rotation;
5022 VPoint.view_scale_ppm = scale_ppm;
5023 if (projection != PROJECTION_UNKNOWN)
5024 VPoint.SetProjectionType(projection);
5025 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5026 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5027
5028 // don't allow latitude above 88 for mercator (90 is infinity)
5029 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5030 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5031 if (VPoint.clat > 89.5)
5032 VPoint.clat = 89.5;
5033 else if (VPoint.clat < -89.5)
5034 VPoint.clat = -89.5;
5035 }
5036
5037 // don't zoom out too far for transverse mercator polyconic until we resolve
5038 // issues
5039 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5040 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5041 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5042
5043 //SetVPRotation(rotation);
5044
5045 if (!g_bopengl) // tilt is not possible without opengl
5046 VPoint.tilt = 0;
5047
5048 if ((VPoint.pix_width <= 0) ||
5049 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5050 return false;
5051
5052 bool bwasValid = VPoint.IsValid();
5053 VPoint.Validate(); // Mark this ViewPoint as OK
5054
5055 // Has the Viewport scale changed? If so, invalidate the vp
5056 if (last_vp.view_scale_ppm != scale_ppm) {
5057 m_cache_vp.Invalidate();
5058 InvalidateGL();
5059 }
5060
5061 // A preliminary value, may be tweaked below
5062 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5063
5064 // recompute cursor position
5065 // and send to interested plugins if the mouse is actually in this window
5066
5067 const wxPoint pt = wxGetMousePosition();
5068 int mouseX = pt.x - GetScreenPosition().x;
5069 int mouseY = pt.y - GetScreenPosition().y;
5070 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5071 (mouseY < VPoint.pix_height)) {
5072 double lat, lon;
5073 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5074 m_cursor_lat = lat;
5075 m_cursor_lon = lon;
5076 if (g_pi_manager) g_pi_manager->SendCursorLatLonToAllPlugIns(lat, lon);
5077 }
5078
5079 if (!VPoint.b_quilt && m_singleChart) {
5080 VPoint.SetBoxes();
5081
5082 // Allow the chart to adjust the new ViewPort for performance optimization
5083 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5084 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5085
5086 // If there is a sensible change in the chart render, refresh the whole
5087 // screen
5088 if ((!m_cache_vp.IsValid()) ||
5089 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5090 Refresh(false);
5091 b_ret = true;
5092 } else {
5093 wxPoint cp_last, cp_this;
5094 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5095 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5096
5097 if (cp_last != cp_this) {
5098 Refresh(false);
5099 b_ret = true;
5100 }
5101 }
5102 // Create the stack
5103 if (m_pCurrentStack) {
5104 assert(ChartData != 0);
5105 int current_db_index;
5106 current_db_index =
5107 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5108
5109 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5110 m_groupIndex);
5111 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5112 }
5113
5114 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5115 }
5116
5117 // Handle the quilted case
5118 if (VPoint.b_quilt) {
5119 if (last_vp.view_scale_ppm != scale_ppm)
5120 m_pQuilt->InvalidateAllQuiltPatchs();
5121
5122 // Create the quilt
5123 if (ChartData /*&& ChartData->IsValid()*/) {
5124 if (!m_pCurrentStack) return false;
5125
5126 int current_db_index;
5127 current_db_index =
5128 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5129
5130 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5131 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5132
5133 // Check to see if the current quilt reference chart is in the new stack
5134 int current_ref_stack_index = -1;
5135 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5136 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5137 current_ref_stack_index = i;
5138 }
5139
5140 if (g_bFullScreenQuilt) {
5141 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5142 }
5143
5144 // We might need a new Reference Chart
5145 bool b_needNewRef = false;
5146
5147 // If the new stack does not contain the current ref chart....
5148 if ((-1 == current_ref_stack_index) &&
5149 (m_pQuilt->GetRefChartdbIndex() >= 0))
5150 b_needNewRef = true;
5151
5152 // Would the current Ref Chart be excessively underzoomed?
5153 // We need to check this here to be sure, since we cannot know where the
5154 // reference chart was assigned. For instance, the reference chart may
5155 // have been selected from the config file, or from a long jump with a
5156 // chart family switch implicit. Anyway, we check to be sure....
5157 bool renderable = true;
5158 ChartBase *referenceChart =
5159 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5160 if (referenceChart) {
5161 double chartMaxScale = referenceChart->GetNormalScaleMax(
5162 GetCanvasScaleFactor(), GetCanvasWidth());
5163 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5164 }
5165 if (!renderable) b_needNewRef = true;
5166
5167 // Need new refchart?
5168 if (b_needNewRef) {
5169 const ChartTableEntry &cte_ref =
5170 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5171 int target_scale = cte_ref.GetScale();
5172 int target_type = cte_ref.GetChartType();
5173 int candidate_stack_index;
5174
5175 // reset the ref chart in a way that does not lead to excessive
5176 // underzoom, for performance reasons Try to find a chart that is the
5177 // same type, and has a scale of just smaller than the current ref
5178 // chart
5179
5180 candidate_stack_index = 0;
5181 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5182 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5183 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5184 int candidate_scale = cte_candidate.GetScale();
5185 int candidate_type = cte_candidate.GetChartType();
5186
5187 if ((candidate_scale >= target_scale) &&
5188 (candidate_type == target_type)) {
5189 bool renderable = true;
5190 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5191 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5192 if (tentative_referenceChart) {
5193 double chartMaxScale =
5194 tentative_referenceChart->GetNormalScaleMax(
5195 GetCanvasScaleFactor(), GetCanvasWidth());
5196 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5197 }
5198
5199 if (renderable) break;
5200 }
5201
5202 candidate_stack_index++;
5203 }
5204
5205 // If that did not work, look for a chart of just larger scale and
5206 // same type
5207 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5208 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5209 while (candidate_stack_index >= 0) {
5210 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5211 if (idx >= 0) {
5212 const ChartTableEntry &cte_candidate =
5213 ChartData->GetChartTableEntry(idx);
5214 int candidate_scale = cte_candidate.GetScale();
5215 int candidate_type = cte_candidate.GetChartType();
5216
5217 if ((candidate_scale <= target_scale) &&
5218 (candidate_type == target_type))
5219 break;
5220 }
5221 candidate_stack_index--;
5222 }
5223 }
5224
5225 // and if that did not work, chose stack entry 0
5226 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5227 (candidate_stack_index < 0))
5228 candidate_stack_index = 0;
5229
5230 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5231
5232 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5233 }
5234
5235 if (!g_bopengl) {
5236 // Preset the VPoint projection type to match what the quilt projection
5237 // type will be
5238 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5239
5240 // Always keep the default Mercator projection if the reference chart is
5241 // not in the PatchList or the scale is too small for it to render.
5242
5243 bool renderable = true;
5244 ChartBase *referenceChart =
5245 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5246 if (referenceChart) {
5247 double chartMaxScale = referenceChart->GetNormalScaleMax(
5248 GetCanvasScaleFactor(), GetCanvasWidth());
5249 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5250 proj = ChartData->GetDBChartProj(ref_db_index);
5251 } else
5252 proj = PROJECTION_MERCATOR;
5253
5254 VPoint.b_MercatorProjectionOverride =
5255 (m_pQuilt->GetnCharts() == 0 || !renderable);
5256
5257 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5258
5259 VPoint.SetProjectionType(proj);
5260 }
5261
5262 VPoint.SetBoxes();
5263
5264 // If this quilt will be a perceptible delta from the existing quilt,
5265 // then refresh the entire screen
5266 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5267 // Allow the quilt to adjust the new ViewPort for performance
5268 // optimization This will normally be only a fractional (i.e.
5269 // sub-pixel) adjustment...
5270 if (b_adjust) m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5271
5272 // ChartData->ClearCacheInUseFlags();
5273 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5274
5275 // wxStopWatch sw;
5276
5277#ifdef __OCPN__ANDROID__
5278 // This is an optimization for panning on touch screen systems.
5279 // The quilt composition is deferred until the OnPaint() message gets
5280 // finally removed and processed from the message queue.
5281 // Takes advantage of the fact that touch-screen pan gestures are
5282 // usually short in distance,
5283 // so not requiring a full quilt rebuild until the pan gesture is
5284 // complete.
5285 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5286 // qDebug() << "Force compose";
5287 m_pQuilt->Compose(VPoint);
5288 } else {
5289 m_pQuilt->Invalidate();
5290 }
5291#else
5292 m_pQuilt->Compose(VPoint);
5293#endif
5294
5295 // printf("comp time %ld\n", sw.Time());
5296
5297 // If the extended chart stack has changed, invalidate any cached
5298 // render bitmap
5299 // if(m_pQuilt->GetXStackHash() != hash1) {
5300 // m_bm_cache_vp.Invalidate();
5301 // InvalidateGL();
5302 // }
5303
5304 ChartData->PurgeCacheUnusedCharts(0.7);
5305
5306 if (b_refresh) Refresh(false);
5307
5308 b_ret = true;
5309 }
5310 }
5311
5312 VPoint.skew = 0.; // Quilting supports 0 Skew
5313 } else if (!g_bopengl) {
5314 OcpnProjType projection = PROJECTION_UNKNOWN;
5315 if (m_singleChart) // viewport projection must match chart projection
5316 // without opengl
5317 projection = m_singleChart->GetChartProjectionType();
5318 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5319 VPoint.SetProjectionType(projection);
5320 }
5321
5322 // Has the Viewport projection changed? If so, invalidate the vp
5323 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5324 m_cache_vp.Invalidate();
5325 InvalidateGL();
5326 }
5327
5328 UpdateCanvasControlBar(); // Refresh the Piano
5329
5330 VPoint.chart_scale = 1.0; // fallback default value
5331
5332 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5333
5334 if (VPoint.GetBBox().GetValid()) {
5335 // Update the viewpoint reference scale
5336 if (m_singleChart)
5337 VPoint.ref_scale = m_singleChart->GetNativeScale();
5338 else
5339 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5340
5341 // Calculate the on-screen displayed actual scale
5342 // by a simple traverse northward from the center point
5343 // of roughly one eighth of the canvas height
5344 wxPoint2DDouble r, r1;
5345
5346 double delta_check =
5347 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5348 delta_check /= 8.;
5349
5350 double check_point = wxMin(89., VPoint.clat);
5351
5352 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5353
5354 double rhumbDist;
5355 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5356 VPoint.clon, 0, &rhumbDist);
5357
5358 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5359 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5360 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5361 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5362
5363 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5364
5365 // A fall back in case of very high zoom-out, giving delta_y == 0
5366 // which can probably only happen with vector charts
5367 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5368
5369 // Another fallback, for highly zoomed out charts
5370 // This adjustment makes the displayed TrueScale correspond to the
5371 // same algorithm used to calculate the chart zoom-out limit for
5372 // ChartDummy.
5373 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5374
5375 if (m_true_scale_ppm)
5376 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5377 else
5378 VPoint.chart_scale = 1.0;
5379
5380 // Create a nice renderable string
5381 double round_factor = 1000.;
5382 if (VPoint.chart_scale <= 1000.)
5383 round_factor = 10.;
5384 else if (VPoint.chart_scale <= 10000.)
5385 round_factor = 100.;
5386 else if (VPoint.chart_scale <= 100000.)
5387 round_factor = 1000.;
5388
5389 double true_scale_display =
5390 wxRound(VPoint.chart_scale / round_factor) * round_factor;
5391 wxString text;
5392
5393 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5394
5395 if (m_displayed_scale_factor > 10.0)
5396 text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5397 m_displayed_scale_factor);
5398 else if (m_displayed_scale_factor > 1.0)
5399 text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5400 m_displayed_scale_factor);
5401 else if (m_displayed_scale_factor > 0.1) {
5402 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5403 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5404 } else if (m_displayed_scale_factor > 0.01) {
5405 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5406 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5407 } else {
5408 text.Printf(
5409 _T("%s %4.0f (---)"), _("Scale"),
5410 true_scale_display); // Generally, no chart, so no chart scale factor
5411 }
5412
5413#ifdef ocpnUSE_GL
5414 if (g_bopengl && g_bShowFPS) {
5415 wxString fps_str;
5416 double fps = 0.;
5417 if (g_gl_ms_per_frame > 0) {
5418 fps = 1000. / g_gl_ms_per_frame;
5419 fps_str.Printf(_T(" %3d fps"), (int)fps);
5420 }
5421 text += fps_str;
5422 }
5423#endif
5424
5425 m_scaleValue = true_scale_display;
5426 m_scaleText = text;
5427 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5428
5429 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5430 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5431 // Check to see if the text will fit in the StatusBar field...
5432 bool b_noshow = false;
5433 {
5434 int w = 0;
5435 int h;
5436 wxClientDC dc(parent_frame->GetStatusBar());
5437 if (dc.IsOk()) {
5438 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5439 dc.SetFont(*templateFont);
5440 dc.GetTextExtent(text, &w, &h);
5441
5442 // If text is too long for the allocated field, try to reduce the text
5443 // string a bit.
5444 wxRect rect;
5445 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5446 if (w && w > rect.width) {
5447 text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5448 m_displayed_scale_factor);
5449 }
5450
5451 // Test again...if too big still, then give it up.
5452 dc.GetTextExtent(text, &w, &h);
5453
5454 if (w && w > rect.width) {
5455 b_noshow = true;
5456 }
5457 }
5458 }
5459
5460 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5461 }
5462 }
5463
5464 // Maintain member vLat/vLon
5465 m_vLat = VPoint.clat;
5466 m_vLon = VPoint.clon;
5467
5468 return b_ret;
5469}
5470
5471// Static Icon definitions for some symbols requiring
5472// scaling/rotation/translation Very specific wxDC draw commands are
5473// necessary to properly render these icons...See the code in
5474// ShipDraw()
5475
5476// This icon was adapted and scaled from the S52 Presentation Library
5477// version 3_03.
5478// Symbol VECGND02
5479
5480static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5481
5482// This ownship icon was adapted and scaled from the S52 Presentation
5483// Library version 3_03 Symbol OWNSHP05
5484static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5485 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5486
5487wxColour ChartCanvas::PredColor() {
5488 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5489 // visibility.
5490 if (SHIP_NORMAL == m_ownship_state)
5491 return GetGlobalColor(_T ( "URED" ));
5492
5493 else if (SHIP_LOWACCURACY == m_ownship_state)
5494 return GetGlobalColor(_T ( "YELO1" ));
5495
5496 return GetGlobalColor(_T ( "NODTA" ));
5497}
5498
5499wxColour ChartCanvas::ShipColor() {
5500 // Establish ship color
5501 // It changes color based on GPS and Chart accuracy/availability
5502
5503 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5504
5505 if (SHIP_LOWACCURACY == m_ownship_state)
5506 return GetGlobalColor(_T ( "YELO1" ));
5507
5508 return GetGlobalColor(_T ( "URED" )); // default is OK
5509}
5510
5511void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc, wxPoint lShipMidPoint) {
5512 dc.SetPen(wxPen(PredColor(), 2));
5513
5514 if (SHIP_NORMAL == m_ownship_state)
5515 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5516 else
5517 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5518
5519 dc.DrawEllipse(lShipMidPoint.x - 10, lShipMidPoint.y - 10, 20, 20);
5520 dc.DrawEllipse(lShipMidPoint.x - 6, lShipMidPoint.y - 6, 12, 12);
5521
5522 dc.DrawLine(lShipMidPoint.x - 12, lShipMidPoint.y, lShipMidPoint.x + 12,
5523 lShipMidPoint.y);
5524 dc.DrawLine(lShipMidPoint.x, lShipMidPoint.y - 12, lShipMidPoint.x,
5525 lShipMidPoint.y + 12);
5526}
5527
5528void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5529 wxPoint GPSOffsetPixels,
5530 wxPoint lGPSPoint) {
5531 // Develop a uniform length for course predictor line dash length, based on
5532 // physical display size Use this reference length to size all other graphics
5533 // elements
5534 float ref_dim = m_display_size_mm / 24;
5535 ref_dim = wxMin(ref_dim, 12);
5536 ref_dim = wxMax(ref_dim, 6);
5537
5538 wxColour cPred = PredColor();
5539
5540 // Establish some graphic element line widths dependent on the platform
5541 // display resolution
5542 // double nominal_line_width_pix = wxMax(1.0,
5543 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5544 // not less than 1 pixel
5545 double nominal_line_width_pix = wxMax(
5546 1.0,
5547 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5548
5549 // If the calculated value is greater than the config file spec value, then
5550 // use it.
5551 if (nominal_line_width_pix > g_cog_predictor_width)
5552 g_cog_predictor_width = nominal_line_width_pix;
5553
5554 // Calculate ownship Position Predictor
5555 wxPoint lPredPoint, lHeadPoint;
5556
5557 float pCog = std::isnan(gCog) ? 0 : gCog;
5558 float pSog = std::isnan(gSog) ? 0 : gSog;
5559
5560 double pred_lat, pred_lon;
5561 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5562 &pred_lat, &pred_lon);
5563 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5564
5565 // test to catch the case where COG/HDG line crosses the screen
5566 LLBBox box;
5567
5568 // Should we draw the Head vector?
5569 // Compare the points lHeadPoint and lPredPoint
5570 // If they differ by more than n pixels, and the head vector is valid, then
5571 // render the head vector
5572
5573 float ndelta_pix = 10.;
5574 double hdg_pred_lat, hdg_pred_lon;
5575 bool b_render_hdt = false;
5576 if (!std::isnan(gHdt)) {
5577 // Calculate ownship Heading pointer as a predictor
5578 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5579 &hdg_pred_lon);
5580 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5581 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5582 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5583 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5584 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5585 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5586 }
5587 }
5588
5589 // draw course over ground if they are longer than the ship
5590 wxPoint lShipMidPoint;
5591 lShipMidPoint.x = lGPSPoint.x + GPSOffsetPixels.x;
5592 lShipMidPoint.y = lGPSPoint.y + GPSOffsetPixels.y;
5593 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5594 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5595
5596 if (lpp >= img_height / 2) {
5597 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5598 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5599 !std::isnan(gSog)) {
5600 // COG Predictor
5601 float dash_length = ref_dim;
5602 wxDash dash_long[2];
5603 dash_long[0] =
5604 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5605 g_cog_predictor_width); // Long dash , in mm <---------+
5606 dash_long[1] = dash_long[0] / 2.0; // Short gap
5607
5608 // On ultra-hi-res displays, do not allow the dashes to be greater than
5609 // 250, since it is defined as (char)
5610 if (dash_length > 250.) {
5611 dash_long[0] = 250. / g_cog_predictor_width;
5612 dash_long[1] = dash_long[0] / 2;
5613 }
5614
5615 wxPen ppPen2(cPred, g_cog_predictor_width, wxPENSTYLE_USER_DASH);
5616 ppPen2.SetDashes(2, dash_long);
5617 dc.SetPen(ppPen2);
5618 dc.StrokeLine(
5619 lGPSPoint.x + GPSOffsetPixels.x, lGPSPoint.y + GPSOffsetPixels.y,
5620 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5621
5622 if (g_cog_predictor_width > 1) {
5623 float line_width = g_cog_predictor_width / 3.;
5624
5625 wxDash dash_long3[2];
5626 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
5627 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
5628
5629 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
5630 wxPENSTYLE_USER_DASH);
5631 ppPen3.SetDashes(2, dash_long3);
5632 dc.SetPen(ppPen3);
5633 dc.StrokeLine(
5634 lGPSPoint.x + GPSOffsetPixels.x, lGPSPoint.y + GPSOffsetPixels.y,
5635 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5636 }
5637
5638 // Prepare COG predictor endpoint icon
5639 double png_pred_icon_scale_factor = .4;
5640 if (g_ShipScaleFactorExp > 1.0)
5641 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5642 if (g_scaler)
5643 png_pred_icon_scale_factor *= 1.0 / g_scaler;
5644
5645 wxPoint icon[4];
5646
5647 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
5648 (float)(lPredPoint.x - lShipMidPoint.x));
5649 cog_rad += (float)PI;
5650
5651 for (int i = 0; i < 4; i++) {
5652 int j = i * 2;
5653 double pxa = (double)(s_png_pred_icon[j]);
5654 double pya = (double)(s_png_pred_icon[j + 1]);
5655
5656 pya *= png_pred_icon_scale_factor;
5657 pxa *= png_pred_icon_scale_factor;
5658
5659 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
5660 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
5661
5662 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
5663 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
5664 }
5665
5666 // Render COG endpoint icon
5667 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
5668 wxPENSTYLE_SOLID);
5669 dc.SetPen(ppPen1);
5670 dc.SetBrush(wxBrush(cPred));
5671
5672 dc.StrokePolygon(4, icon);
5673 }
5674 }
5675
5676 // HDT Predictor
5677 if (b_render_hdt) {
5678 float hdt_dash_length = ref_dim * 0.4;
5679
5680 float hdt_width = g_cog_predictor_width * 0.8;
5681 wxDash dash_short[2];
5682 dash_short[0] =
5683 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
5684 hdt_width); // Short dash , in mm <---------+
5685 dash_short[1] =
5686 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
5687 hdt_width); // Short gap |
5688
5689 wxPen ppPen2(cPred, hdt_width, wxPENSTYLE_USER_DASH);
5690 ppPen2.SetDashes(2, dash_short);
5691
5692 dc.SetPen(ppPen2);
5693 dc.StrokeLine(
5694 lGPSPoint.x + GPSOffsetPixels.x, lGPSPoint.y + GPSOffsetPixels.y,
5695 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
5696
5697 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
5698 dc.SetPen(ppPen1);
5699 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
5700
5701 double nominal_circle_size_pixels = wxMax(
5702 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
5703
5704 // Scale the circle to ChartScaleFactor, slightly softened....
5705 if (g_ShipScaleFactorExp > 1.0)
5706 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5707
5708 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
5709 lHeadPoint.y + GPSOffsetPixels.y,
5710 nominal_circle_size_pixels / 2);
5711 }
5712
5713 // Draw radar rings if activated
5714 if (g_iNavAidRadarRingsNumberVisible) {
5715 double factor = 1.00;
5716 if (g_pNavAidRadarRingsStepUnits == 1) // nautical miles
5717 factor = 1 / 1.852;
5718
5719 factor *= g_fNavAidRadarRingsStep;
5720
5721 double tlat, tlon;
5722 wxPoint r;
5723 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
5724 GetCanvasPointPix(tlat, tlon, &r);
5725
5726 double lpp = sqrt(pow((double)(lGPSPoint.x - r.x), 2) +
5727 pow((double)(lGPSPoint.y - r.y), 2));
5728 int pix_radius = (int)lpp;
5729
5730 extern wxColor GetDimColor(wxColor c);
5731 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
5732
5733 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
5734
5735 dc.SetPen(ppPen1);
5736 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
5737
5738 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
5739 dc.StrokeCircle(lGPSPoint.x, lGPSPoint.y, i * pix_radius);
5740 }
5741}
5742
5743void ChartCanvas::ComputeShipScaleFactor(
5744 float icon_hdt, int ownShipWidth, int ownShipLength, wxPoint &lShipMidPoint,
5745 wxPoint &GPSOffsetPixels, wxPoint lGPSPoint, float &scale_factor_x,
5746 float &scale_factor_y) {
5747 float screenResolution = m_pix_per_mm;
5748
5749 // Calculate the true ship length in exact pixels
5750 double ship_bow_lat, ship_bow_lon;
5751 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
5752 &ship_bow_lat, &ship_bow_lon);
5753 wxPoint lShipBowPoint;
5754 wxPoint2DDouble b_point =
5755 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
5756 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
5757
5758 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
5759 powf((float)(b_point.m_y - a_point.m_y), 2));
5760
5761 // And in mm
5762 float shipLength_mm = shipLength_px / screenResolution;
5763
5764 // Set minimum ownship drawing size
5765 float ownship_min_mm = g_n_ownship_min_mm;
5766 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
5767
5768 // Calculate Nautical Miles distance from midships to gps antenna
5769 float hdt_ant = icon_hdt + 180.;
5770 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
5771 float dx = g_n_gps_antenna_offset_x / 1852.;
5772 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
5773 {
5774 hdt_ant = icon_hdt;
5775 dy = -dy;
5776 }
5777
5778 // If the drawn ship size is going to be clamped, adjust the gps antenna
5779 // offsets
5780 if (shipLength_mm < ownship_min_mm) {
5781 dy /= shipLength_mm / ownship_min_mm;
5782 dx /= shipLength_mm / ownship_min_mm;
5783 }
5784
5785 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
5786
5787 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
5788 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
5789 &ship_mid_lon1);
5790
5791 GetCanvasPointPix(ship_mid_lat1, ship_mid_lon1, &lShipMidPoint);
5792 GPSOffsetPixels.x = lShipMidPoint.x - lGPSPoint.x;
5793 GPSOffsetPixels.y = lShipMidPoint.y - lGPSPoint.y;
5794
5795 float scale_factor = shipLength_px / ownShipLength;
5796
5797 // Calculate a scale factor that would produce a reasonably sized icon
5798 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
5799
5800 // And choose the correct one
5801 scale_factor = wxMax(scale_factor, scale_factor_min);
5802
5803 scale_factor_y = scale_factor;
5804 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
5805 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
5806}
5807
5808void ChartCanvas::ShipDraw(ocpnDC &dc) {
5809 if (!GetVP().IsValid()) return;
5810
5811 wxPoint lGPSPoint, lShipMidPoint, GPSOffsetPixels(0, 0);
5812
5813 // COG/SOG may be undefined in NMEA data stream
5814 float pCog = std::isnan(gCog) ? 0 : gCog;
5815 float pSog = std::isnan(gSog) ? 0 : gSog;
5816
5817 GetCanvasPointPix(gLat, gLon, &lGPSPoint);
5818 lShipMidPoint = lGPSPoint;
5819
5820 // Draw the icon rotated to the COG
5821 // or to the Hdt if available
5822 float icon_hdt = pCog;
5823 if (!std::isnan(gHdt)) icon_hdt = gHdt;
5824
5825 // COG may be undefined in NMEA data stream
5826 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
5827
5828 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
5829 // predictor
5830 double osd_head_lat, osd_head_lon;
5831 wxPoint osd_head_point;
5832
5833 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
5834 &osd_head_lon);
5835
5836 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
5837
5838 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.y),
5839 (float)(osd_head_point.x - lShipMidPoint.x));
5840 icon_rad += (float)PI;
5841
5842 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
5843
5844 // Another draw test ,based on pixels, assuming the ship icon is a fixed
5845 // nominal size and is just barely outside the viewport ....
5846 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
5847
5848 // TODO: fix to include actual size of boat that will be rendered
5849 int img_height = 0;
5850 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
5851 if (GetVP().chart_scale >
5852 300000) // According to S52, this should be 50,000
5853 {
5854 ShipDrawLargeScale(dc, lShipMidPoint);
5855 img_height = 20;
5856 } else {
5857 wxImage pos_image;
5858
5859 // Substitute user ownship image if found
5860 if (m_pos_image_user)
5861 pos_image = m_pos_image_user->Copy();
5862 else if (SHIP_NORMAL == m_ownship_state)
5863 pos_image = m_pos_image_red->Copy();
5864 if (SHIP_LOWACCURACY == m_ownship_state)
5865 pos_image = m_pos_image_yellow->Copy();
5866 else if (SHIP_NORMAL != m_ownship_state)
5867 pos_image = m_pos_image_grey->Copy();
5868
5869 // Substitute user ownship image if found
5870 if (m_pos_image_user) {
5871 pos_image = m_pos_image_user->Copy();
5872
5873 if (SHIP_LOWACCURACY == m_ownship_state)
5874 pos_image = m_pos_image_user_yellow->Copy();
5875 else if (SHIP_NORMAL != m_ownship_state)
5876 pos_image = m_pos_image_user_grey->Copy();
5877 }
5878
5879 img_height = pos_image.GetHeight();
5880
5881 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
5882 g_OwnShipIconType > 0) // use large ship
5883 {
5884 int ownShipWidth = 22; // Default values from s_ownship_icon
5885 int ownShipLength = 84;
5886 if (g_OwnShipIconType == 1) {
5887 ownShipWidth = pos_image.GetWidth();
5888 ownShipLength = pos_image.GetHeight();
5889 }
5890
5891 float scale_factor_x, scale_factor_y;
5892 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
5893 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
5894 scale_factor_x, scale_factor_y);
5895
5896 if (g_OwnShipIconType == 1) { // Scaled bitmap
5897 pos_image.Rescale(ownShipWidth * scale_factor_x,
5898 ownShipLength * scale_factor_y,
5899 wxIMAGE_QUALITY_HIGH);
5900 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
5901 wxImage rot_image =
5902 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
5903
5904 // Simple sharpening algorithm.....
5905 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
5906 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
5907 if (rot_image.GetAlpha(ip, jp) > 64)
5908 rot_image.SetAlpha(ip, jp, 255);
5909
5910 wxBitmap os_bm(rot_image);
5911
5912 int w = os_bm.GetWidth();
5913 int h = os_bm.GetHeight();
5914 img_height = h;
5915
5916 dc.DrawBitmap(os_bm, lShipMidPoint.x - w / 2, lShipMidPoint.y - h / 2,
5917 true);
5918
5919 // Maintain dirty box,, missing in __WXMSW__ library
5920 dc.CalcBoundingBox(lShipMidPoint.x - w / 2, lShipMidPoint.y - h / 2);
5921 dc.CalcBoundingBox(lShipMidPoint.x - w / 2 + w,
5922 lShipMidPoint.y - h / 2 + h);
5923 }
5924
5925 else if (g_OwnShipIconType == 2) { // Scaled Vector
5926 wxPoint ownship_icon[10];
5927
5928 for (int i = 0; i < 10; i++) {
5929 int j = i * 2;
5930 float pxa = (float)(s_ownship_icon[j]);
5931 float pya = (float)(s_ownship_icon[j + 1]);
5932 pya *= scale_factor_y;
5933 pxa *= scale_factor_x;
5934
5935 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
5936 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
5937
5938 ownship_icon[i].x = (int)(px) + lShipMidPoint.x;
5939 ownship_icon[i].y = (int)(py) + lShipMidPoint.y;
5940 }
5941
5942 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
5943 dc.SetPen(ppPen1);
5944 dc.SetBrush(wxBrush(ShipColor()));
5945
5946 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
5947
5948 // draw reference point (midships) cross
5949 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
5950 ownship_icon[7].y);
5951 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
5952 ownship_icon[9].y);
5953 }
5954
5955 img_height = ownShipLength * scale_factor_y;
5956
5957 // Reference point, where the GPS antenna is
5958 int circle_rad = 3;
5959 if (m_pos_image_user) circle_rad = 1;
5960
5961 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
5962 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
5963 dc.StrokeCircle(lGPSPoint.x, lGPSPoint.y, circle_rad);
5964 } else { // Fixed bitmap icon.
5965 /* non opengl, or suboptimal opengl via ocpndc: */
5966 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
5967 wxImage rot_image =
5968 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
5969
5970 // Simple sharpening algorithm.....
5971 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
5972 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
5973 if (rot_image.GetAlpha(ip, jp) > 64)
5974 rot_image.SetAlpha(ip, jp, 255);
5975
5976 wxBitmap os_bm(rot_image);
5977
5978 if (g_ShipScaleFactorExp > 1) {
5979 wxImage scaled_image = os_bm.ConvertToImage();
5980 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
5981 1.0; // soften the scale factor a bit
5982 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
5983 scaled_image.GetHeight() * factor,
5984 wxIMAGE_QUALITY_HIGH));
5985 }
5986 int w = os_bm.GetWidth();
5987 int h = os_bm.GetHeight();
5988 img_height = h;
5989
5990 dc.DrawBitmap(os_bm, lShipMidPoint.x - w / 2, lShipMidPoint.y - h / 2,
5991 true);
5992
5993 // Reference point, where the GPS antenna is
5994 int circle_rad = 3;
5995 if (m_pos_image_user) circle_rad = 1;
5996
5997 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
5998 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
5999 dc.StrokeCircle(lShipMidPoint.x, lShipMidPoint.y, circle_rad);
6000
6001 // Maintain dirty box,, missing in __WXMSW__ library
6002 dc.CalcBoundingBox(lShipMidPoint.x - w / 2, lShipMidPoint.y - h / 2);
6003 dc.CalcBoundingBox(lShipMidPoint.x - w / 2 + w,
6004 lShipMidPoint.y - h / 2 + h);
6005 }
6006 } // ownship draw
6007 }
6008
6009 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6010}
6011
6012/* @ChartCanvas::CalcGridSpacing
6013 **
6014 ** Calculate the major and minor spacing between the lat/lon grid
6015 **
6016 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6017 *window
6018 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6019 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6020 ** @return [void]
6021 */
6022void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6023 float &MinorSpacing) {
6024 // table for calculating the distance between the grids
6025 // [0] view_scale ppm
6026 // [1] spacing between major grid lines in degrees
6027 // [2] spacing between minor grid lines in degrees
6028 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6029 {.000001f, 45.0f, 15.0f},
6030 {.0002f, 30.0f, 10.0f},
6031 {.0003f, 10.0f, 2.0f},
6032 {.0008f, 5.0f, 1.0f},
6033 {.001f, 2.0f, 30.0f / 60.0f},
6034 {.003f, 1.0f, 20.0f / 60.0f},
6035 {.006f, 0.5f, 10.0f / 60.0f},
6036 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6037 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6038 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6039 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6040 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6041 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6042 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6043 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6044
6045 unsigned int tabi;
6046 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6047 if (view_scale_ppm < lltab[tabi][0]) break;
6048 MajorSpacing = lltab[tabi][1]; // major latitude distance
6049 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6050 return;
6051}
6052/* @ChartCanvas::CalcGridText *************************************
6053 **
6054 ** Calculates text to display at the major grid lines
6055 **
6056 ** @param [r] latlon [float] latitude or longitude of grid line
6057 ** @param [r] spacing [float] distance between two major grid lines
6058 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6059 **
6060 ** @return
6061 */
6062
6063wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6064 int deg = (int)fabs(latlon); // degrees
6065 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6066 char postfix;
6067
6068 // calculate postfix letter (NSEW)
6069 if (latlon > 0.0) {
6070 if (bPostfix) {
6071 postfix = 'N';
6072 } else {
6073 postfix = 'E';
6074 }
6075 } else if (latlon < 0.0) {
6076 if (bPostfix) {
6077 postfix = 'S';
6078 } else {
6079 postfix = 'W';
6080 }
6081 } else {
6082 postfix = ' '; // no postfix for equator and greenwich
6083 }
6084 // calculate text, display minutes only if spacing is smaller than one degree
6085
6086 wxString ret;
6087 if (spacing >= 1.0) {
6088 ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6089 } else if (spacing >= (1.0 / 60.0)) {
6090 ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6091 } else {
6092 ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6093 }
6094
6095 return ret;
6096}
6097
6098/* @ChartCanvas::GridDraw *****************************************
6099 **
6100 ** Draws major and minor Lat/Lon Grid on the chart
6101 ** - distance between Grid-lm ines are calculated automatic
6102 ** - major grid lines will be across the whole chart window
6103 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6104 **
6105 ** @param [w] dc [wxDC&] the wx drawing context
6106 **
6107 ** @return [void]
6108 ************************************************************************/
6109void ChartCanvas::GridDraw(ocpnDC &dc) {
6110 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6111
6112 double nlat, elon, slat, wlon;
6113 float lat, lon;
6114 float dlon;
6115 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6116 wxCoord w, h;
6117 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6118 dc.SetPen(GridPen);
6119 dc.SetFont(*m_pgridFont);
6120 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6121
6122 w = m_canvas_width;
6123 h = m_canvas_height;
6124
6125 GetCanvasPixPoint(0, 0, nlat,
6126 wlon); // get lat/lon of upper left point of the window
6127 GetCanvasPixPoint(w, h, slat,
6128 elon); // get lat/lon of lower right point of the window
6129 dlon =
6130 elon -
6131 wlon; // calculate how many degrees of longitude are shown in the window
6132 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6133 {
6134 dlon = dlon + 360.0;
6135 }
6136 // calculate distance between latitude grid lines
6137 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6138
6139 // calculate position of first major latitude grid line
6140 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6141
6142 // Draw Major latitude grid lines and text
6143 while (lat < nlat) {
6144 wxPoint r;
6145 wxString st =
6146 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6147 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6148 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6149 dc.DrawText(st, 0, r.y); // draw text
6150 lat = lat + gridlatMajor;
6151
6152 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6153 }
6154
6155 // calculate position of first minor latitude grid line
6156 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6157
6158 // Draw minor latitude grid lines
6159 while (lat < nlat) {
6160 wxPoint r;
6161 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6162 dc.DrawLine(0, r.y, 10, r.y, false);
6163 dc.DrawLine(w - 10, r.y, w, r.y, false);
6164 lat = lat + gridlatMinor;
6165 }
6166
6167 // calculate distance between grid lines
6168 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6169
6170 // calculate position of first major latitude grid line
6171 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6172
6173 // draw major longitude grid lines
6174 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6175 wxPoint r;
6176 wxString st = CalcGridText(lon, gridlonMajor, false);
6177 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6178 dc.DrawLine(r.x, 0, r.x, h, false);
6179 dc.DrawText(st, r.x, 0);
6180 lon = lon + gridlonMajor;
6181 if (lon > 180.0) {
6182 lon = lon - 360.0;
6183 }
6184
6185 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6186 }
6187
6188 // calculate position of first minor longitude grid line
6189 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6190 // draw minor longitude grid lines
6191 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6192 wxPoint r;
6193 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6194 dc.DrawLine(r.x, 0, r.x, 10, false);
6195 dc.DrawLine(r.x, h - 10, r.x, h, false);
6196 lon = lon + gridlonMinor;
6197 if (lon > 180.0) {
6198 lon = lon - 360.0;
6199 }
6200 }
6201}
6202
6203void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6204 if (0 ) {
6205 double blat, blon, tlat, tlon;
6206 wxPoint r;
6207
6208 int x_origin = m_bDisplayGrid ? 60 : 20;
6209 int y_origin = m_canvas_height - 50;
6210
6211 float dist;
6212 int count;
6213 wxPen pen1, pen2;
6214
6215 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6216 {
6217 dist = 10.0;
6218 count = 5;
6219 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6220 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6221 } else // Draw 1 mile scale as SCALEB10
6222 {
6223 dist = 1.0;
6224 count = 10;
6225 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6226 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6227 }
6228
6229 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6230 double rotation = -VPoint.rotation;
6231
6232 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6233 GetCanvasPointPix(tlat, tlon, &r);
6234 int l1 = (y_origin - r.y) / count;
6235
6236 for (int i = 0; i < count; i++) {
6237 int y = l1 * i;
6238 if (i & 1)
6239 dc.SetPen(pen1);
6240 else
6241 dc.SetPen(pen2);
6242
6243 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6244 }
6245 } else {
6246 double blat, blon, tlat, tlon;
6247
6248 int x_origin = 5.0 * GetPixPerMM();
6249 int chartbar_height = GetChartbarHeight();
6250 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6251 // if (style->chartStatusWindowTransparent)
6252 // chartbar_height = 0;
6253 int y_origin = m_canvas_height - chartbar_height - 5;
6254
6255 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6256 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6257
6258 double d;
6259 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6260 d /= 2;
6261
6262 int unit = g_iDistanceFormat;
6263 if (d < .5 &&
6264 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6265 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6266
6267 // nice number
6268 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6269 float places = floor(logdist), rem = logdist - places;
6270 dist = pow(10, places);
6271
6272 if (rem < .2)
6273 dist /= 5;
6274 else if (rem < .5)
6275 dist /= 2;
6276
6277 wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6278 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6279 double rotation = -VPoint.rotation;
6280
6281 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6282 &tlat, &tlon);
6283 wxPoint r;
6284 GetCanvasPointPix(tlat, tlon, &r);
6285 int l1 = r.x - x_origin;
6286
6287 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6288 12); // Store this for later reference
6289
6290 dc.SetPen(pen1);
6291
6292 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6293 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6294 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6295
6296 dc.SetFont(*m_pgridFont);
6297 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6298 int w, h;
6299 dc.GetTextExtent(s, &w, &h);
6300 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6301 }
6302}
6303
6304void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6305 // Constants?
6306 double da_min = 2.;
6307 double da_max = 6.;
6308 double ra_min = 0.;
6309 double ra_max = 40.;
6310
6311 wxPen pen_save = dc.GetPen();
6312
6313 wxDateTime now = wxDateTime::Now();
6314
6315 dc.SetPen(pen);
6316
6317 int x0, y0, x1, y1;
6318
6319 x0 = x1 = x + radius; // Start point
6320 y0 = y1 = y;
6321 double angle = 0.;
6322 int i = 0;
6323
6324 while (angle < 360.) {
6325 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6326 angle += da;
6327
6328 if (angle > 360.) angle = 360.;
6329
6330 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6331
6332 double r;
6333 if (i & 1)
6334 r = radius + ra;
6335 else
6336 r = radius - ra;
6337
6338 x1 = (int)(x + cos(angle * PI / 180.) * r);
6339 y1 = (int)(y + sin(angle * PI / 180.) * r);
6340
6341 dc.DrawLine(x0, y0, x1, y1);
6342
6343 x0 = x1;
6344 y0 = y1;
6345
6346 i++;
6347 }
6348
6349 dc.DrawLine(x + radius, y, x1, y1); // closure
6350
6351 dc.SetPen(pen_save);
6352}
6353
6354static bool bAnchorSoundPlaying = false;
6355
6356static void onSoundFinished(void *ptr) { bAnchorSoundPlaying = false; }
6357
6358void ChartCanvas::AlertDraw(ocpnDC &dc) {
6359 // Just for prototyping, visual alert for anchorwatch goes here
6360 bool play_sound = false;
6361 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6362 if (AnchorAlertOn1) {
6363 wxPoint TargetPoint;
6364 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6365 &TargetPoint);
6366 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6367 TargetPoint.y, 100);
6368 play_sound = true;
6369 }
6370 } else
6371 AnchorAlertOn1 = false;
6372
6373 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6374 if (AnchorAlertOn2) {
6375 wxPoint TargetPoint;
6376 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6377 &TargetPoint);
6378 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6379 TargetPoint.y, 100);
6380 play_sound = true;
6381 }
6382 } else
6383 AnchorAlertOn2 = false;
6384
6385 if (play_sound && !bAnchorSoundPlaying) {
6386 auto cmd_sound = dynamic_cast<SystemCmdSound*>(g_anchorwatch_sound);
6387 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6388 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6389 if (g_anchorwatch_sound->IsOk()) {
6390 bAnchorSoundPlaying = true;
6391 g_anchorwatch_sound->SetFinishedCallback(onSoundFinished, NULL);
6392 g_anchorwatch_sound->Play();
6393 }
6394 } else if (g_anchorwatch_sound->IsOk()) {
6395 g_anchorwatch_sound->Stop();
6396 }
6397}
6398// End of prototype anchor watch alerting-----------------------
6399
6400void ChartCanvas::UpdateShips() {
6401 // Get the rectangle in the current dc which bounds the "ownship" symbol
6402
6403 wxClientDC dc(this);
6404 if (!dc.IsOk()) return;
6405
6406 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6407 wxMemoryDC temp_dc(test_bitmap);
6408
6409 temp_dc.ResetBoundingBox();
6410 temp_dc.DestroyClippingRegion();
6411 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6412
6413 // Draw the ownship on the temp_dc
6414 ocpnDC ocpndc = ocpnDC(temp_dc);
6415 ShipDraw(ocpndc);
6416
6417 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6418 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6419 if (p) {
6420 wxPoint px;
6421 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6422 ocpndc.CalcBoundingBox(px.x, px.y);
6423 }
6424 }
6425
6426 ship_draw_rect =
6427 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6428 temp_dc.MaxY() - temp_dc.MinY());
6429
6430 wxRect own_ship_update_rect = ship_draw_rect;
6431
6432 if (!own_ship_update_rect.IsEmpty()) {
6433 // The required invalidate rectangle is the union of the last drawn
6434 // rectangle and this drawn rectangle
6435 own_ship_update_rect.Union(ship_draw_last_rect);
6436 own_ship_update_rect.Inflate(2);
6437 }
6438
6439 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6440
6441 ship_draw_last_rect = ship_draw_rect;
6442
6443 temp_dc.SelectObject(wxNullBitmap);
6444}
6445
6446void ChartCanvas::UpdateAlerts() {
6447 // Get the rectangle in the current dc which bounds the detected Alert
6448 // targets
6449
6450 // Use this dc
6451 wxClientDC dc(this);
6452
6453 // Get dc boundary
6454 int sx, sy;
6455 dc.GetSize(&sx, &sy);
6456
6457 // Need a bitmap
6458 wxBitmap test_bitmap(sx, sy, -1);
6459
6460 // Create a memory DC
6461 wxMemoryDC temp_dc;
6462 temp_dc.SelectObject(test_bitmap);
6463
6464 temp_dc.ResetBoundingBox();
6465 temp_dc.DestroyClippingRegion();
6466 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6467
6468 // Draw the Alert Targets on the temp_dc
6469 ocpnDC ocpndc = ocpnDC(temp_dc);
6470 AlertDraw(ocpndc);
6471
6472 // Retrieve the drawing extents
6473 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6474 temp_dc.MaxX() - temp_dc.MinX(),
6475 temp_dc.MaxY() - temp_dc.MinY());
6476
6477 if (!alert_rect.IsEmpty())
6478 alert_rect.Inflate(2); // clear all drawing artifacts
6479
6480 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6481 // The required invalidate rectangle is the union of the last drawn
6482 // rectangle and this drawn rectangle
6483 wxRect alert_update_rect = alert_draw_rect;
6484 alert_update_rect.Union(alert_rect);
6485
6486 // Invalidate the rectangular region
6487 RefreshRect(alert_update_rect, false);
6488 }
6489
6490 // Save this rectangle for next time
6491 alert_draw_rect = alert_rect;
6492
6493 temp_dc.SelectObject(wxNullBitmap); // clean up
6494}
6495
6496void ChartCanvas::UpdateAIS() {
6497 if (!g_pAIS) return;
6498
6499 // Get the rectangle in the current dc which bounds the detected AIS targets
6500
6501 // Use this dc
6502 wxClientDC dc(this);
6503
6504 // Get dc boundary
6505 int sx, sy;
6506 dc.GetSize(&sx, &sy);
6507
6508 wxRect ais_rect;
6509
6510 // How many targets are there?
6511
6512 // If more than "some number", it will be cheaper to refresh the entire
6513 // screen than to build update rectangles for each target.
6514 if (g_pAIS->GetTargetList().size() > 10) {
6515 ais_rect = wxRect(0, 0, sx, sy); // full screen
6516 } else {
6517 // Need a bitmap
6518 wxBitmap test_bitmap(sx, sy, -1);
6519
6520 // Create a memory DC
6521 wxMemoryDC temp_dc;
6522 temp_dc.SelectObject(test_bitmap);
6523
6524 temp_dc.ResetBoundingBox();
6525 temp_dc.DestroyClippingRegion();
6526 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6527
6528 // Draw the AIS Targets on the temp_dc
6529 ocpnDC ocpndc = ocpnDC(temp_dc);
6530 AISDraw(ocpndc, GetVP(), this);
6531 AISDrawAreaNotices(ocpndc, GetVP(), this);
6532
6533 // Retrieve the drawing extents
6534 ais_rect =
6535 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6536 temp_dc.MaxY() - temp_dc.MinY());
6537
6538 if (!ais_rect.IsEmpty())
6539 ais_rect.Inflate(2); // clear all drawing artifacts
6540
6541 temp_dc.SelectObject(wxNullBitmap); // clean up
6542 }
6543
6544 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6545 // The required invalidate rectangle is the union of the last drawn
6546 // rectangle and this drawn rectangle
6547 wxRect ais_update_rect = ais_draw_rect;
6548 ais_update_rect.Union(ais_rect);
6549
6550 // Invalidate the rectangular region
6551 RefreshRect(ais_update_rect, false);
6552 }
6553
6554 // Save this rectangle for next time
6555 ais_draw_rect = ais_rect;
6556}
6557
6558void ChartCanvas::ToggleCPAWarn() {
6559 g_bCPAWarn = !g_bCPAWarn;
6560 wxString mess = _("ON");
6561 if (!g_bCPAWarn) {
6562 g_bTCPA_Max = false;
6563 mess = _("OFF");
6564 }
6565 else { g_bTCPA_Max = true; }
6566 // Print to status bar if available.
6567 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6568 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6569 }
6570 else {
6571 OCPNMessageBox(this,
6572 _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
6573 _("CPA") + _T(" ") + mess, 4, 4);
6574 }
6575}
6576
6577void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6578
6579void ChartCanvas::OnSize(wxSizeEvent &event) {
6580 GetClientSize(&m_canvas_width, &m_canvas_height);
6581
6582
6583 m_canvas_width *= m_displayScale;
6584 m_canvas_height *= m_displayScale;
6585
6586 // Resize the current viewport
6587 VPoint.pix_width = m_canvas_width;
6588 VPoint.pix_height = m_canvas_height;
6589
6590 // Get some canvas metrics
6591
6592 // Rescale to current value, in order to rebuild VPoint data
6593 // structures for new canvas size
6594 SetVPScale(GetVPScale());
6595
6596 m_absolute_min_scale_ppm =
6597 m_canvas_width /
6598 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
6599
6600 // Inform the parent Frame that I am being resized...
6601 gFrame->ProcessCanvasResize();
6602
6603 // if MUIBar is active, size the bar
6604 // if(g_useMUI && !m_muiBar){ // rebuild if
6605 // necessary
6606 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
6607 // m_muiBarHOSize = m_muiBar->GetSize();
6608 // }
6609
6610 if (m_muiBar) {
6611 SetMUIBarPosition();
6612 UpdateFollowButtonState();
6613 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6614 m_muiBar->Raise();
6615 }
6616
6617 // Set up the scroll margins
6618 xr_margin = m_canvas_width * 95 / 100;
6619 xl_margin = m_canvas_width * 5 / 100;
6620 yt_margin = m_canvas_height * 5 / 100;
6621 yb_margin = m_canvas_height * 95 / 100;
6622
6623 if (m_pQuilt)
6624 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
6625
6626 // Resize the scratch BM
6627 delete pscratch_bm;
6628 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6629 m_brepaint_piano = true;
6630
6631 // Resize the Route Calculation BM
6632 m_dc_route.SelectObject(wxNullBitmap);
6633 delete proute_bm;
6634 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6635 m_dc_route.SelectObject(*proute_bm);
6636
6637 // Resize the saved Bitmap
6638 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6639
6640 // Resize the working Bitmap
6641 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6642
6643 // Rescale again, to capture all the changes for new canvas size
6644 SetVPScale(GetVPScale());
6645
6646#ifdef ocpnUSE_GL
6647 if (/*g_bopengl &&*/ m_glcc) {
6648 //FIXME (dave) This can go away?
6649 m_glcc->OnSize(event);
6650 }
6651#endif
6652
6653 FormatPianoKeys();
6654 // Invalidate the whole window
6655 ReloadVP();
6656}
6657
6658void ChartCanvas::ProcessNewGUIScale() {
6659 m_muiBar->Hide();
6660 delete m_muiBar;
6661 m_muiBar = 0;
6662
6663 CreateMUIBar();
6664}
6665
6666void ChartCanvas::CreateMUIBar() {
6667 if (g_useMUI && !m_muiBar) { // rebuild if necessary
6668
6669 // We need to update the m_bENCGroup flag, at least for the initial creation
6670 // of a MUIBar
6671 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
6672
6673 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
6674 m_muiBar->SetColorScheme(m_cs);
6675 m_muiBarHOSize = m_muiBar->GetSize();
6676 }
6677
6678 if (m_muiBar) {
6679 SetMUIBarPosition();
6680 UpdateFollowButtonState();
6681 m_muiBar->UpdateDynamicValues();
6682 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6683 m_muiBar->Raise();
6684 }
6685}
6686
6687void ChartCanvas::SetMUIBarPosition() {
6688 // if MUIBar is active, size the bar
6689 if (m_muiBar) {
6690 // We precalculate the piano width based on the canvas width
6691 int pianoWidth = GetClientSize().x * (g_btouch ? 0.7f : 0.6f);
6692 // if(m_Piano)
6693 // pianoWidth = m_Piano->GetWidth();
6694
6695 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
6696 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
6697 delete m_muiBar;
6698 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
6699 }
6700 }
6701
6702 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
6703 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
6704 delete m_muiBar;
6705 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
6706 }
6707 }
6708
6709 m_muiBar->SetBestPosition();
6710 }
6711}
6712
6713void ChartCanvas::DestroyMuiBar() {
6714 if (m_muiBar) {
6715 m_muiBar->Destroy();
6716 m_muiBar = NULL;
6717 }
6718}
6719
6720void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
6721 if (dbIndex >= 0) {
6722 if (NULL == m_pCIWin) {
6723 m_pCIWin = new ChInfoWin(this);
6724 m_pCIWin->Hide();
6725 }
6726
6727 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
6728 wxString s;
6729 ChartBase *pc = NULL;
6730
6731 // TOCTOU race but worst case will reload chart.
6732 // need to lock it or the background spooler may evict charts in
6733 // OpenChartFromDBAndLock
6734 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
6735 pc = ChartData->OpenChartFromDBAndLock(
6736 dbIndex, FULL_INIT); // this must come from cache
6737
6738 int char_width, char_height;
6739 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
6740 if (pc) ChartData->UnLockCacheChart(dbIndex);
6741
6742 m_pCIWin->SetString(s);
6743 m_pCIWin->FitToChars(char_width, char_height);
6744
6745 wxPoint p;
6746 p.x = x / GetContentScaleFactor();
6747 if ((p.x + m_pCIWin->GetWinSize().x) > (m_canvas_width / GetContentScaleFactor()))
6748 p.x = ((m_canvas_width / GetContentScaleFactor())
6749 - m_pCIWin->GetWinSize().x) / 2; // centered
6750
6751 p.y =
6752 (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor()
6753 - 4 - m_pCIWin->GetWinSize().y;
6754
6755 m_pCIWin->dbIndex = dbIndex;
6756 m_pCIWin->SetPosition(p);
6757 m_pCIWin->SetBitmap();
6758 m_pCIWin->Refresh();
6759 m_pCIWin->Show();
6760 }
6761 } else {
6762 HideChartInfoWindow();
6763 }
6764}
6765
6766void ChartCanvas::HideChartInfoWindow(void) {
6767 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
6768 m_pCIWin->Hide();
6769 m_pCIWin->Destroy();
6770 m_pCIWin = NULL;
6771
6772#ifdef __OCPN__ANDROID__
6773 androidForceFullRepaint();
6774#endif
6775 }
6776}
6777
6778void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
6779 wxMouseEvent ev(wxEVT_MOTION);
6780 ev.m_x = mouse_x;
6781 ev.m_y = mouse_y;
6782 ev.m_leftDown = mouse_leftisdown;
6783
6784 wxEvtHandler *evthp = GetEventHandler();
6785
6786 ::wxPostEvent(evthp, ev);
6787}
6788
6789void ChartCanvas::MovementTimerEvent(wxTimerEvent &) { DoTimedMovement(); }
6790
6791void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
6792
6793bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
6794 int delta) {
6795 if (m_disable_edge_pan) return false;
6796
6797 bool bft = false;
6798 int pan_margin = m_canvas_width * margin / 100;
6799 int pan_timer_set = 200;
6800 double pan_delta = GetVP().pix_width * delta / 100;
6801 int pan_x = 0;
6802 int pan_y = 0;
6803
6804 if (x > m_canvas_width - pan_margin) {
6805 bft = true;
6806 pan_x = pan_delta;
6807 }
6808
6809 else if (x < pan_margin) {
6810 bft = true;
6811 pan_x = -pan_delta;
6812 }
6813
6814 if (y < pan_margin) {
6815 bft = true;
6816 pan_y = -pan_delta;
6817 }
6818
6819 else if (y > m_canvas_height - pan_margin) {
6820 bft = true;
6821 pan_y = pan_delta;
6822 }
6823
6824 // Of course, if dragging, and the mouse left button is not down, we must
6825 // stop the event injection
6826 if (bdragging) {
6827 if (!g_btouch) {
6828 wxMouseState state = ::wxGetMouseState();
6829#if wxCHECK_VERSION(3, 0, 0)
6830 if (!state.LeftIsDown())
6831#else
6832 if (!state.LeftDown())
6833#endif
6834 bft = false;
6835 }
6836 }
6837 if ((bft) && !pPanTimer->IsRunning()) {
6838 PanCanvas(pan_x, pan_y);
6839 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
6840 return true;
6841 }
6842
6843 // This mouse event must not be due to pan timer event injector
6844 // Mouse is out of the pan zone, so prevent any orphan event injection
6845 if ((!bft) && pPanTimer->IsRunning()) {
6846 pPanTimer->Stop();
6847 }
6848
6849 return (false);
6850}
6851
6852// Look for waypoints at the current position.
6853// Used to determine what a mouse event should act on.
6854
6855void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
6856 bool setBeingEdited) {
6857 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
6858 m_pRoutePointEditTarget = NULL;
6859 m_pFoundPoint = NULL;
6860
6861 SelectItem *pFind = NULL;
6862 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
6863 SelectableItemList SelList = pSelect->FindSelectionList(
6864 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
6865 wxSelectableItemListNode *node = SelList.GetFirst();
6866 while (node) {
6867 pFind = node->GetData();
6868
6869 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
6870
6871 // Get an array of all routes using this point
6872 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
6873
6874 // Use route array to determine actual visibility for the point
6875 bool brp_viz = false;
6876 if (m_pEditRouteArray) {
6877 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
6878 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
6879 if (pr->IsVisible()) {
6880 brp_viz = true;
6881 break;
6882 }
6883 }
6884 } else
6885 brp_viz = frp->IsVisible(); // isolated point
6886
6887 if (brp_viz) {
6888 // Use route array to rubberband all affected routes
6889 if (m_pEditRouteArray) // Editing Waypoint as part of route
6890 {
6891 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
6892 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
6893 pr->m_bIsBeingEdited = setBeingEdited;
6894 }
6895 m_bRouteEditing = setBeingEdited;
6896 } else // editing Mark
6897 {
6898 frp->m_bRPIsBeingEdited = setBeingEdited;
6899 m_bMarkEditing = setBeingEdited;
6900 }
6901
6902 m_pRoutePointEditTarget = frp;
6903 m_pFoundPoint = pFind;
6904 break; // out of the while(node)
6905 }
6906
6907 node = node->GetNext();
6908 } // while (node)
6909}
6910
6911void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
6912 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
6913 singleClickEventIsValid = false;
6914 m_DoubleClickTimer->Stop();
6915}
6916
6917bool leftIsDown;
6918
6919bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
6920 if (!m_bChartDragging && !m_bDrawingRoute) {
6921 if (m_Compass && m_Compass->IsShown() &&
6922 m_Compass->GetRect().Contains(event.GetPosition())) {
6923 if (m_Compass->MouseEvent(event)) {
6924 cursor_region = CENTER;
6925 if (!g_btouch) SetCanvasCursor(event);
6926 return true;
6927 }
6928 }
6929
6930 if (MouseEventChartBar(event)) return true;
6931 }
6932 return false;
6933}
6934
6935bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
6936 if (!g_bShowChartBar) return false;
6937
6938 if (!m_Piano->MouseEvent(event)) return false;
6939
6940 cursor_region = CENTER;
6941 if (!g_btouch) SetCanvasCursor(event);
6942 return true;
6943}
6944
6945bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
6946 int x, y;
6947
6948 bool bret = false;
6949
6950 event.GetPosition(&x, &y);
6951
6952 x *= m_displayScale;
6953 y *= m_displayScale;
6954
6955 m_MouseDragging = event.Dragging();
6956
6957 // Some systems produce null drag events, where the pointer position has not
6958 // changed from the previous value. Detect this case, and abort further
6959 // processing (FS#1748)
6960#ifdef __WXMSW__
6961 if (event.Dragging()) {
6962 if ((x == mouse_x) && (y == mouse_y)) return true;
6963 }
6964#endif
6965
6966 mouse_x = x;
6967 mouse_y = y;
6968 mouse_leftisdown = event.LeftDown();
6969 GetCanvasPixPoint(x, y, m_cursor_lat, m_cursor_lon);
6970
6971 // Establish the event region
6972 cursor_region = CENTER;
6973
6974 int chartbar_height = GetChartbarHeight();
6975
6976 if (m_Compass && m_Compass->IsShown() &&
6977 m_Compass->GetRect().Contains(event.GetPosition())) {
6978 cursor_region = CENTER;
6979 } else if (x > xr_margin) {
6980 cursor_region = MID_RIGHT;
6981 } else if (x < xl_margin) {
6982 cursor_region = MID_LEFT;
6983 } else if (y > yb_margin - chartbar_height &&
6984 y < m_canvas_height - chartbar_height) {
6985 cursor_region = MID_TOP;
6986 } else if (y < yt_margin) {
6987 cursor_region = MID_BOT;
6988 } else {
6989 cursor_region = CENTER;
6990 }
6991
6992 if (!g_btouch) SetCanvasCursor(event);
6993
6994 // Protect from leftUp's coming from event handlers in child
6995 // windows who return focus to the canvas.
6996 leftIsDown = event.LeftDown();
6997
6998#ifndef __WXOSX__
6999 if (event.LeftDown()) {
7000 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7001 // The menu bar is temporarily visible due to alt having been pressed.
7002 // Clicking will hide it, and do nothing else.
7003 g_bTempShowMenuBar = false;
7004 parent_frame->ApplyGlobalSettings(false);
7005 return (true);
7006 }
7007 }
7008#endif
7009
7010 // Update modifiers here; some window managers never send the key event
7011 m_modkeys = 0;
7012 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7013 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7014
7015#ifdef __WXMSW__
7016 // TODO Test carefully in other platforms, remove ifdef....
7017 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7018 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7019#endif
7020
7021 if (g_pi_manager)
7022 if (g_pi_manager->SendMouseEventToPlugins(event))
7023 return (true); // PlugIn did something, and does not want the canvas to
7024 // do anything else
7025
7026 // Capture LeftUp's and time them, unless it already came from the timer.
7027
7028 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7029 // Ignore the second LeftUp after the DClick.
7030 if (m_DoubleClickTimer->IsRunning()) {
7031 m_DoubleClickTimer->Stop();
7032 return (true);
7033 }
7034
7035 // Save the event for later running if there is no DClick.
7036 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7037 singleClickEvent = event;
7038 singleClickEventIsValid = true;
7039 return (true);
7040 }
7041
7042 // This logic is necessary on MSW to handle the case where
7043 // a context (right-click) menu is dismissed without action
7044 // by clicking on the chart surface.
7045 // We need to avoid an unintentional pan by eating some clicks...
7046#ifdef __WXMSW__
7047 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7048 if (g_click_stop > 0) {
7049 g_click_stop--;
7050 return (true);
7051 }
7052 }
7053#endif
7054
7055 // Kick off the Rotation control timer
7056 if (GetUpMode() == COURSE_UP_MODE) {
7057 m_b_rot_hidef = false;
7058 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7059 } else
7060 pRotDefTimer->Stop();
7061
7062 // Retrigger the route leg / AIS target popup timer
7063 bool bRoll = !g_btouch;
7064#ifdef __OCPN__ANDROID__
7065 bRoll = g_bRollover;
7066#endif
7067 if (bRoll) {
7068 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7069 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7070 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7071 m_RolloverPopupTimer.Start(
7072 10,
7073 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7074 else
7075 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7076 }
7077
7078 // Retrigger the cursor tracking timer
7079 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7080
7081// Show cursor position on Status Bar, if present
7082// except for GTK, under which status bar updates are very slow
7083// due to Update() call.
7084// In this case, as a workaround, update the status window
7085// after an interval timer (pCurTrackTimer) pops, which will happen
7086// whenever the mouse has stopped moving for specified interval.
7087// See the method OnCursorTrackTimerEvent()
7088#if !defined(__WXGTK__) && !defined(__WXQT__)
7089 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7090#endif
7091
7092 // Send the current cursor lat/lon to all PlugIns requesting it
7093 if (g_pi_manager) {
7094 // Occasionally, MSW will produce nonsense events on right click....
7095 // This results in an error in cursor geo position, so we skip this case
7096 if ((x >= 0) && (y >= 0))
7097 g_pi_manager->SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7098 }
7099
7100 if (!g_btouch) {
7101 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7102 wxPoint p = ClientToScreen(wxPoint(x, y));
7103
7104 // Submerge the toolbar if necessary
7105 if (m_toolBar) {
7106 wxRect rect = m_toolBar->GetScreenRect();
7107 rect.Inflate(20);
7108 if (rect.Contains(p.x, p.y)) m_toolBar->Submerge();
7109 }
7110 }
7111 }
7112
7113 if (1 ) {
7114 // Route Creation Rubber Banding
7115 if (m_routeState >= 2) {
7116 r_rband.x = x;
7117 r_rband.y = y;
7118 m_bDrawingRoute = true;
7119
7120 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7121 Refresh(false);
7122 }
7123
7124 // Measure Tool Rubber Banding
7125 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7126 r_rband.x = x;
7127 r_rband.y = y;
7128 m_bDrawingRoute = true;
7129
7130 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7131 Refresh(false);
7132 }
7133 }
7134 return bret;
7135}
7136
7137void ChartCanvas::CallPopupMenu(int x, int y) {
7138 int mx, my;
7139 mx = x;
7140 my = y;
7141
7142 last_drag.x = mx;
7143 last_drag.y = my;
7144 if (m_routeState) { // creating route?
7145 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
7146 return;
7147 }
7148 // General Right Click
7149 // Look for selectable objects
7150 double slat, slon;
7151 slat = m_cursor_lat;
7152 slon = m_cursor_lon;
7153
7154#if defined(__WXMAC__) || defined(__OCPN__ANDROID__)
7155 wxScreenDC sdc;
7156 ocpnDC dc(sdc);
7157#else
7158 wxClientDC cdc(GetParent());
7159 ocpnDC dc(cdc);
7160#endif
7161
7162 SelectItem *pFindAIS;
7163 SelectItem *pFindRP;
7164 SelectItem *pFindRouteSeg;
7165 SelectItem *pFindTrackSeg;
7166 SelectItem *pFindCurrent = NULL;
7167 SelectItem *pFindTide = NULL;
7168
7169 // Deselect any current objects
7170 if (m_pSelectedRoute) {
7171 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7172 m_pSelectedRoute->DeSelectRoute();
7173#ifdef ocpnUSE_GL
7174 if (g_bopengl && m_glcc) {
7175 InvalidateGL();
7176 Update();
7177 } else
7178#endif
7179 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7180 }
7181
7182 if (m_pFoundRoutePoint) {
7183 m_pFoundRoutePoint->m_bPtIsSelected = false;
7184 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7185 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7186 }
7187
7190 if (g_btouch && m_pRoutePointEditTarget) {
7191 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7192 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7193 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7194 }
7195
7196 // Get all the selectable things at the cursor
7197 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
7198 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7199 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7200 pFindRouteSeg =
7201 pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7202 pFindTrackSeg =
7203 pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7204
7205 if (m_bShowCurrent)
7206 pFindCurrent =
7207 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7208
7209 if (m_bShowTide) // look for tide stations
7210 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7211
7212 int seltype = 0;
7213
7214 // Try for AIS targets first
7215 if (pFindAIS) {
7216 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7217
7218 // Make sure the target data is available
7219 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7220 seltype |= SELTYPE_AISTARGET;
7221 }
7222
7223 // Now the various Route Parts
7224
7225 m_pFoundRoutePoint = NULL;
7226 if (pFindRP) {
7227 RoutePoint *pFirstVizPoint = NULL;
7228 RoutePoint *pFoundActiveRoutePoint = NULL;
7229 RoutePoint *pFoundVizRoutePoint = NULL;
7230 Route *pSelectedActiveRoute = NULL;
7231 Route *pSelectedVizRoute = NULL;
7232
7233 // There is at least one routepoint, so get the whole list
7234 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
7235 SelectableItemList SelList =
7236 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7237 wxSelectableItemListNode *node = SelList.GetFirst();
7238 while (node) {
7239 SelectItem *pFindSel = node->GetData();
7240
7241 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7242
7243 // Get an array of all routes using this point
7244 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7245
7246 // Use route array (if any) to determine actual visibility for this point
7247 bool brp_viz = false;
7248 if (proute_array) {
7249 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7250 Route *pr = (Route *)proute_array->Item(ir);
7251 if (pr->IsVisible()) {
7252 brp_viz = true;
7253 break;
7254 }
7255 }
7256 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7257 // but still exists as a waypoint
7258 brp_viz = prp->IsVisible(); // so treat as isolated point
7259
7260 } else
7261 brp_viz = prp->IsVisible(); // isolated point
7262
7263 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7264
7265 // Use route array to choose the appropriate route
7266 // Give preference to any active route, otherwise select the first visible
7267 // route in the array for this point
7268 m_pSelectedRoute = NULL;
7269 if (proute_array) {
7270 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7271 Route *pr = (Route *)proute_array->Item(ir);
7272 if (pr->m_bRtIsActive) {
7273 pSelectedActiveRoute = pr;
7274 pFoundActiveRoutePoint = prp;
7275 break;
7276 }
7277 }
7278
7279 if (NULL == pSelectedVizRoute) {
7280 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7281 Route *pr = (Route *)proute_array->Item(ir);
7282 if (pr->IsVisible()) {
7283 pSelectedVizRoute = pr;
7284 pFoundVizRoutePoint = prp;
7285 break;
7286 }
7287 }
7288 }
7289
7290 delete proute_array;
7291 }
7292
7293 node = node->GetNext();
7294 }
7295
7296 // Now choose the "best" selections
7297 if (pFoundActiveRoutePoint) {
7298 m_pFoundRoutePoint = pFoundActiveRoutePoint;
7299 m_pSelectedRoute = pSelectedActiveRoute;
7300 } else if (pFoundVizRoutePoint) {
7301 m_pFoundRoutePoint = pFoundVizRoutePoint;
7302 m_pSelectedRoute = pSelectedVizRoute;
7303 } else
7304 // default is first visible point in list
7305 m_pFoundRoutePoint = pFirstVizPoint;
7306
7307 if (m_pSelectedRoute) {
7308 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7309 } else if (m_pFoundRoutePoint)
7310 seltype |= SELTYPE_MARKPOINT;
7311
7312 // Highlite the selected point, to verify the proper right click
7313 // selection
7314 if (m_pFoundRoutePoint) {
7315 m_pFoundRoutePoint->m_bPtIsSelected = true;
7316 wxRect wp_rect;
7317 RoutePointGui(*m_pFoundRoutePoint).CalculateDCRect(m_dc_route, this, &wp_rect);
7318 RefreshRect(wp_rect, true);
7319 }
7320 }
7321
7322 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7323 // routes But call the popup handler with identifier appropriate to the type
7324 if (pFindRouteSeg) // there is at least one select item
7325 {
7326 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
7327 SelectableItemList SelList =
7328 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7329
7330 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
7331 {
7332 // Choose the first visible route containing segment in the list
7333 wxSelectableItemListNode *node = SelList.GetFirst();
7334 while (node) {
7335 SelectItem *pFindSel = node->GetData();
7336
7337 Route *pr = (Route *)pFindSel->m_pData3;
7338 if (pr->IsVisible()) {
7339 m_pSelectedRoute = pr;
7340 break;
7341 }
7342 node = node->GetNext();
7343 }
7344 }
7345
7346 if (m_pSelectedRoute) {
7347 if (NULL == m_pFoundRoutePoint)
7348 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7349
7350 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7351 if (m_pSelectedRoute->m_bRtIsSelected) {
7352#ifdef ocpnUSE_GL
7353 if (g_bopengl && m_glcc) {
7354 InvalidateGL();
7355 Update();
7356 } else
7357#endif
7358 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7359 }
7360
7361 seltype |= SELTYPE_ROUTESEGMENT;
7362 }
7363 }
7364
7365 if (pFindTrackSeg) {
7366 m_pSelectedTrack = NULL;
7367 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
7368 SelectableItemList SelList =
7369 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7370
7371 // Choose the first visible track containing segment in the list
7372 wxSelectableItemListNode *node = SelList.GetFirst();
7373 while (node) {
7374 SelectItem *pFindSel = node->GetData();
7375
7376 Track *pt = (Track *)pFindSel->m_pData3;
7377 if (pt->IsVisible()) {
7378 m_pSelectedTrack = pt;
7379 break;
7380 }
7381 node = node->GetNext();
7382 }
7383
7384 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7385 }
7386
7387 bool bseltc = false;
7388 // if(0 == seltype)
7389 {
7390 if (pFindCurrent) {
7391 // There may be multiple current entries at the same point.
7392 // For example, there often is a current substation (with directions
7393 // specified) co-located with its master. We want to select the
7394 // substation, so that the direction will be properly indicated on the
7395 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
7396 // substation)
7397 IDX_entry *pIDX_best_candidate;
7398
7399 SelectItem *pFind = NULL;
7400 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
7401 SelectableItemList SelList = pSelectTC->FindSelectionList(
7402 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
7403
7404 // Default is first entry
7405 wxSelectableItemListNode *node = SelList.GetFirst();
7406 pFind = node->GetData();
7407 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7408
7409 if (SelList.GetCount() > 1) {
7410 node = node->GetNext();
7411 while (node) {
7412 pFind = node->GetData();
7413 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
7414 if (pIDX_candidate->IDX_type == 'c') {
7415 pIDX_best_candidate = pIDX_candidate;
7416 break;
7417 }
7418
7419 node = node->GetNext();
7420 } // while (node)
7421 } else {
7422 wxSelectableItemListNode *node = SelList.GetFirst();
7423 pFind = node->GetData();
7424 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7425 }
7426
7427 m_pIDXCandidate = pIDX_best_candidate;
7428
7429 if (0 == seltype) {
7430 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
7431 Refresh(false);
7432 bseltc = true;
7433 } else
7434 seltype |= SELTYPE_CURRENTPOINT;
7435 }
7436
7437 else if (pFindTide) {
7438 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
7439
7440 if (0 == seltype) {
7441 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
7442 Refresh(false);
7443 bseltc = true;
7444 } else
7445 seltype |= SELTYPE_TIDEPOINT;
7446 }
7447 }
7448
7449 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7450
7451 if (!bseltc) {
7452 InvokeCanvasMenu(x, y, seltype);
7453
7454 // Clean up if not deleted in InvokeCanvasMenu
7455 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
7456 m_pSelectedRoute->m_bRtIsSelected = false;
7457 }
7458
7459 m_pSelectedRoute = NULL;
7460
7461 if (m_pFoundRoutePoint) {
7462 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
7463 m_pFoundRoutePoint->m_bPtIsSelected = false;
7464 }
7465 m_pFoundRoutePoint = NULL;
7466
7467 Refresh(true);
7468 }
7469
7470 // Seth: Is this refresh needed?
7471 Refresh(false); // needed for MSW, not GTK Why??
7472}
7473bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
7474 // For now just bail out completely if the point clicked is not on the chart
7475 if (std::isnan(m_cursor_lat)) return false;
7476
7477 // Mouse Clicks
7478 bool ret = false; // return true if processed
7479
7480 int x, y, mx, my;
7481 event.GetPosition(&x, &y);
7482 mx = x;
7483 my = y;
7484
7485 // Calculate meaningful SelectRadius
7486 float SelectRadius;
7487 SelectRadius = g_Platform->GetSelectRadiusPix() /
7488 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
7489
7491 // We start with Double Click processing. The first left click just starts a
7492 // timer and is remembered, then we actually do something if there is a
7493 // LeftDClick. If there is, the two single clicks are ignored.
7494
7495 if (event.LeftDClick() && (cursor_region == CENTER)) {
7496 m_DoubleClickTimer->Start();
7497 singleClickEventIsValid = false;
7498
7499 double zlat, zlon;
7500 GetCanvasPixPoint(x, y, zlat, zlon);
7501
7502 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
7503 if (m_bShowAIS) {
7504 SelectItem *pFindAIS;
7505 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
7506
7507 if (pFindAIS) {
7508 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7509 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
7510 wxWindow *pwin = wxDynamicCast(this, wxWindow);
7511 ShowAISTargetQueryDialog(pwin, m_FoundAIS_MMSI);
7512 }
7513 return true;
7514 }
7515 }
7516
7517 SelectableItemList rpSelList =
7518 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
7519 wxSelectableItemListNode *node = rpSelList.GetFirst();
7520 bool b_onRPtarget = false;
7521 while (node) {
7522 SelectItem *pFind = node->GetData();
7523 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7524 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
7525 b_onRPtarget = true;
7526 break;
7527 }
7528 node = node->GetNext();
7529 }
7530
7531 // Double tap with selected RoutePoint or Mark
7532
7533 if (m_pRoutePointEditTarget) {
7534 if (b_onRPtarget) {
7535 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
7536 return true;
7537 } else {
7538 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7539 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7540 if (g_btouch) RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7541 wxRect wp_rect;
7542 RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this, &wp_rect);
7543 m_pRoutePointEditTarget = NULL; // cancel selection
7544 RefreshRect(wp_rect, true);
7545 return true;
7546 }
7547 } else {
7548 node = rpSelList.GetFirst();
7549 if (node) {
7550 SelectItem *pFind = node->GetData();
7551 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7552 if (frp) {
7553 wxArrayPtrVoid *proute_array =
7554 g_pRouteMan->GetRouteArrayContaining(frp);
7555
7556 // Use route array (if any) to determine actual visibility for this
7557 // point
7558 bool brp_viz = false;
7559 if (proute_array) {
7560 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7561 Route *pr = (Route *)proute_array->Item(ir);
7562 if (pr->IsVisible()) {
7563 brp_viz = true;
7564 break;
7565 }
7566 }
7567 if (!brp_viz &&
7568 frp->IsShared()) // is not visible as part of route, but still
7569 // exists as a waypoint
7570 brp_viz = frp->IsVisible(); // so treat as isolated point
7571 } else
7572 brp_viz = frp->IsVisible(); // isolated point
7573
7574 if (brp_viz) {
7575 ShowMarkPropertiesDialog(frp);
7576 return true;
7577 }
7578 }
7579 }
7580 }
7581
7582 SelectItem *cursorItem;
7583 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
7584
7585 if (cursorItem) {
7586 Route *pr = (Route *)cursorItem->m_pData3;
7587 if (pr->IsVisible()) {
7588 ShowRoutePropertiesDialog(_("Route Properties"), pr);
7589 return true;
7590 }
7591 }
7592
7593 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
7594
7595 if (cursorItem) {
7596 Track *pt = (Track *)cursorItem->m_pData3;
7597 if (pt->IsVisible()) {
7598 ShowTrackPropertiesDialog(pt);
7599 return true;
7600 }
7601 }
7602
7603 // Found no object to act on, so show chart info.
7604
7605 ShowObjectQueryWindow(x, y, zlat, zlon);
7606 return true;
7607 }
7608
7610 if (event.LeftDown()) {
7611 // This really should not be needed, but....
7612 // on Windows, when using wxAUIManager, sometimes the focus is lost
7613 // when clicking into another pane, e.g.the AIS target list, and then back
7614 // to this pane. Oddly, some mouse events are not lost, however. Like this
7615 // one....
7616 SetFocus();
7617
7618 last_drag.x = mx;
7619 last_drag.y = my;
7620 leftIsDown = true;
7621
7622 if (!g_btouch) {
7623 if (m_routeState) // creating route?
7624 {
7625 double rlat, rlon;
7626 bool appending = false;
7627 bool inserting = false;
7628 Route *tail = 0;
7629
7630 SetCursor(*pCursorPencil);
7631 rlat = m_cursor_lat;
7632 rlon = m_cursor_lon;
7633
7634 m_bRouteEditing = true;
7635
7636 if (m_routeState == 1) {
7637 m_pMouseRoute = new Route();
7638 pRouteList->Append(m_pMouseRoute);
7639 r_rband.x = x;
7640 r_rband.y = y;
7641 }
7642
7643 // Check to see if there is a nearby point which may be reused
7644 RoutePoint *pMousePoint = NULL;
7645
7646 // Calculate meaningful SelectRadius
7647 double nearby_radius_meters =
7648 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
7649
7650 RoutePoint *pNearbyPoint =
7651 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
7652 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
7653 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
7654 wxArrayPtrVoid *proute_array =
7655 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
7656
7657 // Use route array (if any) to determine actual visibility for this
7658 // point
7659 bool brp_viz = false;
7660 if (proute_array) {
7661 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7662 Route *pr = (Route *)proute_array->Item(ir);
7663 if (pr->IsVisible()) {
7664 brp_viz = true;
7665 break;
7666 }
7667 }
7668 if (!brp_viz &&
7669 pNearbyPoint->IsShared()) // is not visible as part of route,
7670 // but still exists as a waypoint
7671 brp_viz =
7672 pNearbyPoint->IsVisible(); // so treat as isolated point
7673 } else
7674 brp_viz = pNearbyPoint->IsVisible(); // isolated point
7675
7676 if (brp_viz) {
7677 m_FinishRouteOnKillFocus =
7678 false; // Avoid route finish on focus change for message dialog
7679 int dlg_return = OCPNMessageBox(
7680 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
7681 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
7682 m_FinishRouteOnKillFocus = true;
7683
7684 if (dlg_return == wxID_YES) {
7685 pMousePoint = pNearbyPoint;
7686
7687 // Using existing waypoint, so nothing to delete for undo.
7688 if (m_routeState > 1)
7689 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
7690 Undo_HasParent, NULL);
7691
7692 tail =
7693 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
7694 bool procede = false;
7695 if (tail) {
7696 procede = true;
7697 // if (pMousePoint == tail->GetLastPoint()) procede = false;
7698 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
7699 procede = false;
7700 }
7701
7702 if (procede) {
7703 int dlg_return;
7704 m_FinishRouteOnKillFocus = false;
7705 if (m_routeState ==
7706 1) { // first point in new route, preceeding route to be
7707 // added? Not touch case
7708
7709 wxString dmsg =
7710 _("Insert first part of this route in the new route?");
7711 if (tail->GetIndexOf(pMousePoint) ==
7712 tail->GetnPoints()) // Starting on last point of another
7713 // route?
7714 dmsg = _("Insert this route in the new route?");
7715
7716 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
7717 dlg_return = OCPNMessageBox(
7718 this, dmsg, _("OpenCPN Route Create"),
7719 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
7720 m_FinishRouteOnKillFocus = true;
7721
7722 if (dlg_return == wxID_YES) {
7723 inserting = true; // part of the other route will be
7724 // preceeding the new route
7725 }
7726 }
7727 } else {
7728 wxString dmsg =
7729 _("Append last part of this route to the new route?");
7730 if (tail->GetIndexOf(pMousePoint) == 1)
7731 dmsg = _(
7732 "Append this route to the new route?"); // Picking the
7733 // first point
7734 // of another
7735 // route?
7736
7737 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
7738 dlg_return = OCPNMessageBox(
7739 this, dmsg, _("OpenCPN Route Create"),
7740 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
7741 m_FinishRouteOnKillFocus = true;
7742
7743 if (dlg_return == wxID_YES) {
7744 appending = true; // part of the other route will be
7745 // appended to the new route
7746 }
7747 }
7748 }
7749 }
7750
7751 // check all other routes to see if this point appears in any
7752 // other route If it appears in NO other route, then it should e
7753 // considered an isolated mark
7754 if (!FindRouteContainingWaypoint(pMousePoint))
7755 pMousePoint->SetShared(true);
7756 }
7757 }
7758 }
7759
7760 if (NULL == pMousePoint) { // need a new point
7761 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
7762 _T(""), wxEmptyString);
7763 pMousePoint->SetNameShown(false);
7764
7765 pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
7766 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
7767
7768 if (m_routeState > 1)
7769 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
7770 Undo_IsOrphanded, NULL);
7771 }
7772
7773 if (m_pMouseRoute) {
7774 if (m_routeState == 1) {
7775 // First point in the route.
7776 m_pMouseRoute->AddPoint(pMousePoint);
7777 } else {
7778 if (m_pMouseRoute->m_NextLegGreatCircle) {
7779 double rhumbBearing, rhumbDist, gcBearing, gcDist;
7780 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
7781 &rhumbBearing, &rhumbDist);
7782 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
7783 rlat, &gcDist, &gcBearing, NULL);
7784 double gcDistNM = gcDist / 1852.0;
7785
7786 // Empirically found expression to get reasonable route segments.
7787 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
7788 pow(rhumbDist - gcDistNM - 1, 0.5);
7789
7790 wxString msg;
7791 msg << _("For this leg the Great Circle route is ")
7792 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
7793 << _(" shorter than rhumbline.\n\n")
7794 << _("Would you like include the Great Circle routing points "
7795 "for this leg?");
7796
7797 m_FinishRouteOnKillFocus = false;
7798 m_disable_edge_pan = true; // This helps on OS X if MessageBox
7799 // does not fully capture mouse
7800
7801 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
7802 wxYES_NO | wxNO_DEFAULT);
7803
7804 m_disable_edge_pan = false;
7805 m_FinishRouteOnKillFocus = true;
7806
7807 if (answer == wxID_YES) {
7808 RoutePoint *gcPoint;
7809 RoutePoint *prevGcPoint = m_prev_pMousePoint;
7810 wxRealPoint gcCoord;
7811
7812 for (int i = 1; i <= segmentCount; i++) {
7813 double fraction = (double)i * (1.0 / (double)segmentCount);
7814 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
7815 gcDist * fraction, gcBearing,
7816 &gcCoord.x, &gcCoord.y, NULL);
7817
7818 if (i < segmentCount) {
7819 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
7820 _T(""), wxEmptyString);
7821 gcPoint->SetNameShown(false);
7822 pConfig->AddNewWayPoint(gcPoint, -1);
7823 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
7824 gcPoint);
7825 } else {
7826 gcPoint = pMousePoint; // Last point, previously exsisting!
7827 }
7828
7829 m_pMouseRoute->AddPoint(gcPoint);
7830 pSelect->AddSelectableRouteSegment(
7831 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
7832 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
7833 prevGcPoint = gcPoint;
7834 }
7835
7836 undo->CancelUndoableAction(true);
7837
7838 } else {
7839 m_pMouseRoute->AddPoint(pMousePoint);
7840 pSelect->AddSelectableRouteSegment(
7841 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
7842 pMousePoint, m_pMouseRoute);
7843 undo->AfterUndoableAction(m_pMouseRoute);
7844 }
7845 } else {
7846 // Ordinary rhumblinesegment.
7847 m_pMouseRoute->AddPoint(pMousePoint);
7848 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
7849 rlon, m_prev_pMousePoint,
7850 pMousePoint, m_pMouseRoute);
7851 undo->AfterUndoableAction(m_pMouseRoute);
7852 }
7853 }
7854 }
7855 m_prev_rlat = rlat;
7856 m_prev_rlon = rlon;
7857 m_prev_pMousePoint = pMousePoint;
7858 if (m_pMouseRoute)
7859 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
7860
7861 m_routeState++;
7862
7863 if (appending ||
7864 inserting) { // Appending a route or making a new route
7865 int connect = tail->GetIndexOf(pMousePoint);
7866 if (connect == 1) {
7867 inserting = false; // there is nothing to insert
7868 appending = true; // so append
7869 }
7870 int length = tail->GetnPoints();
7871
7872 int i;
7873 int start, stop;
7874 if (appending) {
7875 start = connect + 1;
7876 stop = length;
7877 } else { // inserting
7878 start = 1;
7879 stop = connect;
7880 m_pMouseRoute->RemovePoint(
7881 m_pMouseRoute
7882 ->GetLastPoint()); // Remove the first and only point
7883 }
7884 for (i = start; i <= stop; i++) {
7885 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
7886 if (m_pMouseRoute)
7887 m_pMouseRoute->m_lastMousePointIndex =
7888 m_pMouseRoute->GetnPoints();
7889 m_routeState++;
7890 gFrame->RefreshAllCanvas();
7891 ret = true;
7892 }
7893 m_prev_rlat =
7894 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
7895 m_prev_rlon =
7896 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
7897 m_pMouseRoute->FinalizeForRendering();
7898 }
7899 gFrame->RefreshAllCanvas();
7900 ret = true;
7901 }
7902
7903 else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
7904 {
7905 SetCursor(*pCursorPencil);
7906
7907 if (m_nMeasureState == 1) {
7908 m_pMeasureRoute = new Route();
7909 pRouteList->Append(m_pMeasureRoute);
7910 r_rband.x = x;
7911 r_rband.y = y;
7912 }
7913
7914 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
7915 wxString(_T ( "circle" )),
7916 wxEmptyString, wxEmptyString);
7917 pMousePoint->m_bShowName = false;
7918 pMousePoint->SetShowWaypointRangeRings( false );
7919
7920 m_pMeasureRoute->AddPoint(pMousePoint);
7921
7922 m_prev_rlat = m_cursor_lat;
7923 m_prev_rlon = m_cursor_lon;
7924 m_prev_pMousePoint = pMousePoint;
7925 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
7926
7927 m_nMeasureState++;
7928 gFrame->RefreshAllCanvas();
7929 ret = true;
7930 }
7931
7932 else {
7933 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
7934 }
7935 } // !g_btouch
7936 else { // g_btouch
7937
7938 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
7939 // if near screen edge, pan with injection
7940 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
7941 // return;
7942 // }
7943 }
7944 }
7945
7946 if (ret) return true;
7947 }
7948
7949 if (event.Dragging()) {
7950 // in touch screen mode ensure the finger/cursor is on the selected point's
7951 // radius to allow dragging
7952 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
7953 if (g_btouch) {
7954 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
7955 SelectItem *pFind = NULL;
7956 SelectableItemList SelList = pSelect->FindSelectionList(
7957 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7958 wxSelectableItemListNode *node = SelList.GetFirst();
7959 while (node) {
7960 pFind = node->GetData();
7961 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7962 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
7963 node = node->GetNext();
7964 }
7965 }
7966
7967 // Check for use of dragHandle
7968 if (m_pRoutePointEditTarget &&
7969 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
7970 SelectItem *pFind = NULL;
7971 SelectableItemList SelList = pSelect->FindSelectionList(
7972 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
7973 wxSelectableItemListNode *node = SelList.GetFirst();
7974 while (node) {
7975 pFind = node->GetData();
7976 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7977 if (m_pRoutePointEditTarget == frp) {
7978 m_bIsInRadius = true;
7979 break;
7980 }
7981 node = node->GetNext();
7982 }
7983
7984 if (!m_dragoffsetSet) {
7985 RoutePointGui(*m_pRoutePointEditTarget).PresetDragOffset(this, mouse_x, mouse_y);
7986 m_dragoffsetSet = true;
7987 }
7988 }
7989 }
7990
7991 if (m_bRouteEditing && m_pRoutePointEditTarget) {
7992 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
7993
7994 if (NULL == g_pMarkInfoDialog) {
7995 if (g_bWayPointPreventDragging) DraggingAllowed = false;
7996 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
7997 DraggingAllowed = false;
7998
7999 if (m_pRoutePointEditTarget &&
8000 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8001 DraggingAllowed = false;
8002
8003 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8004
8005 if (DraggingAllowed) {
8006 if (!undo->InUndoableAction()) {
8007 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8008 Undo_NeedsCopy, m_pFoundPoint);
8009 }
8010
8011 // Get the update rectangle for the union of the un-edited routes
8012 wxRect pre_rect;
8013
8014 if (!g_bopengl && m_pEditRouteArray) {
8015 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8016 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8017 // Need to validate route pointer
8018 // Route may be gone due to drgging close to ownship with
8019 // "Delete On Arrival" state set, as in the case of
8020 // navigating to an isolated waypoint on a temporary route
8021 if (g_pRouteMan->IsRouteValid(pr)) {
8022 wxRect route_rect;
8023 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8024 pre_rect.Union(route_rect);
8025 }
8026 }
8027 }
8028
8029 double new_cursor_lat = m_cursor_lat;
8030 double new_cursor_lon = m_cursor_lon;
8031
8032 if (CheckEdgePan(x, y, true, 5, 2))
8033 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8034
8035 // update the point itself
8036 if (g_btouch) {
8037 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8038 // new_cursor_lat, new_cursor_lon);
8039 RoutePointGui(*m_pRoutePointEditTarget).SetPointFromDraghandlePoint(this, mouse_x,
8040 mouse_y);
8041 // update the Drag Handle entry in the pSelect list
8042 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8043 m_pRoutePointEditTarget,
8044 SELTYPE_DRAGHANDLE);
8045 m_pFoundPoint->m_slat =
8046 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8047 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8048 } else {
8049 m_pRoutePointEditTarget->m_lat =
8050 new_cursor_lat; // update the RoutePoint entry
8051 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8052 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8053 m_pFoundPoint->m_slat =
8054 new_cursor_lat; // update the SelectList entry
8055 m_pFoundPoint->m_slon = new_cursor_lon;
8056 }
8057
8058 // Update the MarkProperties Dialog, if currently shown
8059 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8060 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8061 g_pMarkInfoDialog->UpdateProperties(true);
8062 }
8063
8064 if (g_bopengl) {
8065 // InvalidateGL();
8066 Refresh(false);
8067 } else {
8068 // Get the update rectangle for the edited route
8069 wxRect post_rect;
8070
8071 if (m_pEditRouteArray) {
8072 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8073 ir++) {
8074 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8075 if (g_pRouteMan->IsRouteValid(pr)) {
8076 wxRect route_rect;
8077 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8078 post_rect.Union(route_rect);
8079 }
8080 }
8081 }
8082
8083 // Invalidate the union region
8084 pre_rect.Union(post_rect);
8085 RefreshRect(pre_rect, false);
8086 }
8087 gFrame->RefreshCanvasOther(this);
8088 m_bRoutePoinDragging = true;
8089 }
8090 ret = true;
8091 } // if Route Editing
8092
8093 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8094 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8095
8096 if (NULL == g_pMarkInfoDialog) {
8097 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8098 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8099 DraggingAllowed = false;
8100
8101 if (m_pRoutePointEditTarget &&
8102 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8103 DraggingAllowed = false;
8104
8105 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8106
8107 if (DraggingAllowed) {
8108 if (!undo->InUndoableAction()) {
8109 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8110 Undo_NeedsCopy, m_pFoundPoint);
8111 }
8112
8113 // The mark may be an anchorwatch
8114 double lpp1 = 0.;
8115 double lpp2 = 0.;
8116 double lppmax;
8117
8118 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8119 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8120 }
8121 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8122 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8123 }
8124 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8125
8126 // Get the update rectangle for the un-edited mark
8127 wxRect pre_rect;
8128 if (!g_bopengl) {
8129 RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this, &pre_rect);
8130 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8131 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8132 (int)(lppmax - (pre_rect.height / 2)));
8133 }
8134
8135 // update the point itself
8136 if (g_btouch) {
8137 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8138 // m_cursor_lat, m_cursor_lon);
8139 RoutePointGui(*m_pRoutePointEditTarget).SetPointFromDraghandlePoint(this, mouse_x,
8140 mouse_y);
8141 // update the Drag Handle entry in the pSelect list
8142 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8143 m_pRoutePointEditTarget,
8144 SELTYPE_DRAGHANDLE);
8145 m_pFoundPoint->m_slat =
8146 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8147 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8148 } else {
8149 m_pRoutePointEditTarget->m_lat =
8150 m_cursor_lat; // update the RoutePoint entry
8151 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8152 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8153 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8154 m_pFoundPoint->m_slon = m_cursor_lon;
8155 }
8156
8157 // Update the MarkProperties Dialog, if currently shown
8158 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8159 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8160 g_pMarkInfoDialog->UpdateProperties(true);
8161 }
8162
8163 // Invalidate the union region
8164 if (g_bopengl) {
8165 if (!g_btouch) InvalidateGL();
8166 Refresh(false);
8167 } else {
8168 // Get the update rectangle for the edited mark
8169 wxRect post_rect;
8170 RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this,
8171 &post_rect);
8172 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8173 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8174 (int)(lppmax - (post_rect.height / 2)));
8175
8176 // Invalidate the union region
8177 pre_rect.Union(post_rect);
8178 RefreshRect(pre_rect, false);
8179 }
8180 gFrame->RefreshCanvasOther(this);
8181 m_bRoutePoinDragging = true;
8182 }
8183 ret = true;
8184 }
8185
8186 if (ret) return true;
8187 } // dragging
8188
8189 if (event.LeftUp()) {
8190 bool b_startedit_route = false;
8191 m_dragoffsetSet = false;
8192
8193 if (g_btouch) {
8194 m_bChartDragging = false;
8195 m_bIsInRadius = false;
8196
8197 if (m_routeState) // creating route?
8198 {
8199 if (m_bedge_pan) {
8200 m_bedge_pan = false;
8201 return false;
8202 }
8203
8204 double rlat, rlon;
8205 bool appending = false;
8206 bool inserting = false;
8207 Route *tail = 0;
8208
8209 rlat = m_cursor_lat;
8210 rlon = m_cursor_lon;
8211
8212 if (m_pRoutePointEditTarget) {
8213 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8214 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8215 wxRect wp_rect;
8216 RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this, &wp_rect);
8217 RefreshRect(wp_rect, true);
8218 m_pRoutePointEditTarget = NULL;
8219 }
8220 m_bRouteEditing = true;
8221
8222 if (m_routeState == 1) {
8223 m_pMouseRoute = new Route();
8224 m_pMouseRoute->SetHiLite(50);
8225 pRouteList->Append(m_pMouseRoute);
8226 r_rband.x = x;
8227 r_rband.y = y;
8228 }
8229
8230 // Check to see if there is a nearby point which may be reused
8231 RoutePoint *pMousePoint = NULL;
8232
8233 // Calculate meaningful SelectRadius
8234 double nearby_radius_meters =
8235 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8236
8237 RoutePoint *pNearbyPoint =
8238 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8239 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8240 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8241 int dlg_return;
8242#ifndef __WXOSX__
8243 m_FinishRouteOnKillFocus =
8244 false; // Avoid route finish on focus change for message dialog
8245 dlg_return = OCPNMessageBox(
8246 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
8247 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8248 m_FinishRouteOnKillFocus = true;
8249#else
8250 dlg_return = wxID_YES;
8251#endif
8252 if (dlg_return == wxID_YES) {
8253 pMousePoint = pNearbyPoint;
8254
8255 // Using existing waypoint, so nothing to delete for undo.
8256 if (m_routeState > 1)
8257 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8258 Undo_HasParent, NULL);
8259 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8260
8261 bool procede = false;
8262 if (tail) {
8263 procede = true;
8264 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8265 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8266 procede = false;
8267 }
8268
8269 if (procede) {
8270 int dlg_return;
8271 m_FinishRouteOnKillFocus = false;
8272 if (m_routeState == 1) { // first point in new route, preceeding
8273 // route to be added? touch case
8274
8275 wxString dmsg =
8276 _("Insert first part of this route in the new route?");
8277 if (tail->GetIndexOf(pMousePoint) ==
8278 tail->GetnPoints()) // Starting on last point of another
8279 // route?
8280 dmsg = _("Insert this route in the new route?");
8281
8282 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8283 dlg_return =
8284 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8285 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8286 m_FinishRouteOnKillFocus = true;
8287
8288 if (dlg_return == wxID_YES) {
8289 inserting = true; // part of the other route will be
8290 // preceeding the new route
8291 }
8292 }
8293 } else {
8294 wxString dmsg =
8295 _("Append last part of this route to the new route?");
8296 if (tail->GetIndexOf(pMousePoint) == 1)
8297 dmsg = _(
8298 "Append this route to the new route?"); // Picking the
8299 // first point of
8300 // another route?
8301
8302 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8303 dlg_return =
8304 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8305 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8306 m_FinishRouteOnKillFocus = true;
8307
8308 if (dlg_return == wxID_YES) {
8309 appending = true; // part of the other route will be
8310 // appended to the new route
8311 }
8312 }
8313 }
8314 }
8315
8316 // check all other routes to see if this point appears in any other
8317 // route If it appears in NO other route, then it should e
8318 // considered an isolated mark
8319 if (!FindRouteContainingWaypoint(pMousePoint))
8320 pMousePoint->SetShared(true);
8321 }
8322 }
8323
8324 if (NULL == pMousePoint) { // need a new point
8325 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8326 _T(""), wxEmptyString);
8327 pMousePoint->SetNameShown(false);
8328
8329 pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8330 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8331
8332 if (m_routeState > 1)
8333 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8334 Undo_IsOrphanded, NULL);
8335 }
8336
8337 if (m_routeState == 1) {
8338 // First point in the route.
8339 m_pMouseRoute->AddPoint(pMousePoint);
8340 } else {
8341 if (m_pMouseRoute->m_NextLegGreatCircle) {
8342 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8343 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8344 &rhumbBearing, &rhumbDist);
8345 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
8346 &gcDist, &gcBearing, NULL);
8347 double gcDistNM = gcDist / 1852.0;
8348
8349 // Empirically found expression to get reasonable route segments.
8350 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8351 pow(rhumbDist - gcDistNM - 1, 0.5);
8352
8353 wxString msg;
8354 msg << _("For this leg the Great Circle route is ")
8355 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8356 << _(" shorter than rhumbline.\n\n")
8357 << _("Would you like include the Great Circle routing points "
8358 "for this leg?");
8359
8360#ifndef __WXOSX__
8361 m_FinishRouteOnKillFocus = false;
8362 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8363 wxYES_NO | wxNO_DEFAULT);
8364 m_FinishRouteOnKillFocus = true;
8365#else
8366 int answer = wxID_NO;
8367#endif
8368
8369 if (answer == wxID_YES) {
8370 RoutePoint *gcPoint;
8371 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8372 wxRealPoint gcCoord;
8373
8374 for (int i = 1; i <= segmentCount; i++) {
8375 double fraction = (double)i * (1.0 / (double)segmentCount);
8376 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8377 gcDist * fraction, gcBearing,
8378 &gcCoord.x, &gcCoord.y, NULL);
8379
8380 if (i < segmentCount) {
8381 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8382 _T(""), wxEmptyString);
8383 gcPoint->SetNameShown(false);
8384 pConfig->AddNewWayPoint(gcPoint, -1);
8385 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8386 gcPoint);
8387 } else {
8388 gcPoint = pMousePoint; // Last point, previously exsisting!
8389 }
8390
8391 m_pMouseRoute->AddPoint(gcPoint);
8392 pSelect->AddSelectableRouteSegment(
8393 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8394 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8395 prevGcPoint = gcPoint;
8396 }
8397
8398 undo->CancelUndoableAction(true);
8399
8400 } else {
8401 m_pMouseRoute->AddPoint(pMousePoint);
8402 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8403 rlon, m_prev_pMousePoint,
8404 pMousePoint, m_pMouseRoute);
8405 undo->AfterUndoableAction(m_pMouseRoute);
8406 }
8407 } else {
8408 // Ordinary rhumblinesegment.
8409 m_pMouseRoute->AddPoint(pMousePoint);
8410 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8411 rlon, m_prev_pMousePoint,
8412 pMousePoint, m_pMouseRoute);
8413 undo->AfterUndoableAction(m_pMouseRoute);
8414 }
8415 }
8416
8417 m_prev_rlat = rlat;
8418 m_prev_rlon = rlon;
8419 m_prev_pMousePoint = pMousePoint;
8420 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8421
8422 m_routeState++;
8423
8424 if (appending ||
8425 inserting) { // Appending a route or making a new route
8426 int connect = tail->GetIndexOf(pMousePoint);
8427 if (connect == 1) {
8428 inserting = false; // there is nothing to insert
8429 appending = true; // so append
8430 }
8431 int length = tail->GetnPoints();
8432
8433 int i;
8434 int start, stop;
8435 if (appending) {
8436 start = connect + 1;
8437 stop = length;
8438 } else { // inserting
8439 start = 1;
8440 stop = connect;
8441 m_pMouseRoute->RemovePoint(
8442 m_pMouseRoute
8443 ->GetLastPoint()); // Remove the first and only point
8444 }
8445 for (i = start; i <= stop; i++) {
8446 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8447 if (m_pMouseRoute)
8448 m_pMouseRoute->m_lastMousePointIndex =
8449 m_pMouseRoute->GetnPoints();
8450 m_routeState++;
8451 gFrame->RefreshAllCanvas();
8452 ret = true;
8453 }
8454 m_prev_rlat =
8455 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8456 m_prev_rlon =
8457 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8458 m_pMouseRoute->FinalizeForRendering();
8459 }
8460
8461 Refresh(true);
8462 ret = true;
8463 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
8464 {
8465 if (m_bedge_pan) {
8466 m_bedge_pan = false;
8467 return false;
8468 }
8469
8470 if (m_nMeasureState == 1) {
8471 m_pMeasureRoute = new Route();
8472 pRouteList->Append(m_pMeasureRoute);
8473 r_rband.x = x;
8474 r_rband.y = y;
8475 }
8476
8477 if (m_pMeasureRoute){
8478 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8479 wxString(_T ( "circle" )),
8480 wxEmptyString, wxEmptyString);
8481 pMousePoint->m_bShowName = false;
8482
8483 m_pMeasureRoute->AddPoint(pMousePoint);
8484
8485 m_prev_rlat = m_cursor_lat;
8486 m_prev_rlon = m_cursor_lon;
8487 m_prev_pMousePoint = pMousePoint;
8488 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8489
8490 m_nMeasureState++;
8491 }
8492 else {
8493 CancelMeasureRoute();
8494 }
8495
8496 Refresh(true);
8497 ret = true;
8498 } else {
8499 bool bSelectAllowed = true;
8500 if (NULL == g_pMarkInfoDialog) {
8501 if (g_bWayPointPreventDragging) bSelectAllowed = false;
8502 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8503 bSelectAllowed = false;
8504
8505 /*if this left up happens at the end of a route point dragging and if
8506 the cursor/thumb is on the draghandle icon, not on the point iself a new
8507 selection will select nothing and the drag will never be ended, so the
8508 legs around this point never selectable. At this step we don't need a
8509 new selection, just keep the previoulsly selected and dragged point */
8510 if (m_bRoutePoinDragging) bSelectAllowed = false;
8511
8512 if (bSelectAllowed) {
8513 bool b_was_editing_mark = m_bMarkEditing;
8514 bool b_was_editing_route = m_bRouteEditing;
8515 FindRoutePointsAtCursor(SelectRadius,
8516 true); // Possibly selecting a point in a
8517 // route for later dragging
8518
8519 /*route and a mark points in layer can't be dragged so should't be
8520 * selected and no draghandle icon*/
8521 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
8522 m_pRoutePointEditTarget = NULL;
8523
8524 if (!b_was_editing_route) {
8525 if (m_pEditRouteArray) {
8526 b_startedit_route = true;
8527
8528 // Hide the track and route rollover during route point edit, not
8529 // needed, and may be confusing
8530 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
8531 m_pTrackRolloverWin->IsActive(false);
8532 }
8533 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
8534 m_pRouteRolloverWin->IsActive(false);
8535 }
8536
8537 wxRect pre_rect;
8538 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8539 ir++) {
8540 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8541 // Need to validate route pointer
8542 // Route may be gone due to drgging close to ownship with
8543 // "Delete On Arrival" state set, as in the case of
8544 // navigating to an isolated waypoint on a temporary route
8545 if (g_pRouteMan->IsRouteValid(pr)) {
8546 // pr->SetHiLite(50);
8547 wxRect route_rect;
8548 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8549 pre_rect.Union(route_rect);
8550 }
8551 }
8552 RefreshRect(pre_rect, true);
8553 }
8554 } else {
8555 b_startedit_route = false;
8556 }
8557
8558 // Mark editing
8559 if (m_pRoutePointEditTarget) {
8560 if (b_was_editing_mark ||
8561 b_was_editing_route) { // kill previous hilight
8562 if (m_lastRoutePointEditTarget) {
8563 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
8564 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
8565 RoutePointGui(*m_lastRoutePointEditTarget).EnableDragHandle(false);
8566 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
8567 SELTYPE_DRAGHANDLE);
8568 }
8569 }
8570
8571 if (m_pRoutePointEditTarget) {
8572 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
8573 m_pRoutePointEditTarget->m_bPtIsSelected = true;
8574 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
8575 wxPoint2DDouble dragHandlePoint =
8576 RoutePointGui(*m_pRoutePointEditTarget).GetDragHandlePoint(this);
8577 pSelect->AddSelectablePoint(
8578 dragHandlePoint.m_y, dragHandlePoint.m_x,
8579 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
8580 }
8581 } else { // Deselect everything
8582 if (m_lastRoutePointEditTarget) {
8583 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
8584 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
8585 RoutePointGui(*m_lastRoutePointEditTarget).EnableDragHandle(false);
8586 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
8587 SELTYPE_DRAGHANDLE);
8588
8589 // Clear any routes being edited, probably orphans
8590 wxArrayPtrVoid *lastEditRouteArray =
8591 g_pRouteMan->GetRouteArrayContaining(
8592 m_lastRoutePointEditTarget);
8593 if (lastEditRouteArray) {
8594 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
8595 ir++) {
8596 Route *pr = (Route *)lastEditRouteArray->Item(ir);
8597 if (g_pRouteMan->IsRouteValid(pr)) {
8598 pr->m_bIsBeingEdited = false;
8599 }
8600 }
8601 }
8602 }
8603 }
8604
8605 // Do the refresh
8606
8607 if (g_bopengl) {
8608 InvalidateGL();
8609 Refresh(false);
8610 } else {
8611 if (m_lastRoutePointEditTarget) {
8612 wxRect wp_rect;
8613 RoutePointGui(*m_lastRoutePointEditTarget).CalculateDCRect(m_dc_route, this,
8614 &wp_rect);
8615 RefreshRect(wp_rect, true);
8616 }
8617
8618 if (m_pRoutePointEditTarget) {
8619 wxRect wp_rect;
8620 RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this,
8621 &wp_rect);
8622 RefreshRect(wp_rect, true);
8623 }
8624 }
8625 }
8626 } // bSelectAllowed
8627
8628 // Check to see if there is a route or AIS target under the cursor
8629 // If so, start the rollover timer which creates the popup
8630 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale());
8631 bool b_start_rollover = false;
8632 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
8633 SelectItem *pFind = pSelectAIS->FindSelection(
8634 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
8635 if (pFind) b_start_rollover = true;
8636 }
8637
8638 if (!b_start_rollover && !b_startedit_route) {
8639 SelectableItemList SelList = pSelect->FindSelectionList(
8640 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
8641 wxSelectableItemListNode *node = SelList.GetFirst();
8642 while (node) {
8643 SelectItem *pFindSel = node->GetData();
8644
8645 Route *pr = (Route *)pFindSel->m_pData3; // candidate
8646
8647 if (pr && pr->IsVisible()) {
8648 b_start_rollover = true;
8649 break;
8650 }
8651 node = node->GetNext();
8652 } // while
8653 }
8654
8655 if (!b_start_rollover && !b_startedit_route) {
8656 SelectableItemList SelList = pSelect->FindSelectionList(
8657 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
8658 wxSelectableItemListNode *node = SelList.GetFirst();
8659 while (node) {
8660 SelectItem *pFindSel = node->GetData();
8661
8662 Track *tr = (Track *)pFindSel->m_pData3; // candidate
8663
8664 if (tr && tr->IsVisible()) {
8665 b_start_rollover = true;
8666 break;
8667 }
8668 node = node->GetNext();
8669 } // while
8670 }
8671
8672 if (b_start_rollover)
8673 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
8674 wxTIMER_ONE_SHOT);
8675 Route *tail, *current;
8676 bool appending = false;
8677 bool inserting = false;
8678 int connect = 0;
8679 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
8680 // drag
8681 if (m_pRoutePointEditTarget) {
8682 // Check to see if there is a nearby point which may replace the
8683 // dragged one
8684 RoutePoint *pMousePoint = NULL;
8685
8686 int index_last;
8687 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
8688 double nearby_radius_meters =
8689 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8690 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
8691 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
8692 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
8693 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
8694 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
8695 bool duplicate =
8696 false; // ensure we won't create duplicate point in routes
8697 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
8698 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8699 ir++) {
8700 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8701 if (pr && pr->pRoutePointList) {
8702 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
8703 wxNOT_FOUND) {
8704 duplicate = true;
8705 break;
8706 }
8707 }
8708 }
8709 }
8710
8711 // Special case:
8712 // Allow "re-use" of a route's waypoints iff it is a simple
8713 // isolated route. This allows, for instance, creation of a closed
8714 // polygon route
8715 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
8716
8717 if (!duplicate) {
8718 int dlg_return;
8719 dlg_return =
8720 OCPNMessageBox(this,
8721 _("Replace this RoutePoint by the nearby "
8722 "Waypoint?"),
8723 _("OpenCPN RoutePoint change"),
8724 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8725 if (dlg_return == wxID_YES) {
8726 /*double confirmation if the dragged point has been manually
8727 * created which can be important and could be deleted
8728 * unintentionally*/
8729
8730 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
8731 pNearbyPoint);
8732 current = FindRouteContainingWaypoint(
8733 m_pRoutePointEditTarget);
8734
8735 if (tail && current && (tail != current)) {
8736 int dlg_return1;
8737 connect = tail->GetIndexOf(pNearbyPoint);
8738 int index_current_route =
8739 current->GetIndexOf(m_pRoutePointEditTarget);
8740 index_last = current->GetIndexOf(current->GetLastPoint());
8741 dlg_return1 = wxID_NO;
8742 if (index_last ==
8743 index_current_route) { // we are dragging the last
8744 // point of the route
8745 if (connect != tail->GetnPoints()) { // anything to do?
8746
8747 wxString dmsg(
8748 _("Last part of route to be appended to dragged "
8749 "route?"));
8750 if (connect == 1)
8751 dmsg =
8752 _("Full route to be appended to dragged route?");
8753
8754 dlg_return1 = OCPNMessageBox(
8755 this, dmsg, _("OpenCPN Route Create"),
8756 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8757 if (dlg_return1 == wxID_YES) {
8758 appending = true;
8759 }
8760 }
8761 } else if (index_current_route ==
8762 1) { // dragging the first point of the route
8763 if (connect != 1) { // anything to do?
8764
8765 wxString dmsg(
8766 _("First part of route to be inserted into dragged "
8767 "route?"));
8768 if (connect == tail->GetnPoints())
8769 dmsg = _(
8770 "Full route to be inserted into dragged route?");
8771
8772 dlg_return1 = OCPNMessageBox(
8773 this, dmsg, _("OpenCPN Route Create"),
8774 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8775 if (dlg_return1 == wxID_YES) {
8776 inserting = true;
8777 }
8778 }
8779 }
8780 }
8781
8782 if (m_pRoutePointEditTarget->IsShared()) {
8783 // dlg_return = wxID_NO;
8784 dlg_return = OCPNMessageBox(
8785 this,
8786 _("Do you really want to delete and replace this "
8787 "WayPoint") +
8788 _T("\n") + _("which has been created manually?"),
8789 ("OpenCPN RoutePoint warning"),
8790 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8791 }
8792 }
8793 if (dlg_return == wxID_YES) {
8794 pMousePoint = pNearbyPoint;
8795 if (pMousePoint->m_bIsolatedMark) {
8796 pMousePoint->SetShared(true);
8797 }
8798 pMousePoint->m_bIsolatedMark =
8799 false; // definitely no longer isolated
8800 pMousePoint->m_bIsInRoute = true;
8801 }
8802 }
8803 }
8804 }
8805 if (!pMousePoint)
8806 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
8807
8808 if (m_pEditRouteArray) {
8809 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8810 ir++) {
8811 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8812 if (g_pRouteMan->IsRouteValid(pr)) {
8813 if (pMousePoint) { // remove the dragged point and insert the
8814 // nearby
8815 int nRP =
8816 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
8817
8818 pSelect->DeleteAllSelectableRoutePoints(pr);
8819 pSelect->DeleteAllSelectableRouteSegments(pr);
8820
8821 pr->pRoutePointList->Insert(nRP, pMousePoint);
8822 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
8823
8824 pSelect->AddAllSelectableRouteSegments(pr);
8825 pSelect->AddAllSelectableRoutePoints(pr);
8826 }
8827 pr->FinalizeForRendering();
8828 pr->UpdateSegmentDistances();
8829 if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
8830 }
8831 }
8832 }
8833
8834 // Update the RouteProperties Dialog, if currently shown
8835 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
8836 if (m_pEditRouteArray) {
8837 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8838 ir++) {
8839 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8840 if (g_pRouteMan->IsRouteValid(pr)) {
8841 if (pRoutePropDialog->GetRoute() == pr) {
8842 pRoutePropDialog->SetRouteAndUpdate(pr, true);
8843 }
8844 /* cannot edit track points anyway
8845 else if ( ( NULL !=
8846 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
8847 pTrackPropDialog->m_pTrack == pr ) {
8848 pTrackPropDialog->SetTrackAndUpdate(
8849 pr );
8850 }
8851 */
8852 }
8853 }
8854 }
8855 }
8856 if (pMousePoint) { // clear all about the dragged point
8857 pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
8858 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
8859 // Hide mark properties dialog if open on the replaced point
8860 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
8861 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8862 g_pMarkInfoDialog->Hide();
8863
8864 delete m_pRoutePointEditTarget;
8865 m_lastRoutePointEditTarget = NULL;
8866 m_pRoutePointEditTarget = NULL;
8867 undo->AfterUndoableAction(pMousePoint);
8868 undo->InvalidateUndo();
8869 }
8870 }
8871 }
8872
8873 else if (m_bMarkEditing) { // End of way point drag
8874 if (m_pRoutePointEditTarget)
8875 if (m_bRoutePoinDragging)
8876 pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
8877 }
8878
8879 if (m_pRoutePointEditTarget)
8880 undo->AfterUndoableAction(m_pRoutePointEditTarget);
8881
8882 if (!m_pRoutePointEditTarget) {
8883 delete m_pEditRouteArray;
8884 m_pEditRouteArray = NULL;
8885 m_bRouteEditing = false;
8886 }
8887 m_bRoutePoinDragging = false;
8888
8889 if (appending) { // Appending to the route of which the last point is
8890 // dragged onto another route
8891
8892 // copy tail from connect until length to end of current after dragging
8893
8894 int length = tail->GetnPoints();
8895 for (int i = connect + 1; i <= length; i++) {
8896 current->AddPointAndSegment(tail->GetPoint(i), false);
8897 if (current) current->m_lastMousePointIndex = current->GetnPoints();
8898 m_routeState++;
8899 gFrame->RefreshAllCanvas();
8900 ret = true;
8901 }
8902 current->FinalizeForRendering();
8903 current->m_bIsBeingEdited = false;
8904 }
8905 if (inserting) {
8906 for (int i = 1; i < connect; i++) { // numbering in the tail route
8907 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
8908 }
8909 current->FinalizeForRendering();
8910 current->m_bIsBeingEdited = false;
8911 }
8912
8913 // Update the RouteProperties Dialog, if currently shown
8914 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
8915 if (m_pEditRouteArray) {
8916 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8917 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8918 if (g_pRouteMan->IsRouteValid(pr)) {
8919 if (pRoutePropDialog->GetRoute() == pr) {
8920 pRoutePropDialog->SetRouteAndUpdate(pr, true);
8921 }
8922 }
8923 }
8924 }
8925 }
8926
8927 } // g_btouch
8928
8929 else { // !g_btouch
8930 if (m_bRouteEditing) { // End of RoutePoint drag
8931 Route *tail, *current;
8932 bool appending = false;
8933 bool inserting = false;
8934 int connect;
8935 int index_last;
8936 if (m_pRoutePointEditTarget) {
8937 m_pRoutePointEditTarget->m_bBlink = false;
8938 // Check to see if there is a nearby point which may replace the
8939 // dragged one
8940 RoutePoint *pMousePoint = NULL;
8941 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
8942 double nearby_radius_meters =
8943 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8944 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
8945 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
8946 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
8947 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
8948 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
8949 bool duplicate = false; // don't create duplicate point in routes
8950 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
8951 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8952 ir++) {
8953 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8954 if (pr && pr->pRoutePointList) {
8955 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
8956 wxNOT_FOUND) {
8957 duplicate = true;
8958 break;
8959 }
8960 }
8961 }
8962 }
8963
8964 // Special case:
8965 // Allow "re-use" of a route's waypoints iff it is a simple
8966 // isolated route. This allows, for instance, creation of a closed
8967 // polygon route
8968 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
8969
8970 if (!duplicate) {
8971 int dlg_return;
8972 dlg_return =
8973 OCPNMessageBox(this,
8974 _("Replace this RoutePoint by the nearby "
8975 "Waypoint?"),
8976 _("OpenCPN RoutePoint change"),
8977 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8978 if (dlg_return == wxID_YES) {
8979 /*double confirmation if the dragged point has been manually
8980 * created which can be important and could be deleted
8981 * unintentionally*/
8982 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
8983 pNearbyPoint);
8984 current = FindRouteContainingWaypoint(
8985 m_pRoutePointEditTarget);
8986
8987 if (tail && current && (tail != current)) {
8988 int dlg_return1;
8989 connect = tail->GetIndexOf(pNearbyPoint);
8990 int index_current_route =
8991 current->GetIndexOf(m_pRoutePointEditTarget);
8992 index_last = current->GetIndexOf(current->GetLastPoint());
8993 dlg_return1 = wxID_NO;
8994 if (index_last ==
8995 index_current_route) { // we are dragging the last
8996 // point of the route
8997 if (connect != tail->GetnPoints()) { // anything to do?
8998
8999 wxString dmsg(
9000 _("Last part of route to be appended to dragged "
9001 "route?"));
9002 if (connect == 1)
9003 dmsg =
9004 _("Full route to be appended to dragged route?");
9005
9006 dlg_return1 = OCPNMessageBox(
9007 this, dmsg, _("OpenCPN Route Create"),
9008 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9009 if (dlg_return1 == wxID_YES) {
9010 appending = true;
9011 }
9012 }
9013 } else if (index_current_route ==
9014 1) { // dragging the first point of the route
9015 if (connect != 1) { // anything to do?
9016
9017 wxString dmsg(
9018 _("First part of route to be inserted into dragged "
9019 "route?"));
9020 if (connect == tail->GetnPoints())
9021 dmsg = _(
9022 "Full route to be inserted into dragged route?");
9023
9024 dlg_return1 = OCPNMessageBox(
9025 this, dmsg, _("OpenCPN Route Create"),
9026 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9027 if (dlg_return1 == wxID_YES) {
9028 inserting = true;
9029 }
9030 }
9031 }
9032 }
9033
9034 if (m_pRoutePointEditTarget->IsShared()) {
9035 dlg_return = wxID_NO;
9036 dlg_return = OCPNMessageBox(
9037 this,
9038 _("Do you really want to delete and replace this "
9039 "WayPoint") +
9040 _T("\n") + _("which has been created manually?"),
9041 ("OpenCPN RoutePoint warning"),
9042 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9043 }
9044 }
9045 if (dlg_return == wxID_YES) {
9046 pMousePoint = pNearbyPoint;
9047 if (pMousePoint->m_bIsolatedMark) {
9048 pMousePoint->SetShared(true);
9049 }
9050 pMousePoint->m_bIsolatedMark =
9051 false; // definitely no longer isolated
9052 pMousePoint->m_bIsInRoute = true;
9053 }
9054 }
9055 }
9056 }
9057 if (!pMousePoint)
9058 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9059
9060 if (m_pEditRouteArray) {
9061 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9062 ir++) {
9063 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9064 if (g_pRouteMan->IsRouteValid(pr)) {
9065 if (pMousePoint) { // replace dragged point by nearby one
9066 int nRP =
9067 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9068
9069 pSelect->DeleteAllSelectableRoutePoints(pr);
9070 pSelect->DeleteAllSelectableRouteSegments(pr);
9071
9072 pr->pRoutePointList->Insert(nRP, pMousePoint);
9073 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9074
9075 pSelect->AddAllSelectableRouteSegments(pr);
9076 pSelect->AddAllSelectableRoutePoints(pr);
9077 }
9078 pr->FinalizeForRendering();
9079 pr->UpdateSegmentDistances();
9080 pr->m_bIsBeingEdited = false;
9081
9082 if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9083
9084 pr->SetHiLite(0);
9085 }
9086 }
9087 Refresh(false);
9088 }
9089
9090 if (appending) {
9091 // copy tail from connect until length to end of current after
9092 // dragging
9093
9094 int length = tail->GetnPoints();
9095 for (int i = connect + 1; i <= length; i++) {
9096 current->AddPointAndSegment(tail->GetPoint(i), false);
9097 if (current)
9098 current->m_lastMousePointIndex = current->GetnPoints();
9099 m_routeState++;
9100 gFrame->RefreshAllCanvas();
9101 ret = true;
9102 }
9103 current->FinalizeForRendering();
9104 current->m_bIsBeingEdited = false;
9105 FinishRoute();
9106 }
9107 if (inserting) {
9108 for (int i = 1; i < connect; i++) { // numbering in the tail route
9109 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9110 }
9111 current->FinalizeForRendering();
9112 current->m_bIsBeingEdited = false;
9113 }
9114
9115 // Update the RouteProperties Dialog, if currently shown
9116 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9117 if (m_pEditRouteArray) {
9118 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9119 ir++) {
9120 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9121 if (g_pRouteMan->IsRouteValid(pr)) {
9122 if (pRoutePropDialog->GetRoute() == pr) {
9123 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9124 }
9125 }
9126 }
9127 }
9128 }
9129
9130 if (pMousePoint) {
9131 pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9132 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9133 // Hide mark properties dialog if open on the replaced point
9134 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9135 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9136 g_pMarkInfoDialog->Hide();
9137
9138 delete m_pRoutePointEditTarget;
9139 m_lastRoutePointEditTarget = NULL;
9140 undo->AfterUndoableAction(pMousePoint);
9141 undo->InvalidateUndo();
9142 } else {
9143 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9144 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9145
9146 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9147 }
9148
9149 delete m_pEditRouteArray;
9150 m_pEditRouteArray = NULL;
9151 }
9152
9153 InvalidateGL();
9154 m_bRouteEditing = false;
9155 m_pRoutePointEditTarget = NULL;
9156
9157 if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9158 ret = true;
9159 }
9160
9161 else if (m_bMarkEditing) { // end of Waypoint drag
9162 if (m_pRoutePointEditTarget) {
9163 if (m_bRoutePoinDragging)
9164 pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9165 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9166 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9167 wxRect wp_rect;
9168 RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this, &wp_rect);
9169 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9170 RefreshRect(wp_rect, true);
9171 }
9172 m_pRoutePointEditTarget = NULL;
9173 m_bMarkEditing = false;
9174 if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9175 ret = true;
9176 }
9177
9178 else if (leftIsDown) { // left click for chart center
9179 leftIsDown = false;
9180 ret = false;
9181
9182 if (!g_btouch) {
9183 if (!m_bChartDragging && !m_bMeasure_Active) {
9184 } else {
9185 m_bChartDragging = false;
9186 }
9187 }
9188 }
9189 m_bRoutePoinDragging = false;
9190 } // !btouch
9191
9192 if (ret) return true;
9193 } // left up
9194
9195 if (event.RightDown()) {
9196 SetFocus(); // This is to let a plugin know which canvas is right-clicked
9197 last_drag.x = mx;
9198 last_drag.y = my;
9199
9200 if (g_btouch) {
9201 // if( m_pRoutePointEditTarget )
9202 // return false;
9203 }
9204
9205 ret = true;
9206 m_FinishRouteOnKillFocus = false;
9207 CallPopupMenu(mx, my);
9208 m_FinishRouteOnKillFocus = true;
9209 } // Right down
9210
9211 return ret;
9212}
9213
9214bool panleftIsDown;
9215
9216bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
9217 int x, y;
9218 event.GetPosition(&x, &y);
9219
9220 x *= m_displayScale;
9221 y *= m_displayScale;
9222
9223 // Check for wheel rotation
9224 // ideally, should be just longer than the time between
9225 // processing accumulated mouse events from the event queue
9226 // as would happen during screen redraws.
9227 int wheel_dir = event.GetWheelRotation();
9228
9229 if (wheel_dir) {
9230 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
9231 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
9232
9233 double factor = g_mouse_zoom_sensitivity;
9234 if (wheel_dir < 0) factor = 1 / factor;
9235
9236 if (g_bsmoothpanzoom) {
9237 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
9238 if (wheel_dir == m_last_wheel_dir) {
9239 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
9240 // m_zoom_target /= factor;
9241 } else
9242 StopMovement();
9243 } else {
9244 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
9245 m_wheelstopwatch.Start(0);
9246 // m_zoom_target = VPoint.chart_scale / factor;
9247 }
9248 }
9249
9250 m_last_wheel_dir = wheel_dir;
9251
9252 ZoomCanvas(factor, true, false);
9253 }
9254
9255 if (event.LeftDown()) {
9256 // Skip the first left click if it will cause a canvas focus shift
9257 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
9258 // printf("focus shift\n");
9259 return false;
9260 }
9261
9262 last_drag.x = x, last_drag.y = y;
9263 panleftIsDown = true;
9264 }
9265
9266 if (event.LeftUp()) {
9267 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
9268 // seen here.
9269 panleftIsDown = false;
9270
9271 if (!g_btouch) {
9272 if (!m_bChartDragging && !m_bMeasure_Active) {
9273 switch (cursor_region) {
9274 case MID_RIGHT: {
9275 PanCanvas(100, 0);
9276 break;
9277 }
9278
9279 case MID_LEFT: {
9280 PanCanvas(-100, 0);
9281 break;
9282 }
9283
9284 case MID_TOP: {
9285 PanCanvas(0, 100);
9286 break;
9287 }
9288
9289 case MID_BOT: {
9290 PanCanvas(0, -100);
9291 break;
9292 }
9293
9294 case CENTER: {
9295 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
9296 break;
9297 }
9298 }
9299 } else {
9300 m_bChartDragging = false;
9301 }
9302 }
9303 }
9304 }
9305
9306 if (event.Dragging() && event.LeftIsDown()) {
9307 /*
9308 * fixed dragging.
9309 * On my Surface Pro 3 running Arch Linux there is no mouse down event
9310 * before the drag event. Hence, as there is no mouse down event, last_drag
9311 * is not reset before the drag. And that results in one single drag
9312 * session, meaning you cannot drag the map a few miles north, lift your
9313 * finger, and the go even further north. Instead, the map resets itself
9314 * always to the very first drag start (since there is not reset of
9315 * last_drag).
9316 *
9317 * Besides, should not left down and dragging be enough of a situation to
9318 * start a drag procedure?
9319 *
9320 * Anyways, guarded it to be active in touch situations only.
9321 */
9322 if (g_btouch) {
9323 if (false == m_bChartDragging) {
9324 last_drag.x = x, last_drag.y = y;
9325 m_bChartDragging = true;
9326 }
9327 }
9328
9329 if ((last_drag.x != x) || (last_drag.y != y)) {
9330 if(!m_routeState){ // Correct fault on wx32/gtk3, uncommanded dragging on route create.
9331 // github #2994
9332 m_bChartDragging = true;
9333 StartTimedMovement();
9334 m_pan_drag.x += last_drag.x - x;
9335 m_pan_drag.y += last_drag.y - y;
9336
9337 last_drag.x = x, last_drag.y = y;
9338 }
9339
9340 if (g_btouch) {
9341 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
9342 // deactivate next LeftUp to ovoid creating an unexpected point
9343 m_DoubleClickTimer->Start();
9344 singleClickEventIsValid = false;
9345 }
9346 }
9347 }
9348 }
9349
9350 return true;
9351}
9352
9353void ChartCanvas::MouseEvent(wxMouseEvent &event) {
9354 if (MouseEventOverlayWindows(event)) return;
9355
9356 if (MouseEventSetup(event)) return; // handled, no further action required
9357
9358 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
9359}
9360
9361void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
9362 // Switch to the appropriate cursor on mouse movement
9363
9364 wxCursor *ptarget_cursor = pCursorArrow;
9365 if (!pPlugIn_Cursor) {
9366 ptarget_cursor = pCursorArrow;
9367 if ((!m_routeState) &&
9368 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
9369 if (cursor_region == MID_RIGHT) {
9370 ptarget_cursor = pCursorRight;
9371 } else if (cursor_region == MID_LEFT) {
9372 ptarget_cursor = pCursorLeft;
9373 } else if (cursor_region == MID_TOP) {
9374 ptarget_cursor = pCursorDown;
9375 } else if (cursor_region == MID_BOT) {
9376 ptarget_cursor = pCursorUp;
9377 } else {
9378 ptarget_cursor = pCursorArrow;
9379 }
9380 } else if (m_bMeasure_Active ||
9381 m_routeState) // If Measure tool use Pencil Cursor
9382 ptarget_cursor = pCursorPencil;
9383 } else {
9384 ptarget_cursor = pPlugIn_Cursor;
9385 }
9386
9387 SetCursor(*ptarget_cursor);
9388}
9389
9390void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
9391 SetCursor(*pCursorArrow);
9392}
9393
9394void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
9395 ChartPlugInWrapper *target_plugin_chart = NULL;
9396 s57chart *Chs57 = NULL;
9397 wxFileName file;
9398 wxArrayString files;
9399
9400 ChartBase *target_chart = GetChartAtCursor();
9401 if (target_chart) {
9402 file.Assign(target_chart->GetFullPath());
9403 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
9404 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
9405 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
9406 else
9407 Chs57 = dynamic_cast<s57chart *>(target_chart);
9408 } else { // target_chart = null, might be mbtiles
9409 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
9410 unsigned int im = stackIndexArray.size();
9411 int scale = 2147483647; // max 32b integer
9412 if (VPoint.b_quilt && im > 0) {
9413 for (unsigned int is = 0; is < im; is++) {
9414 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
9415 CHART_TYPE_MBTILES) {
9416 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
9417 double lat, lon;
9418 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
9419 if (ChartData->GetChartTableEntry(stackIndexArray[is])
9420 .GetBBox()
9421 .Contains(lat, lon)) {
9422 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
9423 scale) {
9424 scale =
9425 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
9426 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
9427 }
9428 }
9429 }
9430 }
9431 }
9432 }
9433
9434 std::vector<Ais8_001_22 *> area_notices;
9435
9436 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
9437 float vp_scale = GetVPScale();
9438
9439 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
9440 auto target_data = target.second;
9441 if (!target_data->area_notices.empty()) {
9442 for (auto &ani : target_data->area_notices) {
9443 Ais8_001_22 &area_notice = ani.second;
9444
9445 BoundingBox bbox;
9446
9447 for (Ais8_001_22_SubAreaList::iterator sa =
9448 area_notice.sub_areas.begin();
9449 sa != area_notice.sub_areas.end(); ++sa) {
9450 switch (sa->shape) {
9451 case AIS8_001_22_SHAPE_CIRCLE: {
9452 wxPoint target_point;
9453 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
9454 bbox.Expand(target_point);
9455 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
9456 break;
9457 }
9458 case AIS8_001_22_SHAPE_POLYGON:
9459 case AIS8_001_22_SHAPE_POLYLINE: {
9460 for (int i = 0; i < 4; ++i) {
9461 double lat = sa->latitude;
9462 double lon = sa->longitude;
9463 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
9464 &lat, &lon);
9465 wxPoint target_point;
9466 GetCanvasPointPix(lat, lon, &target_point);
9467 bbox.Expand(target_point);
9468 }
9469 }
9470 }
9471 }
9472
9473 if (bbox.PointInBox(x, y)) {
9474 area_notices.push_back(&area_notice);
9475 }
9476 }
9477 }
9478 }
9479 }
9480
9481 if (target_chart || !area_notices.empty() || file.HasName()) {
9482 // Go get the array of all objects at the cursor lat/lon
9483 int sel_rad_pix = 5;
9484 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
9485
9486 // Make sure we always get the lights from an object, even if we are
9487 // currently not displaying lights on the chart.
9488
9489 SetCursor(wxCURSOR_WAIT);
9490 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
9491 if (!lightsVis) SetShowENCLights(true);
9492 ;
9493
9494 ListOfObjRazRules *rule_list = NULL;
9495 ListOfPI_S57Obj *pi_rule_list = NULL;
9496 if (Chs57)
9497 rule_list =
9498 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
9499 else if (target_plugin_chart)
9500 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
9501 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
9502
9503 ListOfObjRazRules *overlay_rule_list = NULL;
9504 ChartBase *overlay_chart = GetOverlayChartAtCursor();
9505 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
9506
9507 if (CHs57_Overlay) {
9508 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
9509 zlat, zlon, SelectRadius, &GetVP());
9510 }
9511
9512 if (!lightsVis) SetShowENCLights(false);
9513
9514 wxString objText;
9515 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
9516 wxString face = dFont->GetFaceName();
9517
9518 if (NULL == g_pObjectQueryDialog) {
9519 g_pObjectQueryDialog =
9520 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
9521 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
9522 }
9523
9524 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
9525 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
9526
9527#ifdef __WXOSX__
9528 // Auto Adjustment for dark mode
9529 fg = g_pObjectQueryDialog->GetForegroundColour();
9530#endif
9531
9532 objText.Printf(
9533 _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
9534 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
9535
9536#ifdef __WXOSX__
9537 int points = dFont->GetPointSize();
9538#else
9539 int points = dFont->GetPointSize() + 1;
9540#endif
9541
9542 int sizes[7];
9543 for (int i = -2; i < 5; i++) {
9544 sizes[i + 2] = points + i + (i > 0 ? i : 0);
9545 }
9546 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
9547
9548 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
9549
9550 if (overlay_rule_list && CHs57_Overlay) {
9551 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
9552 objText << _T("<hr noshade>");
9553 }
9554
9555 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
9556 an != area_notices.end(); ++an) {
9557 objText << _T( "<b>AIS Area Notice:</b> " );
9558 objText << ais8_001_22_notice_names[(*an)->notice_type];
9559 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
9560 (*an)->sub_areas.begin();
9561 sa != (*an)->sub_areas.end(); ++sa)
9562 if (!sa->text.empty()) objText << sa->text;
9563 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
9564 objText << _T( "<hr noshade>" );
9565 }
9566
9567 if (Chs57)
9568 objText << Chs57->CreateObjDescriptions(rule_list);
9569 else if (target_plugin_chart)
9570 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
9571 pi_rule_list);
9572
9573 objText << _T("</font>");
9574 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
9575
9576 // Add the additional info files
9577 wxString AddFiles, filenameOK;
9578 int filecount = 0;
9579 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
9580 // plugin
9581
9582 AddFiles = wxString::Format(
9583 _T("<hr noshade><br><b>Additional info files attached to: </b> ")
9584 _T("<font ")
9585 _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
9586 _T("cellpadding=3>"),
9587 file.GetFullName());
9588 file.Normalize();
9589 file.Assign(file.GetPath(), wxT(""));
9590 wxDir dir( file.GetFullPath() );
9591 wxString filename;
9592 bool cont = dir.GetFirst( &filename, "", wxDIR_FILES );
9593 while ( cont )
9594 {
9595 file.Assign( dir.GetNameWithSep().append( filename) );
9596 wxString FormatString = _T("<td valign=top><font size=-2><a href=\"%s\">%s</a></font></td>");
9597 if( g_ObjQFileExt.Find( file.GetExt().Lower() ) != wxNOT_FOUND )
9598 {
9599 filenameOK=file.GetFullPath();//remember last valid name
9600 // we are making a 3 columns table. New row only every third file
9601 if ( 3*((int)filecount/3) == filecount )
9602 FormatString.Prepend(_T("<tr>")); // new row
9603 else
9604 FormatString.Prepend(_T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty spacer column
9605
9606 AddFiles << wxString::Format(FormatString, file.GetFullPath(), file.GetFullName());
9607 filecount++;
9608 }
9609 cont = dir.GetNext(&filename);
9610 }
9611 objText << AddFiles << _T("</table>");
9612 }
9613
9614 objText << _T("</body></html>");
9615
9616 if (Chs57 || target_plugin_chart || (filecount > 1)) {
9617 g_pObjectQueryDialog->SetHTMLPage(objText);
9618 g_pObjectQueryDialog->Show();
9619 }
9620 if ((!Chs57 && filecount == 1)){ // only one file?, show direktly
9621 //generate an event to avoid double code
9622 wxHtmlLinkInfo hli(filenameOK);
9623 wxHtmlLinkEvent hle(1, hli);
9624 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
9625 }
9626
9627 if (rule_list) rule_list->Clear();
9628 delete rule_list;
9629
9630 if (overlay_rule_list) overlay_rule_list->Clear();
9631 delete overlay_rule_list;
9632
9633 if (pi_rule_list) pi_rule_list->Clear();
9634 delete pi_rule_list;
9635
9636 SetCursor(wxCURSOR_ARROW);
9637 }
9638}
9639
9640void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
9641 bool bNew = false;
9642 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
9643 // Dialog
9644 g_pMarkInfoDialog = new MarkInfoDlg(this);
9645 bNew = true;
9646 }
9647
9648 if (1 /*g_bresponsive*/) {
9649 wxSize canvas_size = GetSize();
9650
9651 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
9652 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
9653
9654 g_pMarkInfoDialog->Layout();
9655
9656 wxPoint canvas_pos = GetPosition();
9657 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
9658
9659 bool newFit = false;
9660 if (canvas_size.x < fitted_size.x) {
9661 fitted_size.x = canvas_size.x - 40;
9662 if (canvas_size.y < fitted_size.y)
9663 fitted_size.y -= 40; // scrollbar added
9664 }
9665 if (canvas_size.y < fitted_size.y) {
9666 fitted_size.y = canvas_size.y - 40;
9667 if (canvas_size.x < fitted_size.x)
9668 fitted_size.x -= 40; // scrollbar added
9669 }
9670
9671 if (newFit) {
9672 g_pMarkInfoDialog->SetSize(fitted_size);
9673 g_pMarkInfoDialog->Centre();
9674 }
9675 }
9676
9677 markPoint->m_bRPIsBeingEdited = false;
9678
9679 g_pMarkInfoDialog->SetRoutePoint(markPoint);
9680 g_pMarkInfoDialog->UpdateProperties();
9681 if (markPoint->m_bIsInLayer) {
9682 wxString caption(wxString::Format(_T("%s, %s: %s"),
9683 _("Waypoint Properties"), _("Layer"),
9684 GetLayerName(markPoint->m_LayerID)));
9685 g_pMarkInfoDialog->SetDialogTitle(caption);
9686 } else
9687 g_pMarkInfoDialog->SetDialogTitle(_("Waypoint Properties"));
9688
9689 g_pMarkInfoDialog->Show();
9690 g_pMarkInfoDialog->Raise();
9691 g_pMarkInfoDialog->InitialFocus();
9692 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
9693}
9694
9695void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
9696 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
9697 pRoutePropDialog->SetRouteAndUpdate(selected);
9698 // pNew->UpdateProperties();
9699 pRoutePropDialog->Show();
9700 pRoutePropDialog->Raise();
9701 return;
9702 pRoutePropDialog = RoutePropDlgImpl::getInstance(
9703 this); // There is one global instance of the RouteProp Dialog
9704
9705 if (g_bresponsive) {
9706 wxSize canvas_size = GetSize();
9707 wxPoint canvas_pos = GetPosition();
9708 wxSize fitted_size = pRoutePropDialog->GetSize();
9709 ;
9710
9711 if (canvas_size.x < fitted_size.x) {
9712 fitted_size.x = canvas_size.x;
9713 if (canvas_size.y < fitted_size.y)
9714 fitted_size.y -= 20; // scrollbar added
9715 }
9716 if (canvas_size.y < fitted_size.y) {
9717 fitted_size.y = canvas_size.y;
9718 if (canvas_size.x < fitted_size.x)
9719 fitted_size.x -= 20; // scrollbar added
9720 }
9721
9722 pRoutePropDialog->SetSize(fitted_size);
9723 pRoutePropDialog->Centre();
9724
9725 // int xp = (canvas_size.x - fitted_size.x)/2;
9726 // int yp = (canvas_size.y - fitted_size.y)/2;
9727
9728 wxPoint xxp = ClientToScreen(canvas_pos);
9729 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
9730 }
9731
9732 pRoutePropDialog->SetRouteAndUpdate(selected);
9733
9734 pRoutePropDialog->Show();
9735
9736 Refresh(false);
9737}
9738
9739void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
9740 pTrackPropDialog = TrackPropDlg::getInstance(
9741 this); // There is one global instance of the RouteProp Dialog
9742
9743 pTrackPropDialog->SetTrackAndUpdate(selected);
9744 pTrackPropDialog->UpdateProperties();
9745
9746 pTrackPropDialog->Show();
9747
9748 Refresh(false);
9749}
9750
9751void pupHandler_PasteWaypoint() {
9752 Kml kml;
9753
9754 int pasteBuffer = kml.ParsePasteBuffer();
9755 RoutePoint *pasted = kml.GetParsedRoutePoint();
9756 if (!pasted) return;
9757
9758 double nearby_radius_meters =
9759 g_Platform->GetSelectRadiusPix() /
9760 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
9761
9762 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
9763 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
9764
9765 int answer = wxID_NO;
9766 if (nearPoint && !nearPoint->m_bIsInLayer) {
9767 wxString msg;
9768 msg << _(
9769 "There is an existing waypoint at the same location as the one you are "
9770 "pasting. Would you like to merge the pasted data with it?\n\n");
9771 msg << _("Answering 'No' will create a new waypoint at the same location.");
9772 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
9773 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
9774 }
9775
9776 if (answer == wxID_YES) {
9777 nearPoint->SetName(pasted->GetName());
9778 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
9779 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
9780 pRouteManagerDialog->UpdateWptListCtrl();
9781 }
9782
9783 if (answer == wxID_NO) {
9784 RoutePoint *newPoint = new RoutePoint(pasted);
9785 newPoint->m_bIsolatedMark = true;
9786 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
9787 newPoint);
9788 pConfig->AddNewWayPoint(newPoint, -1);
9789 pWayPointMan->AddRoutePoint(newPoint);
9790 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
9791 pRouteManagerDialog->UpdateWptListCtrl();
9792 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
9793 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
9794 }
9795
9796 gFrame->InvalidateAllGL();
9797 gFrame->RefreshAllCanvas(false);
9798}
9799
9800void pupHandler_PasteRoute() {
9801 Kml kml;
9802
9803 int pasteBuffer = kml.ParsePasteBuffer();
9804 Route *pasted = kml.GetParsedRoute();
9805 if (!pasted) return;
9806
9807 double nearby_radius_meters =
9808 g_Platform->GetSelectRadiusPix() /
9809 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
9810
9811 RoutePoint *curPoint;
9812 RoutePoint *nearPoint;
9813 RoutePoint *prevPoint = NULL;
9814
9815 bool mergepoints = false;
9816 bool createNewRoute = true;
9817 int existingWaypointCounter = 0;
9818
9819 for (int i = 1; i <= pasted->GetnPoints(); i++) {
9820 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
9821 nearPoint = pWayPointMan->GetNearbyWaypoint(
9822 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
9823 if (nearPoint) {
9824 mergepoints = true;
9825 existingWaypointCounter++;
9826 // Small hack here to avoid both extending RoutePoint and repeating all
9827 // the GetNearbyWaypoint calculations. Use existin data field in
9828 // RoutePoint as temporary storage.
9829 curPoint->m_bPtIsSelected = true;
9830 }
9831 }
9832
9833 int answer = wxID_NO;
9834 if (mergepoints) {
9835 wxString msg;
9836 msg << _(
9837 "There are existing waypoints at the same location as some of the ones "
9838 "you are pasting. Would you like to just merge the pasted data into "
9839 "them?\n\n");
9840 msg << _("Answering 'No' will create all new waypoints for this route.");
9841 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
9842 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9843
9844 if (answer == wxID_CANCEL) {
9845 return;
9846 }
9847 }
9848
9849 // If all waypoints exist since before, and a route with the same name, we
9850 // don't create a new route.
9851 if (mergepoints && answer == wxID_YES &&
9852 existingWaypointCounter == pasted->GetnPoints()) {
9853 wxRouteListNode *route_node = pRouteList->GetFirst();
9854 while (route_node) {
9855 Route *proute = route_node->GetData();
9856
9857 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
9858 createNewRoute = false;
9859 break;
9860 }
9861 route_node = route_node->GetNext();
9862 }
9863 }
9864
9865 Route *newRoute = 0;
9866 RoutePoint *newPoint = 0;
9867
9868 if (createNewRoute) {
9869 newRoute = new Route();
9870 newRoute->m_RouteNameString = pasted->m_RouteNameString;
9871 }
9872
9873 for (int i = 1; i <= pasted->GetnPoints(); i++) {
9874 curPoint = pasted->GetPoint(i);
9875 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
9876 curPoint->m_bPtIsSelected = false;
9877 newPoint = pWayPointMan->GetNearbyWaypoint(
9878 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
9879 newPoint->SetName(curPoint->GetName());
9880 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
9881
9882 if (createNewRoute) newRoute->AddPoint(newPoint);
9883 } else {
9884 curPoint->m_bPtIsSelected = false;
9885
9886 newPoint = new RoutePoint(curPoint);
9887 newPoint->m_bIsolatedMark = false;
9888 newPoint->SetIconName(_T("circle"));
9889 newPoint->m_bIsVisible = true;
9890 newPoint->m_bShowName = false;
9891 newPoint->SetShared(false);
9892
9893 newRoute->AddPoint(newPoint);
9894 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
9895 newPoint);
9896 pConfig->AddNewWayPoint(newPoint, -1);
9897 pWayPointMan->AddRoutePoint(newPoint);
9898 }
9899 if (i > 1 && createNewRoute)
9900 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
9901 curPoint->m_lat, curPoint->m_lon,
9902 prevPoint, newPoint, newRoute);
9903 prevPoint = newPoint;
9904 }
9905
9906 if (createNewRoute) {
9907 pRouteList->Append(newRoute);
9908 pConfig->AddNewRoute(newRoute); // use auto next num
9909
9910 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9911 pRoutePropDialog->SetRouteAndUpdate(newRoute);
9912 }
9913
9914 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
9915 pRouteManagerDialog->UpdateRouteListCtrl();
9916 pRouteManagerDialog->UpdateWptListCtrl();
9917 }
9918 gFrame->InvalidateAllGL();
9919 gFrame->RefreshAllCanvas(false);
9920 }
9921 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
9922 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
9923}
9924
9925void pupHandler_PasteTrack() {
9926 Kml kml;
9927
9928 int pasteBuffer = kml.ParsePasteBuffer();
9929 Track *pasted = kml.GetParsedTrack();
9930 if (!pasted) return;
9931
9932 TrackPoint *curPoint;
9933
9934 Track *newTrack = new Track();
9935 TrackPoint *newPoint;
9936 TrackPoint *prevPoint = NULL;
9937
9938 newTrack->SetName(pasted->GetName());
9939
9940 for (int i = 0; i < pasted->GetnPoints(); i++) {
9941 curPoint = pasted->GetPoint(i);
9942
9943 newPoint = new TrackPoint(curPoint);
9944
9945 wxDateTime now = wxDateTime::Now();
9946 newPoint->SetCreateTime(curPoint->GetCreateTime());
9947
9948 newTrack->AddPoint(newPoint);
9949
9950 if (prevPoint)
9951 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
9952 newPoint->m_lat, newPoint->m_lon,
9953 prevPoint, newPoint, newTrack);
9954
9955 prevPoint = newPoint;
9956 }
9957
9958 g_TrackList.push_back(newTrack);
9959 pConfig->AddNewTrack(newTrack);
9960
9961 gFrame->InvalidateAllGL();
9962 gFrame->RefreshAllCanvas(false);
9963}
9964
9965bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
9966 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
9967 m_pFoundRoutePoint, m_FoundAIS_MMSI,
9968 m_pIDXCandidate);
9969
9970 Connect(
9971 wxEVT_COMMAND_MENU_SELECTED,
9972 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
9973
9974 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
9975
9976 Disconnect(
9977 wxEVT_COMMAND_MENU_SELECTED,
9978 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
9979
9980 delete m_canvasMenu;
9981 m_canvasMenu = NULL;
9982
9983#ifdef __WXQT__
9984 // gFrame->SurfaceToolbar();
9985 // g_MainToolbar->Raise();
9986#endif
9987
9988 return true;
9989}
9990
9991void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
9992 // Pass menu events from the canvas to the menu handler
9993 // This is necessarily in ChartCanvas since that is the menu's parent.
9994 if (m_canvasMenu) {
9995 m_canvasMenu->PopupMenuHandler(event);
9996 }
9997 return;
9998}
9999
10000void ChartCanvas::StartRoute(void) {
10001 // Do not allow more than one canvas to create a route at one time.
10002 if (g_brouteCreating) return;
10003
10004 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10005
10006 g_brouteCreating = true;
10007 m_routeState = 1;
10008 m_bDrawingRoute = false;
10009 SetCursor(*pCursorPencil);
10010 SetCanvasToolbarItemState(ID_ROUTE, true);
10011 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10012
10013 HideGlobalToolbar();
10014
10015#ifdef __OCPN__ANDROID__
10016 androidSetRouteAnnunciator(true);
10017#endif
10018}
10019
10020void ChartCanvas::FinishRoute(void) {
10021 m_routeState = 0;
10022 m_prev_pMousePoint = NULL;
10023 m_bDrawingRoute = false;
10024
10025 SetCanvasToolbarItemState(ID_ROUTE, false);
10026 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10027#ifdef __OCPN__ANDROID__
10028 androidSetRouteAnnunciator(false);
10029#endif
10030
10031 SetCursor(*pCursorArrow);
10032
10033 if (m_pMouseRoute) {
10034 if (m_bAppendingRoute)
10035 pConfig->UpdateRoute(m_pMouseRoute);
10036 else {
10037 if (m_pMouseRoute->GetnPoints() > 1) {
10038 pConfig->AddNewRoute(m_pMouseRoute);
10039 } else {
10040 g_pRouteMan->DeleteRoute(m_pMouseRoute,
10041 NavObjectChanges::getInstance());
10042 m_pMouseRoute = NULL;
10043 }
10044 }
10045 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
10046
10047 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
10048 (pRoutePropDialog->IsShown())) {
10049 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
10050 }
10051
10052 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
10053 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10054 pRouteManagerDialog->UpdateRouteListCtrl();
10055 }
10056 }
10057 m_bAppendingRoute = false;
10058 m_pMouseRoute = NULL;
10059
10060 m_pSelectedRoute = NULL;
10061
10062 undo->InvalidateUndo();
10063 gFrame->RefreshAllCanvas(true);
10064
10065 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
10066
10067 ShowGlobalToolbar();
10068
10069 g_brouteCreating = false;
10070}
10071
10072void ChartCanvas::HideGlobalToolbar() {
10073 if (m_canvasIndex == 0) {
10074 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
10075 }
10076}
10077
10078void ChartCanvas::ShowGlobalToolbar() {
10079 if (m_canvasIndex == 0) {
10080 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
10081 }
10082}
10083
10084void ChartCanvas::ShowAISTargetList(void) {
10085 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
10086 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
10087 }
10088
10089 g_pAISTargetList->UpdateAISTargetList();
10090}
10091
10092void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
10093 if (!m_bShowOutlines) return;
10094
10095 if (!ChartData) return;
10096
10097 int nEntry = ChartData->GetChartTableEntries();
10098
10099 for (int i = 0; i < nEntry; i++) {
10100 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
10101
10102 // Check to see if the candidate chart is in the currently active group
10103 bool b_group_draw = false;
10104 if (m_groupIndex > 0) {
10105 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
10106 int index = pt->GetGroupArray()[ig];
10107 if (m_groupIndex == index) {
10108 b_group_draw = true;
10109 break;
10110 }
10111 }
10112 } else
10113 b_group_draw = true;
10114
10115 if (b_group_draw) RenderChartOutline(dc, i, vp);
10116 }
10117
10118 // On CM93 Composite Charts, draw the outlines of the next smaller
10119 // scale cell
10120 cm93compchart *pcm93 = NULL;
10121 if (VPoint.b_quilt) {
10122 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
10123 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
10124 pcm93 = (cm93compchart *)pch;
10125 break;
10126 }
10127 } else if (m_singleChart &&
10128 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
10129 pcm93 = (cm93compchart *)m_singleChart;
10130
10131 if (pcm93) {
10132 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
10133 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
10134
10135 if (zoom_factor > 8.0) {
10136 wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
10137 dc.SetPen(mPen);
10138 } else {
10139 wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
10140 dc.SetPen(mPen);
10141 }
10142
10143 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
10144 }
10145}
10146
10147void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
10148#ifdef ocpnUSE_GL
10149 if (g_bopengl && m_glcc) {
10150 /* opengl version specially optimized */
10151 m_glcc->RenderChartOutline(dc, dbIndex, vp);
10152 return;
10153 }
10154#endif
10155
10156 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
10157 if (!ChartData->IsChartAvailable(dbIndex)) return;
10158 }
10159
10160 float plylat, plylon;
10161 float plylat1, plylon1;
10162
10163 int pixx, pixy, pixx1, pixy1;
10164
10165 LLBBox box;
10166 ChartData->GetDBBoundingBox(dbIndex, box);
10167
10168 // Don't draw an outline in the case where the chart covers the entire world
10169 // */
10170 if (box.GetLonRange() == 360) return;
10171
10172 double lon_bias = 0;
10173 // chart is outside of viewport lat/lon bounding box
10174 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
10175
10176 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10177
10178 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
10179 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
10180
10181 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
10182 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
10183
10184 else
10185 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
10186
10187 // Are there any aux ply entries?
10188 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10189 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
10190 {
10191 wxPoint r, r1;
10192
10193 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10194 plylon += lon_bias;
10195
10196 GetCanvasPointPix(plylat, plylon, &r);
10197 pixx = r.x;
10198 pixy = r.y;
10199
10200 for (int i = 0; i < nPly - 1; i++) {
10201 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
10202 plylon1 += lon_bias;
10203
10204 GetCanvasPointPix(plylat1, plylon1, &r1);
10205 pixx1 = r1.x;
10206 pixy1 = r1.y;
10207
10208 int pixxs1 = pixx1;
10209 int pixys1 = pixy1;
10210
10211 bool b_skip = false;
10212
10213 if (vp.chart_scale > 5e7) {
10214 // calculate projected distance between these two points in meters
10215 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
10216 pow((double)(pixy1 - pixy), 2)) /
10217 vp.view_scale_ppm;
10218
10219 if (dist > 0.0) {
10220 // calculate GC distance between these two points in meters
10221 double distgc =
10222 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
10223
10224 // If the distances are nonsense, it means that the scale is very
10225 // small and the segment wrapped the world So skip it....
10226 // TODO improve this to draw two segments
10227 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
10228 b_skip = true;
10229 } else
10230 b_skip = true;
10231 }
10232
10233 ClipResult res = cohen_sutherland_line_clip_i(
10234 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10235 if (res != Invisible && !b_skip)
10236 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10237
10238 plylat = plylat1;
10239 plylon = plylon1;
10240 pixx = pixxs1;
10241 pixy = pixys1;
10242 }
10243
10244 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
10245 plylon1 += lon_bias;
10246
10247 GetCanvasPointPix(plylat1, plylon1, &r1);
10248 pixx1 = r1.x;
10249 pixy1 = r1.y;
10250
10251 ClipResult res = cohen_sutherland_line_clip_i(
10252 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10253 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10254 }
10255
10256 else // Use Aux PlyPoints
10257 {
10258 wxPoint r, r1;
10259
10260 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10261 for (int j = 0; j < nAuxPlyEntries; j++) {
10262 int nAuxPly =
10263 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
10264 GetCanvasPointPix(plylat, plylon, &r);
10265 pixx = r.x;
10266 pixy = r.y;
10267
10268 for (int i = 0; i < nAuxPly - 1; i++) {
10269 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
10270
10271 GetCanvasPointPix(plylat1, plylon1, &r1);
10272 pixx1 = r1.x;
10273 pixy1 = r1.y;
10274
10275 int pixxs1 = pixx1;
10276 int pixys1 = pixy1;
10277
10278 bool b_skip = false;
10279
10280 if (vp.chart_scale > 5e7) {
10281 // calculate projected distance between these two points in meters
10282 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
10283 ((pixy1 - pixy) * (pixy1 - pixy))) /
10284 vp.view_scale_ppm;
10285 if (dist > 0.0) {
10286 // calculate GC distance between these two points in meters
10287 double distgc =
10288 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
10289
10290 // If the distances are nonsense, it means that the scale is very
10291 // small and the segment wrapped the world So skip it....
10292 // TODO improve this to draw two segments
10293 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
10294 b_skip = true;
10295 } else
10296 b_skip = true;
10297 }
10298
10299 ClipResult res = cohen_sutherland_line_clip_i(
10300 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10301 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
10302
10303 plylat = plylat1;
10304 plylon = plylon1;
10305 pixx = pixxs1;
10306 pixy = pixys1;
10307 }
10308
10309 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
10310 GetCanvasPointPix(plylat1, plylon1, &r1);
10311 pixx1 = r1.x;
10312 pixy1 = r1.y;
10313
10314 ClipResult res = cohen_sutherland_line_clip_i(
10315 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10316 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10317 }
10318 }
10319}
10320
10321static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
10322 const wxString &second) {
10323 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
10324
10325 int pointsize = dFont->GetPointSize();
10326 pointsize /= OCPN_GetWinDIPScaleFactor();
10327
10328 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
10329 pointsize, dFont->GetFamily(), dFont->GetStyle(),
10330 dFont->GetWeight(), false, dFont->GetFaceName());
10331
10332
10333 dc.SetFont(*psRLI_font);
10334
10335 int w1, h1;
10336 int w2 = 0;
10337 int h2 = 0;
10338 int h, w;
10339
10340 int xp, yp;
10341 int hilite_offset = 3;
10342#ifdef __WXMAC__
10343 wxScreenDC sdc;
10344 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
10345 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
10346#else
10347 dc.GetTextExtent(first, &w1, &h1);
10348 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
10349#endif
10350
10351 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
10352 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
10353
10354 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
10355 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
10356
10357 h = h1 + h2;
10358
10359 xp = ref_point.x - w;
10360 yp = ref_point.y;
10361 yp += hilite_offset;
10362
10363 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
10364
10365 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
10366 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
10367
10368 dc.DrawText(first, xp, yp);
10369 if (second.Len()) dc.DrawText(second, xp, yp + h1);
10370}
10371
10372void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
10373 if (!g_bAllowShipToActive) return;
10374
10375 Route *rt = g_pRouteMan->GetpActiveRoute();
10376 if (!rt) return;
10377
10378 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
10379 wxPoint2DDouble pa, pb;
10380 GetDoubleCanvasPointPix(gLat, gLon, &pa);
10381 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
10382
10383 // set pen
10384 int width =
10385 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
10386 if (rt->m_width != wxPENSTYLE_INVALID)
10387 width = rt->m_width; // set route pen style if any
10388 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
10389 g_shipToActiveStyle, 5)]; // get setting pen style
10390 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
10391 wxColour color =
10392 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
10393 : // set setting route pen color
10394 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
10395 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
10396
10397 dc.SetPen(*mypen);
10398 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
10399
10400 if (!Use_Opengl)
10401 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x, (int)pb.m_y,
10402 GetVP(), true);
10403
10404#ifdef ocpnUSE_GL
10405 else {
10406 glLineWidth(wxMax(g_GLMinSymbolLineWidth, width));
10407 dc.SetGLStipple();
10408
10409#ifdef USE_ANDROID_GLES2
10410 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
10411#else
10412 glColor3ub(color.Red(), color.Green(), color.Blue());
10413 glBegin(GL_LINES);
10414 glVertex2f(pa.m_x, pa.m_y);
10415 glVertex2f(pb.m_x, pb.m_y);
10416 glEnd();
10417#endif
10418 glDisable(GL_LINE_STIPPLE);
10419
10420 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
10421 (int)pb.m_y, GetVP());
10422 }
10423#endif
10424 }
10425}
10426
10427void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
10428 Route *route = 0;
10429 if (m_routeState >= 2) route = m_pMouseRoute;
10430 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
10431 route = m_pMeasureRoute;
10432
10433 if (!route) return;
10434
10435 // Validate route pointer
10436 if( !g_pRouteMan->IsRouteValid(route) )
10437 return;
10438
10439 double render_lat = m_cursor_lat;
10440 double render_lon = m_cursor_lon;
10441
10442 int np = route->GetnPoints();
10443 if (np) {
10444 if (g_btouch && (np > 1)) np--;
10445 RoutePoint rp = route->GetPoint(np);
10446 render_lat = rp.m_lat;
10447 render_lon = rp.m_lon;
10448 }
10449
10450 double rhumbBearing, rhumbDist;
10451 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
10452 &rhumbBearing, &rhumbDist);
10453 double brg = rhumbBearing;
10454 double dist = rhumbDist;
10455
10456 // Skip GreatCircle rubberbanding on touch devices.
10457 if (!g_btouch) {
10458 double gcBearing, gcBearing2, gcDist;
10459 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
10460 m_cursor_lat, &gcDist, &gcBearing,
10461 &gcBearing2);
10462 double gcDistm = gcDist / 1852.0;
10463
10464 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
10465 rhumbBearing = 90.;
10466
10467 wxPoint destPoint, lastPoint;
10468
10469 route->m_NextLegGreatCircle = false;
10470 int milesDiff = rhumbDist - gcDistm;
10471 if (milesDiff > 1) {
10472 brg = gcBearing;
10473 dist = gcDistm;
10474 route->m_NextLegGreatCircle = true;
10475 }
10476
10477 //FIXME (MacOS, the first segment is rendered wrong)
10478 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex, &lastPoint);
10479
10480 if (route->m_NextLegGreatCircle) {
10481 for (int i = 1; i <= milesDiff; i++) {
10482 double p = (double)i * (1.0 / (double)milesDiff);
10483 double pLat, pLon;
10484 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
10485 &pLon, &pLat, &gcBearing2);
10486 destPoint = VPoint.GetPixFromLL(pLat, pLon);
10487 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(), false);
10488 lastPoint = destPoint;
10489 }
10490 } else {
10491 if (r_rband.x && r_rband.y) { // RubberBand disabled?
10492 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(), false);
10493 if (m_bMeasure_DistCircle) {
10494 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
10495 powf((float)(r_rband.y - lastPoint.y), 2));
10496
10497 dc.SetPen(*g_pRouteMan->GetRoutePen());
10498 dc.SetBrush(*wxTRANSPARENT_BRUSH);
10499 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
10500 }
10501 }
10502 }
10503 }
10504
10505 wxString routeInfo;
10506 if (g_bShowTrue)
10507 routeInfo << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
10508 0x00B0);
10509
10510 if (g_bShowMag) {
10511 double latAverage = (m_cursor_lat + render_lat) / 2;
10512 double lonAverage = (m_cursor_lon + render_lon) / 2;
10513 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
10514
10515 routeInfo << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
10516 0x00B0);
10517 }
10518
10519 routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
10520
10521 wxString s0;
10522 if (!route->m_bIsInLayer)
10523 s0.Append(_("Route") + _T(": "));
10524 else
10525 s0.Append(_("Layer Route: "));
10526
10527 double disp_length = route->m_route_length;
10528 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
10529 s0 += FormatDistanceAdaptive(disp_length);
10530
10531 RouteLegInfo(dc, r_rband, routeInfo, s0);
10532
10533 m_brepaint_piano = true;
10534}
10535
10536void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
10537 if (!m_bShowVisibleSectors) return;
10538
10539 if (g_bDeferredInitDone) {
10540 // need to re-evaluate sectors?
10541 double rhumbBearing, rhumbDist;
10542 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
10543 &rhumbBearing, &rhumbDist);
10544
10545 if (rhumbDist > 0.05) // miles
10546 {
10547 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
10548 m_sectorlegsVisible);
10549 m_sector_glat = gLat;
10550 m_sector_glon = gLon;
10551 }
10552 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
10553 }
10554}
10555
10556void ChartCanvas::WarpPointerDeferred(int x, int y) {
10557 warp_x = x;
10558 warp_y = y;
10559 warp_flag = true;
10560}
10561
10562int s_msg;
10563
10564void ChartCanvas::UpdateCanvasS52PLIBConfig() {
10565 if (!ps52plib) return;
10566
10567 if (VPoint.b_quilt) { // quilted
10568 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
10569
10570 if (m_pQuilt->IsQuiltVector()) {
10571 if (ps52plib->GetStateHash() != m_s52StateHash) {
10572 UpdateS52State();
10573 m_s52StateHash = ps52plib->GetStateHash();
10574 }
10575 }
10576 } else {
10577 if (ps52plib->GetStateHash() != m_s52StateHash) {
10578 UpdateS52State();
10579 m_s52StateHash = ps52plib->GetStateHash();
10580 }
10581 }
10582
10583 // Plugin charts
10584 bool bSendPlibState = true;
10585 if (VPoint.b_quilt) { // quilted
10586 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
10587 }
10588
10589 if (bSendPlibState) {
10590 wxJSONValue v;
10591 v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
10592 v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
10593 v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
10594 v[_T("OpenCPN Version Date")] = VERSION_DATE;
10595 v[_T("OpenCPN Version Full")] = VERSION_FULL;
10596
10597 // S52PLIB state
10598 v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
10599 v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
10600 v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
10601 v[_T("OpenCPN S52PLIB ShowAnchorConditions")] =
10602 m_encShowAnchor; // ps52plib->GetAnchorOn();
10603 v[_T("OpenCPN S52PLIB ShowQualityOfData")] =
10604 GetShowENCDataQual(); // ps52plib->GetQualityOfDataOn();
10605 v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
10606 v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
10607
10608 v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
10609
10610 v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
10611 v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
10612
10613 // Global S52 options
10614
10615 v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
10616 v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
10617 v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
10618 v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] = ps52plib->m_bShowS57ImportantTextOnly;
10619 v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
10620 v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
10621 v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
10622 v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
10623 v[_T("OpenCPN S52PLIB ColorShades")] = S52_getMarinerParam( S52_MAR_TWO_SHADES );
10624
10625 // Some global GUI parameters, for completeness
10626 v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
10627 v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
10628 v[_T("OpenCPN Scale Factor Exp")] = g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
10629 v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
10630
10631 wxJSONWriter w;
10632 wxString out;
10633 w.Write(v, out);
10634
10635 if (!g_lastPluginMessage.IsSameAs(out)) {
10636 // printf("message %d %d\n", s_msg++, m_canvasIndex);
10637 g_pi_manager->SendMessageToAllPlugins(wxString(_T("OpenCPN Config")),
10638 out);
10639 }
10640 }
10641}
10642int spaint;
10643int s_in_update;
10644void ChartCanvas::OnPaint(wxPaintEvent &event) {
10645 wxPaintDC dc(this);
10646
10647 // GetToolbar()->Show( m_bToolbarEnable );
10648
10649 // Paint updates may have been externally disabled (temporarily, to avoid
10650 // Yield() recursion performance loss) It is important that the wxPaintDC is
10651 // built, even if we elect to not process this paint message. Otherwise, the
10652 // paint message may not be removed from the message queue, esp on Windows.
10653 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
10654
10655 if (!m_b_paint_enable) {
10656 return;
10657 }
10658
10659 // If necessary, reconfigure the S52 PLIB
10660 UpdateCanvasS52PLIBConfig();
10661
10662#ifdef ocpnUSE_GL
10663 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
10664
10665 if (m_glcc && g_bopengl) {
10666 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
10667 s_in_update++;
10668 m_glcc->Update();
10669 s_in_update--;
10670 }
10671
10672 return;
10673 }
10674#endif
10675
10676 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
10677
10678 wxRegion ru = GetUpdateRegion();
10679
10680 int rx, ry, rwidth, rheight;
10681 ru.GetBox(rx, ry, rwidth, rheight);
10682 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
10683 // rwidth, rheight);
10684
10685#ifdef ocpnUSE_DIBSECTION
10686 ocpnMemDC temp_dc;
10687#else
10688 wxMemoryDC temp_dc;
10689#endif
10690
10691 long height = GetVP().pix_height;
10692
10693#ifdef __WXMAC__
10694 // On OS X we have to explicitly extend the region for the piano area
10695 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
10696 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
10697 height += m_Piano->GetHeight();
10698#endif // __WXMAC__
10699 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
10700
10701 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
10702 if (pthumbwin) {
10703 int thumbx, thumby, thumbsx, thumbsy;
10704 pthumbwin->GetPosition(&thumbx, &thumby);
10705 pthumbwin->GetSize(&thumbsx, &thumbsy);
10706 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
10707
10708 if (pthumbwin->IsShown()) {
10709 rgn_chart.Subtract(rgn_thumbwin);
10710 ru.Subtract(rgn_thumbwin);
10711 }
10712 }
10713
10714 // subtract the chart bar if it isn't transparent, and determine if we need to
10715 // paint it
10716 wxRegion rgn_blit = ru;
10717 if (g_bShowChartBar) {
10718 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
10719 GetClientSize().x, m_Piano->GetHeight());
10720
10721 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
10722 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
10723 if (style->chartStatusWindowTransparent)
10724 m_brepaint_piano = true;
10725 else
10726 ru.Subtract(chart_bar_rect);
10727 }
10728 }
10729
10730 if (m_Compass && m_Compass->IsShown()) {
10731 wxRect compassRect = m_Compass->GetRect();
10732 if (ru.Contains(compassRect) != wxOutRegion) {
10733 ru.Subtract(compassRect);
10734 }
10735 }
10736
10737 // Is this viewpoint the same as the previously painted one?
10738 bool b_newview = true;
10739
10740 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
10741 (m_cache_vp.rotation == VPoint.rotation) &&
10742 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
10743 m_cache_vp.IsValid()) {
10744 b_newview = false;
10745 }
10746
10747 // If the ViewPort is skewed or rotated, we may be able to use the cached
10748 // rotated bitmap.
10749 bool b_rcache_ok = false;
10750 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
10751 b_rcache_ok = !b_newview;
10752
10753 // Make a special VP
10754 if (VPoint.b_MercatorProjectionOverride)
10755 VPoint.SetProjectionType(PROJECTION_MERCATOR);
10756 ViewPort svp = VPoint;
10757
10758 svp.pix_width = svp.rv_rect.width;
10759 svp.pix_height = svp.rv_rect.height;
10760
10761 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
10762 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
10763 // VPoint.rv_rect.height);
10764
10765 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
10766
10767 // If we are going to use the cached rotated image, there is no need to fetch
10768 // any chart data and this will do it...
10769 if (b_rcache_ok) chart_get_region.Clear();
10770
10771 // Blit pan acceleration
10772 if (VPoint.b_quilt) // quilted
10773 {
10774 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
10775
10776 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
10777
10778 bool busy = false;
10779 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
10780 m_cache_vp.rotation != VPoint.rotation)) {
10781 OCPNPlatform::ShowBusySpinner();
10782 busy = true;
10783 }
10784
10785 if ((m_working_bm.GetWidth() != svp.pix_width) ||
10786 (m_working_bm.GetHeight() != svp.pix_height))
10787 m_working_bm.Create(svp.pix_width, svp.pix_height,
10788 -1); // make sure the target is big enoug
10789
10790 if (fabs(VPoint.rotation) < 0.01) {
10791 bool b_save = true;
10792
10793 if (g_SencThreadManager) {
10794 if (g_SencThreadManager->GetJobCount()) {
10795 b_save = false;
10796 m_cache_vp.Invalidate();
10797 }
10798 }
10799
10800 // If the saved wxBitmap from last OnPaint is useable
10801 // calculate the blit parameters
10802
10803 // We can only do screen blit painting if subsequent ViewPorts differ by
10804 // whole pixels So, in small scale bFollow mode, force the full screen
10805 // render. This seems a hack....There may be better logic here.....
10806
10807 // if(m_bFollow)
10808 // b_save = false;
10809
10810 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
10811 if (b_newview) {
10812 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
10813 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
10814
10815 int dy = c_new.y - c_old.y;
10816 int dx = c_new.x - c_old.x;
10817
10818 // printf("In OnPaint Trying Blit dx: %d
10819 // dy:%d\n\n", dx, dy);
10820
10821 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
10822 if (dx || dy) {
10823 // Blit the reuseable portion of the cached wxBitmap to a working
10824 // bitmap
10825 temp_dc.SelectObject(m_working_bm);
10826
10827 wxMemoryDC cache_dc;
10828 cache_dc.SelectObject(m_cached_chart_bm);
10829
10830 if (dy > 0) {
10831 if (dx > 0) {
10832 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
10833 VPoint.pix_height - dy, &cache_dc, dx, dy);
10834 } else {
10835 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
10836 VPoint.pix_height - dy, &cache_dc, 0, dy);
10837 }
10838
10839 } else {
10840 if (dx > 0) {
10841 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
10842 VPoint.pix_height + dy, &cache_dc, dx, 0);
10843 } else {
10844 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
10845 VPoint.pix_height + dy, &cache_dc, 0, 0);
10846 }
10847 }
10848
10849 OCPNRegion update_region;
10850 if (dy) {
10851 if (dy > 0)
10852 update_region.Union(
10853 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
10854 else
10855 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
10856 }
10857
10858 if (dx) {
10859 if (dx > 0)
10860 update_region.Union(
10861 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
10862 else
10863 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
10864 }
10865
10866 // Render the new region
10867 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
10868 update_region);
10869 cache_dc.SelectObject(wxNullBitmap);
10870 } else {
10871 // No sensible (dx, dy) change in the view, so use the cached
10872 // member bitmap
10873 temp_dc.SelectObject(m_cached_chart_bm);
10874 b_save = false;
10875 }
10876 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
10877
10878 } else // not blitable
10879 {
10880 temp_dc.SelectObject(m_working_bm);
10881 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
10882 chart_get_region);
10883 }
10884 } else {
10885 // No change in the view, so use the cached member bitmap2
10886 temp_dc.SelectObject(m_cached_chart_bm);
10887 b_save = false;
10888 }
10889 } else // cached bitmap is not yet valid
10890 {
10891 temp_dc.SelectObject(m_working_bm);
10892 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
10893 chart_get_region);
10894 }
10895
10896 // Save the fully rendered quilt image as a wxBitmap member of this class
10897 if (b_save) {
10898 // if((m_cached_chart_bm.GetWidth() !=
10899 // svp.pix_width) ||
10900 // (m_cached_chart_bm.GetHeight() !=
10901 // svp.pix_height))
10902 // m_cached_chart_bm.Create(svp.pix_width,
10903 // svp.pix_height, -1); // target wxBitmap
10904 // is big enough
10905 wxMemoryDC scratch_dc_0;
10906 scratch_dc_0.SelectObject(m_cached_chart_bm);
10907 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
10908
10909 scratch_dc_0.SelectObject(wxNullBitmap);
10910
10911 m_bm_cache_vp =
10912 VPoint; // save the ViewPort associated with the cached wxBitmap
10913 }
10914 }
10915
10916 else // quilted, rotated
10917 {
10918 temp_dc.SelectObject(m_working_bm);
10919 OCPNRegion chart_get_all_region(
10920 wxRect(0, 0, svp.pix_width, svp.pix_height));
10921 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
10922 chart_get_all_region);
10923 }
10924
10925 if (busy) OCPNPlatform::HideBusySpinner();
10926
10927 }
10928
10929 else // not quilted
10930 {
10931 if (!m_singleChart) {
10932 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
10933 dc.Clear();
10934 return;
10935 }
10936
10937 if (!chart_get_region.IsEmpty()) {
10938 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
10939 }
10940 }
10941
10942 if (temp_dc.IsOk()) {
10943 // Arrange to render the World Chart vector data behind the rendered
10944 // current chart so that uncovered canvas areas show at least the world
10945 // chart.
10946 OCPNRegion chartValidRegion;
10947 if (!VPoint.b_quilt) {
10948 // Make a region covering the current chart on the canvas
10949
10950 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
10951 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
10952 else {
10953 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
10954 // require that the viewport passed here have pix_width and pix_height
10955 // set to the actual display, not the virtual (rv_rect) sizes
10956 // (the vector calculations require the virtual sizes in svp)
10957
10958 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
10959 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
10960 }
10961 } else
10962 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
10963
10964 temp_dc.DestroyClippingRegion();
10965
10966 // Copy current chart region
10967 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
10968
10969 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
10970
10971 if (!backgroundRegion.IsEmpty()) {
10972 // Draw the Background Chart only in the areas NOT covered by the
10973 // current chart view
10974
10975 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
10976 clipping regions with more than 1 rectangle so... */
10977 wxColour water = pWorldBackgroundChart->water;
10978 if (water.IsOk()) {
10979 temp_dc.SetPen(*wxTRANSPARENT_PEN);
10980 temp_dc.SetBrush(wxBrush(water));
10981 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
10982 while (upd.HaveRects()) {
10983 wxRect rect = upd.GetRect();
10984 temp_dc.DrawRectangle(rect);
10985 upd.NextRect();
10986 }
10987 }
10988 // Associate with temp_dc
10989 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
10990 temp_dc.SetDeviceClippingRegion(*clip_region);
10991 delete clip_region;
10992
10993 ocpnDC bgdc(temp_dc);
10994 double r = VPoint.rotation;
10995 SetVPRotation(VPoint.skew);
10996
10997 pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
10998 SetVPRotation(r);
10999 }
11000 } // temp_dc.IsOk();
11001
11002 wxMemoryDC *pChartDC = &temp_dc;
11003 wxMemoryDC rotd_dc;
11004
11005 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11006 // Can we use the current rotated image cache?
11007 if (!b_rcache_ok) {
11008#ifdef __WXMSW__
11009 wxMemoryDC tbase_dc;
11010 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11011 tbase_dc.SelectObject(bm_base);
11012 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11013 tbase_dc.SelectObject(wxNullBitmap);
11014#else
11015 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11016#endif
11017
11018 wxImage base_image;
11019 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11020
11021 // Use a local static image rotator to improve wxWidgets code profile
11022 // Especially, on GTK the wxRound and wxRealPoint functions are very
11023 // expensive.....
11024
11025 double angle = GetVP().skew - GetVP().rotation;
11026 wxImage ri;
11027 bool b_rot_ok = false;
11028 if (base_image.IsOk()) {
11029 ViewPort rot_vp = GetVP();
11030
11031 m_b_rot_hidef = false;
11032
11033 ri = Image_Rotate(
11034 base_image, angle,
11035 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
11036 m_b_rot_hidef, &m_roffset);
11037
11038 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11039 (rot_vp.rotation == VPoint.rotation) &&
11040 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
11041 rot_vp.IsValid() && (ri.IsOk())) {
11042 b_rot_ok = true;
11043 }
11044 }
11045
11046 if (b_rot_ok) {
11047 delete m_prot_bm;
11048 m_prot_bm = new wxBitmap(ri);
11049 }
11050
11051 m_roffset.x += VPoint.rv_rect.x;
11052 m_roffset.y += VPoint.rv_rect.y;
11053 }
11054
11055 if (m_prot_bm && m_prot_bm->IsOk()) {
11056 rotd_dc.SelectObject(*m_prot_bm);
11057 pChartDC = &rotd_dc;
11058 } else {
11059 pChartDC = &temp_dc;
11060 m_roffset = wxPoint(0, 0);
11061 }
11062 } else { // unrotated
11063 pChartDC = &temp_dc;
11064 m_roffset = wxPoint(0, 0);
11065 }
11066
11067 wxPoint offset = m_roffset;
11068
11069 // Save the PixelCache viewpoint for next time
11070 m_cache_vp = VPoint;
11071
11072 // Set up a scratch DC for overlay objects
11073 wxMemoryDC mscratch_dc;
11074 mscratch_dc.SelectObject(*pscratch_bm);
11075
11076 mscratch_dc.ResetBoundingBox();
11077 mscratch_dc.DestroyClippingRegion();
11078 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
11079
11080 // Blit the externally invalidated areas of the chart onto the scratch dc
11081 wxRegionIterator upd(rgn_blit); // get the update rect list
11082 while (upd) {
11083 wxRect rect = upd.GetRect();
11084
11085 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
11086 rect.x - offset.x, rect.y - offset.y);
11087 upd++;
11088 }
11089
11090 // If multi-canvas, indicate which canvas has keyboard focus
11091 // by drawing a simple blue bar at the top.
11092 if (g_canvasConfig != 0) { // multi-canvas?
11093 if (this == wxWindow::FindFocus()) {
11094 g_focusCanvas = this;
11095
11096 wxColour colour = GetGlobalColor(_T("BLUE4"));
11097 mscratch_dc.SetPen(wxPen(colour));
11098 mscratch_dc.SetBrush(wxBrush(colour));
11099
11100 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
11101 mscratch_dc.DrawRectangle(activeRect);
11102 }
11103 }
11104
11105 // Any MBtiles?
11106 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
11107 unsigned int im = stackIndexArray.size();
11108 if (VPoint.b_quilt && im > 0) {
11109 std::vector<int> tiles_to_show;
11110 for (unsigned int is = 0; is < im; is++) {
11111 const ChartTableEntry &cte =
11112 ChartData->GetChartTableEntry(stackIndexArray[is]);
11113 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
11114 continue;
11115 }
11116 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
11117 tiles_to_show.push_back(stackIndexArray[is]);
11118 }
11119 }
11120
11121 if (tiles_to_show.size())
11122 SetAlertString(_("MBTile requires OpenGL to be enabled"));
11123 }
11124
11125 // Draw the rest of the overlay objects directly on the scratch dc
11126 ocpnDC scratch_dc(mscratch_dc);
11127 DrawOverlayObjects(scratch_dc, ru);
11128
11129 if (m_bShowTide) {
11130 RebuildTideSelectList(GetVP().GetBBox());
11131 DrawAllTidesInBBox(scratch_dc, GetVP().GetBBox());
11132 }
11133
11134 if (m_bShowCurrent) {
11135 RebuildCurrentSelectList(GetVP().GetBBox());
11136 DrawAllCurrentsInBBox(scratch_dc, GetVP().GetBBox());
11137 }
11138
11139 if (m_brepaint_piano && g_bShowChartBar) {
11140 int canvas_height = GetClientSize().y;
11141 canvas_height *= m_displayScale;
11142 m_Piano->Paint(canvas_height - m_Piano->GetHeight(), mscratch_dc);
11143 // m_brepaint_piano = false;
11144 }
11145
11146 if (m_Compass) m_Compass->Paint(scratch_dc);
11147
11148 RenderAlertMessage(mscratch_dc, GetVP());
11149
11150 // quiting?
11151 if (g_bquiting) {
11152#ifdef ocpnUSE_DIBSECTION
11153 ocpnMemDC q_dc;
11154#else
11155 wxMemoryDC q_dc;
11156#endif
11157 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
11158 q_dc.SelectObject(qbm);
11159
11160 // Get a copy of the screen
11161 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
11162
11163 // Draw a rectangle over the screen with a stipple brush
11164 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
11165 q_dc.SetBrush(qbr);
11166 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
11167
11168 // Blit back into source
11169 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
11170 wxCOPY);
11171
11172 q_dc.SelectObject(wxNullBitmap);
11173 }
11174#if 0
11175 // It is possible that this two-step method may be reuired for some platforms.
11176 // So, retain in the code base to aid recovery if necessary
11177
11178 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
11179 if( VPoint.b_quilt ) {
11180 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
11181 ChartBase *chart = m_pQuilt->GetRefChart();
11182 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
11183
11184 // Clear the text Global declutter list
11185 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
11186 if(ChPI)
11187 ChPI->ClearPLIBTextList();
11188 else{
11189 if(ps52plib)
11190 ps52plib->ClearTextList();
11191 }
11192
11193 wxMemoryDC t_dc;
11194 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
11195
11196 wxColor maskBackground = wxColour(1,0,0);
11197 t_dc.SelectObject( qbm );
11198 t_dc.SetBackground(wxBrush(maskBackground));
11199 t_dc.Clear();
11200
11201 // Copy the scratch DC into the new bitmap
11202 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
11203
11204 // Render the text to the new bitmap
11205 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
11206 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
11207
11208 // Copy the new bitmap back to the scratch dc
11209 wxRegionIterator upd_final( ru );
11210 while( upd_final ) {
11211 wxRect rect = upd_final.GetRect();
11212 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
11213 upd_final++;
11214 }
11215
11216 t_dc.SelectObject( wxNullBitmap );
11217 }
11218 }
11219 }
11220#endif
11221 // Direct rendering model...
11222 if (VPoint.b_quilt) {
11223 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
11224 ChartBase *chart = m_pQuilt->GetRefChart();
11225 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
11226 // Clear the text Global declutter list
11227 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
11228 if (ChPI)
11229 ChPI->ClearPLIBTextList();
11230 else {
11231 if (ps52plib) ps52plib->ClearTextList();
11232 }
11233
11234 // Render the text directly to the scratch bitmap
11235 OCPNRegion chart_all_text_region(
11236 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
11237
11238 if (g_bShowChartBar && m_Piano) {
11239 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
11240 GetVP().pix_width, m_Piano->GetHeight());
11241
11242 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11243 if (!style->chartStatusWindowTransparent)
11244 chart_all_text_region.Subtract(chart_bar_rect);
11245 }
11246
11247 if (m_Compass && m_Compass->IsShown()) {
11248 wxRect compassRect = m_Compass->GetRect();
11249 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
11250 chart_all_text_region.Subtract(compassRect);
11251 }
11252 }
11253
11254 mscratch_dc.DestroyClippingRegion();
11255
11256 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
11257 chart_all_text_region);
11258 }
11259 }
11260 }
11261
11262 // And finally, blit the scratch dc onto the physical dc
11263 wxRegionIterator upd_final(rgn_blit);
11264 while (upd_final) {
11265 wxRect rect = upd_final.GetRect();
11266 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
11267 rect.y);
11268 upd_final++;
11269 }
11270
11271 // Test code to validate the dc drawing rectangle....
11272 /*
11273 wxRegionIterator upd_ru ( rgn_blit ); // get the update rect list
11274 while ( upd_ru )
11275 {
11276 wxRect rect = upd_ru.GetRect();
11277
11278 dc.SetPen(wxPen(*wxRED));
11279 dc.SetBrush(wxBrush(*wxRED, wxTRANSPARENT));
11280 dc.DrawRectangle(rect);
11281 upd_ru ++ ;
11282 }
11283 */
11284
11285 // Deselect the chart bitmap from the temp_dc, so that it will not be
11286 // destroyed in the temp_dc dtor
11287 temp_dc.SelectObject(wxNullBitmap);
11288 // And for the scratch bitmap
11289 mscratch_dc.SelectObject(wxNullBitmap);
11290
11291 dc.DestroyClippingRegion();
11292
11293 if (m_muiBar) {
11294 m_muiBar->Refresh();
11295 }
11296
11297 PaintCleanup();
11298}
11299
11300void ChartCanvas::PaintCleanup() {
11301 // Handle the current graphic window, if present
11302
11303 if (pCwin) {
11304 pCwin->Show();
11305 if (m_bTCupdate) {
11306 pCwin->Refresh();
11307 pCwin->Update();
11308 }
11309 }
11310
11311 // And set flags for next time
11312 m_bTCupdate = false;
11313
11314 // Handle deferred WarpPointer
11315 if (warp_flag) {
11316 WarpPointer(warp_x, warp_y);
11317 warp_flag = false;
11318 }
11319
11320 // Start movement timer, this runs nearly immediately.
11321 // the reason we cannot simply call it directly is the
11322 // refresh events it emits may be blocked from this paint event
11323 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
11324}
11325
11326#if 0
11327wxColour GetErrorGraphicColor(double val)
11328{
11329 /*
11330 double valm = wxMin(val_max, val);
11331
11332 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
11333 unsigned char red = (unsigned char)(255 * (valm/val_max));
11334
11335 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
11336
11337 hv.saturation = 1.0;
11338 hv.value = 1.0;
11339
11340 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
11341 return wxColour(rv.red, rv.green, rv.blue);
11342 */
11343
11344 // HTML colors taken from NOAA WW3 Web representation
11345 wxColour c;
11346 if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
11347 else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
11348 else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
11349 else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
11350 else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
11351 else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
11352 else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
11353 else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
11354 else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
11355 else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
11356 else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
11357 else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
11358 else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
11359 else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
11360 else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
11361 else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
11362 else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
11363 else if( val >= 48) c.Set(_T("#410000"));
11364
11365 return c;
11366}
11367
11368void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
11369{
11370 wxImage gr_image(vp->pix_width, vp->pix_height);
11371 gr_image.InitAlpha();
11372
11373 double maxval = -10000;
11374 double minval = 10000;
11375
11376 double rlat, rlon;
11377 double glat, glon;
11378
11379 GetCanvasPixPoint(0, 0, rlat, rlon);
11380
11381 for(int i=1; i < vp->pix_height-1; i++)
11382 {
11383 for(int j=0; j < vp->pix_width; j++)
11384 {
11385 // Reference mercator value
11386// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
11387
11388 // Georef value
11389 GetCanvasPixPoint(j, i, glat, glon);
11390
11391 maxval = wxMax(maxval, (glat - rlat));
11392 minval = wxMin(minval, (glat - rlat));
11393
11394 }
11395 rlat = glat;
11396 }
11397
11398 GetCanvasPixPoint(0, 0, rlat, rlon);
11399 for(int i=1; i < vp->pix_height-1; i++)
11400 {
11401 for(int j=0; j < vp->pix_width; j++)
11402 {
11403 // Reference mercator value
11404// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
11405
11406 // Georef value
11407 GetCanvasPixPoint(j, i, glat, glon);
11408
11409 double f = ((glat - rlat)-minval)/(maxval - minval);
11410
11411 double dy = (f * 40);
11412
11413 wxColour c = GetErrorGraphicColor(dy);
11414 unsigned char r = c.Red();
11415 unsigned char g = c.Green();
11416 unsigned char b = c.Blue();
11417
11418 gr_image.SetRGB(j, i, r,g,b);
11419 if((glat - rlat )!= 0)
11420 gr_image.SetAlpha(j, i, 128);
11421 else
11422 gr_image.SetAlpha(j, i, 255);
11423
11424 }
11425 rlat = glat;
11426 }
11427
11428 // Create a Bitmap
11429 wxBitmap *pbm = new wxBitmap(gr_image);
11430 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
11431 pbm->SetMask(gr_mask);
11432
11433 pmdc->DrawBitmap(*pbm, 0,0);
11434
11435 delete pbm;
11436
11437}
11438
11439#endif
11440
11441void ChartCanvas::CancelMouseRoute() {
11442 m_routeState = 0;
11443 m_pMouseRoute = NULL;
11444 m_bDrawingRoute = false;
11445}
11446
11447int ChartCanvas::GetNextContextMenuId() {
11448 return CanvasMenuHandler::GetNextContextMenuId();
11449}
11450
11451bool ChartCanvas::SetCursor(const wxCursor &c) {
11452#ifdef ocpnUSE_GL
11453 if (g_bopengl && m_glcc)
11454 return m_glcc->SetCursor(c);
11455 else
11456#endif
11457 return wxWindow::SetCursor(c);
11458}
11459
11460void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
11461 if (g_bquiting) return;
11462 // Keep the mouse position members up to date
11463 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
11464
11465 // Retrigger the route leg popup timer
11466 // This handles the case when the chart is moving in auto-follow mode,
11467 // but no user mouse input is made. The timer handler may Hide() the
11468 // popup if the chart moved enough n.b. We use slightly longer oneshot
11469 // value to allow this method's Refresh() to complete before potentially
11470 // getting another Refresh() in the popup timer handler.
11471 if (!m_RolloverPopupTimer.IsRunning() &&
11472 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
11473 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
11474 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
11475 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
11476
11477#ifdef ocpnUSE_GL
11478 if (m_glcc && g_bopengl) {
11479 // We need to invalidate the FBO cache to ensure repaint of "grounded"
11480 // overlay objects.
11481 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
11482
11483 m_glcc->Refresh(eraseBackground,
11484 NULL); // We always are going to render the entire screen
11485 // anyway, so make
11486 // sure that the window managers understand the invalid area
11487 // is actually the entire client area.
11488
11489 // We need to selectively Refresh some child windows, if they are visible.
11490 // Note that some children are refreshed elsewhere on timer ticks, so don't
11491 // need attention here.
11492
11493 // Thumbnail chart
11494 if (pthumbwin && pthumbwin->IsShown()) {
11495 pthumbwin->Raise();
11496 pthumbwin->Refresh(false);
11497 }
11498
11499 // ChartInfo window
11500 if (m_pCIWin && m_pCIWin->IsShown()) {
11501 m_pCIWin->Raise();
11502 m_pCIWin->Refresh(false);
11503 }
11504
11505 // if(g_MainToolbar)
11506 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
11507
11508 } else
11509#endif
11510 wxWindow::Refresh(eraseBackground, rect);
11511}
11512
11513void ChartCanvas::Update() {
11514 if (m_glcc && g_bopengl) {
11515#ifdef ocpnUSE_GL
11516 m_glcc->Update();
11517#endif
11518 } else
11519 wxWindow::Update();
11520}
11521
11522void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
11523 if (!pemboss) return;
11524 int x = pemboss->x, y = pemboss->y;
11525 const double factor = 200;
11526
11527 wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
11528 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
11529 wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
11530
11531 // Grab a snipped image out of the chart
11532 wxMemoryDC snip_dc;
11533 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
11534 snip_dc.SelectObject(snip_bmp);
11535
11536 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
11537 snip_dc.SelectObject(wxNullBitmap);
11538
11539 wxImage snip_img = snip_bmp.ConvertToImage();
11540
11541 // Apply Emboss map to the snip image
11542 unsigned char *pdata = snip_img.GetData();
11543 if (pdata) {
11544 for (int y = 0; y < pemboss->height; y++) {
11545 int map_index = (y * pemboss->width);
11546 for (int x = 0; x < pemboss->width; x++) {
11547 double val = (pemboss->pmap[map_index] * factor) / 256.;
11548
11549 int nred = (int)((*pdata) + val);
11550 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
11551 *pdata++ = (unsigned char)nred;
11552
11553 int ngreen = (int)((*pdata) + val);
11554 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
11555 *pdata++ = (unsigned char)ngreen;
11556
11557 int nblue = (int)((*pdata) + val);
11558 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
11559 *pdata++ = (unsigned char)nblue;
11560
11561 map_index++;
11562 }
11563 }
11564 }
11565
11566 // Convert embossed snip to a bitmap
11567 wxBitmap emb_bmp(snip_img);
11568
11569 // Map to another memoryDC
11570 wxMemoryDC result_dc;
11571 result_dc.SelectObject(emb_bmp);
11572
11573 // Blit to target
11574 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
11575
11576 result_dc.SelectObject(wxNullBitmap);
11577}
11578
11579emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
11580 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
11581
11582 if (GetQuiltMode()) {
11583 // disable Overzoom indicator for MBTiles
11584 int refIndex = GetQuiltRefChartdbIndex();
11585 if (refIndex >= 0) {
11586 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
11587 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
11588 if (current_type == CHART_TYPE_MBTILES) {
11589 ChartBase *pChart = m_pQuilt->GetRefChart();
11590 ChartMBTiles *ptc = dynamic_cast<ChartMBTiles *>(pChart);
11591 if (ptc) {
11592 zoom_factor = ptc->GetZoomFactor();
11593 }
11594 }
11595 }
11596
11597 if (zoom_factor <= 3.9) return NULL;
11598 } else {
11599 if (m_singleChart) {
11600 if (zoom_factor <= 3.9) return NULL;
11601 } else
11602 return NULL;
11603 }
11604
11605 if (m_pEM_OverZoom) {
11606 m_pEM_OverZoom->x = 4;
11607 m_pEM_OverZoom->y = 0;
11608 if (g_MainToolbar && IsPrimaryCanvas()) {
11609 wxRect masterToolbarRect = g_MainToolbar->GetRect();
11610 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
11611 }
11612 }
11613 return m_pEM_OverZoom;
11614}
11615
11616void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
11617 GridDraw(dc);
11618
11619 // bool pluginOverlayRender = true;
11620 //
11621 // if(g_canvasConfig > 0){ // Multi canvas
11622 // if(IsPrimaryCanvas())
11623 // pluginOverlayRender = false;
11624 // }
11625
11626 g_overlayCanvas = this;
11627
11628 if (g_pi_manager) {
11629 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
11630 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex, OVERLAY_LEGACY);
11631 }
11632
11633 AISDrawAreaNotices(dc, GetVP(), this);
11634
11635 wxDC *pdc = dc.GetDC();
11636 if (pdc) {
11637 pdc->DestroyClippingRegion();
11638 wxDCClipper(*pdc, ru);
11639 }
11640
11641 if (m_bShowNavobjects) {
11642 DrawAllTracksInBBox(dc, GetVP().GetBBox());
11643 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
11644 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
11645 DrawAnchorWatchPoints(dc);
11646 } else {
11647 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
11648 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
11649 }
11650
11651 AISDraw(dc, GetVP(), this);
11652 ShipDraw(dc);
11653 AlertDraw(dc);
11654
11655 RenderVisibleSectorLights(dc);
11656
11657 RenderAllChartOutlines(dc, GetVP());
11658 RenderRouteLegs(dc);
11659 RenderShipToActive(dc, false);
11660 ScaleBarDraw(dc);
11661 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
11662 if (g_pi_manager) {
11663 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex, OVERLAY_OVER_SHIPS);
11664 }
11665
11666 DrawEmboss(dc, EmbossDepthScale());
11667 DrawEmboss(dc, EmbossOverzoomIndicator(dc));
11668 if (g_pi_manager) {
11669 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex, OVERLAY_OVER_EMBOSS);
11670 }
11671
11672 if (m_pTrackRolloverWin) {
11673 m_pTrackRolloverWin->Draw(dc);
11674 m_brepaint_piano = true;
11675 }
11676
11677 if (m_pRouteRolloverWin) {
11678 m_pRouteRolloverWin->Draw(dc);
11679 m_brepaint_piano = true;
11680 }
11681
11682 if (m_pAISRolloverWin) {
11683 m_pAISRolloverWin->Draw(dc);
11684 m_brepaint_piano = true;
11685 }
11686
11687 if (g_pi_manager) {
11688 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex, OVERLAY_OVER_UI);
11689 }
11690}
11691
11692emboss_data *ChartCanvas::EmbossDepthScale() {
11693 if (!m_bShowDepthUnits) return NULL;
11694
11695 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
11696
11697 if (GetQuiltMode()) {
11698 wxString s = m_pQuilt->GetQuiltDepthUnit();
11699 s.MakeUpper();
11700 if (s == _T("FEET"))
11701 depth_unit_type = DEPTH_UNIT_FEET;
11702 else if (s.StartsWith(_T("FATHOMS")))
11703 depth_unit_type = DEPTH_UNIT_FATHOMS;
11704 else if (s.StartsWith(_T("METERS")))
11705 depth_unit_type = DEPTH_UNIT_METERS;
11706 else if (s.StartsWith(_T("METRES")))
11707 depth_unit_type = DEPTH_UNIT_METERS;
11708 else if (s.StartsWith(_T("METRIC")))
11709 depth_unit_type = DEPTH_UNIT_METERS;
11710 else if (s.StartsWith(_T("METER")))
11711 depth_unit_type = DEPTH_UNIT_METERS;
11712
11713 } else {
11714 if (m_singleChart) {
11715 depth_unit_type = m_singleChart->GetDepthUnitType();
11716 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11717 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
11718 }
11719 }
11720
11721 emboss_data *ped = NULL;
11722 switch (depth_unit_type) {
11723 case DEPTH_UNIT_FEET:
11724 ped = m_pEM_Feet;
11725 break;
11726 case DEPTH_UNIT_METERS:
11727 ped = m_pEM_Meters;
11728 break;
11729 case DEPTH_UNIT_FATHOMS:
11730 ped = m_pEM_Fathoms;
11731 break;
11732 default:
11733 return NULL;
11734 }
11735
11736 ped->x = (GetVP().pix_width - ped->width);
11737
11738 if (m_Compass && m_bShowCompassWin) {
11739 wxRect r = m_Compass->GetRect();
11740 ped->y = r.y + r.height;
11741 } else {
11742 ped->y = 40;
11743 }
11744 return ped;
11745}
11746
11747void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
11748 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11749 wxFont font;
11750 if (style->embossFont == wxEmptyString) {
11751 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
11752 font = *dFont;
11753 font.SetPointSize(60);
11754 font.SetWeight(wxFONTWEIGHT_BOLD);
11755 } else
11756 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
11757 wxFONTWEIGHT_BOLD, false, style->embossFont);
11758
11759 int emboss_width = 500;
11760 int emboss_height = 200;
11761
11762 // Free any existing emboss maps
11763 delete m_pEM_Feet;
11764 delete m_pEM_Meters;
11765 delete m_pEM_Fathoms;
11766
11767 // Create the 3 DepthUnit emboss map structures
11768 m_pEM_Feet =
11769 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
11770 m_pEM_Meters =
11771 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
11772 m_pEM_Fathoms =
11773 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
11774}
11775
11776#define OVERZOOM_TEXT _("OverZoom")
11777
11778void ChartCanvas::SetOverzoomFont() {
11779 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11780 int w, h;
11781
11782 wxFont font;
11783 if (style->embossFont == wxEmptyString) {
11784 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
11785 font = *dFont;
11786 font.SetPointSize(40);
11787 font.SetWeight(wxFONTWEIGHT_BOLD);
11788 } else
11789 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
11790 wxFONTWEIGHT_BOLD, false, style->embossFont);
11791
11792 wxClientDC dc(this);
11793 dc.SetFont(font);
11794 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
11795
11796 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
11797 font.SetPointSize(font.GetPointSize() - 1);
11798 dc.SetFont(font);
11799 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
11800 }
11801 m_overzoomFont = font;
11802 m_overzoomTextWidth = w;
11803 m_overzoomTextHeight = h;
11804}
11805
11806void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
11807 delete m_pEM_OverZoom;
11808
11809 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
11810 m_pEM_OverZoom =
11811 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
11812 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
11813}
11814
11815emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
11816 int height, const wxString &str,
11817 ColorScheme cs) {
11818 int *pmap;
11819
11820 // Create a temporary bitmap
11821 wxBitmap bmp(width, height, -1);
11822
11823 // Create a memory DC
11824 wxMemoryDC temp_dc;
11825 temp_dc.SelectObject(bmp);
11826
11827 // Paint on it
11828 temp_dc.SetBackground(*wxWHITE_BRUSH);
11829 temp_dc.SetTextBackground(*wxWHITE);
11830 temp_dc.SetTextForeground(*wxBLACK);
11831
11832 temp_dc.Clear();
11833
11834 temp_dc.SetFont(font);
11835
11836 int str_w, str_h;
11837 temp_dc.GetTextExtent(str, &str_w, &str_h);
11838 // temp_dc.DrawText( str, width - str_w - 10, 10 );
11839 temp_dc.DrawText(str, 1, 1);
11840
11841 // Deselect the bitmap
11842 temp_dc.SelectObject(wxNullBitmap);
11843
11844 // Convert bitmap the wxImage for manipulation
11845 wxImage img = bmp.ConvertToImage();
11846
11847 int image_width = str_w * 105 / 100;
11848 int image_height = str_h * 105 / 100;
11849 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
11850 wxMin(image_height, img.GetHeight()));
11851 wxImage imgs = img.GetSubImage(r);
11852
11853 double val_factor;
11854 switch (cs) {
11855 case GLOBAL_COLOR_SCHEME_DAY:
11856 default:
11857 val_factor = 1;
11858 break;
11859 case GLOBAL_COLOR_SCHEME_DUSK:
11860 val_factor = .5;
11861 break;
11862 case GLOBAL_COLOR_SCHEME_NIGHT:
11863 val_factor = .25;
11864 break;
11865 }
11866
11867 int val;
11868 int index;
11869 const int w = imgs.GetWidth();
11870 const int h = imgs.GetHeight();
11871 pmap = (int *)calloc(w * h * sizeof(int), 1);
11872 // Create emboss map by differentiating the emboss image
11873 // and storing integer results in pmap
11874 // n.b. since the image is B/W, it is sufficient to check
11875 // one channel (i.e. red) only
11876 for (int y = 1; y < h - 1; y++) {
11877 for (int x = 1; x < w - 1; x++) {
11878 val =
11879 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
11880 val = (int)(val * val_factor);
11881 index = (y * w) + x;
11882 pmap[index] = val;
11883 }
11884 }
11885
11886 emboss_data *pret = new emboss_data;
11887 pret->pmap = pmap;
11888 pret->width = w;
11889 pret->height = h;
11890
11891 return pret;
11892}
11893
11894void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
11895 Track *active_track = NULL;
11896 for (Track* pTrackDraw : g_TrackList) {
11897 if (g_pActiveTrack == pTrackDraw) {
11898 active_track = pTrackDraw;
11899 continue;
11900 }
11901
11902 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
11903 }
11904
11905 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
11906}
11907
11908void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
11909 Track *active_track = NULL;
11910 for (Track* pTrackDraw : g_TrackList) {
11911 if (g_pActiveTrack == pTrackDraw) {
11912 active_track = pTrackDraw;
11913 break;
11914 }
11915 }
11916 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
11917}
11918
11919void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
11920 Route *active_route = NULL;
11921
11922 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
11923 node = node->GetNext()) {
11924 Route *pRouteDraw = node->GetData();
11925 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
11926 active_route = pRouteDraw;
11927 continue;
11928 }
11929
11930 // if(m_canvasIndex == 1)
11931 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
11932 }
11933
11934 // Draw any active or selected route (or track) last, so that is is always on
11935 // top
11936 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
11937}
11938
11939void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
11940 Route *active_route = NULL;
11941
11942 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
11943 node = node->GetNext()) {
11944 Route *pRouteDraw = node->GetData();
11945 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
11946 active_route = pRouteDraw;
11947 break;
11948 }
11949 }
11950 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
11951}
11952
11953void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
11954 if (!pWayPointMan) return;
11955
11956 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
11957
11958 while (node) {
11959 RoutePoint *pWP = node->GetData();
11960 if (pWP) {
11961 if (pWP->m_bIsInRoute) {
11962 node = node->GetNext();
11963 continue;
11964 }
11965
11966 /* technically incorrect... waypoint has bounding box */
11967 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
11968 RoutePointGui(*pWP).Draw(dc, this, NULL);
11969 else {
11970 // Are Range Rings enabled?
11971 if (pWP->GetShowWaypointRangeRings() &&
11972 (pWP->GetWaypointRangeRingsNumber() > 0)) {
11973 double factor = 1.00;
11974 if (pWP->GetWaypointRangeRingsStepUnits() ==
11975 1) // convert kilometers to NMi
11976 factor = 1 / 1.852;
11977
11978 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
11979 pWP->GetWaypointRangeRingsStep() / 60.;
11980 radius *= 2; // Fudge factor
11981
11982 LLBBox radar_box;
11983 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
11984 pWP->m_lat + radius, pWP->m_lon + radius);
11985 if (!BltBBox.IntersectOut(radar_box)) {
11986 RoutePointGui(*pWP).Draw(dc, this, NULL);
11987 }
11988 }
11989 }
11990 }
11991
11992 node = node->GetNext();
11993 }
11994}
11995
11996void ChartCanvas::DrawBlinkObjects(void) {
11997 // All RoutePoints
11998 wxRect update_rect;
11999
12000 if (!pWayPointMan) return;
12001
12002 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12003
12004 while (node) {
12005 RoutePoint *pWP = node->GetData();
12006 if (pWP) {
12007 if (pWP->m_bBlink) {
12008 update_rect.Union(pWP->CurrentRect_in_DC);
12009 }
12010 }
12011
12012 node = node->GetNext();
12013 }
12014 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
12015}
12016
12017void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
12018 // draw anchor watch rings, if activated
12019
12020 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
12021 wxPoint r1, r2;
12022 wxPoint lAnchorPoint1, lAnchorPoint2;
12023 double lpp1 = 0.0;
12024 double lpp2 = 0.0;
12025 if (pAnchorWatchPoint1) {
12026 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
12027 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
12028 &lAnchorPoint1);
12029 }
12030 if (pAnchorWatchPoint2) {
12031 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
12032 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
12033 &lAnchorPoint2);
12034 }
12035
12036 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
12037 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
12038
12039 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
12040 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
12041 dc.SetBrush(*ppBrush);
12042
12043 if (lpp1 > 0) {
12044 dc.SetPen(ppPeng);
12045 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12046 }
12047
12048 if (lpp2 > 0) {
12049 dc.SetPen(ppPeng);
12050 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12051 }
12052
12053 if (lpp1 < 0) {
12054 dc.SetPen(ppPenr);
12055 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12056 }
12057
12058 if (lpp2 < 0) {
12059 dc.SetPen(ppPenr);
12060 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12061 }
12062 }
12063}
12064
12065double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
12066 double lpp = 0.;
12067 wxPoint r1;
12068 wxPoint lAnchorPoint;
12069 double d1 = 0.0;
12070 double dabs;
12071 double tlat1, tlon1;
12072
12073 if (pAnchorWatchPoint) {
12074 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
12075 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
12076 dabs = fabs(d1 / 1852.);
12077 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
12078 &tlat1, &tlon1);
12079 GetCanvasPointPix(tlat1, tlon1, &r1);
12080 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
12081 &lAnchorPoint);
12082 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
12083 pow((double)(lAnchorPoint.y - r1.y), 2));
12084
12085 // This is an entry watch
12086 if (d1 < 0) lpp = -lpp;
12087 }
12088 return lpp;
12089}
12090
12091//------------------------------------------------------------------------------------------
12092// Tides Support
12093//------------------------------------------------------------------------------------------
12094void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
12095 if (!ptcmgr) return;
12096
12097 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
12098
12099 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12100 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12101 double lon = pIDX->IDX_lon;
12102 double lat = pIDX->IDX_lat;
12103
12104 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12105 if ((type == 't') || (type == 'T')) {
12106 if (BBox.Contains(lat, lon)) {
12107 // Manage the point selection list
12108 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
12109 }
12110 }
12111 }
12112}
12113
12114extern wxDateTime gTimeSource;
12115
12116void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
12117 if (!ptcmgr) return;
12118
12119 wxDateTime this_now = gTimeSource;
12120 bool cur_time = !gTimeSource.IsValid();
12121 if (cur_time) this_now = wxDateTime::Now();
12122 time_t t_this_now = this_now.GetTicks();
12123
12124 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12125 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12126 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
12127 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
12128 wxPENSTYLE_SOLID);
12129 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
12130 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
12131 wxPENSTYLE_SOLID);
12132
12133 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
12134 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
12135 // wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush (
12136 // GetGlobalColor ( _T ( "UINFD" ) ), wxSOLID );
12137 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
12138 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
12139 wxBRUSHSTYLE_SOLID);
12140 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
12141 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
12142 wxBRUSHSTYLE_SOLID);
12143
12144 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
12145 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
12146 int font_size = wxMax(10, dFont->GetPointSize());
12147 font_size /= g_Platform->GetDisplayDIPMult(this);
12148 wxFont *plabelFont =
12149 FontMgr::Get().FindOrCreateFont(font_size,
12150 dFont->GetFamily(), dFont->GetStyle(),
12151 dFont->GetWeight(), false,
12152 dFont->GetFaceName());
12153
12154 dc.SetPen(*pblack_pen);
12155 dc.SetBrush(*pgreen_brush);
12156
12157 wxBitmap bm;
12158 switch (m_cs) {
12159 case GLOBAL_COLOR_SCHEME_DAY:
12160 bm = m_bmTideDay;
12161 break;
12162 case GLOBAL_COLOR_SCHEME_DUSK:
12163 bm = m_bmTideDusk;
12164 break;
12165 case GLOBAL_COLOR_SCHEME_NIGHT:
12166 bm = m_bmTideNight;
12167 break;
12168 default:
12169 bm = m_bmTideDay;
12170 break;
12171 }
12172
12173 int bmw = bm.GetWidth();
12174 int bmh = bm.GetHeight();
12175
12176 float scale_factor = 1.0;
12177
12178 // Set the onscreen size of the symbol
12179 // Compensate for various display resolutions
12180 float icon_pixelRefDim = 45;
12181
12182#if 0
12183 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *25 / 1000; // Intended physical rendered size onscreen
12184 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 8);
12185 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 15);
12186 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
12187#endif
12188
12189#ifndef __OCPN__ANDROID__
12190 // another method is simply to declare that the icon shall be x times the size
12191 // of a raster symbol (e.g.BOYLAT)
12192 // This is a bit of a hack that will suffice until until we get fully
12193 // scalable ENC symbol sets
12194 // float nominal_icon_size_pixels = 48; // 3 x 16
12195 // float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
12196
12197 // or, x times size of text font
12198 wxScreenDC sdc;
12199 int height;
12200 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
12201 height *= g_Platform->GetDisplayDIPMult(this);
12202 float nominal_icon_size_pixels = 48; // 3 x 16
12203 float pix_factor = (2 * height) / nominal_icon_size_pixels;
12204
12205
12206#else
12207 // Yet another method goes like this:
12208 // Set the onscreen size of the symbol
12209 // Compensate for various display resolutions
12210 // Develop empirically, making a symbol about 16 mm tall
12211 double symHeight =
12212 icon_pixelRefDim /
12213 GetPixPerMM(); // from draw instructions, symbol is xx pix high
12214 double targetHeight0 = 16.0;
12215
12216 // But we want to scale the size down for smaller displays
12217 double displaySize = m_display_size_mm;
12218 displaySize = wxMax(displaySize, 100);
12219
12220 float targetHeight = wxMin(targetHeight0, displaySize / 15);
12221
12222 double pix_factor = targetHeight / symHeight;
12223#endif
12224
12225 scale_factor *= pix_factor;
12226
12227 float user_scale_factor = g_ChartScaleFactorExp;
12228 if (g_ChartScaleFactorExp > 1.0)
12229 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
12230 1.2; // soften the scale factor a bit
12231
12232 scale_factor *= user_scale_factor;
12233 scale_factor *= GetContentScaleFactor();
12234
12235
12236 {
12237 double lon_last = 0.;
12238 double lat_last = 0.;
12239 double marge = 0.05;
12240 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12241 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12242
12243 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12244 if ((type == 't') || (type == 'T')) // only Tides
12245 {
12246 double lon = pIDX->IDX_lon;
12247 double lat = pIDX->IDX_lat;
12248
12249 if (BBox.ContainsMarge(lat, lon, marge)) {
12250 wxPoint r;
12251 GetCanvasPointPix(lat, lon, &r);
12252 // draw standard icons
12253 if (GetVP().chart_scale > 500000) {
12254 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
12255 }
12256 // draw "extended" icons
12257 else {
12258 dc.SetFont(*plabelFont);
12259 {
12260 {
12261 float val, nowlev;
12262 float ltleve = 0.;
12263 float htleve = 0.;
12264 time_t tctime;
12265 time_t lttime = 0;
12266 time_t httime = 0;
12267 bool wt;
12268 // define if flood or ebb in the last ten minutes and verify if
12269 // data are useable
12270 if (ptcmgr->GetTideFlowSens(
12271 t_this_now, BACKWARD_TEN_MINUTES_STEP,
12272 pIDX->IDX_rec_num, nowlev, val, wt)) {
12273 // search forward the first HW or LW near "now" ( starting at
12274 // "now" - ten minutes )
12275 ptcmgr->GetHightOrLowTide(
12276 t_this_now + BACKWARD_TEN_MINUTES_STEP,
12277 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
12278 wt, pIDX->IDX_rec_num, val, tctime);
12279 if (wt) {
12280 httime = tctime;
12281 htleve = val;
12282 } else {
12283 lttime = tctime;
12284 ltleve = val;
12285 }
12286 wt = !wt;
12287
12288 // then search opposite tide near "now"
12289 if (tctime > t_this_now) // search backward
12290 ptcmgr->GetHightOrLowTide(
12291 t_this_now, BACKWARD_TEN_MINUTES_STEP,
12292 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
12293 pIDX->IDX_rec_num, val, tctime);
12294 else
12295 // or search forward
12296 ptcmgr->GetHightOrLowTide(
12297 t_this_now, FORWARD_TEN_MINUTES_STEP,
12298 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
12299 val, tctime);
12300 if (wt) {
12301 httime = tctime;
12302 htleve = val;
12303 } else {
12304 lttime = tctime;
12305 ltleve = val;
12306 }
12307
12308 // draw the tide rectangle:
12309
12310 // tide icon rectangle has default pre-scaled width = 12 ,
12311 // height = 45
12312 int width = (int)(12 * scale_factor + 0.5);
12313 int height = (int)(45 * scale_factor + 0.5);
12314 int linew = wxMax(1, (int)(scale_factor));
12315 int xDraw = r.x - (width / 2);
12316 int yDraw = r.y - (height / 2);
12317
12318 // process tide state ( %height and flow sens )
12319 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
12320 int hs = (httime > lttime) ? -4 : 4;
12321 hs *= (int)(scale_factor + 0.5);
12322 if (ts > 0.995 || ts < 0.005) hs = 0;
12323 int ht_y = (int)(height * ts);
12324
12325 // draw yellow tide rectangle outlined in black
12326 pblack_pen->SetWidth(linew);
12327 dc.SetPen(*pblack_pen);
12328 dc.SetBrush(*pyelo_brush);
12329 dc.DrawRectangle(xDraw, yDraw, width, height);
12330
12331 // draw blue rectangle as water height, smaller in width than
12332 // yellow rectangle
12333 dc.SetPen(*pblue_pen);
12334 dc.SetBrush(*pblue_brush);
12335 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
12336 (width - (4 * linew)), height - ht_y);
12337
12338 // draw sens arrows (ensure they are not "under-drawn" by top
12339 // line of blue rectangle )
12340 int hl;
12341 wxPoint arrow[3];
12342 arrow[0].x = xDraw + 2 * linew;
12343 arrow[1].x = xDraw + width / 2;
12344 arrow[2].x = xDraw + width - 2 * linew;
12345 pyelo_pen->SetWidth(linew);
12346 pblue_pen->SetWidth(linew);
12347 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
12348 {
12349 hl = (int)(height * 0.25) + yDraw;
12350 arrow[0].y = hl;
12351 arrow[1].y = hl + hs;
12352 arrow[2].y = hl;
12353 if (ts < 0.15)
12354 dc.SetPen(*pyelo_pen);
12355 else
12356 dc.SetPen(*pblue_pen);
12357 dc.DrawLines(3, arrow);
12358 }
12359 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
12360 {
12361 hl = (int)(height * 0.5) + yDraw;
12362 arrow[0].y = hl;
12363 arrow[1].y = hl + hs;
12364 arrow[2].y = hl;
12365 if (ts < 0.40)
12366 dc.SetPen(*pyelo_pen);
12367 else
12368 dc.SetPen(*pblue_pen);
12369 dc.DrawLines(3, arrow);
12370 }
12371 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
12372 {
12373 hl = (int)(height * 0.75) + yDraw;
12374 arrow[0].y = hl;
12375 arrow[1].y = hl + hs;
12376 arrow[2].y = hl;
12377 if (ts < 0.65)
12378 dc.SetPen(*pyelo_pen);
12379 else
12380 dc.SetPen(*pblue_pen);
12381 dc.DrawLines(3, arrow);
12382 }
12383 // draw tide level text
12384 wxString s;
12385 s.Printf(_T("%3.1f"), nowlev);
12386 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
12387 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
12388 int wx1;
12389 dc.GetTextExtent(s, &wx1, NULL);
12390 wx1 *= g_Platform->GetDisplayDIPMult(this);
12391 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
12392 }
12393 }
12394 }
12395 }
12396 }
12397 lon_last = lon;
12398 lat_last = lat;
12399 }
12400 }
12401 }
12402}
12403
12404//------------------------------------------------------------------------------------------
12405// Currents Support
12406//------------------------------------------------------------------------------------------
12407
12408void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
12409 if (!ptcmgr) return;
12410
12411 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
12412
12413 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12414 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12415 double lon = pIDX->IDX_lon;
12416 double lat = pIDX->IDX_lat;
12417
12418 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12419 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
12420 if ((BBox.Contains(lat, lon))) {
12421 // Manage the point selection list
12422 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
12423 }
12424 }
12425 }
12426}
12427
12428void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
12429 if (!ptcmgr) return;
12430
12431 float tcvalue, dir;
12432 bool bnew_val;
12433 char sbuf[20];
12434 wxFont *pTCFont;
12435 double lon_last = 0.;
12436 double lat_last = 0.;
12437 // arrow size for Raz Blanchard : 12 knots north
12438 double marge = 0.2;
12439 bool cur_time = !gTimeSource.IsValid();
12440
12441 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
12442 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
12443
12444 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12445 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12446 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
12447 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
12448 wxPENSTYLE_SOLID);
12449 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
12450 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
12451 wxBRUSHSTYLE_SOLID);
12452 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
12453 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
12454 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
12455 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
12456
12457 double skew_angle = GetVPRotation();
12458
12459 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
12460 int font_size = wxMax(10, dFont->GetPointSize());
12461 font_size /= g_Platform->GetDisplayDIPMult(this);
12462 pTCFont =
12463 FontMgr::Get().FindOrCreateFont(font_size,
12464 dFont->GetFamily(), dFont->GetStyle(),
12465 dFont->GetWeight(), false,
12466 dFont->GetFaceName());
12467
12468
12469 float scale_factor = 1.0;
12470
12471 // Set the onscreen size of the symbol
12472 // Compensate for various display resolutions
12473
12474#if 0
12475 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *3 / 1000; // Intended physical rendered size onscreen
12476 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 2);
12477 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 4);
12478 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
12479#endif
12480
12481#if 0
12482 // another method is simply to declare that the icon shall be x times the size of a raster symbol (e.g.BOYLAT)
12483 // This is a bit of a hack that will suffice until until we get fully scalable ENC symbol sets
12484 float nominal_icon_size_pixels = 6; // 16 / 3
12485 float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
12486#endif
12487
12488#ifndef __OCPN__ANDROID__
12489 // or, x times size of text font
12490 wxScreenDC sdc;
12491 int height;
12492 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
12493 height *= g_Platform->GetDisplayDIPMult(this);
12494 float nominal_icon_size_pixels = 15;
12495 float pix_factor = (1 * height) / nominal_icon_size_pixels;
12496
12497#else
12498 // Yet another method goes like this:
12499 // Set the onscreen size of the symbol
12500 // Compensate for various display resolutions
12501 // Develop empirically....
12502 float icon_pixelRefDim = 5;
12503
12504 double symHeight =
12505 icon_pixelRefDim /
12506 GetPixPerMM(); // from draw instructions, symbol is xx pix high
12507 double targetHeight0 = 2.0;
12508
12509 // But we want to scale the size down for smaller displays
12510 double displaySize = m_display_size_mm;
12511 displaySize = wxMax(displaySize, 100);
12512
12513 float targetHeight = wxMin(targetHeight0, displaySize / 50);
12514 double pix_factor = targetHeight / symHeight;
12515#endif
12516
12517 scale_factor *= pix_factor;
12518
12519 float user_scale_factor = g_ChartScaleFactorExp;
12520 if (g_ChartScaleFactorExp > 1.0)
12521 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
12522 1.2; // soften the scale factor a bit
12523
12524 scale_factor *= user_scale_factor;
12525
12526 scale_factor *= GetContentScaleFactor();
12527
12528 {
12529 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12530 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12531 double lon = pIDX->IDX_lon;
12532 double lat = pIDX->IDX_lat;
12533
12534 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12535 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
12536 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
12537 wxPoint r;
12538 GetCanvasPointPix(lat, lon, &r);
12539
12540 wxPoint d[4]; // points of a diamond at the current station location
12541 int dd = (int)(5.0 * scale_factor + 0.5);
12542 d[0].x = r.x;
12543 d[0].y = r.y + dd;
12544 d[1].x = r.x + dd;
12545 d[1].y = r.y;
12546 d[2].x = r.x;
12547 d[2].y = r.y - dd;
12548 d[3].x = r.x - dd;
12549 d[3].y = r.y;
12550
12551 if (1) {
12552 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
12553 dc.SetPen(*pblack_pen);
12554 dc.SetBrush(*porange_brush);
12555 dc.DrawPolygon(4, d);
12556
12557 if (type == 'C') {
12558 dc.SetBrush(*pblack_brush);
12559 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
12560 }
12561
12562 if (GetVP().chart_scale < 1000000) {
12563 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
12564 continue;
12565 } else
12566 continue;
12567
12568 if (1 /*type == 'c'*/) {
12569 {
12570 // Get the display pixel location of the current station
12571 int pixxc, pixyc;
12572 pixxc = r.x;
12573 pixyc = r.y;
12574
12575 // Adjust drawing size using logarithmic scale. tcvalue is
12576 // current in knots
12577 double a1 = fabs(tcvalue) * 10.;
12578 // Current values <= 0.1 knot will have no arrow
12579 a1 = wxMax(1.0, a1);
12580 double a2 = log10(a1);
12581
12582 float cscale = scale_factor * a2 * 0.4;
12583
12584 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
12585 dc.SetPen(*porange_pen);
12586 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
12587 cscale);
12588 // Draw text, if enabled
12589
12590 if (bDrawCurrentValues) {
12591 dc.SetFont(*pTCFont);
12592 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
12593 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
12594 }
12595 }
12596 } // scale
12597 }
12598 /* This is useful for debugging the TC database
12599 else
12600 {
12601 dc.SetPen ( *porange_pen );
12602 dc.SetBrush ( *pgray_brush );
12603 dc.DrawPolygon ( 4, d );
12604 }
12605 */
12606 }
12607 lon_last = lon;
12608 lat_last = lat;
12609 }
12610 }
12611 }
12612}
12613
12614void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
12615 pCwin = new TCWin(this, x, y, pvIDX);
12616}
12617
12618#define NUM_CURRENT_ARROW_POINTS 9
12619static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
12620 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
12621 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
12622 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
12623
12624void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
12625 double scale) {
12626 if (scale > 1e-2) {
12627 float sin_rot = sin(rot_angle * PI / 180.);
12628 float cos_rot = cos(rot_angle * PI / 180.);
12629
12630 // Move to the first point
12631
12632 float xt = CurrentArrowArray[0].x;
12633 float yt = CurrentArrowArray[0].y;
12634
12635 float xp = (xt * cos_rot) - (yt * sin_rot);
12636 float yp = (xt * sin_rot) + (yt * cos_rot);
12637 int x1 = (int)(xp * scale);
12638 int y1 = (int)(yp * scale);
12639
12640 // Walk thru the point list
12641 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
12642 xt = CurrentArrowArray[ip].x;
12643 yt = CurrentArrowArray[ip].y;
12644
12645 float xp = (xt * cos_rot) - (yt * sin_rot);
12646 float yp = (xt * sin_rot) + (yt * cos_rot);
12647 int x2 = (int)(xp * scale);
12648 int y2 = (int)(yp * scale);
12649
12650 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
12651
12652 x1 = x2;
12653 y1 = y2;
12654 }
12655 }
12656}
12657
12658wxString ChartCanvas::FindValidUploadPort() {
12659 wxString port;
12660 // Try to use the saved persistent upload port first
12661 if (!g_uploadConnection.IsEmpty() &&
12662 g_uploadConnection.StartsWith(_T("Serial"))) {
12663 port = g_uploadConnection;
12664 }
12665
12666 else if (TheConnectionParams()) {
12667 // If there is no persistent upload port recorded (yet)
12668 // then use the first available serial connection which has output defined.
12669 for (size_t i = 0; i < TheConnectionParams()->Count(); i++) {
12670 ConnectionParams *cp = TheConnectionParams()->Item(i);
12671 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
12672 port << _T("Serial:") << cp->Port;
12673 }
12674 }
12675 return port;
12676}
12677
12678void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
12679 if (!win) return;
12680
12681 if (NULL == g_pais_query_dialog_active) {
12682 int pos_x = g_ais_query_dialog_x;
12683 int pos_y = g_ais_query_dialog_y;
12684
12685 if (g_pais_query_dialog_active) {
12686 delete g_pais_query_dialog_active;
12687 g_pais_query_dialog_active = new AISTargetQueryDialog();
12688 } else {
12689 g_pais_query_dialog_active = new AISTargetQueryDialog();
12690 }
12691
12692 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
12693 wxPoint(pos_x, pos_y));
12694
12695 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
12696 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
12697 g_pais_query_dialog_active->SetMMSI(mmsi);
12698 g_pais_query_dialog_active->UpdateText();
12699 wxSize sz = g_pais_query_dialog_active->GetSize();
12700
12701 bool b_reset_pos = false;
12702#ifdef __WXMSW__
12703 // Support MultiMonitor setups which an allow negative window positions.
12704 // If the requested window title bar does not intersect any installed
12705 // monitor, then default to simple primary monitor positioning.
12706 RECT frame_title_rect;
12707 frame_title_rect.left = pos_x;
12708 frame_title_rect.top = pos_y;
12709 frame_title_rect.right = pos_x + sz.x;
12710 frame_title_rect.bottom = pos_y + 30;
12711
12712 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
12713 b_reset_pos = true;
12714#else
12715
12716 // Make sure drag bar (title bar) of window intersects wxClient Area of
12717 // screen, with a little slop...
12718 wxRect window_title_rect; // conservative estimate
12719 window_title_rect.x = pos_x;
12720 window_title_rect.y = pos_y;
12721 window_title_rect.width = sz.x;
12722 window_title_rect.height = 30;
12723
12724 wxRect ClientRect = wxGetClientDisplayRect();
12725 ClientRect.Deflate(
12726 60, 60); // Prevent the new window from being too close to the edge
12727 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
12728
12729#endif
12730
12731 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
12732
12733 } else {
12734 g_pais_query_dialog_active->SetMMSI(mmsi);
12735 g_pais_query_dialog_active->UpdateText();
12736 }
12737
12738 g_pais_query_dialog_active->Show();
12739}
12740
12741void ChartCanvas::ToggleCanvasQuiltMode(void) {
12742 bool cur_mode = GetQuiltMode();
12743
12744 if (!GetQuiltMode())
12745 SetQuiltMode(true);
12746 else if (GetQuiltMode()) {
12747 SetQuiltMode(false);
12748 g_sticky_chart = GetQuiltReferenceChartIndex();
12749 }
12750
12751 if (cur_mode != GetQuiltMode()) {
12752 SetupCanvasQuiltMode();
12753 DoCanvasUpdate();
12754 InvalidateGL();
12755 Refresh();
12756 }
12757 // TODO What to do about this?
12758 // g_bQuiltEnable = GetQuiltMode();
12759
12760 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
12761 if (ps52plib) ps52plib->GenerateStateHash();
12762
12763 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
12764 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
12765}
12766
12767void ChartCanvas::DoCanvasStackDelta(int direction) {
12768 if (!GetQuiltMode()) {
12769 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
12770 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
12771 if ((current_stack_index + direction) < 0) return;
12772
12773 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
12774 int new_dbIndex =
12775 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
12776
12777 if (IsChartQuiltableRef(new_dbIndex)) {
12778 ToggleCanvasQuiltMode();
12779 SelectQuiltRefdbChart(new_dbIndex);
12780 m_bpersistent_quilt = false;
12781 }
12782 } else {
12783 SelectChartFromStack(current_stack_index + direction);
12784 }
12785 } else {
12786 std::vector<int> piano_chart_index_array =
12787 GetQuiltExtendedStackdbIndexArray();
12788 int refdb = GetQuiltRefChartdbIndex();
12789
12790 // Find the ref chart in the stack
12791 int current_index = -1;
12792 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
12793 if (refdb == piano_chart_index_array[i]) {
12794 current_index = i;
12795 break;
12796 }
12797 }
12798 if (current_index == -1) return;
12799
12800 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
12801 int target_family = ctet.GetChartFamily();
12802
12803 int new_index = -1;
12804 int check_index = current_index + direction;
12805 bool found = false;
12806 int check_dbIndex = -1;
12807 int new_dbIndex = -1;
12808
12809 // When quilted. switch within the same chart family
12810 while (!found &&
12811 (unsigned int)check_index < piano_chart_index_array.size() &&
12812 (check_index >= 0)) {
12813 check_dbIndex = piano_chart_index_array[check_index];
12814 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
12815 if (target_family == cte.GetChartFamily()) {
12816 found = true;
12817 new_index = check_index;
12818 new_dbIndex = check_dbIndex;
12819 break;
12820 }
12821
12822 check_index += direction;
12823 }
12824
12825 if (!found) return;
12826
12827 if (!IsChartQuiltableRef(new_dbIndex)) {
12828 ToggleCanvasQuiltMode();
12829 SelectdbChart(new_dbIndex);
12830 m_bpersistent_quilt = true;
12831 } else {
12832 SelectQuiltRefChart(new_index);
12833 }
12834 }
12835
12836 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
12837 // (checkmarks etc)
12838 SetQuiltChartHiLiteIndex(-1);
12839
12840 ReloadVP();
12841}
12842
12843//--------------------------------------------------------------------------------------------------------
12844//
12845// Toolbar support
12846//
12847//--------------------------------------------------------------------------------------------------------
12848
12849void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
12850 // Handle the per-canvas toolbar clicks here
12851
12852 switch (event.GetId()) {
12853 case ID_ZOOMIN: {
12854 ZoomCanvasSimple(g_plus_minus_zoom_factor);
12855 break;
12856 }
12857
12858 case ID_ZOOMOUT: {
12859 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
12860 break;
12861 }
12862
12863 case ID_STKUP:
12864 DoCanvasStackDelta(1);
12865 DoCanvasUpdate();
12866 break;
12867
12868 case ID_STKDN:
12869 DoCanvasStackDelta(-1);
12870 DoCanvasUpdate();
12871 break;
12872
12873 case ID_FOLLOW: {
12874 TogglebFollow();
12875 break;
12876 }
12877
12878 case ID_CURRENT: {
12879 ShowCurrents(!GetbShowCurrent());
12880 ReloadVP();
12881 Refresh(false);
12882 break;
12883 }
12884
12885 case ID_TIDE: {
12886 ShowTides(!GetbShowTide());
12887 ReloadVP();
12888 Refresh(false);
12889 break;
12890 }
12891
12892 case ID_ROUTE: {
12893 if (0 == m_routeState) {
12894 StartRoute();
12895 } else {
12896 FinishRoute();
12897 }
12898
12899#ifdef __OCPN__ANDROID__
12900 androidSetRouteAnnunciator(m_routeState == 1);
12901#endif
12902 break;
12903 }
12904
12905 case ID_AIS: {
12906 SetAISCanvasDisplayStyle(-1);
12907 break;
12908 }
12909
12910 default:
12911 break;
12912 }
12913
12914 // And then let gFrame handle the rest....
12915 event.Skip();
12916}
12917
12918void ChartCanvas::SetToolbarPosition(wxPoint position) {
12919 m_toolbarPosition = position;
12920}
12921
12922void ChartCanvas::SetToolbarOrientation(long orient) {
12923 m_toolbarOrientation = orient;
12924}
12925
12926wxPoint ChartCanvas::GetToolbarPosition() {
12927 if (m_toolBar) {
12928 wxPoint tbp = m_toolBar->GetPosition(); // toolbar is a TLW, so this is
12929 // screen coordinates.
12930 return ScreenToClient(tbp);
12931 } else
12932 return wxPoint(0, 0);
12933}
12934
12935long ChartCanvas::GetToolbarOrientation() {
12936 if (m_toolBar)
12937 return m_toolBar->GetOrient();
12938 else
12939 return 0;
12940}
12941
12942void ChartCanvas::SubmergeToolbar(void) {
12943 if (m_toolBar) m_toolBar->Submerge();
12944}
12945
12946void ChartCanvas::SurfaceToolbar(void) {
12947 if (m_toolBar) m_toolBar->Surface();
12948}
12949
12950bool ChartCanvas::IsToolbarShown() {
12951 bool rv = false;
12952 if (m_toolBar) rv = m_toolBar->IsShown();
12953 return rv;
12954}
12955
12956void ChartCanvas::ToggleToolbar(bool b_smooth) {
12957 if (m_toolBar) {
12958 if (m_toolBar->IsShown()) {
12959 SubmergeToolbar();
12960 } else {
12961 SurfaceToolbar();
12962 m_toolBar->Raise();
12963 }
12964 }
12965}
12966
12967void ChartCanvas::DestroyToolbar() {
12968 if (m_toolBar) m_toolBar->DestroyToolBar();
12969}
12970
12971ocpnFloatingToolbarDialog *ChartCanvas::RequestNewCanvasToolbar(
12972 bool bforcenew) {
12973 return NULL;
12974}
12975
12976// Update inplace the current toolbar with bitmaps corresponding to the
12977// current color scheme
12978void ChartCanvas::UpdateToolbarColorScheme(ColorScheme cs) {
12979 if (!m_toolBar) return;
12980
12981 if (m_toolBar) {
12982 if (m_toolBar->GetColorScheme() != cs) {
12983 m_toolBar->SetColorScheme(cs);
12984
12985 if (m_toolBar->IsToolbarShown()) {
12986 m_toolBar->DestroyToolBar();
12987 m_toolBar->CreateMyToolbar();
12988 if (m_toolBar->isSubmergedToGrabber())
12989 m_toolBar->SubmergeToGrabber(); // Surface(); //SubmergeToGrabber();
12990 }
12991 }
12992 }
12993
12994 if (m_toolBar->GetToolbar()) {
12995 // Re-establish toggle states
12996 m_toolBar->GetToolbar()->ToggleTool(ID_FOLLOW, m_bFollow);
12997 m_toolBar->GetToolbar()->ToggleTool(ID_CURRENT, GetbShowCurrent());
12998 m_toolBar->GetToolbar()->ToggleTool(ID_TIDE, GetbShowTide());
12999 }
13000
13001 return;
13002}
13003
13004void ChartCanvas::SetCanvasToolbarItemState(int tool_id, bool state) {
13005 if (GetToolbar() && GetToolbar()->GetToolbar())
13006 GetToolbar()->GetToolbar()->ToggleTool(tool_id, state);
13007}
13008
13009extern bool g_bAllowShowScaled;
13010
13011void ChartCanvas::SetShowAIS(bool show) {
13012 m_bShowAIS = show;
13013 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13014 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13015}
13016
13017void ChartCanvas::SetAttenAIS(bool show) {
13018 m_bShowAISScaled = show;
13019 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13020 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13021}
13022
13023void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13024 // make some arrays to hold the dfferences between cycle steps
13025 // show all, scaled, hide all
13026 bool bShowAIS_Array[3] = {true, true, false};
13027 bool bShowScaled_Array[3] = {false, true, true};
13028 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13029 _("Attenuate less critical AIS targets"),
13030 _("Hide AIS Targets")};
13031 wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
13032 _T("AIS_Disabled")};
13033 int ArraySize = 3;
13034 int AIS_Toolbar_Switch = 0;
13035 if (StyleIndx == -1) { // -1 means coming from toolbar button
13036 // find current state of switch
13037 for (int i = 1; i < ArraySize; i++) {
13038 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13039 (bShowScaled_Array[i] == m_bShowAISScaled))
13040 AIS_Toolbar_Switch = i;
13041 }
13042 AIS_Toolbar_Switch++; // we did click so continu with next item
13043 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13044 AIS_Toolbar_Switch++;
13045
13046 } else { // coming from menu bar.
13047 AIS_Toolbar_Switch = StyleIndx;
13048 }
13049 // make sure we are not above array
13050 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13051
13052 int AIS_Toolbar_Switch_Next =
13053 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13054 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13055 AIS_Toolbar_Switch_Next++;
13056 if (AIS_Toolbar_Switch_Next >= ArraySize)
13057 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13058
13059 // Set found values to global and member variables
13060 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13061 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13062 if (m_toolBar) {
13063 m_toolBar->SetToolShortHelp(ID_AIS,
13064 ToolShortHelp_Array[AIS_Toolbar_Switch_Next]);
13065 if (m_toolBar->m_pTBAISTool) {
13066 m_toolBar->GetToolbar()->SetToolNormalBitmapEx(
13067 m_toolBar->m_pTBAISTool, iconName_Array[AIS_Toolbar_Switch]);
13068 m_toolBar->GetToolbar()->Refresh();
13069 m_toolBar->m_tblastAISiconName = iconName_Array[AIS_Toolbar_Switch];
13070 }
13071 }
13072}
13073
13074void ChartCanvas::TouchAISToolActive(void) {
13075 if (!m_toolBar) return;
13076
13077 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13078
13079 if (m_toolBar->m_pTBAISTool) {
13080 if ((!g_pAIS->IsAISSuppressed()) && (!g_pAIS->IsAISAlertGeneral())) {
13081 g_nAIS_activity_timer = 5; // seconds
13082
13083 wxString iconName = _T("AIS_Normal_Active");
13084 if (g_pAIS->IsAISAlertGeneral()) iconName = _T("AIS_AlertGeneral_Active");
13085 if (g_pAIS->IsAISSuppressed()) iconName = _T("AIS_Suppressed_Active");
13086 if (!m_bShowAIS) iconName = _T("AIS_Disabled");
13087
13088 if (m_toolBar->m_tblastAISiconName != iconName) {
13089 if (m_toolBar->GetToolbar()) {
13090 m_toolBar->GetToolbar()->SetToolNormalBitmapEx(
13091 m_toolBar->m_pTBAISTool, iconName);
13092 m_toolBar->GetToolbar()->Refresh();
13093 m_toolBar->m_tblastAISiconName = iconName;
13094 }
13095 }
13096 }
13097 }
13098}
13099
13100void ChartCanvas::UpdateAISTBTool(void) {
13101 if (!g_pAIS) return;
13102 if (!m_toolBar) return;
13103
13104 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13105
13106 wxString iconName;
13107
13108 if (m_toolBar->m_pTBAISTool) {
13109 bool b_update = false;
13110
13111 iconName = _T("AIS");
13112 if (g_pAIS->IsAISSuppressed()) iconName = _T("AIS_Suppressed");
13113 if (g_pAIS->IsAISAlertGeneral()) iconName = _T("AIS_AlertGeneral");
13114 if (!m_bShowAIS) iconName = _T("AIS_Disabled");
13115
13116 // Manage timeout for AIS activity indicator
13117 if (g_nAIS_activity_timer) {
13118 g_nAIS_activity_timer--;
13119
13120 if (0 == g_nAIS_activity_timer)
13121 b_update = true;
13122 else {
13123 iconName = _T("AIS_Normal_Active");
13124 if (g_pAIS->IsAISSuppressed()) iconName = _T("AIS_Suppressed_Active");
13125 if (g_pAIS->IsAISAlertGeneral())
13126 iconName = _T("AIS_AlertGeneral_Active");
13127 if (!m_bShowAIS) iconName = _T("AIS_Disabled");
13128 }
13129 }
13130
13131 if ((m_toolBar->m_tblastAISiconName != iconName)) b_update = true;
13132
13133 if (b_update && m_toolBar->GetToolbar()) {
13134 m_toolBar->GetToolbar()->SetToolNormalBitmapEx(m_toolBar->m_pTBAISTool,
13135 iconName);
13136 m_toolBar->GetToolbar()->Refresh();
13137 m_toolBar->m_tblastAISiconName = iconName;
13138 }
13139 }
13140}
13141
13142//---------------------------------------------------------------------------------
13143//
13144// Compass/GPS status icon support
13145//
13146//---------------------------------------------------------------------------------
13147
13148void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13149 // Look for change in overlap or positions
13150 bool b_update = false;
13151 int cc1_edge_comp = 2;
13152 wxRect rect = m_Compass->GetRect();
13153 wxSize parent_size = GetSize();
13154
13155 parent_size *= m_displayScale;
13156
13157 // check to see if it would overlap if it was in its home position (upper
13158 // right)
13159 wxPoint tentative_pt(parent_size.x - rect.width - cc1_edge_comp,
13160 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13161 wxRect tentative_rect(tentative_pt, rect.GetSize());
13162
13163 if (m_toolBar) {
13164 // If the toolbar location has changed, or the proposed compassDialog
13165 // location has changed
13166 if (m_toolBar->GetScreenRect() != m_mainlast_tb_rect || b_force_new) {
13167 wxRect tb_rect = m_toolBar->GetScreenRect();
13168 wxPoint tentative_pt_in_screen(ClientToScreen(tentative_pt));
13169 wxRect tentative_rect_in_screen(tentative_pt_in_screen.x,
13170 tentative_pt_in_screen.y, rect.width,
13171 rect.height);
13172
13173 // if they would not intersect, go ahead and move it to the upper right
13174 // Else it has to be on lower right
13175 if (!tb_rect.Intersects(tentative_rect_in_screen))
13176 m_Compass->Move(tentative_pt);
13177 else
13178 m_Compass->Move(wxPoint(GetSize().x - rect.width - cc1_edge_comp,
13179 GetSize().y - (rect.height + cc1_edge_comp)));
13180
13181 if (rect != m_Compass->GetRect()) {
13182 Refresh(true);
13183 m_brepaint_piano = true;
13184 b_update = true;
13185 }
13186 m_mainlast_tb_rect = tb_rect;
13187 }
13188 } else { // No toolbar, so just place compass in upper right.
13189 m_Compass->Move(tentative_pt);
13190 }
13191
13192 if (m_Compass && m_Compass->IsShown())
13193 m_Compass->UpdateStatus(b_force_new | b_update);
13194
13195 if (b_force_new | b_update) Refresh();
13196}
13197
13198void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13199 ChartTypeEnum New_Type,
13200 ChartFamilyEnum New_Family) {
13201 if (!GetpCurrentStack()) return;
13202 if (!ChartData) return;
13203
13204 if (index < GetpCurrentStack()->nEntry) {
13205 // Open the new chart
13206 ChartBase *pTentative_Chart;
13207 pTentative_Chart = ChartData->OpenStackChartConditional(
13208 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13209
13210 if (pTentative_Chart) {
13211 if (m_singleChart) m_singleChart->Deactivate();
13212
13213 m_singleChart = pTentative_Chart;
13214 m_singleChart->Activate();
13215
13216 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13217 GetpCurrentStack(), m_singleChart->GetFullPath());
13218 }
13219
13220 // Setup the view
13221 double zLat, zLon;
13222 if (m_bFollow) {
13223 zLat = gLat;
13224 zLon = gLon;
13225 } else {
13226 zLat = m_vLat;
13227 zLon = m_vLon;
13228 }
13229
13230 double best_scale_ppm = GetBestVPScale(m_singleChart);
13231 double rotation = GetVPRotation();
13232 double oldskew = GetVPSkew();
13233 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
13234
13235 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
13236 if (fabs(oldskew) > 0.0001) rotation = 0.0;
13237 if (fabs(newskew) > 0.0001) rotation = newskew;
13238 }
13239
13240 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
13241
13242 UpdateGPSCompassStatusBox(true); // Pick up the rotation
13243 }
13244
13245 // refresh Piano
13246 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
13247 if (idx < 0) return;
13248
13249 std::vector<int> piano_active_chart_index_array;
13250 piano_active_chart_index_array.push_back(
13251 GetpCurrentStack()->GetCurrentEntrydbIndex());
13252 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13253}
13254
13255void ChartCanvas::SelectdbChart(int dbindex) {
13256 if (!GetpCurrentStack()) return;
13257 if (!ChartData) return;
13258
13259 if (dbindex >= 0) {
13260 // Open the new chart
13261 ChartBase *pTentative_Chart;
13262 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
13263
13264 if (pTentative_Chart) {
13265 if (m_singleChart) m_singleChart->Deactivate();
13266
13267 m_singleChart = pTentative_Chart;
13268 m_singleChart->Activate();
13269
13270 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13271 GetpCurrentStack(), m_singleChart->GetFullPath());
13272 }
13273
13274 // Setup the view
13275 double zLat, zLon;
13276 if (m_bFollow) {
13277 zLat = gLat;
13278 zLon = gLon;
13279 } else {
13280 zLat = m_vLat;
13281 zLon = m_vLon;
13282 }
13283
13284 double best_scale_ppm = GetBestVPScale(m_singleChart);
13285
13286 if (m_singleChart)
13287 SetViewPoint(zLat, zLon, best_scale_ppm,
13288 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
13289
13290 // SetChartUpdatePeriod( );
13291
13292 // UpdateGPSCompassStatusBox(); // Pick up the rotation
13293 }
13294
13295 // TODO refresh_Piano();
13296}
13297
13298void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
13299 double target_scale = GetVP().view_scale_ppm;
13300
13301 if (!GetQuiltMode()) {
13302 if (GetpCurrentStack()) {
13303 int stack_index = -1;
13304 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
13305 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
13306 if (check_dbIndex < 0) continue;
13307 const ChartTableEntry &cte =
13308 ChartData->GetChartTableEntry(check_dbIndex);
13309 if (type == cte.GetChartType()) {
13310 stack_index = i;
13311 break;
13312 } else if (family == cte.GetChartFamily()) {
13313 stack_index = i;
13314 break;
13315 }
13316 }
13317
13318 if (stack_index >= 0) {
13319 SelectChartFromStack(stack_index);
13320 }
13321 }
13322 } else {
13323 int sel_dbIndex = -1;
13324 std::vector<int> piano_chart_index_array =
13325 GetQuiltExtendedStackdbIndexArray();
13326 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13327 int check_dbIndex = piano_chart_index_array[i];
13328 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13329 if (type == cte.GetChartType()) {
13330 if (IsChartQuiltableRef(check_dbIndex)) {
13331 sel_dbIndex = check_dbIndex;
13332 break;
13333 }
13334 } else if (family == cte.GetChartFamily()) {
13335 if (IsChartQuiltableRef(check_dbIndex)) {
13336 sel_dbIndex = check_dbIndex;
13337 break;
13338 }
13339 }
13340 }
13341
13342 if (sel_dbIndex >= 0) {
13343 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
13344 // Re-qualify the quilt reference chart selection
13345 AdjustQuiltRefChart();
13346 }
13347
13348 // Now reset the scale to the target...
13349 SetVPScale(target_scale);
13350 }
13351
13352 SetQuiltChartHiLiteIndex(-1);
13353
13354 ReloadVP();
13355}
13356
13357bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
13358 return std::find(m_tile_yesshow_index_array.begin(),
13359 m_tile_yesshow_index_array.end(),
13360 index) != m_tile_yesshow_index_array.end();
13361}
13362
13363bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
13364 return std::find(m_tile_noshow_index_array.begin(),
13365 m_tile_noshow_index_array.end(),
13366 index) != m_tile_noshow_index_array.end();
13367}
13368
13369void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
13370 if (std::find(m_tile_noshow_index_array.begin(),
13371 m_tile_noshow_index_array.end(),
13372 index) == m_tile_noshow_index_array.end()) {
13373 m_tile_noshow_index_array.push_back(index);
13374 }
13375}
13376
13377//-------------------------------------------------------------------------------------------------------
13378//
13379// Piano support
13380//
13381//-------------------------------------------------------------------------------------------------------
13382
13383void ChartCanvas::HandlePianoClick(int selected_index, int selected_dbIndex) {
13384 if (g_boptionsactive)
13385 return; // Piano might be invalid due to chartset updates.
13386 if (!m_pCurrentStack) return;
13387 if (!ChartData) return;
13388
13389 // stop movement or on slow computer we may get something like :
13390 // zoom out with the wheel (timer is set)
13391 // quickly click and display a chart, which may zoom in
13392 // but the delayed timer fires first and it zooms out again!
13393 StopMovement();
13394
13395 if (!GetQuiltMode()) {
13396 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
13397 if (IsChartQuiltableRef(selected_dbIndex)) {
13398 ToggleCanvasQuiltMode();
13399 SelectQuiltRefdbChart(selected_dbIndex);
13400 m_bpersistent_quilt = false;
13401 } else {
13402 SelectChartFromStack(selected_index);
13403 }
13404 } else {
13405 SelectChartFromStack(selected_index);
13406 g_sticky_chart = selected_dbIndex;
13407 }
13408
13409 if (m_singleChart)
13410 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
13411
13412 } else {
13413 // Handle MBTiles overlays first
13414 // Left click simply toggles the noshow array index entry
13415 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
13416 bool bfound = false;
13417 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
13418 if (m_tile_noshow_index_array[i] ==
13419 selected_dbIndex) { // chart is in the noshow list
13420 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
13421 i); // erase it
13422 bfound = true;
13423 break;
13424 }
13425 }
13426 if (!bfound) {
13427 m_tile_noshow_index_array.push_back(selected_dbIndex);
13428 }
13429
13430 // If not already present, add this tileset to the "yes_show" array.
13431 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
13432 m_tile_yesshow_index_array.push_back(selected_dbIndex);
13433 }
13434
13435 else {
13436 if (IsChartQuiltableRef(selected_dbIndex)) {
13437 // if( ChartData ) ChartData->PurgeCache();
13438
13439 // If the chart is a vector chart, and of very large scale,
13440 // then we had better set the new scale directly to avoid excessive
13441 // underzoom on, eg, Inland ENCs
13442 bool set_scale = false;
13443 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
13444 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
13445 set_scale = true;
13446 }
13447 }
13448
13449 if (!set_scale) {
13450 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
13451 } else {
13452 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
13453
13454 // Adjust scale so that the selected chart is underzoomed/overzoomed
13455 // by a controlled amount
13456 ChartBase *pc =
13457 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
13458 if (pc) {
13459 double proposed_scale_onscreen =
13460 GetCanvasScaleFactor() / GetVPScale();
13461
13462 if (g_bPreserveScaleOnX) {
13463 proposed_scale_onscreen =
13464 wxMin(proposed_scale_onscreen,
13465 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
13466 GetCanvasWidth()));
13467 } else {
13468 proposed_scale_onscreen =
13469 wxMin(proposed_scale_onscreen,
13470 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
13471 GetCanvasWidth()));
13472
13473 proposed_scale_onscreen =
13474 wxMax(proposed_scale_onscreen,
13475 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
13476 g_b_overzoom_x));
13477 }
13478
13479 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
13480 }
13481 }
13482 } else {
13483 ToggleCanvasQuiltMode();
13484 SelectdbChart(selected_dbIndex);
13485 m_bpersistent_quilt = true;
13486 }
13487 }
13488 }
13489
13490 SetQuiltChartHiLiteIndex(-1);
13491 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13492 // (checkmarks etc)
13493 HideChartInfoWindow();
13494 DoCanvasUpdate();
13495 ReloadVP(); // Pick up the new selections
13496}
13497
13498void ChartCanvas::HandlePianoRClick(int x, int y, int selected_index,
13499 int selected_dbIndex) {
13500 if (g_boptionsactive)
13501 return; // Piano might be invalid due to chartset updates.
13502 if (!GetpCurrentStack()) return;
13503
13504 PianoPopupMenu(x, y, selected_index, selected_dbIndex);
13505 UpdateCanvasControlBar();
13506
13507 SetQuiltChartHiLiteIndex(-1);
13508}
13509
13510void ChartCanvas::HandlePianoRollover(int selected_index,
13511 int selected_dbIndex) {
13512 if (g_boptionsactive)
13513 return; // Piano might be invalid due to chartset updates.
13514 if (!GetpCurrentStack()) return;
13515 if (!ChartData) return;
13516
13517 if (ChartData->IsBusy()) return;
13518
13519 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
13520
13521 if (!GetQuiltMode()) {
13522 ShowChartInfoWindow(key_location.x, selected_dbIndex);
13523 } else {
13524 std::vector<int> piano_chart_index_array =
13525 GetQuiltExtendedStackdbIndexArray();
13526
13527 if ((GetpCurrentStack()->nEntry > 1) ||
13528 (piano_chart_index_array.size() >= 1)) {
13529 ShowChartInfoWindow(key_location.x, selected_dbIndex);
13530 SetQuiltChartHiLiteIndex(selected_dbIndex);
13531
13532 ReloadVP(false); // no VP adjustment allowed
13533 } else if (GetpCurrentStack()->nEntry == 1) {
13534 const ChartTableEntry &cte =
13535 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
13536 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
13537 ShowChartInfoWindow(key_location.x, selected_dbIndex);
13538 ReloadVP(false);
13539 } else if ((-1 == selected_index) && (-1 == selected_dbIndex)) {
13540 ShowChartInfoWindow(key_location.x, selected_dbIndex);
13541 }
13542 }
13543 }
13544}
13545
13546void ChartCanvas::UpdateCanvasControlBar(void) {
13547 if (m_pianoFrozen) return;
13548
13549 if (!GetpCurrentStack()) return;
13550 if (!ChartData) return;
13551 if (!g_bShowChartBar) return;
13552
13553 int sel_type = -1;
13554 int sel_family = -1;
13555
13556 std::vector<int> piano_chart_index_array;
13557 std::vector<int> empty_piano_chart_index_array;
13558
13559 wxString old_hash = m_Piano->GetStoredHash();
13560
13561 if (GetQuiltMode()) {
13562 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
13563 m_Piano->SetKeyArray(piano_chart_index_array);
13564
13565 std::vector<int> piano_active_chart_index_array =
13566 GetQuiltCandidatedbIndexArray();
13567 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13568
13569 std::vector<int> piano_eclipsed_chart_index_array =
13570 GetQuiltEclipsedStackdbIndexArray();
13571 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
13572
13573 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
13574 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
13575
13576 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
13577 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
13578 } else {
13579 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
13580 m_Piano->SetKeyArray(piano_chart_index_array);
13581 // TODO refresh_Piano();
13582
13583 if (m_singleChart) {
13584 sel_type = m_singleChart->GetChartType();
13585 sel_family = m_singleChart->GetChartFamily();
13586 }
13587 }
13588
13589 // Set up the TMerc and Skew arrays
13590 std::vector<int> piano_skew_chart_index_array;
13591 std::vector<int> piano_tmerc_chart_index_array;
13592 std::vector<int> piano_poly_chart_index_array;
13593
13594 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
13595 const ChartTableEntry &ctei =
13596 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
13597 double skew_norm = ctei.GetChartSkew();
13598 if (skew_norm > 180.) skew_norm -= 360.;
13599
13600 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
13601 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
13602
13603 // Polyconic skewed charts should show as skewed
13604 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
13605 if (fabs(skew_norm) > 1.)
13606 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
13607 else
13608 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
13609 } else if (fabs(skew_norm) > 1.)
13610 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
13611 }
13612 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
13613 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
13614 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
13615
13616 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
13617 if (new_hash != old_hash) {
13618 m_Piano->FormatKeys();
13619 HideChartInfoWindow();
13620 m_Piano->ResetRollover();
13621 SetQuiltChartHiLiteIndex(-1);
13622 m_brepaint_piano = true;
13623 }
13624
13625 // Create a bitmask int that describes what Family/Type of charts are shown in
13626 // the bar, and notify the platform.
13627 int mask = 0;
13628 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
13629 const ChartTableEntry &ctei =
13630 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
13631 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
13632 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
13633 if (e == CHART_FAMILY_RASTER) mask |= 1;
13634 if (e == CHART_FAMILY_VECTOR) {
13635 if (t == CHART_TYPE_CM93COMP)
13636 mask |= 4;
13637 else
13638 mask |= 2;
13639 }
13640 }
13641
13642 wxString s_indicated;
13643 if (sel_type == CHART_TYPE_CM93COMP)
13644 s_indicated = _T("cm93");
13645 else {
13646 if (sel_family == CHART_FAMILY_RASTER)
13647 s_indicated = _T("raster");
13648 else if (sel_family == CHART_FAMILY_VECTOR)
13649 s_indicated = _T("vector");
13650 }
13651
13652 g_Platform->setChartTypeMaskSel(mask, s_indicated);
13653}
13654
13655void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
13656
13657void ChartCanvas::PianoPopupMenu(int x, int y, int selected_index,
13658 int selected_dbIndex) {
13659 if (!GetpCurrentStack()) return;
13660
13661 // No context menu if quilting is disabled
13662 if (!GetQuiltMode()) return;
13663
13664 menu_selected_dbIndex = selected_dbIndex;
13665 menu_selected_index = selected_index;
13666
13667 m_piano_ctx_menu = new wxMenu();
13668
13669 // Search the no-show array
13670 bool b_is_in_noshow = false;
13671 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
13672 if (m_quilt_noshow_index_array[i] ==
13673 selected_dbIndex) // chart is in the noshow list
13674 {
13675 b_is_in_noshow = true;
13676 break;
13677 }
13678 }
13679
13680 if (b_is_in_noshow) {
13681 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART, _("Show This Chart"));
13682 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
13683 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
13684 } else if (GetpCurrentStack()->nEntry > 1) {
13685 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
13686 _("Hide This Chart"));
13687 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
13688 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
13689 }
13690
13691 wxPoint pos = wxPoint(x, y - 30);
13692
13693 // Invoke the drop-down menu
13694 if (m_piano_ctx_menu->GetMenuItems().GetCount())
13695 PopupMenu(m_piano_ctx_menu, pos);
13696
13697 delete m_piano_ctx_menu;
13698 m_piano_ctx_menu = NULL;
13699
13700 HideChartInfoWindow();
13701 m_Piano->ResetRollover();
13702
13703 SetQuiltChartHiLiteIndex(-1);
13704
13705 ReloadVP();
13706}
13707
13708void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
13709 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
13710 if (m_quilt_noshow_index_array[i] ==
13711 menu_selected_dbIndex) // chart is in the noshow list
13712 {
13713 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
13714 break;
13715 }
13716 }
13717}
13718
13719void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
13720 if (!GetpCurrentStack()) return;
13721 if (!ChartData) return;
13722
13723 RemoveChartFromQuilt(menu_selected_dbIndex);
13724
13725 // It could happen that the chart being disabled is the reference
13726 // chart....
13727 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
13728 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
13729
13730 int i = menu_selected_index + 1; // select next smaller scale chart
13731 bool b_success = false;
13732 while (i < GetpCurrentStack()->nEntry - 1) {
13733 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
13734 if (type == ChartData->GetDBChartType(dbIndex)) {
13735 SelectQuiltRefChart(i);
13736 b_success = true;
13737 break;
13738 }
13739 i++;
13740 }
13741
13742 // If that did not work, try to select the next larger scale compatible
13743 // chart
13744 if (!b_success) {
13745 i = menu_selected_index - 1;
13746 while (i > 0) {
13747 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
13748 if (type == ChartData->GetDBChartType(dbIndex)) {
13749 SelectQuiltRefChart(i);
13750 b_success = true;
13751 break;
13752 }
13753 i--;
13754 }
13755 }
13756 }
13757}
13758
13759void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
13760 // Remove the item from the list (if it appears) to avoid multiple addition
13761 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
13762 if (m_quilt_noshow_index_array[i] ==
13763 dbIndex) // chart is already in the noshow list
13764 {
13765 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
13766 break;
13767 }
13768 }
13769
13770 m_quilt_noshow_index_array.push_back(dbIndex);
13771}
13772
13773bool ChartCanvas::UpdateS52State() {
13774 bool retval = false;
13775 // printf(" update %d\n", IsPrimaryCanvas());
13776
13777 if (ps52plib) {
13778 ps52plib->SetShowS57Text(m_encShowText);
13779 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
13780 ps52plib->m_bShowSoundg = m_encShowDepth;
13781 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
13782 ps52plib->m_bShowLdisText = m_encShowLightDesc;
13783
13784 // Lights
13785 if (!m_encShowLights) // On, going off
13786 ps52plib->AddObjNoshow("LIGHTS");
13787 else // Off, going on
13788 ps52plib->RemoveObjNoshow("LIGHTS");
13789 ps52plib->SetLightsOff(!m_encShowLights);
13790 ps52plib->m_bExtendLightSectors = true;
13791
13792 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
13793 ps52plib->SetAnchorOn(m_encShowAnchor);
13794 ps52plib->SetQualityOfData(m_encShowDataQual);
13795 }
13796
13797 return retval;
13798}
13799
13800void ChartCanvas::SetShowENCDataQual(bool show) {
13801 m_encShowDataQual = show;
13802 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13803 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13804
13805 m_s52StateHash = 0; // Force a S52 PLIB re-configure
13806}
13807
13808void ChartCanvas::SetShowENCText(bool show) {
13809 m_encShowText = show;
13810 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13811 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13812
13813 m_s52StateHash = 0; // Force a S52 PLIB re-configure
13814}
13815
13816void ChartCanvas::SetENCDisplayCategory(int category) {
13817 m_encDisplayCategory = category;
13818 m_s52StateHash = 0; // Force a S52 PLIB re-configure
13819}
13820
13821void ChartCanvas::SetShowENCDepth(bool show) {
13822 m_encShowDepth = show;
13823 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13824 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13825
13826 m_s52StateHash = 0; // Force a S52 PLIB re-configure
13827}
13828
13829void ChartCanvas::SetShowENCLightDesc(bool show) {
13830 m_encShowLightDesc = show;
13831 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13832 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13833
13834 m_s52StateHash = 0; // Force a S52 PLIB re-configure
13835}
13836
13837void ChartCanvas::SetShowENCBuoyLabels(bool show) {
13838 m_encShowBuoyLabels = show;
13839 m_s52StateHash = 0; // Force a S52 PLIB re-configure
13840}
13841
13842void ChartCanvas::SetShowENCLights(bool show) {
13843 m_encShowLights = show;
13844 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13845 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13846
13847 m_s52StateHash = 0; // Force a S52 PLIB re-configure
13848}
13849
13850void ChartCanvas::SetShowENCAnchor(bool show) {
13851 m_encShowAnchor = show;
13852 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13853 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13854
13855 m_s52StateHash = 0; // Force a S52 PLIB re-configure
13856}
13857
13858wxRect ChartCanvas::GetMUIBarRect() {
13859 wxRect rv;
13860 if (m_muiBar) {
13861 rv = m_muiBar->GetRect();
13862 }
13863
13864 return rv;
13865}
13866
13867void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
13868 if (!GetAlertString().IsEmpty()) {
13869 wxFont *pfont = wxTheFontList->FindOrCreateFont(
13870 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
13871
13872 dc.SetFont(*pfont);
13873 dc.SetPen(*wxTRANSPARENT_PEN);
13874
13875 dc.SetBrush(wxColour(243, 229, 47));
13876 int w, h;
13877 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
13878 h += 2;
13879 // int yp = vp.pix_height - 20 - h;
13880
13881 wxRect sbr = GetScaleBarRect();
13882 int xp = sbr.x + sbr.width + 10;
13883 int yp = (sbr.y + sbr.height) - h;
13884
13885 int wdraw = w + 10;
13886 dc.DrawRectangle(xp, yp, wdraw, h);
13887 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
13888 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
13889 }
13890}
13891
13892//--------------------------------------------------------------------------------------------------------
13893// Screen Brightness Control Support Routines
13894//
13895//--------------------------------------------------------------------------------------------------------
13896
13897#ifdef __UNIX__
13898#define BRIGHT_XCALIB
13899#define __OPCPN_USEICC__
13900#endif
13901
13902#ifdef __OPCPN_USEICC__
13903int CreateSimpleICCProfileFile(const char *file_name, double co_red,
13904 double co_green, double co_blue);
13905
13906wxString temp_file_name;
13907#endif
13908
13909#if 0
13910class ocpnCurtain: public wxDialog
13911{
13912 DECLARE_CLASS( ocpnCurtain )
13913 DECLARE_EVENT_TABLE()
13914
13915public:
13916 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
13917 ~ocpnCurtain( );
13918 bool ProcessEvent(wxEvent& event);
13919
13920};
13921
13922IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
13923
13924BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
13925END_EVENT_TABLE()
13926
13927ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
13928{
13929 wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
13930}
13931
13932ocpnCurtain::~ocpnCurtain()
13933{
13934}
13935
13936bool ocpnCurtain::ProcessEvent(wxEvent& event)
13937{
13938 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
13939 return GetParent()->GetEventHandler()->ProcessEvent(event);
13940}
13941#endif
13942
13943#ifdef _WIN32
13944#include <windows.h>
13945
13946HMODULE hGDI32DLL;
13947typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
13948typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
13949SetDeviceGammaRamp_ptr_type
13950 g_pSetDeviceGammaRamp; // the API entry points in the dll
13951GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
13952
13953WORD *g_pSavedGammaMap;
13954
13955#endif
13956
13957int InitScreenBrightness(void) {
13958#ifdef _WIN32
13959 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
13960 HDC hDC;
13961 BOOL bbr;
13962
13963 if (NULL == hGDI32DLL) {
13964 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
13965
13966 if (NULL != hGDI32DLL) {
13967 // Get the entry points of the required functions
13968 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
13969 hGDI32DLL, "SetDeviceGammaRamp");
13970 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
13971 hGDI32DLL, "GetDeviceGammaRamp");
13972
13973 // If the functions are not found, unload the DLL and return false
13974 if ((NULL == g_pSetDeviceGammaRamp) ||
13975 (NULL == g_pGetDeviceGammaRamp)) {
13976 FreeLibrary(hGDI32DLL);
13977 hGDI32DLL = NULL;
13978 return 0;
13979 }
13980 }
13981 }
13982
13983 // Interface is ready, so....
13984 // Get some storage
13985 if (!g_pSavedGammaMap) {
13986 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
13987
13988 hDC = GetDC(NULL); // Get the full screen DC
13989 bbr = g_pGetDeviceGammaRamp(
13990 hDC, g_pSavedGammaMap); // Get the existing ramp table
13991 ReleaseDC(NULL, hDC); // Release the DC
13992 }
13993
13994 // On Windows hosts, try to adjust the registry to allow full range
13995 // setting of Gamma table This is an undocumented Windows hack.....
13996 wxRegKey *pRegKey = new wxRegKey(
13997 _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
13998 _T("NT\\CurrentVersion\\ICM"));
13999 if (!pRegKey->Exists()) pRegKey->Create();
14000 pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14001
14002 g_brightness_init = true;
14003 return 1;
14004 }
14005
14006 else {
14007 if (NULL == g_pcurtain) {
14008 if (gFrame->CanSetTransparent()) {
14009 // Build the curtain window
14010 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14011 wxPoint(0, 0), ::wxGetDisplaySize(),
14012 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14013 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14014
14015 // g_pcurtain = new ocpnCurtain(gFrame,
14016 // wxPoint(0,0),::wxGetDisplaySize(),
14017 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14018 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14019
14020 g_pcurtain->Hide();
14021
14022 HWND hWnd = GetHwndOf(g_pcurtain);
14023 SetWindowLong(hWnd, GWL_EXSTYLE,
14024 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14025 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14026 g_pcurtain->SetTransparent(0);
14027
14028 g_pcurtain->Maximize();
14029 g_pcurtain->Show();
14030
14031 // All of this is obtuse, but necessary for Windows...
14032 g_pcurtain->Enable();
14033 g_pcurtain->Disable();
14034
14035 gFrame->Disable();
14036 gFrame->Enable();
14037 // SetFocus();
14038 }
14039 }
14040 g_brightness_init = true;
14041
14042 return 1;
14043 }
14044#else
14045 // Look for "xcalib" application
14046 wxString cmd(_T ( "xcalib -version" ));
14047
14048 wxArrayString output;
14049 long r = wxExecute(cmd, output);
14050 if (0 != r)
14051 wxLogMessage(
14052 _T(" External application \"xcalib\" not found. Screen brightness ")
14053 _T("not changed."));
14054
14055 g_brightness_init = true;
14056 return 0;
14057#endif
14058}
14059
14060int RestoreScreenBrightness(void) {
14061#ifdef _WIN32
14062
14063 if (g_pSavedGammaMap) {
14064 HDC hDC = GetDC(NULL); // Get the full screen DC
14065 g_pSetDeviceGammaRamp(hDC,
14066 g_pSavedGammaMap); // Restore the saved ramp table
14067 ReleaseDC(NULL, hDC); // Release the DC
14068
14069 free(g_pSavedGammaMap);
14070 g_pSavedGammaMap = NULL;
14071 }
14072
14073 if (g_pcurtain) {
14074 g_pcurtain->Close();
14075 g_pcurtain->Destroy();
14076 g_pcurtain = NULL;
14077 }
14078
14079 g_brightness_init = false;
14080 return 1;
14081
14082#endif
14083
14084#ifdef BRIGHT_XCALIB
14085 if (g_brightness_init) {
14086 wxString cmd;
14087 cmd = _T("xcalib -clear");
14088 wxExecute(cmd, wxEXEC_ASYNC);
14089 g_brightness_init = false;
14090 }
14091
14092 return 1;
14093#endif
14094
14095 return 0;
14096}
14097
14098// Set brightness. [0..100]
14099int SetScreenBrightness(int brightness) {
14100#ifdef _WIN32
14101
14102 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14103 // some (most modern?) versions of gdi32.dll Load the required library dll,
14104 // if not already in place
14105 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14106 if (g_pcurtain) {
14107 g_pcurtain->Close();
14108 g_pcurtain->Destroy();
14109 g_pcurtain = NULL;
14110 }
14111
14112 InitScreenBrightness();
14113
14114 if (NULL == hGDI32DLL) {
14115 // Unicode stuff.....
14116 wchar_t wdll_name[80];
14117 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14118 LPCWSTR cstr = wdll_name;
14119
14120 hGDI32DLL = LoadLibrary(cstr);
14121
14122 if (NULL != hGDI32DLL) {
14123 // Get the entry points of the required functions
14124 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14125 hGDI32DLL, "SetDeviceGammaRamp");
14126 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14127 hGDI32DLL, "GetDeviceGammaRamp");
14128
14129 // If the functions are not found, unload the DLL and return false
14130 if ((NULL == g_pSetDeviceGammaRamp) ||
14131 (NULL == g_pGetDeviceGammaRamp)) {
14132 FreeLibrary(hGDI32DLL);
14133 hGDI32DLL = NULL;
14134 return 0;
14135 }
14136 }
14137 }
14138
14139 HDC hDC = GetDC(NULL); // Get the full screen DC
14140
14141 /*
14142 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14143 if (cmcap != CM_GAMMA_RAMP)
14144 {
14145 wxLogMessage(_T(" Video hardware does not support brightness control by
14146 gamma ramp adjustment.")); return false;
14147 }
14148 */
14149
14150 int increment = brightness * 256 / 100;
14151
14152 // Build the Gamma Ramp table
14153 WORD GammaTable[3][256];
14154
14155 int table_val = 0;
14156 for (int i = 0; i < 256; i++) {
14157 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14158 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14159 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14160
14161 table_val += increment;
14162
14163 if (table_val > 65535) table_val = 65535;
14164 }
14165
14166 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
14167 ReleaseDC(NULL, hDC); // Release the DC
14168
14169 return 1;
14170 } else {
14171 if (g_pSavedGammaMap) {
14172 HDC hDC = GetDC(NULL); // Get the full screen DC
14173 g_pSetDeviceGammaRamp(hDC,
14174 g_pSavedGammaMap); // Restore the saved ramp table
14175 ReleaseDC(NULL, hDC); // Release the DC
14176 }
14177
14178 if (brightness < 100) {
14179 if (NULL == g_pcurtain) InitScreenBrightness();
14180
14181 if (g_pcurtain) {
14182 int sbrite = wxMax(1, brightness);
14183 sbrite = wxMin(100, sbrite);
14184
14185 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
14186 }
14187 } else {
14188 if (g_pcurtain) {
14189 g_pcurtain->Close();
14190 g_pcurtain->Destroy();
14191 g_pcurtain = NULL;
14192 }
14193 }
14194
14195 return 1;
14196 }
14197
14198#endif
14199
14200#ifdef BRIGHT_XCALIB
14201
14202 if (!g_brightness_init) {
14203 last_brightness = 100;
14204 g_brightness_init = true;
14205 temp_file_name = wxFileName::CreateTempFileName(_T(""));
14206 InitScreenBrightness();
14207 }
14208
14209#ifdef __OPCPN_USEICC__
14210 // Create a dead simple temporary ICC profile file, with gamma ramps set as
14211 // desired, and then activate this temporary profile using xcalib <filename>
14212 if (!CreateSimpleICCProfileFile(
14213 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
14214 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
14215 wxString cmd(_T ( "xcalib " ));
14216 cmd += temp_file_name;
14217
14218 wxExecute(cmd, wxEXEC_ASYNC);
14219 }
14220
14221#else
14222 // Or, use "xcalib -co" to set overall contrast value
14223 // This is not as nice, since the -co parameter wants to be a fraction of
14224 // the current contrast, and values greater than 100 are not allowed. As a
14225 // result, increases of contrast must do a "-clear" step first, which
14226 // produces objectionable flashing.
14227 if (brightness > last_brightness) {
14228 wxString cmd;
14229 cmd = _T("xcalib -clear");
14230 wxExecute(cmd, wxEXEC_ASYNC);
14231
14232 ::wxMilliSleep(10);
14233
14234 int brite_adj = wxMax(1, brightness);
14235 cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
14236 wxExecute(cmd, wxEXEC_ASYNC);
14237 } else {
14238 int brite_adj = wxMax(1, brightness);
14239 int factor = (brite_adj * 100) / last_brightness;
14240 factor = wxMax(1, factor);
14241 wxString cmd;
14242 cmd.Printf(_T("xcalib -co %2d -a"), factor);
14243 wxExecute(cmd, wxEXEC_ASYNC);
14244 }
14245
14246#endif
14247
14248 last_brightness = brightness;
14249
14250#endif
14251
14252 return 0;
14253}
14254
14255#ifdef __OPCPN_USEICC__
14256
14257#define MLUT_TAG 0x6d4c5554L
14258#define VCGT_TAG 0x76636774L
14259
14260int GetIntEndian(unsigned char *s) {
14261 int ret;
14262 unsigned char *p;
14263 int i;
14264
14265 p = (unsigned char *)&ret;
14266
14267 if (1)
14268 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
14269 else
14270 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
14271
14272 return ret;
14273}
14274
14275unsigned short GetShortEndian(unsigned char *s) {
14276 unsigned short ret;
14277 unsigned char *p;
14278 int i;
14279
14280 p = (unsigned char *)&ret;
14281
14282 if (1)
14283 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
14284 else
14285 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
14286
14287 return ret;
14288}
14289
14290// Create a very simple Gamma correction file readable by xcalib
14291int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14292 double co_green, double co_blue) {
14293 FILE *fp;
14294
14295 if (file_name) {
14296 fp = fopen(file_name, "wb");
14297 if (!fp) return -1; /* file can not be created */
14298 } else
14299 return -1; /* filename char pointer not valid */
14300
14301 // Write header
14302 char header[128];
14303 for (int i = 0; i < 128; i++) header[i] = 0;
14304
14305 fwrite(header, 128, 1, fp);
14306
14307 // Num tags
14308 int numTags0 = 1;
14309 int numTags = GetIntEndian((unsigned char *)&numTags0);
14310 fwrite(&numTags, 1, 4, fp);
14311
14312 int tagName0 = VCGT_TAG;
14313 int tagName = GetIntEndian((unsigned char *)&tagName0);
14314 fwrite(&tagName, 1, 4, fp);
14315
14316 int tagOffset0 = 128 + 4 * sizeof(int);
14317 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
14318 fwrite(&tagOffset, 1, 4, fp);
14319
14320 int tagSize0 = 1;
14321 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
14322 fwrite(&tagSize, 1, 4, fp);
14323
14324 fwrite(&tagName, 1, 4, fp); // another copy of tag
14325
14326 fwrite(&tagName, 1, 4, fp); // dummy
14327
14328 // Table type
14329
14330 /* VideoCardGammaTable (The simplest type) */
14331 int gammatype0 = 0;
14332 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
14333 fwrite(&gammatype, 1, 4, fp);
14334
14335 int numChannels0 = 3;
14336 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
14337 fwrite(&numChannels, 1, 2, fp);
14338
14339 int numEntries0 = 256;
14340 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
14341 fwrite(&numEntries, 1, 2, fp);
14342
14343 int entrySize0 = 1;
14344 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
14345 fwrite(&entrySize, 1, 2, fp);
14346
14347 unsigned char ramp[256];
14348
14349 // Red ramp
14350 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
14351 fwrite(ramp, 256, 1, fp);
14352
14353 // Green ramp
14354 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
14355 fwrite(ramp, 256, 1, fp);
14356
14357 // Blue ramp
14358 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
14359 fwrite(ramp, 256, 1, fp);
14360
14361 fclose(fp);
14362
14363 return 0;
14364}
14365#endif // __OPCPN_USEICC__
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.
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition: chcanv.cpp:6945
Definition: IDX_entry.h:41
Definition: kml.h:54
Definition: MUIBar.h:56
Class MarkInfoDef.
Definition: MarkInfo.h:197
Definition: piano.h:49
Definition: Quilt.h:84
bool Compose(const ViewPort &vp)
Definition: Quilt.cpp:1643
Definition: route.h:70
bool ActivateNextPoint(Route *pr, bool skipped)
Definition: routeman.cpp:387
bool DeleteRoute(Route *pRoute, NavObjectChanges *nav_obj_changes)
Definition: routeman.cpp:726
Definition: select.h:51
Definition: tcmgr.h:86
Definition: TCWin.h:46
Class TrackPropDlg.
Definition: TrackPropDlg.h:87
bool UpdateProperties()
Definition: track.h:79
Definition: undo.h:60
bool RenderNextSmallerCellOutlines(ocpnDC &dc, ViewPort &vp, ChartCanvas *cc)
Definition: cm93.cpp:5698
Definition: ocpndc.h:55
Definition: Quilt.cpp:864