1 /*
2 	Implements the "Generic special text window" feature.
3 
4 	Messages from server channel 255 are displayed in a text
5 	window that automatically pops up.  Each message is treated
6 	as a new block of text to display.  Any existing pop up
7 	window will be closed and the text discarded.
8 
9 	The first line of the message is used as the widow title.
10 	If there are more lines, they are displayed in a text widget.
11 	If the text width will not fit on screen, the text is wrapped.
12 	If all the lines can not be displayed on screen, a scroll bar
13 	will be added.
14 	An "OK" button is provided at the base of the window.
15 	The use of this feature can be disabled via the Server tab
16 	in the game options window.
17 	Great length have been taken to make sure this works with
18 	different fonts and font sizes!
19 
20 	bluap aka pjbroad November 2006
21 */
22 
23 /* todo
24 bugs:
25 	font width fudge for non standard font
26 may be do:
27 	reused last position for new windows if suitable
28 	key to display last message window?
29 */
30 
31 #include <string.h>
32 #include <stdlib.h>
33 
34 #include "serverpopup.h"
35 #include "asc.h"
36 #include "chat.h"
37 #include "elwindows.h"
38 #include "gamewin.h"
39 #include "gl_init.h"
40 #include "misc.h"
41 #include "text.h"
42 #include "widgets.h"
43 
44 /* these are visible externally and exported in the header file */
45 int server_pop_chan = CHAT_POPUP;
46 int use_server_pop_win = 1;
47 
48 /* these are visible only to this code module but are needed by handlers for example */
49 static const int sep = 5;
50 /* initialised by initialise() */
51 static int server_popup_win = -1;
52 static text_message widget_text;
53 static int textId;
54 static int buttonId;
55 static int scroll_id;
56 static int text_widget_width;
57 static int text_widget_height;
58 static int actual_scroll_width;
59 static int num_text_lines;
60 static int scroll_line;
61 
62 /*
63 	return the offset into the text_message of the beginning of the specified line
64 	if the line number does not exist, set the passed line to the last line
65 	there is a similar function in text.c find_line_nr() but it is only for chat channel windows
66 */
67 
get_line_number_offset(int * line,const text_message * const wt)68 static int get_line_number_offset(int *line, const text_message * const wt)
69 {
70 	int i, curr_line, last_start, is_start;
71 	for (i=0, curr_line=0, last_start=0, is_start=1; i<wt->len; ++i)
72 	{
73 		/* if a line start */
74 		if (is_start){
75 			/* return the start offset if this is the line number we want */
76 			if (curr_line == *line){
77 				 return i;
78 			}
79 			last_start = i; /* remember in case this is the last line */
80 			is_start = 0;
81 		}
82 		/* if a line end */
83 		if (wt->data[i] == '\n' || wt->data[i] == '\r'){
84 			curr_line++;
85 			is_start = 1;
86 		}
87 	}
88 	/* the line number was not found so set the passed value to the actual last line number */
89 	*line = curr_line;
90 	return last_start;
91 }
92 
93 
94 /* set the displayed start of the text_message to the current scroll line */
set_text_line()95 static void set_text_line()
96 {
97 	int offset = get_line_number_offset(&scroll_line, &widget_text);
98 	text_field_set_buf_pos( server_popup_win, textId, 0, offset );
99 }
100 
101 
102 /* scroll click handler, set the displayed text start to the new scroll position */
scroll_click(widget_list * widget,int mx,int my,Uint32 flags)103 static int scroll_click(widget_list *widget, int mx, int my, Uint32 flags)
104 {
105 	scroll_line = vscrollbar_get_pos( server_popup_win, scroll_id );
106 	set_text_line();
107 	return 1;
108 }
109 
110 
111 /* scroll drag handler, set the displayed text start to the new scroll position */
scroll_drag(widget_list * widget,int mx,int my,Uint32 flags,int dx,int dy)112 static int scroll_drag(widget_list *widget, int mx, int my, Uint32 flags, int dx, int dy)
113 {
114 	return scroll_click(widget, mx, my, flags);
115 }
116 
117 
118 /* if the mouse scroll wheel is used, move the scroll bar if we have one */
click_handler(window_info * win,int mx,int my,Uint32 flags)119 static int click_handler(window_info *win, int mx, int my, Uint32 flags)
120 {
121 	if (!actual_scroll_width){
122 		return 1;
123 	}
124 	if (flags & ELW_WHEEL_UP){
125 		vscrollbar_scroll_up(server_popup_win, scroll_id);
126 	}
127 	else if (flags & ELW_WHEEL_DOWN){
128 		vscrollbar_scroll_down(server_popup_win, scroll_id);
129 	}
130 	scroll_line = vscrollbar_get_pos(server_popup_win, scroll_id);
131 	set_text_line();
132 	return 1;
133 }
134 
135 
136 /* static vars are only required for window lifetime */
initialise()137 static void initialise()
138 {
139 	server_popup_win = -1;
140 	textId = 101;
141 	buttonId = 102;
142 	scroll_id = 103;
143 	text_widget_width = 0;
144 	text_widget_height = 0;
145 	actual_scroll_width = 0;
146 	num_text_lines = 0;
147 	scroll_line = 0;
148 }
149 
150 
151 /* destroy all the widgets, deallocate memory and destroy the main window */
close_handler(widget_list * widget,int mx,int my,Uint32 flags)152 static int close_handler(widget_list *widget, int mx, int my, Uint32 flags)
153 {
154 	widget_destroy(server_popup_win, scroll_id);
155 	widget_destroy(server_popup_win, buttonId);
156 	widget_destroy(server_popup_win, textId);
157 	free_text_message_data(&widget_text);
158 	destroy_window(server_popup_win);
159 	initialise();
160 	return 1;
161 }
162 
163 
server_popup_get_text_height(int num_lines)164 static int server_popup_get_text_height(int num_lines)
165 {
166 	if (num_lines > 0)
167 		return 1 + 2 * sep + get_text_height(num_lines, CHAT_FONT, 1.0);
168 	else
169 		return 0;
170 }
171 
172 
get_non_text_height(void)173 static int get_non_text_height(void)
174 {
175 	return 3 * sep + widget_get_height(server_popup_win, buttonId);
176 }
177 
178 
get_height(int num_lines)179 static int get_height(int num_lines)
180 {
181 	return server_popup_get_text_height(num_lines) + get_non_text_height();
182 }
183 
184 
set_min_window_size(window_info * win)185 static void set_min_window_size(window_info *win)
186 {
187 	int min_height = get_height((text_message_is_empty (&widget_text)) ?0: 1);
188 	int min_width = win->box_size + 2 * sep + widget_get_width(server_popup_win, buttonId);
189 	int min_text_width = win->box_size + 2 * sep + (int)(5 * DEFAULT_FIXED_FONT_WIDTH);
190 	if (min_width < min_text_width)
191 		min_width = min_text_width;
192 	set_window_min_size (win->window_id, min_width, min_height);
193 }
194 
195 
196 /* the window resize handler, keep things neat and add scroll bar if required */
resize_handler(window_info * win,int width,int height)197 static int resize_handler(window_info *win, int width, int height)
198 {
199 	/* if there is no text widget, we're done */
200 	if (text_message_is_empty (&widget_text)) {
201 		return 1;
202 	}
203 
204 	/* set the text widget height */
205 	text_widget_height = height - get_non_text_height();
206 
207 	/* remove any existing scroll bar */
208 	widget_destroy(server_popup_win, scroll_id);
209 	actual_scroll_width = 0;
210 
211 	/* only add a scroll bar if needed, i.e. more lines than we can display */
212 	if (text_widget_height < server_popup_get_text_height(num_text_lines)){
213 		actual_scroll_width = win->box_size;
214 	}
215 
216 	/* set the text widget width, allowing for a scroll bar if required */
217 	text_widget_width = width - (2*sep + actual_scroll_width);
218 
219 	/* resize the text widget and rewrap the text as the size will have changed */
220 	widget_resize(server_popup_win, textId, text_widget_width, text_widget_height);
221 	if (!text_message_is_empty (&widget_text))
222 	{
223 		num_text_lines = rewrap_message(&widget_text, CHAT_FONT, 1.0,
224 			text_widget_width - 2*sep, NULL);
225 	}
226 
227 	/* if we (only now) need a scroll bar, adjust again */
228 	if (!actual_scroll_width && (text_widget_height < server_popup_get_text_height(num_text_lines)))
229 	{
230 		actual_scroll_width = win->box_size;
231 		text_widget_width = width - (2*sep + actual_scroll_width);
232 		widget_resize(server_popup_win, textId, text_widget_width, text_widget_height);
233 		/* rewrap the text again as the available width is now less */
234 		if (!text_message_is_empty (&widget_text))
235 		{
236 			num_text_lines = rewrap_message(&widget_text, CHAT_FONT, 1.0,
237 				text_widget_width - 2*sep, NULL);
238 		}
239 	}
240 
241 	/* if the text widget is really short, the scroll bar can extent beyond the height */
242 	/* could be considered a bug in the scroll widget - try to avoid anyway */
243 	if (actual_scroll_width)
244 	{
245 		int local_min = get_non_text_height() + max2i(server_popup_get_text_height(1), 3 * win->box_size);
246 		if (height < local_min)
247 		{
248 			resize_window(server_popup_win, width, local_min);
249 			return 1;
250 		}
251 	}
252 
253 	/* create the scroll bar reusing any existing scroll position */
254 	if (actual_scroll_width)
255 	{
256 		scroll_id = vscrollbar_add_extended( win->window_id, scroll_id, NULL,
257 			width - win->box_size, sep, win->box_size, text_widget_height,
258 			0, 1, scroll_line, 1, num_text_lines);
259 		widget_set_OnDrag(server_popup_win, scroll_id, scroll_drag);
260 		widget_set_OnClick(server_popup_win, scroll_id, scroll_click);
261 		set_text_line();
262 
263 		/* if we have a scroll bar, enable the title and resize properties */
264 		win->flags |= ELW_RESIZEABLE | ELW_TITLE_BAR;
265 	}
266 	/* if no scroll bar, make sure the text is at the start of the buffer */
267 	else
268 	{
269 		scroll_line = 0;
270 		text_field_set_buf_pos( server_popup_win, textId, 0, 0 );
271 		if (win->flags & ELW_RESIZEABLE)
272 			win->flags ^= ELW_RESIZEABLE;
273 		if (win->flags & ELW_TITLE_BAR)
274 			win->flags ^= ELW_TITLE_BAR;
275 	}
276 
277 	/* move the text widget and OK button to the middle of the window bottom */
278 	widget_move(win->window_id, buttonId, (win->len_x - widget_get_width(win->window_id, buttonId) - actual_scroll_width)/2,
279 		win->len_y - sep - widget_get_height(win->window_id, buttonId));
280 
281 	return 1;
282 } /* end resize_handler */
283 
284 
ui_scale_handler(window_info * win)285 static int ui_scale_handler(window_info *win)
286 {
287 	button_resize(win->window_id, buttonId, 0, 0, win->current_scale);
288 	set_min_window_size(win);
289 	resize_window(win->window_id, win->len_x, ((actual_scroll_width) ?win->len_y : get_height(num_text_lines) ));
290 	return 1;
291 }
292 
set_actual_window_size(window_info * win)293 static void set_actual_window_size(window_info *win)
294 {
295 	const int unusable_width = 2 * sep + HUD_MARGIN_X;
296 	const int unusable_height = 2 * sep + HUD_MARGIN_Y;
297 	int winWidth, winHeight;
298 
299 	/* do a pre-wrap of the text to the maximum screen width we can use
300 		 this will avoid the later wrap (after the resize) changing the number of lines */
301 	if (!text_message_is_empty(&widget_text))
302 	{
303 		num_text_lines = rewrap_message(&widget_text, CHAT_FONT, 1.0,
304 			(window_width - unusable_width) - 4*sep, NULL);
305 	}
306 
307 	/* calc the text widget height from the number of lines */
308 	winHeight = get_non_text_height();
309 	if (!text_message_is_empty(&widget_text))
310 	{
311 		text_widget_height = server_popup_get_text_height(num_text_lines);
312 		winHeight = text_widget_height + get_non_text_height();
313 	}
314 
315 	/* but limit to a maximum */
316 	if (winHeight > window_height - unusable_height - ((actual_scroll_width) ? win->title_height : 0))
317 	{
318 		winHeight = window_height - unusable_height - ((actual_scroll_width) ? win->title_height : 0);
319 		text_widget_height = winHeight - get_non_text_height();
320 
321 		/* if we'll need a scroll bar allow for it in the width calulation */
322 		if (!text_message_is_empty(&widget_text) && (text_widget_height < server_popup_get_text_height(num_text_lines)))
323 			actual_scroll_width = win->box_size;
324 	}
325 
326 	/* calc the require window width for the text size */
327 	if (!text_message_is_empty (&widget_text)) {
328 		/* The fudge is because the line wrapping code allows for a cursor to fit on the last
329 		 * position in the line, but this is not reflected in the actual width of the line. Hence,
330 		 * if the last character in the line is less wide than the cursor, the calculated width will
331 		 * be too small according to rewrap_message(). Add the width of a cursor to the required
332 		 * width to be certain it is large enough.
333 		 */
334 		int fudge = get_char_width_zoom('_', CHAT_FONT, win->current_scale);
335 		text_widget_width = fudge + 2*sep + widget_text.max_line_width;
336 	} else {
337 		text_widget_width = 0;
338 	}
339 	winWidth = text_widget_width + 2*sep + actual_scroll_width;
340 
341 	/* but limit to a maximum */
342 	winWidth = min2i(winWidth, window_width - unusable_width);
343 
344 	/* resize the window now we have the required size */
345 	/* new sizes and positions for the widgets will be calculated by the callback */
346 	resize_window(server_popup_win, winWidth, winHeight);
347 
348 	/* calculate the best position then move the window */
349 	move_window(server_popup_win, -1, 0, sep + (window_width - unusable_width - win->len_x)/2, sep + (window_height - unusable_height - win->len_y)/2);
350 }
351 
font_change_handler(window_info * win,font_cat cat)352 static int font_change_handler(window_info *win, font_cat cat)
353 {
354 	if (cat != win->font_category && cat != CHAT_FONT)
355 		return 0;
356 	set_min_window_size(win);
357 	set_actual_window_size(win);
358 	return 1;
359 }
360 
361 /*
362 	Create the server popup window, destroying an existing window first.
363 */
display_server_popup_win(const unsigned char * message)364 void display_server_popup_win(const unsigned char* message)
365 {
366 	int winWidth = 0;
367 	int winHeight = 0;
368 	Uint32 win_property_flags;
369 	window_info *win = NULL;
370 	int msg_len = strlen((const char*)message);
371 
372 	/* exit now if message empty */
373 	if (!*message)
374 		return;
375 
376 	/* write the message to the log file */
377 	write_to_log(CHAT_SERVER, message, msg_len);
378 
379 	/* if the window already exists, copy new message to end */
380 	if (server_popup_win >= 0)
381 	{
382 		const char *sep_str = "\n\n";
383 		win = &windows_list.window[server_popup_win];
384 
385 		/* resize to hold new message text + separator */
386 		widget_set_size(server_popup_win, textId, 1.0);
387 		resize_text_message_data(&widget_text, widget_text.len + 3*(msg_len+strlen(sep_str)));
388 
389 		/* copy the message text into the text buffer */
390 		safe_strcat(widget_text.data, sep_str, widget_text.size);
391 		safe_strcat(widget_text.data, (const char*)message, widget_text.size);
392 		widget_text.len = strlen(widget_text.data);
393 
394 		/* this will re-wrap the text and add a scrollbar, title and resize widget as required */
395 		resize_handler(win, win->len_x, win->len_y);
396 
397 		/* always show the window */
398 		show_window(server_popup_win);
399 
400 	} else {
401 		/* restart from scratch and initialise the window text widget text buffer */
402 		initialise();
403 		init_text_message(&widget_text, 3*msg_len);
404 		set_text_message_data(&widget_text, (const char*)message);
405 	}
406 
407 	if (server_popup_win < 0){
408 		/* create the window with initial size and location */
409 		win_property_flags = ELW_USE_UISCALE|ELW_DRAGGABLE|ELW_USE_BACKGROUND|ELW_USE_BORDER|ELW_SHOW|ELW_ALPHA_BORDER|ELW_SWITCHABLE_OPAQUE;
410 		server_popup_win = create_window( "", -1, 0,
411 			0, 0, winWidth, winHeight, win_property_flags);
412 		set_window_handler( server_popup_win, ELW_HANDLER_RESIZE, &resize_handler);
413 		set_window_handler( server_popup_win, ELW_HANDLER_CLICK, &click_handler);
414 		set_window_handler( server_popup_win, ELW_HANDLER_UI_SCALE, &ui_scale_handler);
415 		set_window_handler( server_popup_win, ELW_HANDLER_FONT_CHANGE, &font_change_handler);
416 
417 		if (server_popup_win >= 0 && server_popup_win < windows_list.num_windows)
418 			win = &windows_list.window[server_popup_win];
419 
420 		/* create the OK button, setup its click handler and get its structure */
421 		buttonId = button_add_extended (server_popup_win, buttonId, NULL, 0,
422 			 0, 0, 0, 0, win->current_scale, "OK");
423 		widget_set_OnClick(server_popup_win, buttonId, close_handler);
424 	}
425 	if (win == NULL)
426 		return;
427 
428 	/* create the text widget */
429 	if ((!text_message_is_empty (&widget_text)) && (widget_find(server_popup_win, textId) == NULL))
430 	{
431 		textId = text_field_add_extended(server_popup_win, textId, NULL, sep, sep,
432 			window_width, window_height, TEXT_FIELD_NO_KEYPRESS,
433 			CHAT_FONT, 1.0, &widget_text, 1, FILTER_NONE, sep, sep);
434 	}
435 
436 	set_min_window_size(win);
437 	set_actual_window_size(win);
438 } /* end display_server_popup_win() */
439