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(¬e_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, '&', "&");
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, ¬e_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