1 /******************************************
2  * notepad.c - Player notes in xml format *
3  *  includes generic keypress handlers    *
4  *  and popup window functionality        *
5  ******************************************/
6 
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <libxml/parser.h>
11 #include <ctype.h>
12 #include <SDL.h>
13 #include "asc.h"
14 #include "context_menu.h"
15 #include "elwindows.h"
16 #include "errors.h"
17 #include "io/elfilewrapper.h"
18 #include "gamewin.h"
19 #include "gl_init.h"
20 #include "init.h"
21 #include "hud.h"
22 #include "loginwin.h"
23 #include "misc.h"
24 #include "notepad.h"
25 #include "tabs.h"
26 #include "text.h"
27 #include "translate.h"
28 
29 
30 /******************************************
31  *             Popup Section              *
32  ******************************************/
33 
init_ipu(INPUT_POPUP * ipu,int parent,int maxlen,int rows,int cols,void cancel (void *),void input (const char *,void *))34 void init_ipu (INPUT_POPUP *ipu, int parent, int maxlen, int rows, int cols, void cancel(void *), void input(const char *, void *))
35 {
36 	ipu->text_flags = TEXT_FIELD_BORDER|TEXT_FIELD_EDITABLE|TEXT_FIELD_NO_KEYPRESS|TEXT_FIELD_MOUSE_EDITABLE;
37 	ipu->text_flags |= (rows>1) ?TEXT_FIELD_SCROLLBAR :0;
38 
39 	ipu->popup_win = ipu->popup_field = ipu->popup_line = ipu->popup_label
40 		= ipu->popup_ok = ipu->popup_no = -1;
41 	ipu->x = ipu->y = 0;
42 
43 	ipu->popup_cancel = cancel;
44 	ipu->popup_input = input;
45 	ipu->popup_line_text = maxlen ? calloc(maxlen+1, 1) : NULL;
46 	ipu->data = NULL;
47 
48 	ipu->parent = parent;
49 	ipu->maxlen = maxlen;
50 	ipu->rows = rows;
51 	ipu->cols = cols;
52 	ipu->accept_do_not_close = ipu->allow_nonprint_chars = 0;
53 }
54 
clear_popup_window(INPUT_POPUP * ipu)55 void clear_popup_window(INPUT_POPUP *ipu)
56 {
57 	if (ipu->rows == 1)
58 		pword_clear(ipu->popup_win, ipu->popup_line);
59 	else
60 		text_field_clear (ipu->popup_win, ipu->popup_field);
61 	hide_window (ipu->popup_win);
62 }
63 
close_ipu(INPUT_POPUP * ipu)64 void close_ipu (INPUT_POPUP *ipu)
65 {
66 	if (ipu->popup_line_text != NULL)
67 	{
68 		free(ipu->popup_line_text);
69 		ipu->popup_line_text = NULL;
70 	}
71 	if (ipu->popup_win > 0)
72 	{
73 		destroy_window(ipu->popup_win);
74 		clear_text_message_data (&ipu->popup_text);
75 		free_text_message_data (&ipu->popup_text);
76 		init_ipu(ipu, -1, 0, 0, 0, NULL, NULL);
77 	}
78 }
79 
ipu_from_widget(widget_list * w)80 static INPUT_POPUP *ipu_from_widget (widget_list *w)
81 {
82 	if ((w == NULL) || (w->window_id < 0) || (windows_list.num_windows < w->window_id)
83 		|| (windows_list.window[w->window_id].data == NULL))
84 	{
85 		fprintf(stderr, "%s: NULL pointer error\n", __FUNCTION__);
86 		return NULL;
87 	}
88 	return (INPUT_POPUP *)windows_list.window[w->window_id].data;
89 }
90 
ipu_from_window(window_info * win)91 static INPUT_POPUP *ipu_from_window (window_info *win)
92 {
93 	if ((win == NULL) || (win->data == NULL))
94 	{
95 		fprintf(stderr, "%s: NULL pointer error\n", __FUNCTION__);
96 		return NULL;
97 	}
98 	return (INPUT_POPUP *)win->data;
99 }
100 
accept_popup_window(INPUT_POPUP * ipu)101 static void accept_popup_window (INPUT_POPUP *ipu)
102 {
103 	int istart, iend, itmp, len;
104 	unsigned char *data;
105 
106 	if (ipu->rows == 1)
107 	{
108 		data = ipu->popup_line_text;
109 		len = strlen((const char*)data);
110 	}
111 	else
112 	{
113 		data = (unsigned char*)ipu->popup_text.data;
114 		len = ipu->popup_text.len;
115 	}
116 
117 	// skip leading spaces
118 	istart = 0;
119 	while ( istart < len && isspace (data[istart]) )
120 		istart++;
121 	if (istart >= len)
122 		// empty string
123 		return;
124 
125 	// remove soft breaks
126 	iend = itmp = istart;
127 	for (iend = istart, itmp = istart; iend < len; ++iend)
128 	{
129 		if (data[iend] != '\r')
130 			data[itmp++] = data[iend];
131 	}
132 	len = itmp;
133 
134 	// stop at first non-printable character if allow_nonprint_chars not set
135 	if (!ipu->allow_nonprint_chars)
136 	{
137 		iend = istart;
138 		while (iend < len && is_printable(data[iend]))
139 			++iend;
140 		if (iend == istart)
141 			// empty string
142 			return;
143 	}
144 
145 	// send the entered text to the window owner then clear up
146 	data[iend] = '\0';
147 	if (ipu->popup_input != NULL)
148 		(*ipu->popup_input) ((char*)&data[istart], ipu->data);
149 	if (!ipu->accept_do_not_close)
150 		clear_popup_window (ipu);
151 }
152 
popup_cancel_button_handler(widget_list * w,int UNUSED (mx),int UNUSED (my),Uint32 flags)153 static int popup_cancel_button_handler(widget_list *w,
154 	int UNUSED(mx), int UNUSED(my), Uint32 flags)
155 {
156 	INPUT_POPUP *ipu = ipu_from_widget(w);
157 	if (ipu == NULL) return 0;
158 
159 	// only handle mouse button clicks, not scroll wheels moves
160 	if ( (flags & ELW_MOUSE_BUTTON) == 0) return 0;
161 
162 	// call the cancel function of the window owner then clear up
163 	if (ipu->popup_cancel != NULL)
164 		(*ipu->popup_cancel) (ipu->data);
165 	clear_popup_window (ipu);
166 
167 	return 1;
168 }
169 
popup_ok_button_handler(widget_list * w,int UNUSED (mx),int UNUSED (my),Uint32 flags)170 static int popup_ok_button_handler(widget_list *w,
171 	int UNUSED(mx), int UNUSED(my), Uint32 flags)
172 {
173 	INPUT_POPUP *ipu = ipu_from_widget(w);
174 	if (ipu == NULL) return 0;
175 
176 	// only handle mouse button clicks, not scroll wheels moves
177 	if ( (flags & ELW_MOUSE_BUTTON) == 0) return 0;
178 
179 	accept_popup_window (ipu);
180 
181 	return 1;
182 }
183 
popup_keypress_handler(window_info * win,int UNUSED (mx),int UNUSED (my),SDL_Keycode key_code,Uint32 key_unicode,Uint16 key_mod)184 static int popup_keypress_handler(window_info *win,
185 	int UNUSED(mx), int UNUSED(my), SDL_Keycode key_code, Uint32 key_unicode, Uint16 key_mod)
186 {
187 	INPUT_POPUP *ipu = ipu_from_window(win);
188 	if (ipu == NULL) return 0;
189 
190 	if (key_code == SDLK_RETURN || key_code == SDLK_KP_ENTER)
191 	{
192 		accept_popup_window (ipu);
193 		return 1;
194 	}
195 	else if (key_code == SDLK_ESCAPE)
196 	{
197 		if (ipu->popup_cancel != NULL)
198 			(*ipu->popup_cancel) (ipu->data);
199 		clear_popup_window (ipu);
200 		return 1;
201 	}
202 	else
203 	{
204 		// send other key presses to the text field
205 		int widget_id = ipu->rows == 1 ? ipu->popup_line : ipu->popup_field;
206 		widget_list *tfw = widget_find (win->window_id, widget_id);
207 		if (tfw != NULL)
208 		{
209 			// FIXME? This is a bit hackish, we don't allow the
210 			// widget to process keypresses, so that we end up
211 			// in this handler. But now we need the default
212 			// widget handler to take care of this keypress, so
213 			// we clear the flag, let the widget handle it, then
214 			// set the flag again.
215 			int res;
216 			tfw->Flags &= ~TEXT_FIELD_NO_KEYPRESS;
217 			res = widget_handle_keypress (tfw, mx - tfw->pos_x, my - tfw->pos_y, key_code, key_unicode, key_mod);
218 			tfw->Flags |= TEXT_FIELD_NO_KEYPRESS;
219 			return res;
220 		}
221 	}
222 
223 	// shouldn't get here
224 	return 0;
225 }
226 
popup_ui_scale_handler(window_info * win)227 static int popup_ui_scale_handler(window_info *win)
228 {
229 	int seperator = (int)(0.5 + win->current_scale * 10);
230 	int y_len = seperator;
231 	int char_width = get_avg_char_width_zoom(win->font_category, win->current_scale);
232 	int max_x = 0;
233 	int ok_w = 0;
234 	int no_w = 0;
235 	int tmp = 0;
236 	INPUT_POPUP *ipu = ipu_from_window(win);
237 	if (ipu == NULL) return 0;
238 
239 	widget_set_size(win->window_id, ipu->popup_label, win->current_scale);
240 	widget_resize(win->window_id, ipu->popup_label, 0, 0);
241 	max_x = 2 * seperator + widget_get_width(win->window_id, ipu->popup_label);
242 
243 	if (ipu->rows == 1)
244 	{
245 		widget_set_flags(win->window_id, ipu->popup_field, WIDGET_DISABLED);
246 		widget_unset_flags(win->window_id, ipu->popup_line, WIDGET_DISABLED);
247 
248 		widget_set_size(win->window_id, ipu->popup_line, win->current_scale);
249 		widget_resize(win->window_id, ipu->popup_line,
250 			ipu->cols * char_width + 2 * seperator + 2 * 5,
251 			1 + ipu->rows * win->default_font_len_y + 2 * 5);
252 		// FIXME: hack - the text feld scrollbar does not function correct until the next resize, so do it twice for now
253 		widget_resize(win->window_id, ipu->popup_line,
254 			ipu->cols * char_width + 2 * seperator + 2 * 5,
255 			1 + ipu->rows * win->default_font_len_y + 2 * 5);
256 		max_x = max2i(max_x, 2 * seperator + widget_get_width(win->window_id, ipu->popup_line));
257 
258 		button_resize(win->window_id, ipu->popup_ok, 0, 0, win->current_scale);
259 		button_resize(win->window_id, ipu->popup_no, 0, 0, win->current_scale);
260 
261 		ok_w = widget_get_width(win->window_id, ipu->popup_ok);
262 		no_w = widget_get_width(win->window_id, ipu->popup_no);
263 		max_x = max2i(max_x, ok_w + no_w + 3 * seperator);
264 		tmp = (max_x - ok_w - no_w) / 3;
265 
266 		widget_move(win->window_id, ipu->popup_label, (max_x - widget_get_width(win->window_id, ipu->popup_label)) / 2, y_len);
267 		y_len += widget_get_height(win->window_id, ipu->popup_label) + seperator;
268 
269 		widget_move(win->window_id, ipu->popup_line, (max_x - widget_get_width(win->window_id, ipu->popup_line)) / 2, y_len);
270 		y_len += widget_get_height(win->window_id, ipu->popup_line) + seperator;
271 	}
272 	else
273 	{
274 		widget_set_flags(win->window_id, ipu->popup_field, ipu->text_flags);
275 		widget_set_flags(win->window_id, ipu->popup_line, WIDGET_DISABLED);
276 
277 		widget_set_size(win->window_id, ipu->popup_field, win->current_scale);
278 		widget_resize(win->window_id, ipu->popup_field,
279 			ipu->cols * char_width + 2 * seperator + 2 * 5,
280 			1 + ipu->rows * win->default_font_len_y + 2 * 5);
281 		// FIXME: hack - the text feld scrollbar does not function correct until the next resize, so do it twice for now
282 		widget_resize(win->window_id, ipu->popup_field,
283 			ipu->cols * char_width + 2 * seperator + 2 * 5,
284 			1 + ipu->rows * win->default_font_len_y + 2 * 5);
285 		max_x = max2i(max_x, 2 * seperator + widget_get_width(win->window_id, ipu->popup_field));
286 
287 		button_resize(win->window_id, ipu->popup_ok, 0, 0, win->current_scale);
288 		button_resize(win->window_id, ipu->popup_no, 0, 0, win->current_scale);
289 
290 		ok_w = widget_get_width(win->window_id, ipu->popup_ok);
291 		no_w = widget_get_width(win->window_id, ipu->popup_no);
292 		max_x = max2i(max_x, ok_w + no_w + 3 * seperator);
293 		tmp = (max_x - ok_w - no_w) / 3;
294 
295 		widget_move(win->window_id, ipu->popup_label, (max_x - widget_get_width(win->window_id, ipu->popup_label)) / 2, y_len);
296 		y_len += widget_get_height(win->window_id, ipu->popup_label) + seperator;
297 
298 		widget_move(win->window_id, ipu->popup_field, (max_x - widget_get_width(win->window_id, ipu->popup_field)) / 2, y_len);
299 		y_len += widget_get_height(win->window_id, ipu->popup_field) + seperator;
300 	}
301 
302 	widget_move(win->window_id, ipu->popup_ok, tmp, y_len);
303 	widget_move(win->window_id, ipu->popup_no, 2 * tmp + ok_w, y_len);
304 	y_len += widget_get_height(win->window_id, ipu->popup_ok) + seperator;
305 
306 	resize_window(win->window_id, max_x, y_len);
307 
308 	return 1;
309 }
310 
centre_popup_window(INPUT_POPUP * ipu)311 void centre_popup_window(INPUT_POPUP *ipu)
312 {
313 	window_info *win = NULL;
314 	if (ipu == NULL) return;
315 	if (ipu->popup_win < 0 || ipu->popup_win >= windows_list.num_windows) return;
316 
317 	win = &windows_list.window[ipu->popup_win];
318 
319 	if ((ipu->parent > -1) && (ipu->parent < windows_list.num_windows))
320 	{
321 		window_info *pwin = &windows_list.window[ipu->parent];
322 		move_window(win->window_id, win->pos_id,win->pos_loc, win->pos_x + (pwin->len_x - win->len_x) / 2, win->pos_y + (pwin->len_y - win->len_y) / 2);
323 	}
324 	else
325 	{
326 		move_window(win->window_id, win->pos_id,win->pos_loc, (window_width - win->len_x) / 2, (window_height - win->len_y) / 2);
327 	}
328 }
329 
330 
display_popup_win(INPUT_POPUP * ipu,const char * label)331 void display_popup_win (INPUT_POPUP *ipu, const char* label)
332 {
333 	if(ipu->popup_win < 0)
334 	{
335 		window_info *win = NULL;
336 		int widget_id = 100;
337 
338 		Uint32 flags = (ELW_USE_UISCALE|ELW_WIN_DEFAULT) & ~ELW_CLOSE_BOX;
339 		ipu->popup_win = create_window (win_prompt, ipu->parent, 0, ipu->x, ipu->y, 0, 0, flags);
340 
341 		if (ipu->popup_win >= 0 && ipu->popup_win < windows_list.num_windows)
342 			win = &windows_list.window[ipu->popup_win];
343 		else
344 		{
345 			ipu->popup_win = -1;
346 			return;
347 		}
348 
349 		// clear the buffers
350 		*ipu->popup_line_text = '\0';
351 		init_text_message (&ipu->popup_text, ipu->maxlen);
352 		set_text_message_color (&ipu->popup_text, gui_color[0], gui_color[1], gui_color[2]);
353 
354 		// Label
355 		ipu->popup_label = label_add_extended(ipu->popup_win, widget_id++, NULL, 0, 0, 0, win->current_scale, label);
356 
357 		// Input
358 		ipu->popup_field = text_field_add_extended(ipu->popup_win, widget_id++, NULL, 0, 0, 0, 0,
359 			ipu->text_flags, win->font_category, 1.0, &ipu->popup_text, 1, FILTER_ALL, 5, 5);
360 		widget_set_flags(win->window_id, ipu->popup_field, WIDGET_DISABLED);
361 		ipu->popup_line = pword_field_add_extended(ipu->popup_win, widget_id++,
362 			NULL, 0, 0, 0, 0, P_TEXT, 1.0, ipu->popup_line_text, ipu->maxlen);
363 		widget_set_flags(win->window_id, ipu->popup_line, WIDGET_DISABLED);
364 
365 		// Accept
366 		ipu->popup_ok = button_add_extended (ipu->popup_win, widget_id++, NULL, 0, 0, 0, 0, 0, 1.0, button_okay);
367 		widget_set_OnClick (ipu->popup_win, ipu->popup_ok, popup_ok_button_handler);
368 
369 		// Reject
370 		ipu->popup_no = button_add_extended (ipu->popup_win, widget_id++, NULL, 0, 0, 0, 0, 0, 1.0, button_cancel);
371 		widget_set_OnClick (ipu->popup_win, ipu->popup_no, popup_cancel_button_handler);
372 
373 		set_window_handler (ipu->popup_win, ELW_HANDLER_KEYPRESS, (int (*)())&popup_keypress_handler);
374 		set_window_handler (ipu->popup_win, ELW_HANDLER_UI_SCALE, popup_ui_scale_handler);
375 
376 		win->data = ipu;
377 		popup_ui_scale_handler(win);
378 	}
379 	else
380 	{
381 		if ((ipu->parent > -1) && (ipu->parent < windows_list.num_windows))
382 		{
383 			window_info *win = &windows_list.window[ipu->parent];
384 			move_window(ipu->popup_win, ipu->parent, 0, win->pos_x+ipu->x, win->pos_y+ipu->y);
385 		}
386 		text_field_clear(ipu->popup_win, ipu->popup_field);
387 		label_set_text (ipu->popup_win, ipu->popup_label, label);
388 		show_window (ipu->popup_win);
389 		select_window (ipu->popup_win);
390 		popup_ui_scale_handler(&windows_list.window[ipu->popup_win]);
391 	}
392 }
393 
394 
395 /******************************************
396  *             Notepad Section            *
397  ******************************************/
398 
399 //Macro Definitions
400 #define NOTE_LIST_INIT_SIZE 5
401 #define NOTE_NAME_LEN       25
402 
403 #define MIN_NOTE_SIZE	128
404 
405 // Private to this module
406 typedef struct
407 {
408 	char name[NOTE_NAME_LEN];	// Name to display on tab title.
409 	int window;			// Track which window it owns.
410 	int input;			// Track it's text buffer
411 	int button;			// Track it's close button
412 	text_message text;		// Data in the window.
413 	int button_id;			// Button for opening the note
414 } note;
415 
416 static note *note_list = 0;
417 static int note_list_size = 0;
418 static const char* default_file_name = "notes.xml";
419 static const char* character_file_name = "notes_%s.xml";
420 static char notes_file_name[128] = { 0 };
421 static int using_named_notes = 0;
422 static size_t cm_save_id = CM_INIT_VALUE;
423 
424 // Widgets and Windows
425 int notepad_win = -1;
426 static int note_tabcollection_id = -1;
427 static int main_note_tab_id = -1;
428 static int new_note_button_id = -1;
429 static int save_notes_button_id = -1;
430 static int note_button_scroll_id = -1;
431 
432 int notepad_loaded = 0;
433 static const float note_tab_zoom = DEFAULT_SMALL_RATIO * 0.9;
434 static float note_button_zoom = 0;
435 static int note_widget_id = 0;
436 static INPUT_POPUP popup_str;
437 
438 // Misc.
439 static unsigned short nr_notes = 0;
440 static int widget_space = 0;
441 
442 // Note selection button parameters
443 static const int note_button_max_rows = 10;
444 static int note_button_y_offset = 0;
445 static int note_button_width = 0;
446 static int note_button_height = 0;
447 static int note_button_space = 0;
448 
449 // Help message
450 static const char* note_message;
451 static const char* timed_note_message;
452 static Uint32 note_message_timer = 0;
453 
note_button_set_pos(int id)454 void note_button_set_pos (int id)
455 {
456 	int scroll_pos = vscrollbar_get_pos (main_note_tab_id, note_button_scroll_id);
457 	int row = id / 2 - scroll_pos;
458 
459 	if (row < 0 || row >= note_button_max_rows)
460 	{
461 		widget_set_flags (main_note_tab_id, note_list[id].button_id, WIDGET_INVISIBLE);
462 	}
463 	else
464 	{
465 		int x = widget_space + (id % 2) * (note_button_width + widget_space);
466 		int y = note_button_y_offset + (note_button_height + note_button_space) * row;
467 
468 		widget_unset_flags (main_note_tab_id, note_list[id].button_id, WIDGET_INVISIBLE);
469 		widget_move (main_note_tab_id, note_list[id].button_id, x, y);
470 	}
471 }
472 
note_button_scroll_handler()473 static int note_button_scroll_handler()
474 {
475 	int i;
476 
477 	for (i = 0; i < nr_notes; i++)
478 		note_button_set_pos (i);
479 
480 	return 1;
481 }
482 
scroll_to_note_button(int nr)483 static void scroll_to_note_button(int nr)
484 {
485 	int pos = vscrollbar_get_pos (main_note_tab_id, note_button_scroll_id);
486 	int row = nr / 2;
487 
488 	if (row <= pos)
489 	{
490 		vscrollbar_set_pos (main_note_tab_id, note_button_scroll_id, row);
491 		note_button_scroll_handler ();
492 	}
493 	else if (row >= pos + note_button_max_rows)
494 	{
495 		vscrollbar_set_pos (main_note_tab_id, note_button_scroll_id, row - note_button_max_rows + 1);
496 		note_button_scroll_handler ();
497 	}
498 }
499 
update_note_button_scrollbar(int nr)500 static void update_note_button_scrollbar(int nr)
501 {
502 	int nr_rows = (nr_notes+1) / 2;
503 
504 	if (nr_rows <= note_button_max_rows)
505 	{
506 		vscrollbar_set_bar_len (main_note_tab_id, note_button_scroll_id, 0);
507 		scroll_to_note_button (0);
508 	}
509 	else
510 	{
511 		vscrollbar_set_bar_len (main_note_tab_id, note_button_scroll_id, nr_rows - note_button_max_rows);
512 		scroll_to_note_button (nr);
513 	}
514 }
515 
init_note(int id,const char * name,const char * content)516 static void init_note (int id, const char* name, const char* content)
517 {
518 	int nsize = MIN_NOTE_SIZE;
519 
520 	if (content)
521 	{
522 		int len = strlen (content);
523 		while (nsize <= len)
524 			nsize += nsize;
525 	}
526 
527 	init_text_message (&(note_list[id].text), nsize);
528 	set_text_message_data (&(note_list[id].text), content);
529 	set_text_message_color (&(note_list[id].text), gui_color[0], gui_color[1], gui_color[2]);
530 
531 	note_list[id].button_id = -1;
532 	note_list[id].window = -1;
533 	safe_strncpy (note_list[id].name, name, sizeof (note_list[id].name));
534 }
535 
increase_note_storage(void)536 static void increase_note_storage(void)
537 {
538 	int new_size = note_list_size * 2;
539 	note_list = realloc (note_list, new_size * sizeof (note));
540 	memset(&note_list[note_list_size], 0, (new_size - note_list_size) * sizeof (note) );
541 	note_list_size = new_size;
542 }
543 
notepad_load_file()544 static int notepad_load_file()
545 {
546 	xmlDocPtr doc;
547 	xmlNodePtr cur;
548 
549 	if (note_list == 0)
550 	{
551 		note_list = calloc (NOTE_LIST_INIT_SIZE, sizeof (note));
552 		note_list_size = NOTE_LIST_INIT_SIZE;
553 	}
554 
555 	notepad_loaded = 1;
556 
557 	safe_snprintf(notes_file_name, sizeof(notes_file_name), character_file_name, get_lowercase_username());
558 	if (el_file_exists_config(notes_file_name))
559 		using_named_notes = 1;
560 	else
561 	{
562 		using_named_notes = 0;
563 		safe_snprintf(notes_file_name, sizeof(notes_file_name), default_file_name);
564 	}
565 
566 	doc = xmlParseFile (notes_file_name);
567 	if (doc == NULL)
568 	{
569 		LOG_ERROR (cant_parse_notes);
570 		return 0;
571 	}
572 
573 	cur = xmlDocGetRootElement (doc);
574 	if (cur == NULL)
575 	{
576 		// Not an error, just an empty notepad
577 		//LOG_ERROR ("Empty xml notepad. It will be overwritten.");
578 		xmlFreeDoc(doc);
579 		return 0;
580 	}
581 
582 	if (xmlStrcasecmp (cur->name, (const xmlChar *) "PAD"))
583 	{
584 		LOG_ERROR (notes_wrong);
585 		xmlFreeDoc(doc);
586 		return 0;
587 	}
588 
589 	// Load child node
590 	cur = cur->xmlChildrenNode;
591 	// Loop while we have a node, copying ATTRIBS, etc
592 	while (cur != NULL)
593 	{
594 		if ((!xmlStrcasecmp (cur->name, (const xmlChar *)"NOTE")))
595 		{
596 			xmlChar* xmlName = xmlGetProp (cur, BAD_CAST "NAME");
597 			char* name = fromUTF8 (xmlName, strlen ((const char*) xmlName));
598 			char* data = NULL;
599 			if (cur->children)
600 				 data = fromUTF8 (cur->children->content, strlen ((const char*)cur->children->content));
601 
602 			if (nr_notes >= note_list_size)
603 				increase_note_storage();
604 
605 			init_note (nr_notes, name, data);
606 
607 			if (data) free (data);
608 			free (name);
609 			xmlFree (xmlName);
610 
611 			nr_notes++;
612 		}
613 		else if(cur->type == XML_ELEMENT_NODE)
614 		{
615 			LOG_ERROR ("%s: [%s]", wrong_note_node, cur->name);
616 		}
617 		cur = cur->next;         // Advance to the next node.
618 	}
619 	return 1;
620 }
621 
click_save_handler(widget_list * w,int UNUSED (mx),int UNUSED (my),Uint32 flags)622 static int click_save_handler(widget_list *w, int UNUSED(mx), int UNUSED(my),
623 	Uint32 flags)
624 {
625 	// only handle mouse button clicks, not scroll wheels moves
626 	// Update: don't check when saving on exit (w == NULL)
627 	if ( (flags & ELW_MOUSE_BUTTON) == 0 && w != NULL) return 0;
628 
629 	if (notepad_save_file())
630 	{
631 		if (using_named_notes)
632 			timed_note_message = character_notes_saved_str;
633 		else
634 			timed_note_message = note_saved;
635 	}
636 	else
637 		timed_note_message = note_save_failed;
638 	note_message_timer  = SDL_GetTicks();
639 
640 	return 1;
641 }
642 
mouseover_save_handler(widget_list * widget,int mx,int my)643 static int mouseover_save_handler(widget_list *widget, int mx, int my)
644 {
645 	note_message = notes_save_tooltip_str;
646 	return 1;
647 }
648 
cm_set_file_name_handler(window_info * win,int widget_id,int mx,int my,int option)649 static int cm_set_file_name_handler(window_info *win, int widget_id, int mx, int my, int option)
650 {
651 	if (option == 0)
652 	{
653 		cm_grey_line(cm_save_id, 0, 1);
654 		safe_snprintf(notes_file_name, sizeof(notes_file_name), character_file_name, get_lowercase_username());
655 		timed_note_message = using_character_notes_str;
656 		note_message_timer  = SDL_GetTicks();
657 		using_named_notes = 1;
658 		cm_grey_line(cm_save_id, 0, 1);
659 		return 1;
660 	}
661 	else
662 		return 0;
663 }
664 
notepad_save_file()665 int notepad_save_file()
666 {
667 	int i;
668 	char file[256];
669 	xmlDocPtr doc = NULL;                      // document pointer
670 	xmlNodePtr root_node = NULL, node = NULL;  // node pointers
671 
672 	safe_snprintf (file, sizeof (file), "%s%s", configdir, notes_file_name);
673 
674 	doc = xmlNewDoc (BAD_CAST "1.0");
675 	root_node = xmlNewNode (NULL, BAD_CAST "PAD");
676 	xmlDocSetRootElement (doc, root_node);
677 	for (i = 0; i < nr_notes; i++)
678 	{
679 		xmlChar* data;
680 		char* subst_string = NULL;
681 
682 		// libxml2 expects all data in UTF-8 encoding.
683 		xmlChar* name = toUTF8 (note_list[i].name, strlen (note_list[i].name));
684 		substitute_char_with_string (note_list[i].text.data, &subst_string, '&', "&amp;");
685 		data = toUTF8 (subst_string, strlen(subst_string));
686 
687 		node = xmlNewChild (root_node, NULL, BAD_CAST "NOTE", data);
688 		xmlNewProp (node, BAD_CAST "NAME", name);
689 
690 		free (subst_string);
691 		free (data);
692 		free (name);
693 	}
694 
695 	if (xmlSaveFormatFileEnc (file, doc, "UTF-8", 1) < 0)
696 	{
697 #ifndef WINDOWS
698 		// error writing. try the data directory
699 		safe_snprintf (file, sizeof (file), "%s/%s", datadir, notes_file_name);
700 		if (xmlSaveFormatFileEnc(file, doc, "UTF-8", 1) < 0)
701 		{
702 			LOG_ERROR(cant_save_notes, file);
703 		}
704 #else
705 		LOG_ERROR(cant_save_notes, file);
706 #endif
707 		return 0;
708 	}
709 
710 	// Success!
711 	return 1;
712 }
713 
714 
notepad_remove_category(widget_list * UNUSED (w),int UNUSED (mx),int UNUSED (my),Uint32 flags)715 static int notepad_remove_category(widget_list* UNUSED(w),
716 	int UNUSED(mx), int UNUSED(my), Uint32 flags)
717 {
718 	static Uint32 last_click = 0;
719 	int i, id = -1, cur_tab, t;
720 
721 	// only handle mouse button clicks, not scroll wheels moves
722 	if ( (flags & ELW_MOUSE_BUTTON) == 0) return 0;
723 
724 	if (!safe_button_click(&last_click))
725 		return 1;
726 
727 	t = tab_collection_get_tab_id (notepad_win, note_tabcollection_id);
728 	cur_tab = tab_collection_get_tab (notepad_win, note_tabcollection_id);
729 
730 	for (i = 0; i < nr_notes; i++)
731 	{
732 		if (t == note_list[i].window)
733 		{
734 			id = i;
735 			break;
736 		}
737 	}
738 	if (id >= nr_notes || id == -1)
739 	{
740 		return 0;
741 	}
742 
743 	tab_collection_close_tab (notepad_win, note_tabcollection_id, cur_tab);
744 	widget_destroy (main_note_tab_id, note_list[id].button_id);
745 	free_text_message_data (&(note_list[id].text));
746 
747 	// shift all notes after the deleted note one up
748 	if (id < nr_notes-1)
749 	{
750 		memmove (&(note_list[id]), &(note_list[id+1]), (nr_notes-id-1) * sizeof (note));
751 		for ( ; id < nr_notes-1; id++)
752 			note_button_set_pos (id);
753 	}
754 	nr_notes--;
755 
756 	update_note_button_scrollbar(0);
757 
758 	return 1;
759 }
760 
note_tab_destroy(window_info * w)761 static int note_tab_destroy(window_info *w)
762 {
763 	int i;
764 
765 	for(i = 0; i < nr_notes; i++)
766 	{
767 		if (note_list[i].window == w->window_id)
768 		{
769 			note_list[i].window = -1;
770 			return 1;
771 		}
772 	}
773 
774 	return 0;
775 }
776 
mouseover_remove_handler()777 static int mouseover_remove_handler()
778 {
779 	if (!disable_double_click && show_help_text)
780 		note_message = dc_note_remove;
781 	return 1;
782 }
783 
open_note_tab_continued(int id)784 static void open_note_tab_continued(int id)
785 {
786 	widget_list *remove_but = NULL;
787 	window_info *tab_win = NULL;
788 	int tf_x = 0;
789 	int tf_y = 0;
790 	int tf_width = 0;
791 	int tf_height = 0;
792 	int tab;
793 
794 	note_list[id].window = tab_add (notepad_win, note_tabcollection_id, note_list[id].name, 0, 1, ELW_USE_UISCALE);
795 	set_window_custom_scale(note_list[id].window, MW_INFO);
796 	if (note_list[id].window < 0 || note_list[id].window > windows_list.num_windows)
797 		return;
798 	tab_win = &windows_list.window[note_list[id].window];
799 	widget_set_color (notepad_win, note_list[id].window, gui_color[0], gui_color[1], gui_color[2]);
800 	set_window_handler (note_list[id].window, ELW_HANDLER_DESTROY, note_tab_destroy);
801 
802 	// remove button
803 	note_list[id].button = button_add (note_list[id].window, NULL, button_remove_category, widget_space, widget_space);
804 	remove_but = widget_find(note_list[id].window, note_list[id].button);
805 	button_resize(note_list[id].window, note_list[id].button, 0, 0, tab_win->current_scale);
806 	widget_set_OnClick(note_list[id].window, note_list[id].button, notepad_remove_category);
807 	widget_set_OnMouseover(note_list[id].window, note_list[id].button, mouseover_remove_handler);
808 
809 	// input text field
810 	tf_x = widget_space;
811 	tf_y = widget_space * 2 + remove_but->len_y;
812 	tf_width = tab_win->len_x - 2 * widget_space;
813 	tf_height = tab_win->len_y - widget_space * 3 - remove_but->len_y;
814 	note_list[id].input = text_field_add_extended(note_list[id].window, note_widget_id++,
815 		NULL, tf_x, tf_y, tf_width, tf_height,
816 		TEXT_FIELD_BORDER|TEXT_FIELD_EDITABLE|TEXT_FIELD_CAN_GROW|TEXT_FIELD_SCROLLBAR,
817 		NOTE_FONT, tab_win->current_scale, &note_list[id].text,
818 		1, FILTER_ALL, widget_space, widget_space);
819 
820 	tab = tab_collection_get_tab_nr (notepad_win, note_tabcollection_id, note_list[id].window);
821 	tab_collection_select_tab (notepad_win, note_tabcollection_id, tab);
822 }
823 
open_note_tab(widget_list * UNUSED (w),int UNUSED (mx),int UNUSED (my),Uint32 flags)824 static int open_note_tab(widget_list* UNUSED(w),
825 	int UNUSED(mx), int UNUSED(my), Uint32 flags)
826 {
827 	int i = 0;
828 
829 	// only handle mouse button clicks, not scroll wheels moves
830 	if ( (flags & ELW_MOUSE_BUTTON) == 0) return -1;
831 
832 	for(i = 0; i < nr_notes; i++)
833 	{
834 		if (w->id == note_list[i].button_id)
835 		{
836 			if (note_list[i].window < 0)
837 			{
838 				open_note_tab_continued(i);
839 			}
840 			else
841 			{
842 				int tab = tab_collection_get_tab_nr(notepad_win, note_tabcollection_id, note_list[i].window);
843 				tab_collection_select_tab(notepad_win, note_tabcollection_id, tab);
844 			}
845 			return 1;
846 		}
847 	}
848 
849 	// Button not found, shouldn't get here
850 	return 0;
851 }
852 
note_button_add(int nr,int next_id)853 static void note_button_add(int nr, int next_id)
854 {
855 	note_list[nr].button_id = button_add_extended (main_note_tab_id, next_id, NULL, 0, 0, note_button_width, note_button_height, 0, note_button_zoom, note_list[nr].name);
856 	widget_set_OnClick (main_note_tab_id, note_list[nr].button_id, open_note_tab);
857 	note_button_set_pos (nr);
858 	update_note_button_scrollbar (nr);
859 }
860 
notepad_add_continued(const char * name,void * UNUSED (data))861 static void notepad_add_continued(const char *name, void* UNUSED(data))
862 {
863 	int i = nr_notes++;
864 	int potential_id, next_id = 0;
865 
866 	if (i >= note_list_size)
867 		increase_note_storage();
868 
869 	for (potential_id = 0; potential_id < nr_notes; potential_id++)
870 	{
871 		int test_id;
872 		int found_id = 0;
873 		for (test_id=0; test_id<nr_notes; test_id++)
874 		{
875 			widget_list *w = widget_find(main_note_tab_id,
876 				note_list[test_id].button_id);
877 			if (w && w->id == potential_id)
878 			{
879 				found_id = 1;
880 				break;
881 			}
882 		}
883 		if (!found_id)
884 		{
885 			next_id = potential_id;
886 			break;
887 		}
888 	}
889 
890 	init_note (i, name, NULL);
891 	note_button_add (i, next_id);
892 
893 	open_note_tab_continued (i);
894 }
895 
896 
notepad_add_category(widget_list * UNUSED (w),int UNUSED (mx),int UNUSED (my),Uint32 flags)897 static int notepad_add_category(widget_list* UNUSED(w),
898 	int UNUSED(mx), int UNUSED(my), Uint32 flags)
899 {
900 	// only handle mouse button clicks, not scroll wheels moves
901 	if ( (flags & ELW_MOUSE_BUTTON) == 0) return 0;
902 
903 	if (nr_notes >= note_list_size)
904 		increase_note_storage();
905 
906 	display_popup_win (&popup_str, label_note_name);
907 	centre_popup_window (&popup_str);
908 	return 1;
909 }
910 
display_notepad_handler(window_info * win)911 static int display_notepad_handler(window_info *win)
912 {
913 	if (timed_note_message && *timed_note_message)
914 	{
915 		if ((tab_collection_get_tab(notepad_win, note_tabcollection_id) != 0) || (SDL_GetTicks() - note_message_timer > 5000))
916 		{
917 			timed_note_message = NULL;
918 			note_message_timer = 0;
919 		}
920 		else
921 			show_help(timed_note_message, 0, win->len_y+10, win->current_scale);
922 	}
923 	else if (note_message && *note_message)
924 	{
925 		show_help(note_message, 0, win->len_y+10, win->current_scale);
926 		note_message = NULL;
927 	}
928 
929 	return 1;
930 }
931 
click_buttonwin_handler(window_info * UNUSED (win),int UNUSED (mx),int UNUSED (my),Uint32 flags)932 static int click_buttonwin_handler(window_info* UNUSED(win),
933 	int UNUSED(mx), int UNUSED(my), Uint32 flags)
934 {
935 	widget_list *w = widget_find(main_note_tab_id, note_button_scroll_id);
936 	if ((w == NULL) || (w->Flags & WIDGET_INVISIBLE))
937 		return 0;
938 	if (flags&ELW_WHEEL_UP)
939 	{
940 		vscrollbar_scroll_up(main_note_tab_id, note_button_scroll_id);
941 		note_button_scroll_handler();
942 	}
943 	else if(flags&ELW_WHEEL_DOWN)
944 	{
945 		vscrollbar_scroll_down(main_note_tab_id, note_button_scroll_id);
946 		note_button_scroll_handler();
947 	}
948 	return 1;
949 }
950 
resize_buttonwin_handler(window_info * win,int new_width,int new_height)951 static int resize_buttonwin_handler(window_info *win, int new_width, int new_height)
952 {
953 	widget_list *scroll_w = widget_find(win->window_id, note_button_scroll_id);
954 	widget_list *wnew = widget_find(main_note_tab_id, new_note_button_id);
955 	widget_list *wsave = widget_find(main_note_tab_id, save_notes_button_id);
956 	int tab_tag_height = tab_collection_calc_tab_height(win->font_category,
957 		win->current_scale * note_tab_zoom);
958 	int nr;
959 	int but_space;
960 
961 	button_resize(main_note_tab_id, new_note_button_id, 0, 0, win->current_scale);
962 	button_resize(main_note_tab_id, save_notes_button_id, 0, 0, win->current_scale);
963 
964 	but_space = (win->len_x - win->box_size - widget_space * 4) / 2;
965 	if ((scroll_w == NULL) || (scroll_w->Flags & WIDGET_INVISIBLE))
966 		but_space += (win->box_size + widget_space) / 2;
967 
968 	widget_move(main_note_tab_id, new_note_button_id,
969 		widget_space + (but_space - wnew->len_x)/2, 2*widget_space);
970 	widget_move(main_note_tab_id, save_notes_button_id,
971 		2 * widget_space + but_space + (but_space - wsave->len_x)/2, 2*widget_space);
972 
973 	note_button_zoom = win->current_scale * 0.8;
974 	note_button_width = but_space;
975 	note_button_y_offset = (int)(0.5 + 3 * widget_space + wnew->len_y);
976 
977 	for(nr = 0; nr < nr_notes; nr++)
978 		button_resize(main_note_tab_id, note_list[nr].button_id, note_button_width, 0, note_button_zoom);
979 	if (nr_notes > 0)
980 	{
981 		widget_list *tbut = widget_find(main_note_tab_id, note_list[0].button_id);
982 		note_button_height = tbut->len_y;
983 	}
984 	else
985 		note_button_height = (int)(0.5 + win->current_scale * 22);
986 
987 	note_button_space = (int)((float)(win->len_y - note_button_y_offset - tab_tag_height) / (float)note_button_max_rows) - note_button_height;
988 
989 	widget_resize(win->window_id, note_button_scroll_id, win->box_size, note_button_max_rows * (note_button_height + note_button_space) - widget_space);
990 	widget_move(win->window_id, note_button_scroll_id, win->len_x - win->box_size - widget_space, note_button_y_offset);
991 
992 	for(nr = 0; nr < nr_notes; nr++)
993 		note_button_set_pos(nr);
994 
995 	return 0;
996 }
997 
notepad_win_close_tabs(void)998 static void notepad_win_close_tabs(void)
999 {
1000 	widget_list *wid = widget_find (notepad_win, note_tabcollection_id);
1001 	tab_collection *col = NULL;
1002 	int closed_a_tab = -1;
1003 
1004 	if ((wid == NULL) || ((col = (tab_collection *) wid->widget_info) == NULL))
1005 		return;
1006 
1007 	do
1008 	{
1009 		int i;
1010 		closed_a_tab = -1;
1011 		for(i = 0; i < col->nr_tabs; i++)
1012 		{
1013 			if (col->tabs[i].content_id != main_note_tab_id)
1014 			{
1015 				closed_a_tab = tab_collection_close_tab(notepad_win, note_tabcollection_id, i);
1016 				break;
1017 			}
1018 		}
1019 	}
1020 	while (closed_a_tab >= 0);
1021 }
1022 
resize_notepad_handler(window_info * win,int new_width,int new_height)1023 static int resize_notepad_handler(window_info *win, int new_width, int new_height)
1024 {
1025 	widget_list *w = widget_find (win->window_id, note_tabcollection_id);
1026 	int tab_tag_height = 0;
1027 
1028 	notepad_win_close_tabs();
1029 
1030 	widget_space = (int)(0.5 + win->current_scale * 5);
1031 	widget_set_size(win->window_id, note_tabcollection_id, win->current_scale * note_tab_zoom);
1032 	tab_tag_height = tab_collection_calc_tab_height(win->font_category,
1033 		win->current_scale * note_tab_zoom);
1034 
1035 	widget_resize(win->window_id, note_tabcollection_id, new_width, new_height - widget_space);
1036 	widget_move(win->window_id, note_tabcollection_id, 0, widget_space);
1037 
1038 	tab_collection_resize(w, new_width, new_height - widget_space);
1039 	tab_collection_move(w, win->pos_x, win->pos_y + tab_tag_height);
1040 
1041 	close_ipu(&popup_str);
1042 	init_ipu (&popup_str, main_note_tab_id, NOTE_NAME_LEN+1, 1, NOTE_NAME_LEN+2, NULL, notepad_add_continued);
1043 
1044 	update_note_button_scrollbar (0);
1045 
1046 	return 0;
1047 }
1048 
ui_scale_notepad_handler(window_info * win)1049 static int ui_scale_notepad_handler(window_info *win)
1050 {
1051 	int new_width = calc_button_width((const unsigned char*)button_new_category, win->font_category,
1052 		win->current_scale);
1053 	int save_width = calc_button_width((const unsigned char*)button_save_notes, win->font_category,
1054 		win->current_scale);
1055 	int min_width = 2*max2i(new_width, save_width) + 4 * widget_space + ELW_BOX_SIZE;
1056 	win->min_len_x = min_width;
1057 	return 1;
1058 }
1059 
change_notepad_font_handler(window_info * win,font_cat cat)1060 static int change_notepad_font_handler(window_info *win, font_cat cat)
1061 {
1062 	if (cat != win->font_category)
1063 		return 0;
1064 	ui_scale_notepad_handler(win);
1065 	return 1;
1066 }
1067 
fill_notepad_window(int window_id)1068 void fill_notepad_window(int window_id)
1069 {
1070 	int i;
1071 	notepad_win = window_id;
1072 	set_window_custom_scale(window_id, MW_INFO);
1073 	set_window_handler(window_id, ELW_HANDLER_DISPLAY, &display_notepad_handler);
1074 	set_window_handler(window_id, ELW_HANDLER_CLICK, &click_buttonwin_handler);
1075 	set_window_handler(window_id, ELW_HANDLER_RESIZE, &resize_notepad_handler );
1076 	set_window_handler(window_id, ELW_HANDLER_UI_SCALE, &ui_scale_notepad_handler);
1077 	set_window_handler(window_id, ELW_HANDLER_FONT_CHANGE, &change_notepad_font_handler);
1078 	if (window_id >= 0 && window_id < windows_list.num_windows)
1079 		ui_scale_notepad_handler(&windows_list.window[window_id]);
1080 
1081 	note_tabcollection_id = tab_collection_add (window_id, NULL, 0, 0, 0, 0);
1082 	main_note_tab_id = tab_add (window_id, note_tabcollection_id, tab_main, 0, 0, ELW_USE_UISCALE);
1083 	set_window_custom_scale(main_note_tab_id, MW_INFO);
1084 	widget_set_color (window_id, main_note_tab_id, gui_color[0], gui_color[1], gui_color[2]);
1085 	set_window_handler(main_note_tab_id, ELW_HANDLER_CLICK, &click_buttonwin_handler);
1086 	set_window_handler(main_note_tab_id, ELW_HANDLER_RESIZE, &resize_buttonwin_handler);
1087 
1088 	// Add Category
1089 	new_note_button_id = button_add(main_note_tab_id, NULL, button_new_category, 0, 0);
1090 	widget_set_OnClick(main_note_tab_id, new_note_button_id, notepad_add_category);
1091 
1092 	// Save Notes
1093 	save_notes_button_id = button_add(main_note_tab_id, NULL, button_save_notes, 0, 0);
1094 	widget_set_OnClick(main_note_tab_id, save_notes_button_id, click_save_handler);
1095 
1096 	cm_save_id = cm_create(cm_use_character_notepad_str, cm_set_file_name_handler);
1097 	cm_add_widget(cm_save_id, main_note_tab_id, save_notes_button_id);
1098 	widget_set_OnMouseover(main_note_tab_id, save_notes_button_id, mouseover_save_handler);
1099 
1100 	note_button_scroll_id = vscrollbar_add (main_note_tab_id, NULL, 0, 0, 0, 0);
1101 	widget_set_OnClick (main_note_tab_id, note_button_scroll_id, note_button_scroll_handler);
1102 	widget_set_OnDrag (main_note_tab_id, note_button_scroll_id, note_button_scroll_handler);
1103 
1104 	notepad_load_file ();
1105 	// Add the note selection buttons
1106 	for(i = 0; i < nr_notes; i++)
1107 		note_button_add (i, i);
1108 
1109 	if (using_named_notes)
1110 		cm_grey_line(cm_save_id, 0, 1);
1111 }
1112