android/GlueIOIOPort: fix spurious errors after IOIO baud rate change
[xcsoar.git] / src / PopupMessage.cpp
blob27db93ff2f2af7cdae1607a7eea3d4b98c66cd14
1 /*
3 Copyright_License {
5 XCSoar Glide Computer - http://www.xcsoar.org/
6 Copyright (C) 2000-2013 The XCSoar Project
7 A detailed list of copyright holders can be found in the file "AUTHORS".
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 #include "PopupMessage.hpp"
27 #include "Look/Fonts.hpp"
28 #include "Screen/SingleWindow.hpp"
29 #include "Screen/Layout.hpp"
30 #include "Screen/Font.hpp"
31 #include "LocalPath.hpp"
32 #include "Audio/Sound.hpp"
33 #include "LogFile.hpp"
34 #include "Language/Language.hpp"
35 #include "StatusMessage.hpp"
36 #include "UISettings.hpp"
37 #include "OS/Clock.hpp"
39 #include <tchar.h>
40 #include <stdio.h>
41 #include <algorithm>
43 using std::min;
44 using std::max;
46 void
47 PopupMessage::Message::Set(Type _type, unsigned _tshow, const TCHAR *_text,
48 unsigned now)
50 type = _type;
51 tshow = _tshow;
52 tstart = now;
53 texpiry = now;
54 text = _text;
57 bool
58 PopupMessage::Message::Update(unsigned now)
60 if (IsUnknown())
61 // ignore unknown messages
62 return false;
64 if (IsNewlyExpired(now))
65 // this message has expired for first time
66 return true;
68 // new message has been added
69 if (IsNew()) {
70 // set new expiry time.
71 texpiry = now + tshow;
72 // this is a new message..
73 return true;
76 return false;
79 bool
80 PopupMessage::Message::AppendTo(StaticString<2000> &buffer, unsigned now)
82 if (IsUnknown())
83 // ignore unknown messages
84 return false;
86 if (texpiry < now) {
87 texpiry = tstart - 1;
88 // reset expiry so we don't refresh
89 return false;
92 if (!buffer.empty())
93 buffer.append(_T("\r\n"));
94 buffer.append(text);
95 return true;
98 PopupMessage::PopupMessage(const StatusMessageList &_status_messages,
99 SingleWindow &_parent, const UISettings &_settings)
100 :status_messages(_status_messages),
101 parent(_parent),
102 settings(_settings),
103 n_visible(0),
104 enable_sound(true)
108 void
109 PopupMessage::Create(const PixelRect _rc)
111 rc = _rc;
113 LargeTextWindowStyle style;
114 style.Border();
115 style.SetCenter();
116 style.Hide();
118 LargeTextWindow::Create(parent, GetRect(100), style);
120 SetFont(Fonts::map_bold);
121 InstallWndProc();
124 bool
125 PopupMessage::OnMouseDown(PixelScalar x, PixelScalar y)
127 // acknowledge with click/touch
128 Acknowledge(MSG_UNKNOWN);
130 return true;
133 PixelRect
134 PopupMessage::GetRect(UPixelScalar height) const
136 PixelRect rthis;
138 if (settings.popup_message_position == UISettings::PopupMessagePosition::TOP_LEFT) {
139 rthis.top = 0;
140 rthis.left = 0;
141 rthis.bottom = height;
142 rthis.right = Layout::FastScale(206);
143 // TODO code: this shouldn't be hard-coded
144 } else {
145 PixelScalar width =// min((rc.right-rc.left)*0.8,tsize.cx);
146 (PixelScalar)((rc.right - rc.left) * 0.9);
147 PixelScalar midx = (rc.right + rc.left) / 2;
148 PixelScalar midy = (rc.bottom + rc.top) / 2;
149 PixelScalar h1 = height / 2;
150 PixelScalar h2 = height - h1;
151 rthis.left = midx-width/2;
152 rthis.right = midx+width/2;
153 rthis.top = midy-h1;
154 rthis.bottom = midy+h2;
157 return rthis;
160 void
161 PopupMessage::UpdateTextAndLayout(const TCHAR *text)
163 if (StringIsEmpty(text)) {
164 Hide();
165 } else {
166 SetText(text);
168 const UPixelScalar font_height = Fonts::map_bold.GetHeight();
170 unsigned n_lines = max(n_visible, max(1u, GetRowCount()));
172 PixelScalar height = min((PixelScalar)((rc.bottom-rc.top) * 0.8),
173 (PixelScalar)(font_height * (n_lines + 1)));
175 PixelRect rthis = GetRect(height);
176 #ifdef USE_GDI
177 PixelRect old_rc = GetPosition();
178 if (rthis.left != old_rc.left || rthis.right != old_rc.right) {
179 /* on Windows, the TEXT control can never change its text style
180 after it has been created, so we have to destroy it and
181 create a new one */
182 Destroy();
183 Create(rthis);
184 SetText(text);
185 } else
186 #endif
187 Move(rthis);
189 ShowOnTop();
193 bool
194 PopupMessage::Render()
196 mutex.Lock();
197 if (parent.HasDialog()) {
198 mutex.Unlock();
199 return false;
202 const unsigned now = MonotonicClockMS();
204 // this has to be done quickly, since it happens in GUI thread
205 // at subsecond interval
207 // first loop through all messages, and determine which should be
208 // made invisible that were previously visible, or
209 // new messages
211 bool changed = false;
212 for (unsigned i = 0; i < MAXMESSAGES; ++i)
213 changed = messages[i].Update(now) || changed;
215 static bool doresize = false;
217 if (!changed) {
218 mutex.Unlock();
220 if (doresize) {
221 doresize = false;
222 // do one extra resize after display so we are sure we get all
223 // the text (workaround bug in getlinecount)
224 UpdateTextAndLayout(text);
226 return false;
229 // ok, we've changed the visible messages, so need to regenerate the
230 // text box
232 doresize = true;
233 text.clear();
234 n_visible = 0;
235 for (unsigned i = 0; i < MAXMESSAGES; ++i)
236 if (messages[i].AppendTo(text, now))
237 n_visible++;
239 mutex.Unlock();
241 UpdateTextAndLayout(text);
243 return true;
247 PopupMessage::GetEmptySlot()
249 // find oldest message that is no longer visible
251 // todo: make this more robust with respect to message types and if can't
252 // find anything to remove..
253 unsigned imin = 0;
254 for (unsigned i = 0, tmin = 0; i < MAXMESSAGES; i++) {
255 if (i == 0 || messages[i].tstart < tmin) {
256 tmin = messages[i].tstart;
257 imin = i;
260 return imin;
263 void
264 PopupMessage::AddMessage(unsigned tshow, Type type, const TCHAR *Text)
266 assert(mutex.IsLockedByCurrent());
268 const unsigned now = MonotonicClockMS();
270 int i = GetEmptySlot();
271 messages[i].Set(type, tshow, Text, now);
274 void
275 PopupMessage::Repeat(Type type)
277 int imax = -1;
279 mutex.Lock();
280 const unsigned now = MonotonicClockMS();
282 // find most recent non-visible message
284 for (unsigned i = 0, tmax = 0; i < MAXMESSAGES; i++) {
285 if (messages[i].texpiry < now &&
286 messages[i].tstart > tmax &&
287 (messages[i].type == type || type == 0)) {
288 imax = i;
289 tmax = messages[i].tstart;
293 if (imax >= 0) {
294 messages[imax].tstart = now;
295 messages[imax].texpiry = messages[imax].tstart;
298 mutex.Unlock();
301 bool
302 PopupMessage::Acknowledge(Type type)
304 ScopeLock protect(mutex);
305 const unsigned now = MonotonicClockMS();
307 for (unsigned i = 0; i < MAXMESSAGES; i++) {
308 if (messages[i].texpiry > messages[i].tstart &&
309 (type == MSG_UNKNOWN || type == messages[i].type)) {
310 // message was previously visible, so make it expire now.
311 messages[i].texpiry = now - 1;
312 return true;
315 return false;
318 // DoMessage is designed to delegate what to do for a message
319 // The "what to do" can be defined in a configuration file
320 // Defaults for each message include:
321 // - Text to display (including multiple languages)
322 // - Text to display extra - NOT multiple language
323 // (eg: If Airspace Warning - what details - airfield name is in data file, already
324 // covers multiple languages).
325 // - ShowStatusMessage - including font size and delay
326 // - Sound to play - What sound to play
327 // - Log - Keep the message on the log/history window (goes to log file and history)
329 // TODO code: (need to discuss) Consider moving almost all this functionality into AddMessage ?
331 void
332 PopupMessage::AddMessage(const TCHAR* text, const TCHAR *data)
334 ScopeLock protect(mutex);
336 StatusMessage msg = status_messages.First();
337 const StatusMessage *found = status_messages.Find(text);
338 if (found != NULL)
339 msg = *found;
341 if (enable_sound && msg.sound != NULL)
342 PlayResource(msg.sound);
344 // TODO code: consider what is a sensible size?
345 if (msg.visible) {
346 TCHAR msgcache[1024];
347 _tcscpy(msgcache, text);
348 if (data != NULL) {
349 _tcscat(msgcache, _T(" "));
350 _tcscat(msgcache, data);
353 AddMessage(msg.delay_ms, MSG_USERINTERFACE, msgcache);