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