po: update French translation
[xcsoar.git] / src / PopupMessage.cpp
blob4f3b7522ceeb36f80e8b9c6e4b6527adbbc3ef98
1 /*
3 Copyright_License {
5 XCSoar Glide Computer - http://www.xcsoar.org/
6 Copyright (C) 2000-2012 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 "Protection.hpp"
28 #include "Screen/AnyCanvas.hpp"
29 #include "Screen/Fonts.hpp"
30 #include "Screen/SingleWindow.hpp"
31 #include "Screen/Layout.hpp"
32 #include "LocalPath.hpp"
33 #include "Audio/Sound.hpp"
34 #include "LogFile.hpp"
35 #include "ComputerSettings.hpp"
36 #include "Language/Language.hpp"
37 #include "StatusMessage.hpp"
38 #include "UISettings.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(int _type, int _tshow, const TCHAR *_text, int now)
50 type = _type;
51 tshow = _tshow;
52 tstart = now;
53 texpiry = now;
54 text = _text;
57 bool
58 PopupMessage::Message::Update(int 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(TCHAR *buffer, int 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[0] != _T('\0'))
93 _tcscat(buffer, _T("\r\n"));
94 _tcscat(buffer, 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 nvisible(0),
104 enable_sound(true)
106 clock.Update();
109 void
110 PopupMessage::set(const PixelRect _rc)
112 rc = _rc;
114 EditWindowStyle style;
115 style.Border();
116 style.center();
117 style.multiline();
118 style.read_only();
119 style.Hide();
121 EditWindow::set(parent, GetRect(100), style);
123 set_font(Fonts::map_bold);
124 InstallWndProc();
127 bool
128 PopupMessage::OnMouseDown(PixelScalar x, PixelScalar y)
130 // acknowledge with click/touch
131 Acknowledge(0);
133 return true;
136 PixelRect
137 PopupMessage::GetRect(UPixelScalar height) const
139 PixelRect rthis;
141 if (settings.popup_message_position == UISettings::smAlignTopLeft){
142 rthis.top = 0;
143 rthis.left = 0;
144 rthis.bottom = height;
145 rthis.right = Layout::FastScale(206);
146 // TODO code: this shouldn't be hard-coded
147 } else {
148 PixelScalar width =// min((rc.right-rc.left)*0.8,tsize.cx);
149 (PixelScalar)((rc.right - rc.left) * 0.9);
150 PixelScalar midx = (rc.right + rc.left) / 2;
151 PixelScalar midy = (rc.bottom + rc.top) / 2;
152 PixelScalar h1 = height / 2;
153 PixelScalar h2 = height - h1;
154 rthis.left = midx-width/2;
155 rthis.right = midx+width/2;
156 rthis.top = midy-h1;
157 rthis.bottom = midy+h2;
160 return rthis;
163 void
164 PopupMessage::Resize()
166 if (*msgText == _T('\0')) {
167 hide();
168 } else {
169 set_text(msgText);
171 AnyCanvas canvas;
172 canvas.Select(Fonts::map_bold);
173 PixelSize tsize = canvas.CalcTextSize(msgText);
175 int linecount = max((unsigned)nvisible, max((unsigned)1, get_row_count()));
177 PixelScalar height = min((PixelScalar)((rc.bottom-rc.top) * 0.8),
178 (PixelScalar)(tsize.cy * (linecount + 1)));
180 PixelRect rthis = GetRect(height);
181 #ifdef USE_GDI
182 PixelRect old_rc = get_position();
183 if (rthis.left != old_rc.left || rthis.right != old_rc.right) {
184 /* on Windows, the TEXT control can never change its text style
185 after it has been created, so we have to destroy it and
186 create a new one */
187 reset();
188 set(rc);
189 set_text(msgText);
191 #endif
193 move(rthis.left, rthis.top,
194 rthis.right - rthis.left,
195 rthis.bottom - rthis.top);
196 show_on_top();
200 bool
201 PopupMessage::Render()
203 if (!globalRunningEvent.Test())
204 return false;
206 mutex.Lock();
207 if (parent.HasDialog()) {
208 mutex.Unlock();
209 return false;
212 int fpsTime = clock.Elapsed();
214 // this has to be done quickly, since it happens in GUI thread
215 // at subsecond interval
217 // first loop through all messages, and determine which should be
218 // made invisible that were previously visible, or
219 // new messages
221 bool changed = false;
222 for (unsigned i = 0; i < MAXMESSAGES; ++i)
223 changed = messages[i].Update(fpsTime) || changed;
225 static bool doresize = false;
227 if (!changed) {
228 mutex.Unlock();
230 if (doresize) {
231 doresize = false;
232 // do one extra resize after display so we are sure we get all
233 // the text (workaround bug in getlinecount)
234 Resize();
236 return false;
239 // ok, we've changed the visible messages, so need to regenerate the
240 // text box
242 doresize = true;
243 msgText[0] = _T('\0');
244 nvisible = 0;
245 for (unsigned i = 0; i < MAXMESSAGES; ++i)
246 if (messages[i].AppendTo(msgText, fpsTime))
247 nvisible++;
249 mutex.Unlock();
251 Resize();
253 return true;
257 PopupMessage::GetEmptySlot()
259 // find oldest message that is no longer visible
261 // todo: make this more robust with respect to message types and if can't
262 // find anything to remove..
263 int i;
264 int tmin = 0;
265 int imin = 0;
266 for (i = 0; i < MAXMESSAGES; i++) {
267 if (i == 0 || messages[i].tstart < tmin) {
268 tmin = messages[i].tstart;
269 imin = i;
272 return imin;
275 void
276 PopupMessage::AddMessage(int tshow, int type, const TCHAR *Text)
278 int i;
279 int fpsTime = clock.Elapsed();
280 i = GetEmptySlot();
282 messages[i].Set(type, tshow, Text, fpsTime);
285 void
286 PopupMessage::Repeat(int type)
288 int i;
289 int tmax = 0;
290 int imax = -1;
292 mutex.Lock();
294 int fpsTime = clock.Elapsed();
296 // find most recent non-visible message
298 for (i = 0; i < MAXMESSAGES; i++) {
299 if (messages[i].texpiry < fpsTime &&
300 messages[i].tstart > tmax &&
301 (messages[i].type == type || type == 0)) {
302 imax = i;
303 tmax = messages[i].tstart;
307 if (imax >= 0) {
308 messages[imax].tstart = fpsTime;
309 messages[imax].texpiry = messages[imax].tstart;
312 mutex.Unlock();
315 bool
316 PopupMessage::Acknowledge(int type)
318 ScopeLock protect(mutex);
319 int i;
320 int fpsTime = clock.Elapsed();
322 for (i = 0; i < MAXMESSAGES; i++) {
323 if (messages[i].texpiry > messages[i].tstart &&
324 (type == 0 || type == messages[i].type)) {
325 // message was previously visible, so make it expire now.
326 messages[i].texpiry = fpsTime - 1;
327 return true;
330 return false;
333 // DoMessage is designed to delegate what to do for a message
334 // The "what to do" can be defined in a configuration file
335 // Defaults for each message include:
336 // - Text to display (including multiple languages)
337 // - Text to display extra - NOT multiple language
338 // (eg: If Airspace Warning - what details - airfield name is in data file, already
339 // covers multiple languages).
340 // - ShowStatusMessage - including font size and delay
341 // - Sound to play - What sound to play
342 // - Log - Keep the message on the log/history window (goes to log file and history)
344 // TODO code: (need to discuss) Consider moving almost all this functionality into AddMessage ?
346 void
347 PopupMessage::AddMessage(const TCHAR* text, const TCHAR *data)
349 ScopeLock protect(mutex);
351 StatusMessageSTRUCT LocalMessage = status_messages.First();
352 const StatusMessageSTRUCT *found = status_messages.Find(text);
353 if (found != NULL)
354 LocalMessage = *found;
356 if (enable_sound && LocalMessage.doSound)
357 PlayResource(LocalMessage.sound);
359 // TODO code: consider what is a sensible size?
360 TCHAR msgcache[1024];
361 if (LocalMessage.doStatus) {
363 _tcscpy(msgcache, text);
364 if (data != NULL) {
365 _tcscat(msgcache, _T(" "));
366 _tcscat(msgcache, data);
369 AddMessage(LocalMessage.delay_ms, MSG_USERINTERFACE, msgcache);