1 /*
2  * Schism Tracker - a cross-platform Impulse Tracker clone
3  * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4  * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5  * copyright (c) 2009 Storlek & Mrs. Brisby
6  * copyright (c) 2010-2012 Storlek
7  * URL: http://schismtracker.org/
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 /* --->> WARNING <<---
25  *
26  * This is an excellent example of how NOT to write a text editor.
27  * IMHO, the best way to add a song message is by writing it in some
28  * other program and attaching it to the song with something like
29  * ZaStaR's ITTXT utility (hmm, maybe I should rewrite that, too ^_^) so
30  * I'm not *really* concerned about the fact that this code completely
31  * sucks. Just remember, this ain't Xcode. */
32 
33 #include "headers.h"
34 
35 #include "song.h"
36 #include "clippy.h"
37 
38 #include <ctype.h>
39 #include <assert.h>
40 
41 /* --------------------------------------------------------------------- */
42 
43 static struct widget widgets_message[1];
44 
45 static int top_line = 0;
46 static int cursor_line = 0;
47 static int cursor_char = 0;
48 /* this is the absolute cursor position from top of message.
49  * (should be updated whenever cursor_line/cursor_char change) */
50 static int cursor_pos = 0;
51 
52 static int edit_mode = 0;
53 
54 /* nonzero => message should use the alternate font */
55 static int message_extfont = 1;
56 
57 /* This is a bit weird... Impulse Tracker usually wraps at 74, but if
58  * the line doesn't have any spaces in it (like in a solid line of
59  * dashes or something) it gets wrapped at 75. I'm using 75 for
60  * everything because it's always nice to have a bit extra space :) */
61 #define LINE_WRAP 75
62 
63 /* --------------------------------------------------------------------- */
64 
65 static int message_handle_key_editmode(struct key_event * k);
66 static int message_handle_key_viewmode(struct key_event * k);
67 
68 /* --------------------------------------------------------------------- */
69 
70 /* returns the number of characters on the nth line of text, setting ptr
71  * to the first character on the line. if it there are fewer than n
72  * lines, ptr is set to the \0 at the end of the string, and the
73  * function returns -1. note: if *ptr == text, weird things will
74  * probably happen, so don't do that. */
get_nth_line(char * text,int n,char ** ptr)75 static int get_nth_line(char *text, int n, char **ptr)
76 {
77 	char *tmp;
78 
79 	assert(text != NULL);
80 
81 	*ptr = text;
82 	while (n > 0) {
83 		n--;
84 		*ptr = strpbrk(*ptr, "\xd\xa");
85 		if (!(*ptr)) {
86 			*ptr = text + strlen(text);
87 			return -1;
88 		}
89 		if ((*ptr)[0] == 13 && (*ptr)[1] == 10)
90 			*ptr += 2;
91 		else
92 			(*ptr)++;
93 	}
94 
95 	tmp = strpbrk(*ptr, "\xd\xa");
96 	return (tmp ? (unsigned) (tmp - *ptr) : strlen(*ptr));
97 }
set_absolute_position(char * text,int pos,int * line,int * ch)98 static void set_absolute_position(char *text, int pos, int *line, int *ch)
99 {
100 	int len;
101 	char *ptr;
102 
103 	*line = *ch = 0;
104 	ptr = NULL;
105 	while (pos > 0) {
106 		len = get_nth_line(text, *line, &ptr);
107 		if (len < 0) {
108 			/* end of file */
109 			(*line) = (*line) - 1;
110 			if (*line < 0) *line = 0;
111 			len = get_nth_line(text, *line, &ptr);
112 			if (len < 0) {
113 				*ch = 0;
114 			} else {
115 				*ch = len;
116 			}
117 			pos = 0;
118 
119 		} else if (len >= pos) {
120 			*ch = pos;
121 			pos = 0;
122 		} else {
123 			pos -= (len+1); /* EOC */
124 			(*line) = (*line) + 1;
125 		}
126 	}
127 }
get_absolute_position(char * text,int line,int character)128 static int get_absolute_position(char *text, int line, int character)
129 {
130 	int len;
131 	char *ptr;
132 
133 	ptr = NULL;
134 	len = get_nth_line(text, line, &ptr);
135 	if (len < 0) {
136 		return 0;
137 	}
138 	/* hrm... what if cursor_char > len? */
139 	return (ptr - text) + character;
140 }
141 
142 /* --------------------------------------------------------------------- */
143 
message_reposition(void)144 static void message_reposition(void)
145 {
146 	if (cursor_line < top_line) {
147 		top_line = cursor_line;
148 	} else if (cursor_line > top_line + 34) {
149 		top_line = cursor_line - 34;
150 	}
151 }
152 
153 /* --------------------------------------------------------------------- */
154 
155 /* returns 1 if a character was actually added */
message_add_char(int newchar,int position)156 static int message_add_char(int newchar, int position)
157 {
158 	int len = strlen(current_song->message);
159 
160 	if (len == MAX_MESSAGE) {
161 		dialog_create(DIALOG_OK, "  Song message too long!  ", NULL, NULL, 0, NULL);
162 		return 0;
163 	}
164 	if (position < 0 || position > len) {
165 		log_appendf(4, "message_add_char: position=%d, len=%d - shouldn't happen!", position, len);
166 		return 0;
167 	}
168 
169 	memmove(current_song->message + position + 1, current_song->message + position, len - position);
170 	current_song->message[len + 1] = 0;
171 	current_song->message[position] = (unsigned char)newchar;
172 	return 1;
173 }
174 
175 /* this returns the new length of the line */
message_wrap_line(char * bol_ptr)176 static int message_wrap_line(char *bol_ptr)
177 {
178 	char *eol_ptr;
179 	char *last_space = NULL;
180 	char *tmp = bol_ptr;
181 
182 	if (!bol_ptr)
183 		/* shouldn't happen, but... */
184 		return 0;
185 
186 	eol_ptr = strpbrk(bol_ptr, "\xd\xa");
187 	if (!eol_ptr)
188 		eol_ptr = bol_ptr + strlen(bol_ptr);
189 
190 	for (;;) {
191 		tmp = strpbrk((tmp + 1), " \t");
192 		if (tmp == NULL || tmp > eol_ptr
193 		    || tmp - bol_ptr > LINE_WRAP)
194 			break;
195 		last_space = tmp;
196 	}
197 
198 	if (last_space) {
199 		*last_space = 13;
200 		return last_space - bol_ptr;
201 	} else {
202 		/* what, no spaces to cut at? chop it mercilessly. */
203 		if (message_add_char(13, bol_ptr + LINE_WRAP - current_song->message)
204 		    == 0)
205 			/* ack, the message is too long to wrap the line!
206 			 * gonna have to resort to something ugly. */
207 			bol_ptr[LINE_WRAP] = 13;
208 		return LINE_WRAP;
209 	}
210 }
211 
212 /* --------------------------------------------------------------------- */
text(char * line,int len,int n)213 static void text(char *line, int len, int n)
214 {
215 	unsigned char ch;
216 	int fg = (message_extfont ? 12 : 6);
217 	int  i;
218 
219 	for (i = 0; line[i] && i < len; i++) {
220 		ch = line[i];
221 
222 		if (ch == ' ') {
223 			draw_char(' ', 2+i, 13+n, 3,0);
224 		} else {
225 			(message_extfont ? draw_char_bios : draw_char)(ch, 2+i, 13+n, fg, 0);
226 		}
227 	}
228 }
229 
message_draw(void)230 static void message_draw(void)
231 {
232 	char *line, *prevline = current_song->message;
233 	int len = get_nth_line(current_song->message, top_line, &line);
234 	int n, cp, clipl, clipr;
235 	int skipc, cutc;
236 
237 	draw_fill_chars(2, 13, 77, 47, 0);
238 
239 	if (clippy_owner(CLIPPY_SELECT) == widgets_message) {
240 		clipl = widgets_message[0].clip_start;
241 		clipr = widgets_message[0].clip_end;
242 		if (clipl > clipr) {
243 			cp = clipl;
244 			clipl = clipr;
245 			clipr = cp;
246 		}
247 	} else {
248 		clipl = clipr = -1;
249 	}
250 
251 	for (n = 0; n < 35; n++) {
252 		if (len < 0) {
253 			break;
254 		} else if (len > 0) {
255 			/* FIXME | shouldn't need this check here,
256 			 * FIXME | because the line should already be
257 			 * FIXME | short enough to fit */
258 			if (len > LINE_WRAP)
259 				len = LINE_WRAP;
260 			text(line, len, n);
261 
262 			if (clipl > -1) {
263 				cp = line - current_song->message;
264 				skipc = clipl - cp;
265 				cutc = clipr - clipl;
266 				if (skipc < 0) {
267 					cutc += skipc; /* ... -skipc */
268 					skipc = 0;
269 				}
270 				if (cutc < 0) cutc = 0;
271 				if (cutc > (len-skipc)) cutc = (len-skipc);
272 				if (cutc > 0 && skipc < len) {
273 					if (message_extfont)
274 						draw_text_bios_len(line+skipc, cutc, 2+skipc, 13 + n, 6, 8);
275 					else
276 						draw_text_len(line+skipc, cutc, 2+skipc, 13 + n, 6, 8);
277 				}
278 			}
279 		}
280 		if (edit_mode) {
281 			draw_char(20, 2 + len, 13 + n, 1, 0);
282 		}
283 		prevline = line;
284 		len = get_nth_line(prevline, 1, &line);
285 	}
286 
287 	if (edit_mode && len < 0) {
288 		/* end of the message */
289 		len = get_nth_line(prevline, 0, &line);
290 		/* FIXME: see above */
291 		if (len > LINE_WRAP)
292 			len = LINE_WRAP;
293 		draw_char(20, 2 + len, 13 + n - 1, 2, 0);
294 	}
295 
296 	if (edit_mode) {
297 		/* draw the cursor */
298 		len = get_nth_line(current_song->message, cursor_line, &line);
299 
300 		/* FIXME: ... ugh */
301 		if (len > LINE_WRAP)
302 			len = LINE_WRAP;
303 		if (cursor_char > LINE_WRAP + 1)
304 			cursor_char = LINE_WRAP + 1;
305 
306 		if (cursor_char >= len) {
307 			(message_extfont ? draw_char_bios : draw_char)
308 				(20, 2 + cursor_char, 13 + (cursor_line - top_line), 0, 3);
309 		} else {
310 			(message_extfont ? draw_char_bios : draw_char)
311 				(line[cursor_char], 2 + cursor_char, 13 + (cursor_line - top_line), 8, 3);
312 		}
313 	}
314 }
315 
316 /* --------------------------------------------------------------------- */
317 
message_set_editmode(void)318 static inline void message_set_editmode(void)
319 {
320 	edit_mode = 1;
321 	widgets_message[0].accept_text = 1;
322 	top_line = cursor_line = cursor_char = cursor_pos = 0;
323 	widgets_message[0].d.other.handle_key = message_handle_key_editmode;
324 
325 	status.flags |= NEED_UPDATE;
326 }
327 
message_set_viewmode(void)328 static inline void message_set_viewmode(void)
329 {
330 	edit_mode = 0;
331 	widgets_message[0].accept_text = 0;
332 	widgets_message[0].d.other.handle_key = message_handle_key_viewmode;
333 
334 	status.flags |= NEED_UPDATE;
335 }
336 
337 /* --------------------------------------------------------------------- */
338 
message_insert_char(int c)339 static void message_insert_char(int c)
340 {
341 	char *ptr;
342 	int n;
343 
344 	if (!edit_mode)
345 		return;
346 
347 	memused_songchanged();
348 	if (c == '\t') {
349 		/* Find the number of characters until the next tab stop.
350 		 * (This is new behaviour; Impulse Tracker just inserts
351 		 * eight characters regardless of the cursor position.) */
352 		n = 8 - cursor_char % 8;
353 		if (cursor_char + n > LINE_WRAP) {
354 			message_insert_char('\r');
355 		} else {
356 			do {
357 				if (!message_add_char(' ', cursor_pos))
358 					break;
359 				cursor_char++;
360 				cursor_pos++;
361 				n--;
362 			} while (n);
363 		}
364 	} else if (c < 32 && c != '\r') {
365 		return;
366 	} else {
367 		if (!message_add_char(c, cursor_pos))
368 			return;
369 		cursor_pos++;
370 		if (c == '\r') {
371 			cursor_char = 0;
372 			cursor_line++;
373 		} else {
374 			cursor_char++;
375 		}
376 	}
377 	if (get_nth_line(current_song->message, cursor_line, &ptr) >= LINE_WRAP) {
378 		message_wrap_line(ptr);
379 	}
380 	if (cursor_char >= LINE_WRAP) {
381 		cursor_char = get_nth_line(current_song->message, ++cursor_line, &ptr);
382 		cursor_pos = get_absolute_position(current_song->message, cursor_line, cursor_char);
383 	}
384 
385 	message_reposition();
386 	status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE;
387 }
388 
message_delete_char(void)389 static void message_delete_char(void)
390 {
391 	int len = strlen(current_song->message);
392 	char *ptr;
393 
394 	if (cursor_pos == 0)
395 		return;
396 	memmove(current_song->message + cursor_pos - 1, current_song->message + cursor_pos,
397 		len - cursor_pos + 1);
398 	current_song->message[MAX_MESSAGE] = 0;
399 	cursor_pos--;
400 	if (cursor_char == 0) {
401 		cursor_line--;
402 		cursor_char = get_nth_line(current_song->message, cursor_line, &ptr);
403 	} else {
404 		cursor_char--;
405 	}
406 
407 	message_reposition();
408 	status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE;
409 }
410 
message_delete_next_char(void)411 static void message_delete_next_char(void)
412 {
413 	int len = strlen(current_song->message);
414 
415 	if (cursor_pos == len)
416 		return;
417 	memmove(current_song->message + cursor_pos, current_song->message + cursor_pos + 1,
418 		len - cursor_pos);
419 	current_song->message[MAX_MESSAGE] = 0;
420 
421 	status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE;
422 }
423 
message_delete_line(void)424 static void message_delete_line(void)
425 {
426 	int len;
427 	int movelen;
428 	char *ptr;
429 
430 	len = get_nth_line(current_song->message, cursor_line, &ptr);
431 	if (len < 0)
432 		return;
433 	if (ptr[len] == 13 && ptr[len + 1] == 10)
434 		len++;
435 	movelen = (current_song->message + strlen(current_song->message) - ptr);
436 	if (movelen == 0)
437 		return;
438 	memmove(ptr, ptr + len + 1, movelen);
439 	len = get_nth_line(current_song->message, cursor_line, &ptr);
440 	if (cursor_char > len) {
441 		cursor_char = len;
442 		cursor_pos = get_absolute_position(current_song->message, cursor_line, cursor_char);
443 	}
444 	message_reposition();
445 	status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE;
446 }
447 
message_clear(UNUSED void * data)448 static void message_clear(UNUSED void *data)
449 {
450 	current_song->message[0] = 0;
451 	memused_songchanged();
452 	message_set_viewmode();
453 	status.flags |= SONG_NEEDS_SAVE;
454 }
455 
456 /* --------------------------------------------------------------------- */
457 
prompt_message_clear(void)458 static void prompt_message_clear(void)
459 {
460 	dialog_create(DIALOG_OK_CANCEL, "Clear song message?", message_clear, NULL, 1, NULL);
461 }
462 
463 /* --------------------------------------------------------------------- */
464 
message_handle_key_viewmode(struct key_event * k)465 static int message_handle_key_viewmode(struct key_event * k)
466 {
467 	if (k->state == KEY_PRESS) {
468 		if (k->mouse == MOUSE_SCROLL_UP) {
469 			top_line -= MOUSE_SCROLL_LINES;
470 		} else if (k->mouse == MOUSE_SCROLL_DOWN) {
471 			top_line += MOUSE_SCROLL_LINES;
472 		} else if (k->mouse == MOUSE_CLICK) {
473 			message_set_editmode();
474 			return message_handle_key_editmode(k);
475 		}
476 	}
477 
478 	switch (k->sym) {
479 	case SDLK_UP:
480 		if (k->state == KEY_RELEASE)
481 			return 0;
482 		top_line--;
483 		break;
484 	case SDLK_DOWN:
485 		if (k->state == KEY_RELEASE)
486 			return 0;
487 		top_line++;
488 		break;
489 	case SDLK_PAGEUP:
490 		if (k->state == KEY_RELEASE)
491 			return 0;
492 		top_line -= 35;
493 		break;
494 	case SDLK_PAGEDOWN:
495 		if (k->state == KEY_RELEASE)
496 			return 0;
497 		top_line += 35;
498 		break;
499 	case SDLK_HOME:
500 		if (k->state == KEY_RELEASE)
501 			return 0;
502 		top_line = 0;
503 		break;
504 	case SDLK_END:
505 		if (k->state == KEY_RELEASE)
506 			return 0;
507 		top_line = get_num_lines(current_song->message) - 34;
508 		break;
509 	case SDLK_t:
510 		if (k->state == KEY_RELEASE)
511 			return 0;
512 		if (k->mod & KMOD_CTRL) {
513 			message_extfont = !message_extfont;
514 			break;
515 		}
516 		return 1;
517 	case SDLK_RETURN:
518 		if (k->state == KEY_PRESS)
519 			return 0;
520 		message_set_editmode();
521 		return 1;
522 	default:
523 		return 0;
524 	}
525 
526 	if (top_line < 0)
527 		top_line = 0;
528 
529 	status.flags |= NEED_UPDATE;
530 
531 	return 1;
532 }
_delete_selection(void)533 static void _delete_selection(void)
534 {
535 	int len = strlen(current_song->message);
536 	int eat;
537 
538 	cursor_pos = widgets_message[0].clip_start;
539 	if (cursor_pos > widgets_message[0].clip_end) {
540 		cursor_pos = widgets_message[0].clip_end;
541 		eat = widgets_message[0].clip_start - cursor_pos;
542 	} else {
543 		eat = widgets_message[0].clip_end - cursor_pos;
544 	}
545 	clippy_select(NULL, NULL, 0);
546 	if (cursor_pos == len)
547 		return;
548 	memmove(current_song->message + cursor_pos, current_song->message + cursor_pos + eat + 1,
549 		((len - cursor_pos) - eat)+1);
550 	current_song->message[MAX_MESSAGE] = 0;
551 	set_absolute_position(current_song->message, cursor_pos, &cursor_line, &cursor_char);
552 	message_reposition();
553 
554 	status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE;
555 }
556 
message_handle_key_editmode(struct key_event * k)557 static int message_handle_key_editmode(struct key_event * k)
558 {
559 	int line_len, num_lines = -1;
560 	int new_cursor_line = cursor_line;
561 	int new_cursor_char = cursor_char;
562 	char *ptr;
563 	int doing_drag = 0;
564 	int clipl, clipr, cp;
565 
566 	if (k->mouse == MOUSE_SCROLL_UP) {
567 		if (k->state == KEY_RELEASE)
568 			return 0;
569 		new_cursor_line -= MOUSE_SCROLL_LINES;
570 	} else if (k->mouse == MOUSE_SCROLL_DOWN) {
571 		if (k->state == KEY_RELEASE)
572 			return 0;
573 		new_cursor_line += MOUSE_SCROLL_LINES;
574 	} else if (k->mouse == MOUSE_CLICK && k->mouse_button == 2) {
575 		if (k->state == KEY_RELEASE)
576 			status.flags |= CLIPPY_PASTE_SELECTION;
577 		return 1;
578 	} else if (k->mouse == MOUSE_CLICK) {
579 		if (k->x >= 2 && k->x <= 77 && k->y >= 13 && k->y <= 47) {
580 			new_cursor_line = (k->y - 13) + top_line;
581 			new_cursor_char = (k->x - 2);
582 			if (k->sx != k->x || k->sy != k->y) {
583 				/* yay drag operation */
584 				cp = get_absolute_position(current_song->message, (k->sy-13)+top_line,
585 							(k->sx-2));
586 				widgets_message[0].clip_start = cp;
587 				doing_drag = 1;
588 			}
589 		}
590 	}
591 
592 	line_len = get_nth_line(current_song->message, cursor_line, &ptr);
593 
594 
595 	switch (k->sym) {
596 	case SDLK_UP:
597 		if (!NO_MODIFIER(k->mod))
598 			return 0;
599 		if (k->state == KEY_RELEASE)
600 			return 1;
601 		new_cursor_line--;
602 		break;
603 	case SDLK_DOWN:
604 		if (!NO_MODIFIER(k->mod))
605 			return 0;
606 		if (k->state == KEY_RELEASE)
607 			return 1;
608 		new_cursor_line++;
609 		break;
610 	case SDLK_LEFT:
611 		if (!NO_MODIFIER(k->mod))
612 			return 0;
613 		if (k->state == KEY_RELEASE)
614 			return 1;
615 		new_cursor_char--;
616 		break;
617 	case SDLK_RIGHT:
618 		if (!NO_MODIFIER(k->mod))
619 			return 0;
620 		if (k->state == KEY_RELEASE)
621 			return 1;
622 		new_cursor_char++;
623 		break;
624 	case SDLK_PAGEUP:
625 		if (!NO_MODIFIER(k->mod))
626 			return 0;
627 		if (k->state == KEY_RELEASE)
628 			return 1;
629 		new_cursor_line -= 35;
630 		break;
631 	case SDLK_PAGEDOWN:
632 		if (!NO_MODIFIER(k->mod))
633 			return 0;
634 		if (k->state == KEY_RELEASE)
635 			return 1;
636 		new_cursor_line += 35;
637 		break;
638 	case SDLK_HOME:
639 		if (k->state == KEY_RELEASE)
640 			return 1;
641 		if (k->mod & KMOD_CTRL)
642 			new_cursor_line = 0;
643 		else
644 			new_cursor_char = 0;
645 		break;
646 	case SDLK_END:
647 		if (k->state == KEY_RELEASE)
648 			return 1;
649 		if (k->mod & KMOD_CTRL) {
650 			num_lines = get_num_lines(current_song->message);
651 			new_cursor_line = num_lines;
652 		} else {
653 			new_cursor_char = line_len;
654 		}
655 		break;
656 	case SDLK_ESCAPE:
657 		if (!NO_MODIFIER(k->mod))
658 			return 0;
659 		if (k->state == KEY_RELEASE)
660 			return 1;
661 		message_set_viewmode();
662 		memused_songchanged();
663 		return 1;
664 	case SDLK_BACKSPACE:
665 		if (!NO_MODIFIER(k->mod))
666 			return 0;
667 		if (k->state == KEY_RELEASE)
668 			return 1;
669 		if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) {
670 			_delete_selection();
671 		} else {
672 			message_delete_char();
673 		}
674 		return 1;
675 	case SDLK_DELETE:
676 		if (!NO_MODIFIER(k->mod))
677 			return 0;
678 		if (k->state == KEY_RELEASE)
679 			return 1;
680 		if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) {
681 			_delete_selection();
682 		} else {
683 			message_delete_next_char();
684 		}
685 		return 1;
686 	default:
687 		if (k->mod & KMOD_CTRL) {
688 			if (k->state == KEY_RELEASE)
689 				return 1;
690 			if (k->sym == SDLK_t) {
691 				message_extfont = !message_extfont;
692 				break;
693 			} else if (k->sym == SDLK_y) {
694 				clippy_select(NULL, NULL, 0);
695 				message_delete_line();
696 				break;
697 			}
698 		} else if (k->mod & KMOD_ALT) {
699 			if (k->state == KEY_RELEASE)
700 				return 1;
701 			if (k->sym == SDLK_c) {
702 				prompt_message_clear();
703 				return 1;
704 			}
705 		} else if (k->mouse == MOUSE_NONE) {
706 			if (k->unicode == '\r' || k->unicode == '\t'
707 			|| k->unicode >= 32) {
708 				if (k->state == KEY_RELEASE)
709 					return 1;
710 				if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) {
711 					_delete_selection();
712 				}
713 				if (k->mod & (KMOD_SHIFT|KMOD_CAPS)) {
714 					message_insert_char(toupper((unsigned int)k->unicode));
715 				} else {
716 					message_insert_char(k->unicode);
717 				}
718 				return 1;
719 			}
720 			return 0;
721 		}
722 
723 		if (k->mouse != MOUSE_CLICK)
724 			return 0;
725 
726 		if (k->state == KEY_RELEASE)
727 			return 1;
728 		if (!doing_drag) {
729 			clippy_select(NULL, NULL, 0);
730 		}
731 	}
732 
733 	if (new_cursor_line != cursor_line) {
734 		if (num_lines == -1)
735 			num_lines = get_num_lines(current_song->message);
736 
737 		if (new_cursor_line < 0)
738 			new_cursor_line = 0;
739 		else if (new_cursor_line > num_lines)
740 			new_cursor_line = num_lines;
741 
742 		/* make sure the cursor doesn't go past the new eol */
743 		line_len = get_nth_line(current_song->message, new_cursor_line, &ptr);
744 		if (new_cursor_char > line_len)
745 			new_cursor_char = line_len;
746 
747 		cursor_char = new_cursor_char;
748 		cursor_line = new_cursor_line;
749 	} else if (new_cursor_char != cursor_char) {
750 	/* we say "else" here ESPECIALLY because the mouse can only come
751 	in the top section - not because it's some clever optimization */
752 		if (new_cursor_char < 0) {
753 			if (cursor_line == 0) {
754 				new_cursor_char = cursor_char;
755 			} else {
756 				cursor_line--;
757 				new_cursor_char =
758 					get_nth_line(current_song->message, cursor_line, &ptr);
759 			}
760 
761 		} else if (new_cursor_char >
762 			   get_nth_line(current_song->message, cursor_line, &ptr)) {
763 			if (cursor_line == get_num_lines(current_song->message)) {
764 				new_cursor_char = cursor_char;
765 			} else {
766 				cursor_line++;
767 				new_cursor_char = 0;
768 			}
769 		}
770 		cursor_char = new_cursor_char;
771 	}
772 
773 	message_reposition();
774 	cursor_pos = get_absolute_position(current_song->message, cursor_line, cursor_char);
775 
776 	if (doing_drag) {
777 		widgets_message[0].clip_end = cursor_pos;
778 
779 		clipl = widgets_message[0].clip_start;
780 		clipr = widgets_message[0].clip_end;
781 		if (clipl > clipr) {
782 			cp = clipl;
783 			clipl = clipr;
784 			clipr = cp;
785 		}
786 		clippy_select(widgets_message, (current_song->message+clipl), clipr-clipl);
787 	}
788 
789 	status.flags |= NEED_UPDATE;
790 
791 	return 1;
792 }
793 
794 /* --------------------------------------------------------------------- */
795 
message_draw_const(void)796 static void message_draw_const(void)
797 {
798 	draw_box(1, 12, 78, 48, BOX_THICK | BOX_INNER | BOX_INSET);
799 }
800 
song_changed_cb(void)801 static void song_changed_cb(void)
802 {
803 	char *line, *prevline;
804 	int len;
805 
806 	edit_mode = 0;
807 	widgets_message[0].accept_text = 0;
808 	widgets_message[0].d.other.handle_key = message_handle_key_viewmode;
809 	top_line = 0;
810 
811 	len = get_nth_line(current_song->message, 0, &line);
812 	while (len >= 0) {
813 		if (len > LINE_WRAP)
814 			message_wrap_line(line);
815 		prevline = line;
816 		len = get_nth_line(prevline, 1, &line);
817 	}
818 
819 	if (status.current_page == PAGE_MESSAGE)
820 		status.flags |= NEED_UPDATE;
821 }
822 
823 /* --------------------------------------------------------------------- */
824 
message_load_page(struct page * page)825 void message_load_page(struct page *page)
826 {
827 	page->title = "Message Editor (Shift-F9)";
828 	page->draw_const = message_draw_const;
829 	page->song_changed_cb = song_changed_cb;
830 	page->total_widgets = 1;
831 	page->widgets = widgets_message;
832 	page->help_index = HELP_MESSAGE_EDITOR;
833 
834 	create_other(widgets_message + 0, 0, message_handle_key_viewmode, message_draw);
835 	widgets_message[0].accept_text = edit_mode;
836 }
837 
message_reset_selection(void)838 void message_reset_selection(void)
839 {
840 	widgets_message[0].clip_start = widgets_message[0].clip_end = 0;
841 }
842