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