1 /**@file texteditor/texteditor.c  Text editor/viewer.
2  * $Id: texteditor.c,v 1.6 2005/07/03 00:47:11 kvance Exp $
3  * @author Ryan Phillips
4  *
5  * Copyright (C) 2003 Ryan Phillips <bitman@users.sf.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 
27 #include "texteditor.h"
28 #include "display.h"
29 
30 #include "kevedit/screen.h"
31 
32 #include "structures/svector.h"
33 #include "zzm.h"
34 #include "libzzt2/zztoop.h"
35 #include "display/colours.h"
36 
37 #include "register.h"
38 #include "select.h"
39 #include "help/help.h"
40 
41 #include "themes/theme.h"
42 
43 #include "synth/synth.h"
44 #include "synth/zzm.h"
45 
46 #include "display/display.h"
47 
48 #include <stdlib.h>
49 #include <stdio.h>
50 #include <string.h>
51 #include <ctype.h>
52 
53 int texteditReadyToEdit(texteditor * editor);
54 void texteditHandleInput(texteditor * editor);
55 
56 void texteditHandleScrolling(texteditor * editor);
57 void texteditHandleEditMovement(texteditor * editor);
58 void texteditHandleEditKey(texteditor * editor);
59 
60 int texteditGrabTitle(texteditor * editor);
61 int texteditIgnoreFirstLine(texteditor * editor);
62 void texteditValidatePosition(texteditor * editor);
63 
64 void texteditCursorLeft(texteditor * editor);
65 void texteditCursorRight(texteditor * editor);
66 void texteditHelpZOC(texteditor * editor);
67 
68 void texteditZZMPlay(texteditor * editor, int slurflag);
69 void texteditZZMLoad(texteditor * editor);
70 void texteditZZMRip(texteditor * editor);
71 
72 void texteditFileOpen(texteditor * editor, int insertflag);
73 void texteditFileSave(texteditor * editor, char * prompt);
74 
75 void texteditInsertSpaces(texteditor * editor, int count);
76 void texteditInsertNewline(texteditor * editor);
77 
78 void texteditBackspace(texteditor * editor);
79 void texteditDelete(texteditor * editor);
80 void texteditDeleteLine(texteditor * editor);
81 
82 void texteditInsertASCII(texteditor * editor);
83 void texteditInsertCharacter(texteditor * editor, int ch);
84 void texteditInsertCharAndWrap(texteditor * editor, int ch);
85 
86 
87 /**
88  * @relates texteditor
89  * @brief Create a text editor.
90  *
91  * @param title  Dialog title to use by default.
92  * @param text   Text to be editted or viewed. Use NULL to start with empty
93  *               text.
94  * @param d      A display method.
95  *
96  * Editing or viewing begins on the line indicated by text->cur.
97  *
98  * You may modify highlightflag, insertflag, and wrapwidth before
99  * using the texteditor object.
100  *
101  * @return a new texteditor.
102  **/
createtexteditor(char * title,stringvector * text,displaymethod * d)103 texteditor * createtexteditor(char * title, stringvector * text, displaymethod * d)
104 {
105 	texteditor * editor = (texteditor *) malloc(sizeof(texteditor));
106 
107 	if (editor == NULL)
108 		return NULL;
109 
110 	editor->d = d;
111 	editor->title = str_dup(title);
112 
113 	/* Initialize the OOP drawer. */
114 	zztoopInitDrawer(&(editor->drawer));
115 	editor->drawer.display = editor->d;
116 	editor->drawer.length = TEXTED_MAXWIDTH;
117 
118 	editor->text = text;
119 
120 	/* If no text is provided, allocate */
121 	if (editor->text == NULL) {
122 		editor->text = (stringvector *) malloc(sizeof(stringvector));
123 		initstringvector(editor->text);
124 	}
125 
126 	/* Remember, text may be NULL, so use editor->text. */
127 	editor->curline = editor->text->cur;
128 	editor->pos = 0;
129 
130 	editor->editflag = 1;
131 	editor->highlightflag = 1;
132 	editor->insertflag = 1;
133 
134 	editor->linewidth = TEXTED_MAXWIDTH + 1;
135 	editor->wrapwidth = TEXTED_MAXWIDTH;
136 
137 	editor->updateflags = TUD_ALL;
138 	editor->exitflag = 0;
139 
140 	editor->key = DKEY_NONE;
141 
142 	editor->selectflag = 0;
143 	editor->selectpos = -1;
144 	editor->selectlineoffset = 0;
145 
146 	return editor;
147 }
148 
149 /**
150  * @relates texteditor
151  * @brief Delete a texteditor.
152  *
153  * Call this function on a texteditor when it is no longer needed.
154  * editor->text will not be affected.
155  **/
deletetexteditor(texteditor * editor)156 void deletetexteditor(texteditor * editor)
157 {
158 	if (editor == NULL)
159 		return;
160 
161 	if (editor->title != NULL) {
162 		free(editor->title);
163 		editor->title = NULL;
164 	}
165 
166 	free(editor);
167 }
168 
169 /**
170  * @relates texteditor
171  * @brief Delete the text used by a text editor.
172  *
173  * Call this before deletetexteditor() if the text is no longer
174  * needed and can be free()ed.
175  **/
deletetexteditortext(texteditor * editor)176 void deletetexteditortext(texteditor * editor)
177 {
178 	if (editor->text == NULL)
179 		return;
180 
181 	deletestringvector(editor->text);
182 
183 	editor->text = NULL;
184 }
185 
186 /**
187  * @relates texteditor
188  * @brief Perform text editing.
189  **/
textedit(texteditor * editor)190 void textedit(texteditor * editor)
191 {
192 	if (!texteditReadyToEdit(editor))
193 		return;
194 
195 	while (!editor->exitflag) {
196 		/* Start by updating the display. */
197 		texteditUpdateDisplay(editor);
198 
199 		/* Get a key from the display. */
200 		editor->key = editor->d->getch();
201 
202 		/* Handle the input. */
203 		texteditHandleInput(editor);
204 	}
205 
206 	editor->text->cur = editor->curline;
207 }
208 
209 
210 /**
211  * @relates texteditor
212  * @brief Prepare an editor before editing starts.
213  *
214  * @returns true if the editor is ready.
215  **/
texteditReadyToEdit(texteditor * editor)216 int texteditReadyToEdit(texteditor * editor)
217 {
218 	/* An editor without text is like a party without music. */
219 	if (editor->text == NULL)
220 		return 0;
221 
222 	/* If the text is empty, provide a blank line */
223 	if (editor->text->cur == NULL)
224 		pushstring(editor->text, str_duplen("", editor->linewidth));
225 
226 	/* Try grabbing a title from the text itself */
227 	if (texteditGrabTitle(editor))
228 		/* When not editing, make sure curline is not the first line */
229 		if (!editor->editflag && editor->curline == editor->text->first)
230 			editor->curline = editor->curline->next;
231 
232 	/* If the current line is NULL, grab the current line from text. */
233 	if (editor->curline == NULL)
234 		editor->curline = editor->text->cur;
235 
236 	editor->updateflags |= TUD_ALL;
237 
238 	return 1;
239 }
240 
241 /**
242  * @relates texteditor
243  * @brief Handle all possible types of input.
244  **/
texteditHandleInput(texteditor * editor)245 void texteditHandleInput(texteditor * editor)
246 {
247 	/* Check for exit key. */
248 	if (editor->key == DKEY_ESC)
249 		editor->exitflag = 1;
250 
251 	/* Allow user to copy text. */
252 	texteditHandleCopy(editor);
253 	/* Perform editting. */
254 	texteditHandleEditKey(editor);
255 
256 	/* Allow user to select text. */
257 	texteditHandleSelection(editor);
258 
259 	/* Handle cursor movements. */
260 	texteditHandleScrolling(editor);
261 	texteditHandleEditMovement(editor);
262 }
263 
264 /**
265  * @relates texteditor
266  * @brief Handle scrolling keys.
267  **/
texteditHandleScrolling(texteditor * editor)268 void texteditHandleScrolling(texteditor * editor)
269 {
270 	int i;
271 
272 	switch (editor->key) {
273 		case DKEY_UP:  /* Up Arrow */
274 			if (editor->curline->prev == NULL ||
275 			    (texteditIgnoreFirstLine(editor) &&
276 			     editor->curline->prev == editor->text->first))
277 				return;
278 
279 			editor->curline = editor->curline->prev;
280 
281 			texteditValidatePosition(editor);
282 
283 			/* Selection offset increases */
284 			if (editor->selectflag)
285 				editor->selectlineoffset++;
286 
287 			editor->updateflags |= TUD_EDITAREA;
288 			return;
289 
290 		case DKEY_DOWN:  /* Down Arrow */
291 			if (editor->curline->next == NULL)
292 				return;
293 
294 			editor->curline = editor->curline->next;
295 
296 			texteditValidatePosition(editor);
297 
298 			/* Selection offset decreases */
299 			if (editor->selectflag)
300 				editor->selectlineoffset--;
301 
302 			editor->updateflags |= TUD_EDITAREA;
303 			return;
304 
305 		case DKEY_PAGEUP:  /* Page Up */
306 			i = 0;
307 			while (i < TEXTED_PAGELENGTH &&
308 			       editor->curline->prev != NULL &&
309 			       !(texteditIgnoreFirstLine(editor) &&
310 			         editor->curline->prev == editor->text->first)) {
311 				/* Advance to the next line */
312 				editor->curline = editor->curline->prev;
313 				if (editor->selectflag)
314 					editor->selectlineoffset++;
315 				i++;
316 			}
317 
318 			texteditValidatePosition(editor);
319 
320 			editor->updateflags |= TUD_EDITAREA;
321 			return;
322 
323 		case DKEY_PAGEDOWN:  /* Page Down */
324 			for (i = 0; i < TEXTED_PAGELENGTH &&
325 			            editor->curline->next != NULL; i++) {
326 				/* Return to the previous line */
327 				editor->curline = editor->curline->next;
328 				if (editor->selectflag)
329 					editor->selectlineoffset--;
330 			}
331 
332 			texteditValidatePosition(editor);
333 
334 			editor->updateflags |= TUD_EDITAREA;
335 			return;
336 	}
337 }
338 
339 /**
340  * @relates texteditor
341  * @brief Handle edit movement.
342  **/
texteditHandleEditMovement(texteditor * editor)343 void texteditHandleEditMovement(texteditor * editor)
344 {
345 	if (!editor->editflag)
346 		return;
347 
348 	switch (editor->key) {
349 		case DKEY_LEFT:  /* Left Arrow */
350 			texteditCursorLeft(editor);
351 			break;
352 
353 		case DKEY_RIGHT:    /* Right Arrow */
354 			texteditCursorRight(editor);
355 			break;
356 
357 		case DKEY_HOME: /* Home */
358 			editor->pos = 0;
359 			break;
360 
361 		case DKEY_END: /* End */
362 			editor->pos = strlen(editor->curline->s);
363 			break;
364 	}
365 }
366 
367 /**
368  * @relates texteditor
369  * @brief Handle an edit keypress.
370  **/
texteditHandleEditKey(texteditor * editor)371 void texteditHandleEditKey(texteditor * editor)
372 {
373 	if (!editor->editflag)
374 		return;
375 
376 	switch (editor->key) {
377 
378 		/********** Insert & Delete ***********/
379 
380 		case DKEY_INSERT:
381 			/* Insert */
382 			editor->insertflag = !editor->insertflag;
383 			editor->updateflags |= TUD_PANEL;
384 			break;
385 
386 		case DKEY_DELETE:
387 			texteditDelete(editor);
388 			break;
389 
390 		/****** Help dialog ******/
391 
392 		case DKEY_F1: /* F1: help dialog */
393 			texteditHelpZOC(editor);
394 			break;
395 
396 		/********* ZZM Testing ********************/
397 		case DKEY_CTRL_T:
398 			/* Play slurred music */
399 			texteditZZMPlay(editor, 1);
400 			break;
401 
402 		case DKEY_ALT_T:
403 			/* Play music with slight break between notes */
404 			texteditZZMPlay(editor, 0);
405 			break;
406 
407 		/********* File access operations *********/
408 		case DKEY_ALT_O: /* alt+o: open file */
409 			texteditFileOpen(editor, 0);
410 			break;
411 
412 		case DKEY_ALT_I: /* alt+i: insert file */
413 			texteditFileOpen(editor, 1);
414 			break;
415 
416 		case DKEY_ALT_S: /* alt-s: save to file */
417 			texteditFileSave(editor, "Save Object Code As");
418 			break;
419 
420 		case DKEY_ALT_M: /* alt-m: load .zzm music */
421 			texteditZZMLoad(editor);
422 			break;
423 
424 		case DKEY_CTRL_R: /* ctrl-r: rip music */
425 			texteditZZMRip(editor);
426 			break;
427 
428 		/******** Cut operation *********/
429 
430 		case DKEY_CTRL_DELETE:    /* ctrl-delete: clear selected text */
431 		case DKEY_CTRL_X:         /* ctrl-x: cut selected text */
432 			texteditClearSelectedText(editor);
433 			break;
434 
435 		case DKEY_CTRL_V:     /* ctrl-v: paste register */
436 			texteditPaste(editor);
437 			break;
438 
439 		case DKEY_TAB: /* Tab */
440 			texteditInsertSpaces(editor, 4);
441 			break;
442 
443 		case DKEY_ENTER: /* Enter */
444 			texteditInsertNewline(editor);
445 			break;
446 
447 		case DKEY_BACKSPACE: /* Backspace */
448 			texteditBackspace(editor);
449 			break;
450 
451 		case DKEY_CTRL_Y: /* ctrl-y: delete line */
452 			texteditDeleteLine(editor);
453 			break;
454 
455 		case DKEY_ESC: /* escape when done */
456 			editor->exitflag = 1;
457 			break;
458 
459 		case DKEY_CTRL_A: /* ctrl-a: insert ascii char/decimal-value */
460 			texteditInsertASCII(editor);
461 			break;
462 
463 		default:
464 			/* Insert keyboard character */
465 			if (editor->key < 0x7F && editor->key > 0x1F)
466 				texteditInsertCharacter(editor, editor->key);
467 			break;
468 	}
469 }
470 
471 /**
472  * @relates texteditor
473  * @brief Grab the title using zzt syntax.
474  *
475  * @returns true if a title is grabbed.
476  **/
texteditGrabTitle(texteditor * editor)477 int texteditGrabTitle(texteditor * editor)
478 {
479 	stringnode* first = editor->text->first;
480 
481 	/* Look for @title on first line */
482 	if (first != NULL &&
483 	    first->s[0] == '@' &&
484 			first->s[1] != '\x0') {
485 		/* Use the first line as the title */
486 		if (editor->title != NULL)
487 			free(editor->title);
488 		editor->title = str_dup(first->s + 1);
489 
490 		editor->updateflags |= TUD_TITLE;
491 
492 		return 1;
493 	}
494 
495 	return 0;
496 }
497 
498 /**
499  * @relates texteditor
500  * @brief Check whether first line should be ignored.
501  *
502  * @returns true if the first line should be ignored.
503  */
texteditIgnoreFirstLine(texteditor * editor)504 int texteditIgnoreFirstLine(texteditor * editor)
505 {
506 	/* Ignore the first line when not editing and
507 	 * when the first line starts with an @ */
508 	return !editor->editflag && editor->text->first->s[0] == '@';
509 }
510 
511 /**
512  * @relates texteditor
513  * @brief Make sure that pos is valid for the current line.
514  **/
texteditValidatePosition(texteditor * editor)515 void texteditValidatePosition(texteditor * editor)
516 {
517 	if (editor->pos > strlen(editor->curline->s))
518 		editor->pos = strlen(editor->curline->s);
519 }
520 
521 /**
522  * @relates texteditor
523  * @brief Move the cursor left, wrapping as necessary.
524  **/
texteditCursorLeft(texteditor * editor)525 void texteditCursorLeft(texteditor * editor)
526 {
527 	if (editor->pos > 0)
528 		editor->pos--;
529 	else {
530 		/* Move to end of previous line (or current line) */
531 		if (editor->curline->prev != NULL) {
532 			editor->curline = editor->curline->prev;
533 			editor->updateflags |= TUD_EDITAREA;
534 
535 			/* Update selectlineoffset */
536 			if (editor->selectflag)
537 				editor->selectlineoffset++;
538 		}
539 		editor->pos = strlen(editor->curline->s);
540 	}
541 }
542 
543 /**
544  * @relates texteditor
545  * @brief Move the cursor right, wrapping as necessary.
546  **/
texteditCursorRight(texteditor * editor)547 void texteditCursorRight(texteditor * editor)
548 {
549 	if (editor->pos < strlen(editor->curline->s))
550 		editor->pos++;
551 	else {
552 		/* Move to begining of next line (or current line) */
553 		if (editor->curline->next != NULL) {
554 			editor->curline = editor->curline->next;
555 			editor->updateflags |= TUD_EDITAREA;
556 
557 			/* Update selectlineoffset */
558 			if (editor->selectflag)
559 				editor->selectlineoffset--;
560 		}
561 		editor->pos = 0;
562 	}
563 }
564 
565 /**
566  * @relates texteditor
567  * @brief Open the ZZT-OOP help dialog. If the current line contains a ZZT-OOP
568  * command, look up that command.
569  **/
texteditHelpZOC(texteditor * editor)570 void texteditHelpZOC(texteditor * editor)
571 {
572 	int i;
573 
574 	/* Look for #command on current line for lookup in help */
575 	i = editor->pos;
576 	while (i > 0 && editor->curline->s[i] != '#')
577 		i--;
578 
579 	if (editor->curline->s[i] == '#') {
580 		/* Lookup the command in the language reference */
581 		char * command;
582 
583 		command = str_dup(editor->curline->s + i + 1);
584 		for (i = 0; command[i] != ' ' && command[i] != '\0'; i++)
585 			;
586 		command[i] = '\0';
587 
588 		if (zztoopFindCommand(command) == -1) {
589 			/* If it's not a valid command, don't bother looking for it */
590 			command[0] = '\0';
591 		}
592 
593 		/* Display the help file with the command as the topic */
594 		helpsectiontopic("langref", command, editor->d);
595 
596 		free(command);
597 	} else {
598 		/* Display the oop help file */
599 		helpsectiontopic("langref", NULL, editor->d);
600 	}
601 
602 	editor->updateflags |= TUD_ALL;
603 }
604 
605 /**
606  * @relates texteditor
607  * @brief Play any ZZM music found in the editor.
608  *
609  * @param slurflag  Slur notes together when true (ZZT-style).
610  */
texteditZZMPlay(texteditor * editor,int slurflag)611 void texteditZZMPlay(texteditor * editor, int slurflag)
612 {
613 	/** @TODO: do a damn good job of testing music */
614 	/* Idea: create a copy of *editor so that we can mess around with curline and
615 	 * pos and such, using existing functions to do the bulk of the display. */
616 #if 0
617 	editor->text->cur = editor->curline;
618 	testMusic(editor->text, slurflag, editor->linewidth, flags, editor->d);
619 #endif
620 
621 	/* Create a new view of the editor data. This allows us to move the cursor
622 	 * and change display settings for the new view without affecting editor. */
623 	const char* playString = "#play ";
624 	const int playStringLen = 6;
625 
626 	texteditor editorCopy = *editor;
627 	texteditor* view = &editorCopy;
628 
629 	int done;
630 
631 #ifdef SDL
632 	SDL_AudioSpec spec;
633 #endif
634 
635 	/* Display everything, in case the editor has not been displayed recently. */
636 	view->updateflags |= TUD_ALL;
637 
638 #ifdef SDL
639 	/* IF opening the audio device fails, return now before we crash something. */
640 	if (OpenSynth(&spec))
641 		return;
642 #endif
643 
644 	done = 0;
645 
646 	/* Loop through the stringvector looking for #play statements */
647 	while (view->curline != NULL && !done) {
648 		char* tune = strstr(view->curline->s, "#");
649 		if (tune != NULL && str_equ(tune, playString, STREQU_UNCASE | STREQU_RFRONT)) {
650 			/* Current note and settings */
651 			musicalNote note = zzmGetDefaultNote();
652 			musicSettings settings = zzmGetDefaultSettings();
653 
654 			int xoffset = tune - view->curline->s + playStringLen;
655 			tune += playStringLen;  /* Advance to notes! */
656 
657 			/* Change the slur setting */
658 			note.slur = slurflag;
659 
660 			while (note.src_pos < strlen(tune) && !done) {
661 				if (view->d->getkey() != DKEY_NONE)
662 					done = 1;
663 
664 				/* Move the cursor and re-display before playing note. */
665 				view->pos = note.src_pos + xoffset;
666 				texteditUpdateDisplay(view);
667 
668 				note = zzmGetNote(tune, note);
669 
670 #ifdef DOS
671 				pcSpeakerPlayNote(note, settings);
672 #elif defined SDL
673 				SynthPlayNote(spec, note, settings);
674 #endif
675 			}
676 		}
677 		view->curline = view->curline->next;
678 
679 		/* Re-display edit area since the current line has changed. */
680 		view->updateflags |= TUD_EDITAREA;
681 	}
682 
683 #ifdef SDL
684 	/* TODO: instead of just sitting here, display the progress of playback */
685 	/* Wait until the music is done or the user presses a key */
686 	while (!IsSynthBufferEmpty() && view->d->getkey() == DKEY_NONE)
687 		;
688 
689 	CloseSynth();
690 #elif defined DOS
691 	pcSpeakerFinish();
692 #endif
693 
694 	/* No need to free the view, it only exists on the stack. */
695 
696 	/* The edit area needs to be redisplayed now. */
697 	editor->updateflags |= TUD_EDITAREA;
698 }
699 
700 /**
701  * @relates texteditor
702  * @brief Load music from a ZZM file.
703  **/
texteditZZMLoad(texteditor * editor)704 void texteditZZMLoad(texteditor * editor)
705 {
706 	char* filename;
707 	filename = filedialog(".", "zzm", "Choose ZZT Music (ZZM) File",
708 												FTYPE_ALL, editor->d);
709 
710 	if (filename != NULL) {
711 		stringvector zzmv;
712 		zzmv = filetosvector(filename, 80, 80);
713 		if (zzmv.first != NULL) {
714 			stringvector song;
715 			song = zzmpullsong(&zzmv, zzmpicksong(&zzmv, editor->d));
716 			if (song.first != NULL) {
717 				/* copy song into editor->text */
718 				editor->text->cur = editor->curline;
719 				for (song.cur = song.first; song.cur != NULL; song.cur = song.cur->next) {
720 					char * insline = str_duplen("#play ", editor->linewidth);
721 					strncat(insline, song.cur->s, editor->linewidth - 6);
722 
723 					preinsertstring(editor->text, insline);
724 				}
725 				deletestringvector(&song);
726 			}
727 			deletestringvector(&zzmv);
728 		}
729 	}
730 	free(filename);
731 
732 	editor->updateflags |= TUD_EDITAREA | TUD_TITLE | TUD_PANEL;
733 }
734 
735 /**
736  * @relates texteditor
737  * @brief Rip music and browse it.
738  *
739  * This allows ZZM music files to be created from object code.
740  *
741  * @TODO: Document this feature and make it more usable.
742  */
texteditZZMRip(texteditor * editor)743 void texteditZZMRip(texteditor * editor)
744 {
745 	/* This is mostly worthless just now */
746 	stringvector ripped;
747 
748 	editor->text->cur = editor->curline;
749 
750 	ripped = zzmripsong(editor->text, 4);
751 	scrolldialog("Ripped Music", &ripped, editor->d);
752 
753 	deletestringvector(&ripped);
754 	editor->updateflags |= TUD_ALL;
755 }
756 
757 /**
758  * @relates texteditor
759  * @brief Open a file for editting.
760  *
761  * @param insertflag  Insert file at cursor when true; otherwise overwrite all
762  *                    existing text.
763  **/
texteditFileOpen(texteditor * editor,int insertflag)764 void texteditFileOpen(texteditor * editor, int insertflag)
765 {
766 	stringvector filetypelist;
767 	char* filename = NULL;
768 
769 	initstringvector(&filetypelist);
770 
771 	pushstring(&filetypelist, "*.zoc");
772 	pushstring(&filetypelist, "*.txt");
773 	pushstring(&filetypelist, "*.hlp");
774 	pushstring(&filetypelist, "*.zzm");
775 	pushstring(&filetypelist, "*.*");
776 	if (editbox("Select A File Type", &filetypelist, 0, 1, editor->d) == 27) {
777 		editor->updateflags |= TUD_EDITAREA | TUD_TITLE;
778 		return;
779 	}
780 
781 	if (filetypelist.cur != NULL)
782 		filename =
783 			filedialog(".", filetypelist.cur->s + 2,
784 								 (!insertflag ?
785 									 "Open ZZT Object Code (ZOC) File" :
786 									 "Insert ZZT Object Code (ZOC) File"),
787 								 FTYPE_ALL, editor->d);
788 
789 	if (filename != NULL && strlen(filename) != 0) {
790 		stringvector filetext;
791 		filetext = filetosvector(filename, editor->wrapwidth, editor->linewidth);
792 		if (filetext.first != NULL) {
793 
794 			/* TODO: remember the filename for future reference */
795 
796 			if (!insertflag) {
797 				/* erase & replace editor->text */
798 				deletestringvector(editor->text);
799 				*editor->text = filetext;
800 				editor->curline = editor->text->first;
801 				editor->pos = 0;
802 			} else {
803 				/* insert filetext before editor->curline */
804 				editor->text->cur = editor->curline;
805 
806 				/* TODO: this code should be in an svector function */
807 				if (editor->text->cur == editor->text->first) {
808 					/* first node */
809 					editor->text->first = filetext.first;
810 					editor->text->cur->prev = filetext.last;
811 					filetext.last->next = editor->text->cur;
812 					editor->curline = filetext.first;
813 				} else if (editor->text->cur->prev != NULL) {
814 					/* middle/end node */
815 					filetext.first->prev = editor->text->cur->prev;
816 					editor->text->cur->prev->next = filetext.first;
817 					filetext.last->next = editor->text->cur;
818 					editor->text->cur->prev = filetext.last;
819 					editor->curline = filetext.first;
820 				} else {
821 					/* this code should be unreachable */
822 					deletestringvector(&filetext);
823 				}
824 			} /* esle */
825 		}	/* fi file selected */
826 	}		/* fi not empty */
827 
828 	free(filename);
829 	removestringvector(&filetypelist);
830 
831 	editor->updateflags |= TUD_EDITAREA | TUD_TITLE | TUD_PANEL;
832 }
833 
834 /**
835  * @relates texteditor
836  * @brief Save text to a file.
837  * @param prompt  Prompt to give user.
838  */
texteditFileSave(texteditor * editor,char * prompt)839 void texteditFileSave(texteditor * editor, char * prompt)
840 {
841 	char* filename;
842 
843 	/* TODO: Use a stored filename, rather than empty by default */
844 	filename = filenamedialog("", "", prompt, 1, editor->d);
845 
846 	if (filename != NULL) {
847 		/* Save to the file */
848 		svectortofile(editor->text, filename);
849 
850 		/* TODO: Remember the file name */
851 		free(filename);
852 	}
853 	editor->updateflags |= TUD_EDITAREA | TUD_PANEL | TUD_PANEL;
854 }
855 
856 /**
857  * @relates texteditor
858  * @brief Insert a number of spaces at the cursor position.
859  *
860  * @param count  Number of spaces to insert.
861  **/
texteditInsertSpaces(texteditor * editor,int count)862 void texteditInsertSpaces(texteditor * editor, int count)
863 {
864 	int i;
865 
866 	/* Make sure the spaces will fit on the line */
867 	count = count - (editor->pos % count);
868 
869 	for (i = 0; i < count; i++)
870 		texteditInsertCharacter(editor, ' ');
871 }
872 
873 /**
874  * @relates texteditor
875  * @brief Insert a newline at the cursor.
876  **/
texteditInsertNewline(texteditor * editor)877 void texteditInsertNewline(texteditor * editor)
878 {
879 	char * nextline;
880 
881 	/* Copy everything after the cursor */
882 	nextline = str_duplen(editor->curline->s + editor->pos, editor->linewidth);
883 
884 #if 0
885 	nextline = (char*) malloc(editor->linewidth + 2);
886 	for (i = editor->pos, j = 0; i < strlen(editor->curline->s); i++, j++)
887 		nextline[j] = editor->curline->s[i];
888 	tmpstr[j] = 0;
889 #endif
890 
891 	/* Truncate the current line */
892 	editor->curline->s[editor->pos] = '\x0';
893 
894 	/* Insert nextline */
895 	editor->text->cur = editor->curline;
896 	insertstring(editor->text, nextline);
897 
898 	/* Advance to the new line */
899 	editor->curline = editor->curline->next;
900 	editor->pos = 0;
901 
902 	editor->updateflags |= TUD_EDITAREA;
903 }
904 
905 /**
906  * @relates texteditor
907  * @brief Delete the character before the cursor.
908  */
texteditBackspace(texteditor * editor)909 void texteditBackspace(texteditor * editor)
910 {
911 	int i;
912 
913 	if (editor->selectflag) {
914 		texteditClearSelectedText(editor);
915 		return;
916 	}
917 
918 	if (editor->pos > 0) {
919 		/* Slide everything at or after the cursor back */
920 		for (i = editor->pos - 1; i < strlen(editor->curline->s); i++)
921 			editor->curline->s[i] = editor->curline->s[i+1];
922 
923 		/* Cursor moves back too */
924 		editor->pos--;
925 		editor->updateflags |= TUD_CENTER;
926 	}
927 	else if (editor->curline->prev != NULL) {
928 		if (strlen(editor->curline->s) == 0) {
929 			/* remove current line & move up & to eol */
930 			editor->text->cur = editor->curline;
931 			editor->curline = editor->curline->prev;
932 			editor->pos = strlen(editor->curline->s);
933 
934 			deletestring(editor->text);
935 
936 			editor->updateflags |= TUD_TOP | TUD_CENTER;
937 		}
938 		else if (strlen(editor->curline->prev->s) == 0) {
939 			/* remove previous line */
940 			editor->text->cur = editor->curline->prev;
941 			deletestring(editor->text);
942 
943 			/* update center too, in case @ line has moved to top now */
944 			editor->updateflags |= TUD_TOP | TUD_CENTER;
945 		}
946 		else if (strlen(editor->curline->prev->s) + 1 < editor->wrapwidth) {
947 			/* merge lines; wordwrap */
948 			char * prevline;
949 
950 			i = strlen(editor->curline->prev->s);
951 
952 			/* Previous line must have a space at the end */
953 			if (editor->curline->prev->s[i-1] != ' ' && editor->curline->s[0] != ' ') {
954 				/* add a space at the end */
955 				editor->curline->prev->s[i]     = ' ';
956 				editor->curline->prev->s[i + 1] = 0;
957 			}
958 
959 			/* Grab the previous line */
960 			editor->text->cur = editor->curline->prev;
961 			prevline = removestring(editor->text);
962 			editor->text->cur = editor->curline;
963 
964 			/* Wrap the previous line onto the beginning of the current line */
965 			editor->pos = wordwrap(editor->text, prevline, 0, 0, editor->wrapwidth, editor->linewidth);
966 			editor->curline = editor->text->cur;
967 
968 			free(prevline);
969 			editor->updateflags |= TUD_EDITAREA;
970 		}
971 	}
972 }
973 
974 /**
975  * @relates texteditor
976  * @brief Delete the character under the cursor.
977  **/
texteditDelete(texteditor * editor)978 void texteditDelete(texteditor * editor)
979 {
980 	int i;
981 
982 	if (editor->selectflag) {
983 		texteditClearSelectedText(editor);
984 		return;
985 	}
986 
987 	if (editor->pos < strlen(editor->curline->s)) {
988 		/* Slide everything after the cursor backward */
989 		for (i = editor->pos; i < strlen(editor->curline->s); i++)
990 			editor->curline->s[i] = editor->curline->s[i+1];
991 		editor->updateflags |= TUD_CENTER;
992 	}
993 	else if (strlen(editor->curline->s) == 0 && !(editor->text->first == editor->text->last)) {
994 		/* This string is empty: destroy */
995 		editor->text->cur = editor->curline;
996 		deletestring(editor->text);
997 		editor->curline = editor->text->cur;
998 		editor->pos = strlen(editor->curline->s);
999 		editor->updateflags |= TUD_EDITAREA;
1000 	}
1001 	else if (editor->curline->next != NULL) {
1002 		if (strlen(editor->curline->next->s) == 0) {
1003 			/* Next string is empty: destroy */
1004 			editor->text->cur = editor->curline->next;
1005 			deletestring(editor->text);
1006 			editor->updateflags |= TUD_BOTTOM;
1007 		}
1008 		else if (strlen(editor->curline->s) + 1 < editor->wrapwidth) {
1009 			char * nextline;
1010 
1011 			/* merge lines; wordwrap */
1012 			i = strlen(editor->curline->s);
1013 			if (editor->curline->s[i-1] != ' ' && editor->curline->next->s[0] != ' ') {
1014 				/* add a space at the end */
1015 				editor->curline->s[i]   = ' ';
1016 				editor->curline->s[++i] = 0;
1017 			}
1018 
1019 			/* Remove the next line */
1020 			editor->text->cur = editor->curline->next;
1021 			nextline = removestring(editor->text);
1022 			editor->text->cur = editor->curline;
1023 
1024 			/* Wordwrap the next line onto the end of the current line */
1025 			editor->pos = wordwrap(editor->text, nextline, i, -1, editor->wrapwidth, editor->linewidth);
1026 			editor->curline = editor->text->cur;
1027 
1028 			free(nextline);
1029 			editor->updateflags |= TUD_CENTER | TUD_BOTTOM | TUD_TOP;
1030 		}
1031 	}
1032 }
1033 
1034 
1035 /**
1036  * @relates texteditor
1037  * @brief Delete the current line.
1038  **/
texteditDeleteLine(texteditor * editor)1039 void texteditDeleteLine(texteditor * editor)
1040 {
1041 	/* Move cursor to the beginning of the line */
1042 	editor->pos = 0;
1043 	editor->text->cur = editor->curline;
1044 
1045 	if (editor->curline->next != NULL) {
1046 		/* Delete the current line, moving curline forward */
1047 		editor->curline = editor->curline->next;
1048 		deletestring(editor->text);
1049 		editor->updateflags |= TUD_CENTER | TUD_BOTTOM;
1050 	}
1051 	else if (editor->curline->prev != NULL) {
1052 		/* Delete the current line, moving curline backward */
1053 		editor->curline = editor->curline->prev;
1054 		deletestring(editor->text);
1055 		editor->updateflags |= TUD_TOP | TUD_CENTER;
1056 	}
1057 	else {
1058 		/* Clear the current (and only) line */
1059 		editor->curline->s[0] = 0;
1060 		editor->updateflags |= TUD_CENTER;
1061 	}
1062 }
1063 
1064 /**
1065  * @relates texteditor
1066  * @brief Prompt the user for an ASCII character to insert. If the current line
1067  * begins with a #char command, modify the argument to #char instead.
1068  **/
texteditInsertASCII(texteditor * editor)1069 void texteditInsertASCII(texteditor * editor)
1070 {
1071 	static int selChar; /* TODO: static isn't the way to go, or is it? */
1072 	int choice;
1073 
1074 	editor->updateflags |= TUD_EDITAREA;
1075 
1076 	/* TODO: let #char be anywhere on the line */
1077 
1078 	if (str_equ(editor->curline->s, "#char", STREQU_UNCASE | STREQU_RFRONT)) {
1079 		/* Change character number for a #char command */
1080 		char * number;
1081 
1082 		/* append decimal value for ascii char */
1083 
1084 		sscanf(editor->curline->s + 5, "%d", &selChar);
1085 		choice = charselect(editor->d, selChar);
1086 		if (choice == -1)
1087 			return;
1088 
1089 		editor->curline->s[5] = ' ';
1090 		editor->curline->s[6] = '\x0';
1091 
1092 		/* change the character to a string */
1093 		number = str_duplen("", 64);
1094 		sprintf(number, "%d", choice);
1095 
1096 		strcat(editor->curline->s, number);
1097 
1098 		free(number);
1099 
1100 		texteditValidatePosition(editor);
1101 		editor->updateflags |= TUD_EDITAREA;
1102 	} else {
1103 		/* insert ascii char */
1104 
1105 		choice = charselect(editor->d, selChar);
1106 		if (choice == -1)
1107 			return;
1108 
1109 		texteditInsertCharacter(editor, choice);
1110 	}
1111 
1112 	/* Remember the choice for future reference */
1113 	selChar = choice;
1114 }
1115 
1116 /**
1117  * @relates texteditor
1118  * @brief Insert a character at the cursor position.
1119  *
1120  * @param ch  character to insert.
1121  **/
texteditInsertCharacter(texteditor * editor,int ch)1122 void texteditInsertCharacter(texteditor * editor, int ch)
1123 {
1124 	int i;
1125 
1126 	ch = ch & 0xFF;  /* Clear all but the first 8 bits */
1127 
1128 	/* Don't insert end-of-line character. */
1129 	if (ch == 0)
1130 		return;
1131 
1132 	if (editor->insertflag) {
1133 		/* insert */
1134 		if (strlen(editor->curline->s) < (editor->wrapwidth?editor->wrapwidth:editor->linewidth)) {
1135 			/* insert if there is room */
1136 			for (i = strlen(editor->curline->s) + 1; i > editor->pos; i--)
1137 				editor->curline->s[i] = editor->curline->s[i-1];
1138 
1139 			editor->curline->s[editor->pos++] = ch;
1140 			editor->updateflags |= TUD_CENTER;
1141 		}
1142 		else if (editor->wrapwidth) {
1143 			/* no room; wordwrap */
1144 			texteditInsertCharAndWrap(editor, ch);
1145 		}
1146 	}
1147 	else {
1148 		/* easy replace */
1149 		if (editor->curline->s[editor->pos] == 0) {
1150 			/* Insert at the end of the line; not so easy. */
1151 			if (strlen(editor->curline->s) < (editor->wrapwidth?editor->wrapwidth:editor->linewidth)) {
1152 				editor->curline->s[editor->pos+1] = 0;
1153 				editor->curline->s[editor->pos++] = ch;
1154 				editor->updateflags |= TUD_CENTER;
1155 			}
1156 			else if (editor->wrapwidth) {
1157 				/* no room; wordwrap */
1158 				texteditInsertCharAndWrap(editor, ch);
1159 			}
1160 		}
1161 		else {
1162 			editor->curline->s[editor->pos++] = ch;
1163 			editor->updateflags |= TUD_CENTER;
1164 		}
1165 	}
1166 }
1167 
1168 /**
1169  * @relates texteditor
1170  * @brief Insert a character onto a full line using wordwrap.
1171  *
1172  * @param ch  Character to insert.
1173  **/
texteditInsertCharAndWrap(texteditor * editor,int ch)1174 void texteditInsertCharAndWrap(texteditor * editor, int ch)
1175 {
1176 	char * inserttext = str_duplen("", 2);
1177 	inserttext[0] = ch;
1178 	inserttext[1] = 0;
1179 
1180 	editor->text->cur = editor->curline;
1181 
1182 	editor->pos = wordwrap(editor->text, inserttext, editor->pos, editor->pos, editor->wrapwidth, editor->linewidth);
1183 
1184 	editor->curline = editor->text->cur;
1185 
1186 	free(inserttext);
1187 	editor->updateflags |= TUD_EDITAREA;
1188 }
1189 
1190 
1191