4 XCSoar Glide Computer - http://www.xcsoar.org/
5 Copyright (C) 2000-2013 The XCSoar Project
6 A detailed list of copyright holders can be found in the file "AUTHORS".
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include "MainWindow.hpp"
25 #include "Startup.hpp"
26 #include "MapWindow/GlueMapWindow.hpp"
28 #include "InfoBoxes/InfoBoxManager.hpp"
29 #include "InfoBoxes/InfoBoxLayout.hpp"
30 #include "Interface.hpp"
31 #include "ActionInterface.hpp"
32 #include "UIActions.hpp"
33 #include "PageActions.hpp"
34 #include "Input/InputEvents.hpp"
35 #include "Menu/ButtonLabel.hpp"
36 #include "Screen/Layout.hpp"
37 #include "Screen/Blank.hpp"
38 #include "Dialogs/Airspace/AirspaceWarningDialog.hpp"
39 #include "Audio/Sound.hpp"
40 #include "Components.hpp"
41 #include "ProcessTimer.hpp"
42 #include "LogFile.hpp"
43 #include "Gauge/GaugeFLARM.hpp"
44 #include "Gauge/GaugeThermalAssistant.hpp"
45 #include "Gauge/GlueGaugeVario.hpp"
46 #include "Menu/MenuBar.hpp"
47 #include "Form/Form.hpp"
48 #include "Widget/Widget.hpp"
49 #include "UtilsSystem.hpp"
50 #include "Look/Fonts.hpp"
51 #include "Look/Look.hpp"
52 #include "Profile/ProfileKeys.hpp"
53 #include "Profile/Profile.hpp"
54 #include "ProgressGlue.hpp"
55 #include "UIState.hpp"
56 #include "DrawThread.hpp"
57 #include "UIReceiveBlackboard.hpp"
58 #include "Event/Idle.hpp"
61 #include "Screen/OpenGL/Cache.hpp"
64 #if !defined(WIN32) && !defined(ANDROID)
68 #if !defined(WIN32) && !defined(ANDROID)
69 #include <unistd.h> /* for execl() */
74 GetBottomWidgetRect(const PixelRect
&rc
, const Widget
*bottom_widget
)
76 if (bottom_widget
== nullptr) {
77 /* no bottom widget: return empty rectangle, map uses the whole
79 PixelRect result
= rc
;
80 result
.top
= result
.bottom
;
84 const UPixelScalar requested_height
= bottom_widget
->GetMinimumSize().cy
;
86 if (requested_height
> 0) {
87 const UPixelScalar max_height
= (rc
.bottom
- rc
.top
) / 2;
88 height
= std::min(max_height
, requested_height
);
90 const UPixelScalar recommended_height
= (rc
.bottom
- rc
.top
) / 3;
91 height
= recommended_height
;
94 PixelRect result
= rc
;
95 result
.top
= result
.bottom
- height
;
101 GetMapRectAbove(const PixelRect
&rc
, const PixelRect
&bottom_rect
)
103 PixelRect result
= rc
;
104 result
.bottom
= bottom_rect
.top
;
108 MainWindow::MainWindow(const StatusMessageList
&status_messages
)
110 map(nullptr), bottom_widget(nullptr), widget(nullptr), vario(*this),
111 traffic_gauge(*this),
112 suppress_traffic_gauge(false), force_traffic_gauge(false),
113 thermal_assistant(*this),
115 popup(status_messages
, *this, CommonInterface::GetUISettings()),
118 #ifndef ENABLE_OPENGL
119 draw_suspended(false),
121 restore_page_pending(false),
122 airspace_warning_pending(false)
127 * Destructor of the MainWindow-Class
130 MainWindow::~MainWindow()
136 MainWindow::Create(PixelSize size
, TopWindowStyle style
)
138 style
.EnableDoubleClicks();
139 SingleWindow::Create(title
, size
, style
);
146 const TCHAR
*msg
= _T("Font initialisation failed");
149 LogFormat(_T("%s"), msg
);
151 /* now try to get a GUI error message out to the user */
153 MessageBox(NULL
, msg
, _T("XCSoar"), MB_ICONEXCLAMATION
|MB_OK
);
154 #elif !defined(ANDROID)
155 execl("/usr/bin/xmessage", "xmessage", msg
, NULL
);
156 execl("/usr/X11/bin/xmessage", "xmessage", msg
, NULL
);
162 MainWindow::Initialise()
164 Layout::Initialize(GetSize());
166 LogFormat("Initialise fonts");
167 if (!Fonts::Initialize()) {
175 look
->Initialise(Fonts::map
, Fonts::map_bold
, Fonts::map_label
);
179 MainWindow::InitialiseConfigured()
181 const UISettings
&ui_settings
= CommonInterface::GetUISettings();
183 PixelRect rc
= GetClientRect();
185 const InfoBoxLayout::Layout ib_layout
=
186 InfoBoxLayout::Calculate(rc
, ui_settings
.info_boxes
.geometry
);
188 Fonts::SizeInfoboxFont(ib_layout
.control_width
);
190 if (ui_settings
.custom_fonts
) {
191 LogFormat("Load custom fonts");
192 if (!Fonts::LoadCustom()) {
193 LogFormat("Failed to load custom fonts");
194 if (!Fonts::Initialize()) {
201 /* fonts may have changed, discard all pre-rendered font
207 assert(look
!= NULL
);
208 look
->InitialiseConfigured(CommonInterface::GetUISettings(),
209 Fonts::map
, Fonts::map_bold
, Fonts::map_label
,
210 Fonts::cdi
, Fonts::monospace
,
211 Fonts::infobox
, Fonts::infobox_small
,
213 Fonts::infobox_units
,
217 InfoBoxManager::Create(*this, ib_layout
, look
->info_box
, look
->units
);
218 map_rect
= ib_layout
.remaining
;
220 ButtonLabel::CreateButtonLabels(*this);
221 ButtonLabel::SetFont(Fonts::map_bold
);
223 ReinitialiseLayout_vario(ib_layout
);
225 ReinitialiseLayoutTA(rc
, ib_layout
);
227 WindowStyle hidden_border
;
228 hidden_border
.Hide();
229 hidden_border
.Border();
231 ReinitialiseLayout_flarm(rc
, ib_layout
);
233 map
= new GlueMapWindow(*look
);
234 map
->SetComputerSettings(CommonInterface::GetComputerSettings());
235 map
->SetMapSettings(CommonInterface::GetMapSettings());
236 map
->SetUIState(CommonInterface::GetUIState());
237 map
->Create(*this, map_rect
);
243 MainWindow::Deinitialise()
245 InfoBoxManager::Destroy();
246 ButtonLabel::Destroy();
250 // During destruction of GlueMapWindow WM_SETFOCUS gets called for
251 // MainWindow which tries to set the focus to GlueMapWindow. Prevent
252 // this issue by setting map to NULL before calling delete.
253 GlueMapWindow
*temp_map
= map
;
258 traffic_gauge
.Clear();
259 thermal_assistant
.Clear();
266 MainWindow::ReinitialiseLayout_vario(const InfoBoxLayout::Layout
&layout
)
268 if (!layout
.HasVario()) {
273 if (!vario
.IsDefined())
274 vario
.Set(new GlueGaugeVario(CommonInterface::GetLiveBlackboard(),
275 look
->vario
, look
->units
));
277 vario
.Move(layout
.vario
);
280 // XXX vario->BringToTop();
284 MainWindow::ReinitialiseLayoutTA(PixelRect rc
,
285 const InfoBoxLayout::Layout
&layout
)
287 UPixelScalar sz
= std::min(layout
.control_height
,
288 layout
.control_width
) * 2;
289 rc
.right
= rc
.left
+ sz
;
290 rc
.top
= rc
.bottom
- sz
;
291 thermal_assistant
.Move(rc
);
295 MainWindow::ReinitialiseLayout()
300 dialogs
.top()->ReinitialiseLayout(); // adapt simulator prompt
302 /* without the MapWindow, it is safe to assume that the MainWindow
303 is just being initialized, and the InfoBoxes aren't initialized
304 yet either, so there is nothing to do here */
308 #ifndef ENABLE_OPENGL
309 if (draw_thread
== NULL
)
310 /* no layout changes during startup */
314 InfoBoxManager::Destroy();
316 const UISettings
&ui_settings
= CommonInterface::GetUISettings();
318 const PixelRect rc
= GetClientRect();
319 const InfoBoxLayout::Layout ib_layout
=
320 InfoBoxLayout::Calculate(rc
, ui_settings
.info_boxes
.geometry
);
322 Fonts::SizeInfoboxFont(ib_layout
.control_width
);
324 InfoBoxManager::Create(*this, ib_layout
, look
->info_box
, look
->units
);
325 InfoBoxManager::ProcessTimer();
326 map_rect
= ib_layout
.remaining
;
331 ReinitialiseLayout_vario(ib_layout
);
333 ReinitialiseLayout_flarm(rc
, ib_layout
);
335 ReinitialiseLayoutTA(rc
, ib_layout
);
339 InfoBoxManager::Hide();
341 InfoBoxManager::Show();
343 const PixelRect main_rect
= GetMainRect();
344 const PixelRect bottom_rect
= GetBottomWidgetRect(main_rect
,
347 if (bottom_widget
!= nullptr)
348 bottom_widget
->Move(bottom_rect
);
350 map
->Move(GetMapRectAbove(main_rect
, bottom_rect
));
355 widget
->Move(GetMainRect(rc
));
358 // move topmost dialog to fit into the current layout, or close it
360 dialogs
.top()->ReinitialiseLayout();
364 map
->BringToBottom();
368 MainWindow::ReinitialiseLayout_flarm(PixelRect rc
, const InfoBoxLayout::Layout ib_layout
)
370 TrafficSettings::GaugeLocation val
=
371 CommonInterface::GetUISettings().traffic
.gauge_location
;
373 // Automatic mode - follow info boxes
374 if (val
== TrafficSettings::GaugeLocation::Auto
) {
375 switch (InfoBoxManager::layout
.geometry
) {
376 case InfoBoxSettings::Geometry::TOP_8
:
377 val
= TrafficSettings::GaugeLocation::TopRight
;
379 case InfoBoxSettings::Geometry::LEFT_8
:
380 val
= TrafficSettings::GaugeLocation::BottomLeft
;
382 case InfoBoxSettings::Geometry::TOP_12
:
383 val
= TrafficSettings::GaugeLocation::TopLeft
;
386 val
= TrafficSettings::GaugeLocation::BottomRight
; // Assume bottom right unles...
392 case TrafficSettings::GaugeLocation::TopLeft
:
393 rc
.right
= rc
.left
+ ib_layout
.control_width
* 2;
395 rc
.bottom
= rc
.top
+ ib_layout
.control_height
* 2;
399 case TrafficSettings::GaugeLocation::TopRight
:
400 rc
.left
= rc
.right
- ib_layout
.control_width
* 2 + 1;
401 rc
.bottom
= rc
.top
+ ib_layout
.control_height
* 2;
405 case TrafficSettings::GaugeLocation::BottomLeft
:
406 rc
.right
= rc
.left
+ ib_layout
.control_width
* 2;
408 rc
.top
= rc
.bottom
- ib_layout
.control_height
* 2 + 1;
411 case TrafficSettings::GaugeLocation::CentreTop
:
412 rc
.left
= (rc
.left
+ rc
.right
) / 2 - ib_layout
.control_width
;
413 rc
.right
= rc
.left
+ ib_layout
.control_width
* 2 - 1;
414 rc
.bottom
= rc
.top
+ ib_layout
.control_height
* 2;
418 case TrafficSettings::GaugeLocation::CentreBottom
:
419 rc
.left
= (rc
.left
+ rc
.right
) / 2 - ib_layout
.control_width
;
420 rc
.right
= rc
.left
+ ib_layout
.control_width
* 2 - 1;
421 rc
.top
= rc
.bottom
- ib_layout
.control_height
* 2 + 1;
424 default: // aka flBottomRight
425 rc
.left
= rc
.right
- ib_layout
.control_width
* 2 + 1;
426 rc
.top
= rc
.bottom
- ib_layout
.control_height
* 2 + 1;
430 traffic_gauge
.Move(rc
);
434 MainWindow::Destroy()
438 TopWindow::Destroy();
442 MainWindow::FinishStartup()
444 timer
.Schedule(500); // 2 times per second
450 MainWindow::BeginShutdown()
456 MainWindow::SuspendThreads()
459 map
->SuspendThreads();
463 MainWindow::ResumeThreads()
466 map
->ResumeThreads();
470 MainWindow::SetDefaultFocus()
472 if (map
!= NULL
&& widget
== NULL
)
474 else if (widget
== NULL
|| !widget
->SetFocus())
479 MainWindow::FullRedraw()
485 // Windows event handlers
488 MainWindow::OnResize(PixelSize new_size
)
490 SingleWindow::OnResize(new_size
);
492 Layout::Initialize(new_size
);
494 ReinitialiseLayout();
497 /* the map being created already is an indicator that XCSoar is
498 running already, and so we assume the menu buttons have been
500 map
->BringToBottom();
503 const PixelRect rc
= GetClientRect();
504 ButtonLabel::OnResize(rc
);
505 ProgressGlue::Move(rc
);
509 MainWindow::OnActivate()
511 SingleWindow::OnActivate();
519 MainWindow::OnSetFocus()
521 SingleWindow::OnSetFocus();
524 /* the main window should never have the keyboard focus; if we
525 happen to get the focus despite of that, forward it to the map
526 window to make keyboard shortcuts work */
527 if (map
!= NULL
&& widget
== NULL
)
529 else if (widget
!= NULL
)
532 /* recover the dialog focus if it got lost */
533 GetTopDialog().FocusFirstControl();
537 MainWindow::StopDragging()
547 MainWindow::OnCancelMode()
549 SingleWindow::OnCancelMode();
554 MainWindow::OnMouseDown(PixelScalar x
, PixelScalar y
)
556 if (SingleWindow::OnMouseDown(x
, y
))
559 if (!dragging
&& !HasDialog()) {
562 gestures
.Start(x
, y
, Layout::Scale(20));
569 MainWindow::OnMouseUp(PixelScalar x
, PixelScalar y
)
571 if (SingleWindow::OnMouseUp(x
, y
))
577 const TCHAR
*gesture
= gestures
.Finish();
578 if (gesture
&& InputEvents::processGesture(gesture
))
586 MainWindow::OnMouseDouble(PixelScalar x
, PixelScalar y
)
588 if (SingleWindow::OnMouseDouble(x
, y
))
594 InputEvents::ShowMenu();
599 MainWindow::OnMouseMove(PixelScalar x
, PixelScalar y
, unsigned keys
)
601 if (SingleWindow::OnMouseMove(x
, y
, keys
))
605 gestures
.Update(x
, y
);
611 MainWindow::OnKeyDown(unsigned key_code
)
613 return InputEvents::processKey(key_code
) ||
614 SingleWindow::OnKeyDown(key_code
);
618 MainWindow::OnTimer(WindowTimer
&_timer
)
621 return SingleWindow::OnTimer(_timer
);
625 UpdateGaugeVisibility();
627 if (!CommonInterface::GetUISettings().enable_thermal_assistant_gauge
) {
628 thermal_assistant
.Clear();
629 } else if (!CommonInterface::Calculated().circling
||
630 InputEvents::IsFlavour(_T("TA"))) {
631 thermal_assistant
.Hide();
632 } else if (!HasDialog()) {
633 if (!thermal_assistant
.IsDefined())
634 thermal_assistant
.Set(new GaugeThermalAssistant(CommonInterface::GetLiveBlackboard(),
635 look
->thermal_assistant_gauge
));
637 if (!thermal_assistant
.IsVisible()) {
638 thermal_assistant
.Show();
640 GaugeThermalAssistant
*widget
=
641 (GaugeThermalAssistant
*)thermal_assistant
.Get();
646 battery_timer
.Process();
652 MainWindow::OnUser(unsigned id
)
654 ProtectedAirspaceWarningManager
*airspace_warnings
;
656 switch ((Command
)id
) {
657 case Command::AIRSPACE_WARNING
:
658 airspace_warnings
= GetAirspaceWarnings();
659 if (!airspace_warning_pending
|| airspace_warnings
== NULL
)
662 airspace_warning_pending
= false;
663 if (dlgAirspaceWarningVisible())
664 /* already visible */
667 /* un-blank the display, play a sound and show the dialog */
669 PlayResource(_T("IDR_WAV_BEEPBWEEP"));
670 dlgAirspaceWarningsShowModal(*this, *airspace_warnings
, true);
673 case Command::GPS_UPDATE
:
674 UIReceiveSensorData();
677 case Command::CALCULATED_UPDATE
:
678 UIReceiveCalculatedData();
681 case Command::RESTORE_PAGE
:
682 if (restore_page_pending
)
683 PageActions::Restore();
691 MainWindow::OnDestroy()
698 SingleWindow::OnDestroy();
701 bool MainWindow::OnClose() {
702 if (HasDialog() || !IsRunning())
703 /* no shutdown dialog if XCSoar hasn't completed initialization
704 yet (e.g. if we are in the simulator prompt) */
705 return SingleWindow::OnClose();
707 if (UIActions::CheckShutdown()) {
714 MainWindow::SetFullScreen(bool _full_screen
)
716 if (_full_screen
== FullScreen
)
719 FullScreen
= _full_screen
;
722 InfoBoxManager::Hide();
724 InfoBoxManager::Show();
727 widget
->Move(GetMainRect());
730 map
->FastMove(GetMainRect());
732 // the repaint will be triggered by the DrawThread
734 UpdateVarioGaugeVisibility();
738 MainWindow::SetTerrain(RasterTerrain
*terrain
)
741 map
->SetTerrain(terrain
);
745 MainWindow::SetTopography(TopographyStore
*topography
)
748 map
->SetTopography(topography
);
752 MainWindow::SetComputerSettings(const ComputerSettings
&settings_computer
)
755 map
->SetComputerSettings(settings_computer
);
759 MainWindow::SetMapSettings(const MapSettings
&settings_map
)
762 map
->SetMapSettings(settings_map
);
766 MainWindow::SetUIState(const UIState
&ui_state
)
769 map
->SetUIState(ui_state
);
775 MainWindow::GetMapIfActive()
777 return IsMapActive() ? map
: NULL
;
781 MainWindow::ActivateMap()
783 restore_page_pending
= false;
788 if (widget
!= NULL
) {
793 if (bottom_widget
!= nullptr)
794 bottom_widget
->Show(GetBottomWidgetRect(GetMainRect(),
797 #ifndef ENABLE_OPENGL
798 if (draw_suspended
) {
799 draw_suspended
= false;
800 draw_thread
->Resume();
809 MainWindow::DeferredRestorePage()
811 if (restore_page_pending
)
814 restore_page_pending
= true;
815 SendUser((unsigned)Command::RESTORE_PAGE
);
819 MainWindow::KillWidget()
830 InputEvents::SetFlavour(NULL
);
834 MainWindow::KillBottomWidget()
836 if (bottom_widget
== nullptr)
839 if (widget
== nullptr)
840 bottom_widget
->Hide();
841 bottom_widget
->Unprepare();
842 delete bottom_widget
;
846 MainWindow::SetBottomWidget(Widget
*_widget
)
848 if (bottom_widget
== nullptr && _widget
== nullptr)
851 if (map
== nullptr) {
852 /* this doesn't work without a map */
859 bottom_widget
= _widget
;
861 const PixelRect main_rect
= GetMainRect();
862 const PixelRect bottom_rect
= GetBottomWidgetRect(main_rect
,
865 if (bottom_widget
!= nullptr) {
866 bottom_widget
->Initialise(*this, bottom_rect
);
867 bottom_widget
->Prepare(*this, bottom_rect
);
869 if (widget
== nullptr)
870 bottom_widget
->Show(bottom_rect
);
873 map
->Move(GetMapRectAbove(main_rect
, bottom_rect
));
878 MainWindow::SetWidget(Widget
*_widget
)
880 assert(_widget
!= NULL
);
882 restore_page_pending
= false;
884 /* delete the old widget */
887 /* hide the map (might be hidden already) */
891 #ifndef ENABLE_OPENGL
892 if (!draw_suspended
) {
893 draw_suspended
= true;
894 draw_thread
->BeginSuspend();
899 if (bottom_widget
!= nullptr)
900 bottom_widget
->Hide();
904 const PixelRect rc
= GetMainRect();
905 widget
->Initialise(*this, rc
);
906 widget
->Prepare(*this, rc
);
909 if (!widget
->SetFocus())
914 MainWindow::GetFlavourWidget(const TCHAR
*flavour
)
916 return InputEvents::IsFlavour(flavour
)
922 MainWindow::UpdateVarioGaugeVisibility()
924 bool full_screen
= GetFullScreen();
926 vario
.SetVisible(!full_screen
&&
927 !CommonInterface::GetUIState().screen_blanked
);
931 MainWindow::UpdateGaugeVisibility()
933 UpdateVarioGaugeVisibility();
934 UpdateTrafficGaugeVisibility();
938 MainWindow::UpdateTrafficGaugeVisibility()
940 const FlarmData
&flarm
= CommonInterface::Basic().flarm
;
942 bool traffic_visible
=
943 (force_traffic_gauge
||
944 (CommonInterface::GetUISettings().traffic
.enable_gauge
&&
945 !flarm
.traffic
.IsEmpty())) &&
946 !CommonInterface::GetUIState().screen_blanked
&&
947 /* hide the traffic gauge while the traffic widget is visible, to
948 avoid showing the same information twice */
949 !InputEvents::IsFlavour(_T("Traffic"));
951 if (traffic_visible
&& suppress_traffic_gauge
) {
952 if (flarm
.status
.available
&&
953 flarm
.status
.alarm_level
!= FlarmTraffic::AlarmType::NONE
)
954 suppress_traffic_gauge
= false;
956 traffic_visible
= false;
959 if (traffic_visible
) {
963 if (!traffic_gauge
.IsDefined())
964 traffic_gauge
.Set(new GaugeFLARM(CommonInterface::GetLiveBlackboard(),
965 GetLook().flarm_gauge
));
967 if (!traffic_gauge
.IsVisible()) {
968 traffic_gauge
.Show();
970 GaugeFLARM
*widget
= (GaugeFLARM
*)traffic_gauge
.Get();
974 traffic_gauge
.Hide();
977 const MapWindowProjection
&
978 MainWindow::GetProjection() const
983 return map
->VisibleProjection();
987 MainWindow::ToggleSuppressFLARMRadar()
989 suppress_traffic_gauge
= !suppress_traffic_gauge
;
993 MainWindow::ToggleForceFLARMRadar()
995 force_traffic_gauge
= !force_traffic_gauge
;
996 CommonInterface::SetUISettings().traffic
.enable_gauge
= force_traffic_gauge
;
1002 MainWindow::OnPause()
1004 if (!IsRunning() && HasDialog())
1005 /* suspending before initialization has finished doesn't leave
1006 anything worth resuming, so let's just quit now */
1009 SingleWindow::OnPause();
1012 #endif /* ANDROID */