SectorZone: add attribute arc_boundary
[xcsoar.git] / src / PopupMessage.cpp
blobb8a9a627ba269f11611aeba963b56c3b45ffacc2
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 "ComputerSettings.hpp"
35 #include "Language/Language.hpp"
36 #include "StatusMessage.hpp"
37 #include "UISettings.hpp"
38 #include "OS/Clock.hpp"
40 #include <tchar.h>
41 #include <stdio.h>
42 #include <algorithm>
44 using std::min;
45 using std::max;
47 void
48 PopupMessage::Message::Set(Type _type, unsigned _tshow, const TCHAR *_text,
49 unsigned now)
51 type = _type;
52 tshow = _tshow;
53 tstart = now;
54 texpiry = now;
55 text = _text;
58 bool
59 PopupMessage::Message::Update(unsigned now)
61 if (IsUnknown())
62 // ignore unknown messages
63 return false;
65 if (IsNewlyExpired(now))
66 // this message has expired for first time
67 return true;
69 // new message has been added
70 if (IsNew()) {
71 // set new expiry time.
72 texpiry = now + tshow;
73 // this is a new message..
74 return true;
77 return false;
80 bool
81 PopupMessage::Message::AppendTo(StaticString<2000> &buffer, unsigned now)
83 if (IsUnknown())
84 // ignore unknown messages
85 return false;
87 if (texpiry < now) {
88 texpiry = tstart - 1;
89 // reset expiry so we don't refresh
90 return false;
93 if (!buffer.empty())
94 buffer.append(_T("\r\n"));
95 buffer.append(text);
96 return true;
99 PopupMessage::PopupMessage(const StatusMessageList &_status_messages,
100 SingleWindow &_parent, const UISettings &_settings)
101 :status_messages(_status_messages),
102 parent(_parent),
103 settings(_settings),
104 n_visible(0),
105 enable_sound(true)
109 void
110 PopupMessage::Create(const PixelRect _rc)
112 rc = _rc;
114 LargeTextWindowStyle style;
115 style.Border();
116 style.SetCenter();
117 style.Hide();
119 LargeTextWindow::Create(parent, GetRect(100), style);
121 SetFont(Fonts::map_bold);
122 InstallWndProc();
125 bool
126 PopupMessage::OnMouseDown(PixelScalar x, PixelScalar y)
128 // acknowledge with click/touch
129 Acknowledge(MSG_UNKNOWN);
131 return true;
134 PixelRect
135 PopupMessage::GetRect(UPixelScalar height) const
137 PixelRect rthis;
139 if (settings.popup_message_position == UISettings::PopupMessagePosition::TOP_LEFT) {
140 rthis.top = 0;
141 rthis.left = 0;
142 rthis.bottom = height;
143 rthis.right = Layout::FastScale(206);
144 // TODO code: this shouldn't be hard-coded
145 } else {
146 PixelScalar width =// min((rc.right-rc.left)*0.8,tsize.cx);
147 (PixelScalar)((rc.right - rc.left) * 0.9);
148 PixelScalar midx = (rc.right + rc.left) / 2;
149 PixelScalar midy = (rc.bottom + rc.top) / 2;
150 PixelScalar h1 = height / 2;
151 PixelScalar h2 = height - h1;
152 rthis.left = midx-width/2;
153 rthis.right = midx+width/2;
154 rthis.top = midy-h1;
155 rthis.bottom = midy+h2;
158 return rthis;
161 void
162 PopupMessage::UpdateTextAndLayout(const TCHAR *text)
164 if (StringIsEmpty(text)) {
165 Hide();
166 } else {
167 SetText(text);
169 const UPixelScalar font_height = Fonts::map_bold.GetHeight();
171 unsigned n_lines = max(n_visible, max(1u, GetRowCount()));
173 PixelScalar height = min((PixelScalar)((rc.bottom-rc.top) * 0.8),
174 (PixelScalar)(font_height * (n_lines + 1)));
176 PixelRect rthis = GetRect(height);
177 #ifdef USE_GDI
178 PixelRect old_rc = GetPosition();
179 if (rthis.left != old_rc.left || rthis.right != old_rc.right) {
180 /* on Windows, the TEXT control can never change its text style
181 after it has been created, so we have to destroy it and
182 create a new one */
183 Destroy();
184 Create(rthis);
185 SetText(text);
186 } else
187 #endif
188 Move(rthis);
190 ShowOnTop();
194 bool
195 PopupMessage::Render()
197 mutex.Lock();
198 if (parent.HasDialog()) {
199 mutex.Unlock();
200 return false;
203 const unsigned now = MonotonicClockMS();
205 // this has to be done quickly, since it happens in GUI thread
206 // at subsecond interval
208 // first loop through all messages, and determine which should be
209 // made invisible that were previously visible, or
210 // new messages
212 bool changed = false;
213 for (unsigned i = 0; i < MAXMESSAGES; ++i)
214 changed = messages[i].Update(now) || changed;
216 static bool doresize = false;
218 if (!changed) {
219 mutex.Unlock();
221 if (doresize) {
222 doresize = false;
223 // do one extra resize after display so we are sure we get all
224 // the text (workaround bug in getlinecount)
225 UpdateTextAndLayout(text);
227 return false;
230 // ok, we've changed the visible messages, so need to regenerate the
231 // text box
233 doresize = true;
234 text.clear();
235 n_visible = 0;
236 for (unsigned i = 0; i < MAXMESSAGES; ++i)
237 if (messages[i].AppendTo(text, now))
238 n_visible++;
240 mutex.Unlock();
242 UpdateTextAndLayout(text);
244 return true;
248 PopupMessage::GetEmptySlot()
250 // find oldest message that is no longer visible
252 // todo: make this more robust with respect to message types and if can't
253 // find anything to remove..
254 unsigned imin = 0;
255 for (unsigned i = 0, tmin = 0; i < MAXMESSAGES; i++) {
256 if (i == 0 || messages[i].tstart < tmin) {
257 tmin = messages[i].tstart;
258 imin = i;
261 return imin;
264 void
265 PopupMessage::AddMessage(unsigned tshow, Type type, const TCHAR *Text)
267 assert(mutex.IsLockedByCurrent());
269 const unsigned now = MonotonicClockMS();
271 int i = GetEmptySlot();
272 messages[i].Set(type, tshow, Text, now);
275 void
276 PopupMessage::Repeat(Type type)
278 int imax = -1;
280 mutex.Lock();
281 const unsigned now = MonotonicClockMS();
283 // find most recent non-visible message
285 for (unsigned i = 0, tmax = 0; i < MAXMESSAGES; i++) {
286 if (messages[i].texpiry < now &&
287 messages[i].tstart > tmax &&
288 (messages[i].type == type || type == 0)) {
289 imax = i;
290 tmax = messages[i].tstart;
294 if (imax >= 0) {
295 messages[imax].tstart = now;
296 messages[imax].texpiry = messages[imax].tstart;
299 mutex.Unlock();
302 bool
303 PopupMessage::Acknowledge(Type type)
305 ScopeLock protect(mutex);
306 const unsigned now = MonotonicClockMS();
308 for (unsigned i = 0; i < MAXMESSAGES; i++) {
309 if (messages[i].texpiry > messages[i].tstart &&
310 (type == MSG_UNKNOWN || type == messages[i].type)) {
311 // message was previously visible, so make it expire now.
312 messages[i].texpiry = now - 1;
313 return true;
316 return false;
319 // DoMessage is designed to delegate what to do for a message
320 // The "what to do" can be defined in a configuration file
321 // Defaults for each message include:
322 // - Text to display (including multiple languages)
323 // - Text to display extra - NOT multiple language
324 // (eg: If Airspace Warning - what details - airfield name is in data file, already
325 // covers multiple languages).
326 // - ShowStatusMessage - including font size and delay
327 // - Sound to play - What sound to play
328 // - Log - Keep the message on the log/history window (goes to log file and history)
330 // TODO code: (need to discuss) Consider moving almost all this functionality into AddMessage ?
332 void
333 PopupMessage::AddMessage(const TCHAR* text, const TCHAR *data)
335 ScopeLock protect(mutex);
337 StatusMessage msg = status_messages.First();
338 const StatusMessage *found = status_messages.Find(text);
339 if (found != NULL)
340 msg = *found;
342 if (enable_sound && msg.sound != NULL)
343 PlayResource(msg.sound);
345 // TODO code: consider what is a sensible size?
346 if (msg.visible) {
347 TCHAR msgcache[1024];
348 _tcscpy(msgcache, text);
349 if (data != NULL) {
350 _tcscat(msgcache, _T(" "));
351 _tcscat(msgcache, data);
354 AddMessage(msg.delay_ms, MSG_USERINTERFACE, msgcache);