1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4   Copyright (C) 2018-2019 EXL <exlmotodev@gmail.com>
5 
6   This software is provided 'as-is', without any express or implied
7   warranty.  In no event will the authors be held liable for any damages
8   arising from the use of this software.
9 
10   Permission is granted to anyone to use this software for any purpose,
11   including commercial applications, and to alter it and redistribute it
12   freely, subject to the following restrictions:
13 
14   1. The origin of this software must not be misrepresented; you must not
15      claim that you wrote the original software. If you use this software
16      in a product, an acknowledgment in the product documentation would be
17      appreciated but is not required.
18   2. Altered source versions must be plainly marked as such, and must not be
19      misrepresented as being the original software.
20   3. This notice may not be removed or altered from any source distribution.
21 */
22 
23 #include "../../SDL_internal.h"
24 
25 #if SDL_VIDEO_DRIVER_HAIKU
26 
27 #include "SDL_messagebox.h"
28 
29 /* For application signature. */
30 #include "../../main/haiku/SDL_BeApp.h"
31 
32 #include <Alert.h>
33 #include <Application.h>
34 #include <Button.h>
35 #include <Font.h>
36 #include <Layout.h>
37 #include <String.h>
38 #include <TextView.h>
39 #include <View.h>
40 #include <Window.h>
41 
42 #include <InterfaceDefs.h>
43 #include <SupportDefs.h>
44 #include <GraphicsDefs.h>
45 
46 #include <new>
47 #include <vector>
48 #include <algorithm>
49 
50 enum
51 {
52 	G_CLOSE_BUTTON_ID   = -1,
53 	G_DEFAULT_BUTTON_ID = 0,
54 	G_MAX_STRING_LENGTH_BYTES = 120
55 };
56 
57 class HAIKU_SDL_MessageBox : public BAlert
58 {
59 	float fComputedMessageBoxWidth;
60 
61 	BTextView *fMessageBoxTextView;
62 
63 	int fCloseButton;
64 	int fDefaultButton;
65 
66 	bool fCustomColorScheme;
67 	bool fThereIsLongLine;
68 	rgb_color fTextColor;
69 
70 	const char *fTitle;
71 	const char *HAIKU_SDL_DefTitle;
72 	const char *HAIKU_SDL_DefMessage;
73 	const char *HAIKU_SDL_DefButton;
74 
75 	std::vector<const SDL_MessageBoxButtonData *> fButtons;
76 
77 	static bool
SortButtonsPredicate(const SDL_MessageBoxButtonData * aButtonLeft,const SDL_MessageBoxButtonData * aButtonRight)78 	SortButtonsPredicate(const SDL_MessageBoxButtonData *aButtonLeft,
79 	                                 const SDL_MessageBoxButtonData *aButtonRight)
80 	{
81 		return aButtonLeft->buttonid < aButtonRight->buttonid;
82 	}
83 
84 	alert_type
ConvertMessageBoxType(const SDL_MessageBoxFlags aWindowType) const85 	ConvertMessageBoxType(const SDL_MessageBoxFlags aWindowType) const
86 	{
87 		switch (aWindowType)
88 		{
89 			default:
90 			case SDL_MESSAGEBOX_WARNING:
91 			{
92 				return B_WARNING_ALERT;
93 			}
94 			case SDL_MESSAGEBOX_ERROR:
95 			{
96 				return B_STOP_ALERT;
97 			}
98 			case SDL_MESSAGEBOX_INFORMATION:
99 			{
100 				return B_INFO_ALERT;
101 			}
102 		}
103 	}
104 
105 	rgb_color
ConvertColorType(const SDL_MessageBoxColor * aColor) const106 	ConvertColorType(const SDL_MessageBoxColor *aColor) const
107 	{
108 		rgb_color color = { aColor->r, aColor->g, aColor->b, color.alpha = 255 };
109 		return color;
110 	}
111 
112 	int32
GetLeftPanelWidth(void) const113 	GetLeftPanelWidth(void) const
114 	{
115 		// See file "haiku/src/kits/interface/Alert.cpp" for this magic numbers.
116 		//    IconStripeWidth = 30 * Scale
117 		//    IconSize = 32 * Scale
118 		//    Scale = max_c(1, ((int32)be_plain_font->Size() + 15) / 16)
119 		//    RealWidth = (IconStripeWidth * Scale) + (IconSize * Scale)
120 
121 		int32 scale = max_c(1, ((int32)be_plain_font->Size() + 15) / 16);
122 		return (30 * scale) + (32 * scale);
123 	}
124 
125 	void
UpdateTextViewWidth(void)126 	UpdateTextViewWidth(void)
127 	{
128 		fComputedMessageBoxWidth = fMessageBoxTextView->PreferredSize().Width() + GetLeftPanelWidth();
129 	}
130 
131 	void
ParseSdlMessageBoxData(const SDL_MessageBoxData * aMessageBoxData)132 	ParseSdlMessageBoxData(const SDL_MessageBoxData *aMessageBoxData)
133 	{
134 		if (aMessageBoxData == NULL)
135 		{
136 			SetTitle(HAIKU_SDL_DefTitle);
137 			SetMessageText(HAIKU_SDL_DefMessage);
138 			AddButton(HAIKU_SDL_DefButton);
139 			return;
140 		}
141 
142 		if (aMessageBoxData->numbuttons <= 0)
143 		{
144 			AddButton(HAIKU_SDL_DefButton);
145 		}
146 		else
147 		{
148 			AddSdlButtons(aMessageBoxData->buttons, aMessageBoxData->numbuttons);
149 		}
150 
151 		if (aMessageBoxData->colorScheme != NULL)
152 		{
153 			fCustomColorScheme = true;
154 			ApplyAndParseColorScheme(aMessageBoxData->colorScheme);
155 		}
156 
157 		(aMessageBoxData->title != NULL) ?
158 			SetTitle(aMessageBoxData->title) : SetTitle(HAIKU_SDL_DefTitle);
159 		(aMessageBoxData->message != NULL) ?
160 			SetMessageText(aMessageBoxData->message) : SetMessageText(HAIKU_SDL_DefMessage);
161 
162 		SetType(ConvertMessageBoxType(static_cast<SDL_MessageBoxFlags>(aMessageBoxData->flags)));
163 	}
164 
165 	void
ApplyAndParseColorScheme(const SDL_MessageBoxColorScheme * aColorScheme)166 	ApplyAndParseColorScheme(const SDL_MessageBoxColorScheme *aColorScheme)
167 	{
168 		SetBackgroundColor(&aColorScheme->colors[SDL_MESSAGEBOX_COLOR_BACKGROUND]);
169 		fTextColor = ConvertColorType(&aColorScheme->colors[SDL_MESSAGEBOX_COLOR_TEXT]);
170 		SetButtonColors(&aColorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER],
171 		                &aColorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND],
172 		                &aColorScheme->colors[SDL_MESSAGEBOX_COLOR_TEXT],
173 		                &aColorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED]);
174 	}
175 
176 	void
SetButtonColors(const SDL_MessageBoxColor * aBorderColor,const SDL_MessageBoxColor * aBackgroundColor,const SDL_MessageBoxColor * aTextColor,const SDL_MessageBoxColor * aSelectedColor)177 	SetButtonColors(const SDL_MessageBoxColor *aBorderColor,
178 	                const SDL_MessageBoxColor *aBackgroundColor,
179 	                const SDL_MessageBoxColor *aTextColor,
180 	                const SDL_MessageBoxColor *aSelectedColor)
181 	{
182 		if (fCustomColorScheme)
183 		{
184 			int32 countButtons = CountButtons();
185 			for (int i = 0; i < countButtons; ++i)
186 			{
187 				ButtonAt(i)->SetViewColor(ConvertColorType(aBorderColor));
188 				ButtonAt(i)->SetLowColor(ConvertColorType(aBackgroundColor));
189 
190 				// This doesn't work. See this why:
191 				// https://github.com/haiku/haiku/commit/de9c53f8f5008c7b3b0af75d944a628e17f6dffe
192 				// Let it remain.
193 				ButtonAt(i)->SetHighColor(ConvertColorType(aTextColor));
194 			}
195 		}
196 		// TODO: Not Implemented.
197 		// Is it even necessary?!
198 		(void)aSelectedColor;
199 	}
200 
201 	void
SetBackgroundColor(const SDL_MessageBoxColor * aColor)202 	SetBackgroundColor(const SDL_MessageBoxColor *aColor)
203 	{
204 		rgb_color background = ConvertColorType(aColor);
205 
206 		GetLayout()->View()->SetViewColor(background);
207 		// See file "haiku/src/kits/interface/Alert.cpp", the "TAlertView" is the internal name of the left panel.
208 		FindView("TAlertView")->SetViewColor(background);
209 		fMessageBoxTextView->SetViewColor(background);
210 	}
211 
212 	bool
CheckLongLines(const char * aMessage)213 	CheckLongLines(const char *aMessage)
214 	{
215 		int final = 0;
216 
217 		// This UTF-8 friendly.
218 		BString message = aMessage;
219 		int32 length = message.CountChars();
220 
221 		for (int i = 0, c = 0; i < length; ++i)
222 		{
223 			c++;
224 			if (*(message.CharAt(i)) == '\n')
225 			{
226 				c = 0;
227 			}
228 			if (c > final)
229 			{
230 				final = c;
231 			}
232 		}
233 
234 		return (final > G_MAX_STRING_LENGTH_BYTES);
235 	}
236 
237 	void
SetMessageText(const char * aMessage)238 	SetMessageText(const char *aMessage)
239 	{
240 		fThereIsLongLine = CheckLongLines(aMessage);
241 		if (fThereIsLongLine)
242 		{
243 			fMessageBoxTextView->SetWordWrap(true);
244 		}
245 
246 		rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
247 		if (fCustomColorScheme)
248 		{
249 			textColor = fTextColor;
250 		}
251 
252 		/*
253 		if (fNoTitledWindow)
254 		{
255 			fMessageBoxTextView->SetFontAndColor(be_bold_font);
256 			fMessageBoxTextView->Insert(fTitle);
257 			fMessageBoxTextView->Insert("\n\n");
258 			fMessageBoxTextView->SetFontAndColor(be_plain_font);
259 		}
260 		*/
261 
262 		fMessageBoxTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor);
263 		fMessageBoxTextView->Insert(aMessage);
264 
265 		// Be sure to call update width method.
266 		UpdateTextViewWidth();
267 	}
268 
269 	void
AddSdlButtons(const SDL_MessageBoxButtonData * aButtons,int aNumButtons)270 	AddSdlButtons(const SDL_MessageBoxButtonData *aButtons, int aNumButtons)
271 	{
272 		for (int i = 0; i < aNumButtons; ++i)
273 		{
274 			fButtons.push_back(&aButtons[i]);
275 		}
276 
277 		std::sort(fButtons.begin(), fButtons.end(), &HAIKU_SDL_MessageBox::SortButtonsPredicate);
278 
279 		size_t countButtons = fButtons.size();
280 		for (size_t i = 0; i < countButtons; ++i)
281 		{
282 			switch (fButtons[i]->flags)
283 			{
284 				case SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT:
285 				{
286 					fCloseButton = static_cast<int>(i);
287 					break;
288 				}
289 				case SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT:
290 				{
291 					fDefaultButton = static_cast<int>(i);
292 					break;
293 				}
294 				default:
295 				{
296 					break;
297 				}
298 			}
299 			AddButton(fButtons[i]->text);
300 		}
301 
302 		SetDefaultButton(ButtonAt(fDefaultButton));
303 	}
304 
305 public:
306 	explicit
HAIKU_SDL_MessageBox(const SDL_MessageBoxData * aMessageBoxData)307 	HAIKU_SDL_MessageBox(const SDL_MessageBoxData *aMessageBoxData)
308 		: BAlert(NULL, NULL, NULL, NULL, NULL, B_WIDTH_FROM_LABEL, B_WARNING_ALERT),
309 		  fComputedMessageBoxWidth(0.0f),
310 		  fCloseButton(G_CLOSE_BUTTON_ID), fDefaultButton(G_DEFAULT_BUTTON_ID),
311 		  fCustomColorScheme(false), fThereIsLongLine(false),
312 		  HAIKU_SDL_DefTitle("SDL2 MessageBox"),
313 		  HAIKU_SDL_DefMessage("Some information has been lost."),
314 		  HAIKU_SDL_DefButton("OK")
315 	{
316 		// MessageBox settings.
317 		// We need a title to display it.
318 		SetLook(B_TITLED_WINDOW_LOOK);
319 		SetFlags(Flags() | B_CLOSE_ON_ESCAPE);
320 
321 		// MessageBox TextView settings.
322 		fMessageBoxTextView = TextView();
323 		fMessageBoxTextView->SetWordWrap(false);
324 		fMessageBoxTextView->SetStylable(true);
325 
326 		ParseSdlMessageBoxData(aMessageBoxData);
327 	}
328 
329 	int
GetCloseButtonId(void) const330 	GetCloseButtonId(void) const
331 	{
332 		return fCloseButton;
333 	}
334 
335 	virtual
~HAIKU_SDL_MessageBox(void)336 	~HAIKU_SDL_MessageBox(void)
337 	{
338 		fButtons.clear();
339 	}
340 
341 protected:
342 	virtual void
FrameResized(float aNewWidth,float aNewHeight)343 	FrameResized(float aNewWidth, float aNewHeight)
344 	{
345 		if (fComputedMessageBoxWidth > aNewWidth)
346 		{
347 			ResizeTo(fComputedMessageBoxWidth, aNewHeight);
348 		}
349 		else
350 		{
351 			BAlert::FrameResized(aNewWidth, aNewHeight);
352 		}
353 	}
354 
355 	virtual void
SetTitle(const char * aTitle)356 	SetTitle(const char* aTitle)
357 	{
358 		fTitle = aTitle;
359 		BAlert::SetTitle(aTitle);
360 	}
361 };
362 
363 #ifdef __cplusplus
364 extern "C" {
365 #endif
366 
367 int
HAIKU_ShowMessageBox(const SDL_MessageBoxData * messageboxdata,int * buttonid)368 HAIKU_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
369 {
370 	// Initialize button by closed or error value first.
371 	*buttonid = G_CLOSE_BUTTON_ID;
372 
373 	// We need to check "be_app" pointer to "NULL". The "messageboxdata->window" pointer isn't appropriate here
374 	// because it is possible to create a MessageBox from another thread. This fixes the following errors:
375 	// "You need a valid BApplication object before interacting with the app_server."
376 	// "2 BApplication objects were created. Only one is allowed."
377 	BApplication *application = NULL;
378 	if (be_app == NULL)
379 	{
380 		application = new(std::nothrow) BApplication(signature);
381 		if (application == NULL)
382 		{
383 			return SDL_SetError("Cannot create the BApplication object. Lack of memory?");
384 		}
385 	}
386 
387 	HAIKU_SDL_MessageBox *SDL_MessageBox = new(std::nothrow) HAIKU_SDL_MessageBox(messageboxdata);
388 	if (SDL_MessageBox == NULL)
389 	{
390 		return SDL_SetError("Cannot create the HAIKU_SDL_MessageBox (BAlert inheritor) object. Lack of memory?");
391 	}
392 	const int closeButton = SDL_MessageBox->GetCloseButtonId();
393 	int pushedButton = SDL_MessageBox->Go();
394 
395 	// The close button is equivalent to pressing Escape.
396 	if (closeButton != G_CLOSE_BUTTON_ID && pushedButton == G_CLOSE_BUTTON_ID)
397 	{
398 		pushedButton = closeButton;
399 	}
400 
401 	// It's deleted by itself after the "Go()" method was executed.
402 	/*
403 	if (messageBox != NULL)
404 	{
405 		delete messageBox;
406 	}
407 	*/
408 	if (application != NULL)
409 	{
410 		delete application;
411 	}
412 
413 	// Initialize button by real pushed value then.
414 	*buttonid = pushedButton;
415 
416 	return 0;
417 }
418 
419 #ifdef __cplusplus
420 }
421 #endif
422 
423 #endif /* SDL_VIDEO_DRIVER_HAIKU */
424 
425 /* vi: set ts=4 sw=4 expandtab: */
426