OS no longer mentioned in information window.
[tangerine.git] / workbench / tools / Edit / Jed.c
blob43efcdd6009d30b53aeb8dc566e32be92a3806a7
1 /**********************************************************
2 ** jed.c : An simple, fast and efficient text editor **
3 ** Written by T.Pierron and C.Guillaume. **
4 ** Started on august 1998. **
5 **-------------------------------------------------------**
6 ** Special requirements: WorkBench 2.0, v36 or above **
7 **********************************************************/
9 #include <intuition/intuition.h> /* Std types */
10 #include <libraries/gadtools.h> /* Menu events */
11 #include <dos/dos.h> /* Standard error codes */
12 #include <exec/memory.h> /* Memory allocation */
13 #include "Version.h"
14 #include "Jed.h"
15 #include "DiskIO.h"
16 #include "Events.h"
17 #include "Utility.h"
18 #include "Macros.h"
19 #include "Search.h"
20 #include "ProtoTypes.h"
22 #define DEBUG_STUFF /* Only activated if DEBUG macro is defined */
23 #include "Debug.h"
25 #define CATCOMP_NUMBERS /* We will need the string id */
26 #include "strings.h"
28 /* Linked list of opened project */
29 Project edit = NULL;
30 ULONG sigbits=0, sigrcvd, sigport, err_time;
31 UBYTE clear=TRUE, BufTxt[256];
32 pfnSelectFunc move_selection;
34 /* Static string for version command */
35 const char Version[]=SVER;
37 /* Shared libraires we'll need to open */
38 struct IntuitionBase *IntuitionBase = NULL;
39 struct GfxBase *GfxBase = NULL;
40 struct Library *KeymapBase = NULL;
41 struct Library *GadToolsBase = NULL;
42 struct Library *AslBase = NULL;
43 struct LocaleBase *LocaleBase = NULL;
44 struct Library *DiskfontBase = NULL;
45 struct UtilityBase *UtilityBase = NULL;
46 struct Library *IFFParseBase = NULL;
48 struct IntuiMessage *msg, msgbuf; /* Used to collect events */
50 StartUpArgs args;
52 #if DEBUG
53 ULONG bmem, amem;
54 #endif
56 /*** MAIN LOOP ***/
57 int main(int argc, char *argv[])
59 #if DEBUG
60 bmem = AvailMem( MEMF_PUBLIC );
61 #endif
63 ParseArgs(&args, argc, argv);
65 /* Look first if Jano isn't already running */
66 if( find_janoed( &args ) ) cleanup(0, RETURN_OK);
68 /* Some global initialization */
69 init_searchtable();
71 /* Optionnal libraries */
72 AslBase = (struct Library *) OpenLibrary("asl.library", 36);
73 LocaleBase = (struct LocaleBase *) OpenLibrary("locale.library", 38);
74 DiskfontBase = (struct Library *) OpenLibrary("diskfont.library", 0);
75 IFFParseBase = (struct Library *) OpenLibrary("iffparse.library",36);
77 if(LocaleBase) InitLocale(); /* Localize the prog */
79 /* Open the required ROM libraries */
80 if( (IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 36)) &&
81 (GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 36)) &&
82 (GadToolsBase = (struct Library *) OpenLibrary("gadtools.library", 36)) &&
83 (KeymapBase = (struct Library *) OpenLibrary("keymap.library", 36)) &&
84 (UtilityBase = (struct UtilityBase *) OpenLibrary("utility.library", 36)) )
86 init_macros();
87 set_default_prefs(&prefs, IntuitionBase->ActiveScreen);
89 load_prefs(&prefs, NULL); /* See if it exists a config file */
90 sigport = create_port();
92 /* Create whether an empty project or an existing one */
93 if( ( edit = create_projects(NULL, args.sa_ArgLst, args.sa_NbArgs) ) )
95 /* Open the main interface */
96 if(setup() == 0)
98 /* Makes edit project visible */
99 reshape_panel(edit);
100 active_project(edit,TRUE);
101 clear_brcorner();
103 dispatch_events();
105 } else cleanup(ErrMsg(ERR_NOGUI), RETURN_FAIL);
107 } else cleanup(ErrMsg(ERR_BADOS), RETURN_FAIL);
109 /* Hope that all were well... */
110 cleanup(0, RETURN_OK);
112 return 0;
115 /*** Deallocate ressources properly ***/
116 void cleanup(UBYTE *msg, int errcode)
118 CBClose();
119 close_port();
120 CloseMainWnd(1);
121 CleanupLocale();
122 free_macros();
123 free_diskio_alloc(); /* ASL */
124 if(args.sa_Free) FreeVec(args.sa_ArgLst);
125 if(DiskfontBase) CloseLibrary(DiskfontBase);
126 if(LocaleBase) CloseLibrary((struct Library *)LocaleBase);
127 if(AslBase) CloseLibrary(AslBase);
128 if(IFFParseBase) CloseLibrary(IFFParseBase);
129 if(UtilityBase) CloseLibrary((struct Library *)UtilityBase);
130 if(KeymapBase) CloseLibrary(KeymapBase);
131 if(GadToolsBase) CloseLibrary(GadToolsBase);
132 if(GfxBase) CloseLibrary((struct Library *)GfxBase);
133 if(IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
134 if(msg) puts(msg);
136 #if DEBUG
137 /* Here can be compared good programs with the others :-) */
138 amem = AvailMem( MEMF_PUBLIC );
139 if(amem < bmem) printf("Possible memory lost of %d bytes\n", bmem-amem);
140 #endif
141 exit(errcode);
144 /*** Handle events coming from main window: ***/
145 void dispatch_events()
147 extern ULONG sigmainwnd, swinsig;
148 extern UBYTE record;
149 BYTE scrolldisp=0, state=0, cnt=0, mark=0, quit = 0;
151 while( quit == 0 )
153 /* Active collect, when pressing arrow gadgets */
154 sigrcvd = (state==0 ? Wait(sigbits) : sigmainwnd);
156 /* if(sigrcvd & SIGBREAKF_CTRL_C) break;
158 else */ if(sigrcvd & sigport) { handle_port(); continue; }
160 else if(sigrcvd & swinsig) { handle_search(); continue; }
162 /* Collect messages posted to the window port */
163 while( ( msg = (struct IntuiMessage *) GetMsg(Wnd->UserPort) ) )
165 /* Copy the entire message into the buffer */
166 CopyMemQuick(msg, &msgbuf, sizeof(msgbuf));
167 ReplyMsg( (struct Message *) msg );
169 switch( msgbuf.Class )
171 case IDCMP_CLOSEWINDOW: handle_menu(112); break;
172 case IDCMP_RAWKEY:
173 handle_kbd(edit);
174 if(record) {
175 if(record == 1) reg_act_com(MAC_ACT_SHORTCUT, msgbuf.Code, msgbuf.Qualifier);
176 else record &= 0x7f;
178 break;
179 case IDCMP_INTUITICKS:
180 /* An error message which needs to be removed? */
181 if(err_time == 0) err_time = msgbuf.Seconds;
182 if(err_time && msgbuf.Seconds-err_time>4) StopError(Wnd);
183 break;
184 case IDCMP_MOUSEBUTTONS:
185 /* Click somewhere in the text */
186 switch( msgbuf.Code )
188 case SELECTDOWN:
189 /* Click over the project bar ? */
190 if(msgbuf.MouseY < gui.top)
192 edit = select_panel(edit, msgbuf.MouseX);
193 break;
196 click(edit, msgbuf.MouseX, msgbuf.MouseY, FALSE);
198 /* Shift-click to use columnar selection */
199 if( ( move_selection = SwitchSelect(edit, msgbuf.Qualifier & SHIFTKEYS ? 1:0, 1) ) )
200 mark=TRUE;
201 break;
202 case SELECTUP:
203 if(mark) unclick(edit);
204 mark=FALSE; scrolldisp=0; break;
206 break;
207 case IDCMP_NEWSIZE:
208 new_size(EDIT_ALL);
209 break;
210 case IDCMP_GADGETDOWN: /* Left scroll bar */
211 if(msgbuf.IAddress == (APTR) &Prop->down) state=1;
212 if(msgbuf.IAddress == (APTR) &Prop->up) state=2;
213 break;
214 case IDCMP_GADGETUP: /* Arrows or prop gadget */
215 state=0;
216 if(msgbuf.IAddress == (APTR) Prop)
217 scroll_disp(edit, FALSE), scrolldisp=0;
218 break;
219 case IDCMP_MOUSEMOVE:
220 if(mark) scrolldisp=2;
221 else
222 if(Prop->scroller.Flags & GFLG_SELECTED) scrolldisp=1;
223 break;
224 case IDCMP_MENUPICK:
225 { struct MenuItem * Item;
226 ULONG MenuId;
228 /* Multi-selection of menu entries */
229 while(msgbuf.Code != MENUNULL)
230 if( (Item = ItemAddress( Menu, msgbuf.Code )) )
232 /* stegerg: get NextSelect here in case menu action causes screen
233 to be closed/reopened in which case item becomes invalid.
234 Also assuming here that user in such case will not use
235 multiselection, ie. that nextselect will be MENUNULL.
237 If that's not the case it would mean more trouble and to protect
238 against that one would need to check if during handle_menu() the
239 screen has been closed/reopened and in that case break out of
240 the menu multiselection loop here. */
242 UWORD nextselect = Item->NextSelect;
244 MenuId = (ULONG)GTMENUITEM_USERDATA( Item );
245 handle_menu( MenuId );
247 if(record) reg_act_com(MAC_ACT_COM_MENU, MenuId, msgbuf.Qualifier);
248 else record &= 0x7f;
250 msgbuf.Code = nextselect;
255 /* Reduces the number of IDCMP mousemove messages to process */
256 if(scrolldisp==1) scroll_disp(edit, FALSE), scrolldisp=0;
257 if(scrolldisp==2) { scrolldisp=0; goto moveit; }
259 /* User may want to auto-scroll the display using arrow gadgets */
260 if(state && (mark || (((struct Gadget *)Prop)[state].Flags & GFLG_SELECTED))) {
261 /* Slow down animation: */
262 WaitTOF(); cnt++;
263 if(cnt>1) {
264 cnt=0;
265 if(autoscroll(edit,state==1 ? 1:-1)==0) state=0;
266 else if(mark) {
267 LONG x , y; moveit:
268 /* Adjust mouse position */
269 x = (msgbuf.MouseX-gui.left) / XSIZE;
270 y = (msgbuf.MouseY-gui.top) / YSIZE;
271 if(x < 0) x = 0; if(x >= gui.nbcol) x = gui.nbcol-1;
272 if(y < 0) y = -1; if(y > gui.nbline) y = gui.nbline;
273 edit->nbrwc = (x += edit->left_pos);
274 y += (LONG)edit->top_line;
275 if( x != edit->ccp.xc || y != edit->ccp.yc )
276 /* Move the selected stream */
277 if( !(state = move_selection(edit,x,y)) )
278 set_cursor_line(edit, y, edit->top_line),
279 inv_curs(edit,TRUE);
282 } /* endif: arrow gadget pressed or autoscroll */
286 /*** Refresh display, according to new window size ***/
287 void new_size(UBYTE Flags)
289 inv_curs(edit, FALSE);
290 adjust_win(Wnd,NbProject>1); /* Adjust internal variables */
291 SetABPenDrMd(RP, pen.fg, pen.bg, JAM2);
292 clear_brcorner();
293 prop_adj(edit);
294 edit->left_pos = curs_visible(edit, edit->top_line);
295 edit->xcurs = (edit->nbrc - edit->left_pos)*XSIZE + gui.left;
296 if(Flags & EDIT_GUI) reshape_panel(edit);
297 if(Flags & EDIT_AREA) redraw_content(edit,edit->show,gui.topcurs,gui.nbline);
298 inv_curs(edit,TRUE);
301 /*** Scroll display according to right prop gadget ***/
302 void scroll_disp(Project p, BOOL adjust)
304 ULONG pos = ((struct PropInfo *)((struct Gadget*)Prop)->SpecialInfo)->VertPot *
305 (p->max_lines - gui.nbline) / MAXPOT;
307 if(p->max_lines>gui.nbline && pos!=p->top_line)
309 if(p->ccp.select)
310 /* If selection mode is on, don't move cursor */
311 p->ycurs-=(pos-p->top_line)*YSIZE,
312 scroll_xy(p, p->left_pos, pos, adjust);
313 else
314 /* Be sure cursor is always in the edit area */
315 inv_curs(p,FALSE),
316 scroll_xy(p, curs_visible(p,pos), pos, adjust),
317 inv_curs(p,TRUE);
321 /*** Be sure that cursor is always visible ***/
322 LONG curs_visible(Project p, LONG newtop)
324 if(p->nbl < newtop) {
325 /* The cursor is above the top line */
326 p->ycurs = gui.topcurs;
327 p->nbl = newtop;
328 goto adj_edited;
329 } else if(p->nbl >= newtop+gui.nbline) {
330 register LONG nb;
331 register LINE *ln;
333 /* The cursor is below the bottom line */
334 p->nbl = newtop+gui.nbline-1;
335 p->ycurs = gui.botcurs;
337 adj_edited:
338 for(ln=p->the_line, nb=p->nbl; nb--; ln=ln->next);
339 if(ln) p->edited=ln;
340 draw_info(p);
342 } else
343 /* Is between the top and bottom line */
344 p->ycurs = (p->nbl-newtop) * YSIZE+ gui.topcurs;
346 /* Jump too far to the right? */
347 newtop=p->left_pos + gui.nbcol - 1;
348 if(p->nbrc>newtop) p->nbrwc=newtop;
349 /* Too far on the left */
350 if((newtop = p->nbrc<p->left_pos)) p->nbrwc=p->left_pos;
352 /* Adjust cursor pos */
353 newtop = adjust_rc(p->edited, p->nbrwc, &p->nbc, newtop);
354 if(p->nbrc != newtop)
355 p->nbrc = newtop, draw_info(p);
357 p->xcurs = (p->nbrc-p->left_pos)*XSIZE + gui.left;
358 return center_horiz(p);
361 /*** Center the display horizontally to show the cursor ***/
362 LONG center_horiz(Project p)
364 /* Return the new left position */
365 if(p->nbrc < p->left_pos)
366 if(p->nbrc > gui.xstep) return (LONG)(p->nbrc - gui.xstep);
367 else return 0;
368 else
369 if(p->nbrc >= p->left_pos+gui.nbcol)
370 return (LONG)(p->nbrc + gui.xstep - gui.nbcol + 1);
371 else return (LONG)p->left_pos;
374 /*** Center the display vertically to show the cursor ***/
375 LONG center_vert(Project p)
377 /* Return the new top position */
378 if(p->nbl < p->top_line || p->nbl >= p->top_line+gui.nbline)
380 LONG newtop=p->nbl - (gui.nbline>>1) + 4;
381 return newtop<0 ? 0:newtop;
383 return (LONG)p->top_line;
386 /*** Low-level text rendering at current rastport position ***/
387 void write_text(Project p, LINE *ln)
389 static UBYTE ts;
390 register UBYTE *str,*buf;
391 register LONG nb, nbc, size = ln->size;
392 static LONG stsel, ensel;
394 /* Look if line is partially selected */
395 if(ln->flags)
396 stsel = (ln->flags & FIRSTSEL ? p->ccp.startsel:0),
397 ensel = (ln->flags & LASTSEL ? p->ccp.endsel:0x7FFFFFFF);
398 else stsel=ensel=0x7FFFFFFF;
400 /* Find the first char to display */
401 for(str=ln->stream, nb=p->left_pos, nbc=0; nbc<nb; str++, size--)
402 if(*str=='\t') nbc+=(ts=tabstop(nbc));
403 else nbc++;
405 nbc-=nb; stsel-=nb; ensel-=nb;
406 if(nbc>=0 && size>=0)
408 /* Tricky case: line begins with an overlapping tabstop */
409 if(nbc>0) {
410 memset(BufTxt,' ',nbc);
411 if(stsel<nbc) stsel=(stsel<=nbc-ts ? 0:nbc);
412 if(ensel<nbc) ensel=nbc;
415 /* Copy the string to a temp buffer */
416 for(nb=size, buf=BufTxt+nbc; nbc<gui.nbcol && nb; str++, nb--)
417 if(*str=='\t') {
418 register UBYTE ts = tabstop(nbc+p->left_pos);
419 memset(buf,' ',ts);
420 nbc+=ts; buf+=ts;
421 if(stsel<nbc && stsel>nbc-ts) stsel=nbc;
422 if(ensel<nbc && ensel>nbc-ts) ensel=nbc;
423 } else *buf++ = *str, nbc++;
425 /* Overlapping tabulation ? */
426 if(nbc>=gui.nbcol) nbc=gui.nbcol;
427 else *buf=' ',nbc++;
429 /* Optimize rendering of unselected line */
430 if(ensel==stsel || stsel>nbc || ensel<0) Text(RP,BufTxt,nbc);
431 else {
432 buf=BufTxt; nb=stsel;
433 if(nb > 0) Text(RP,BufTxt,nb); else nb=0;
434 if(ensel>nbc) ensel=nbc;
435 SetABPenDrMd(RP,pen.fgfill,pen.bgfill,JAM2);
436 Text(RP,buf+nb,ensel-nb);
437 SetABPenDrMd(RP,pen.fg,pen.bg,JAM2);
438 if(ensel!=nbc) Text(RP,buf+ensel,nbc-ensel);
441 if(clear && gui.right>RP->cp_x) {
442 /* Clear the end of line, like ClearEOL(rp), but without overlapping borders :-( */
443 SetAPen(RP,pen.bg); nb=RP->cp_y-BASEL;
444 RectFill(RP,RP->cp_x,nb,gui.right,nb+YSIZE-1);
445 SetAPen(RP,pen.fg);
449 /*** Delta horizontal scroll, with boundary check ***/
450 void scroll_xdelta(Project p, LONG x)
452 if(x<0)
454 x=-x;
455 if(x>p->left_pos) x=0; else x=p->left_pos-x;
456 } else x+=p->left_pos;
457 /* Refresh only if different pos */
458 if(x!=p->left_pos)
460 if(!p->ccp.select) inv_curs(p,FALSE);
461 scroll_xy(p, x, p->top_line, FALSE);
462 /* Is cursor always visible? */
463 x=p->xcurs;
464 curs_visible(p, p->top_line);
465 if(x!=p->xcurs && p->ccp.select)
466 move_selection(p, p->nbrwc, p->nbl);
468 SetAPen(RP,pen.fg); inv_curs(p,TRUE);
472 /*** Delta vertical scroll, with boundary check ***/
473 void scroll_ydelta(Project p, LONG y)
475 LONG pos=p->top_line+y;
476 /* Clamp values to the boundary */
478 if(pos<0) pos=0;
479 #if 0
480 if(pos>(LONG)p->max_lines-(LONG)gui.nbline) pos=p->max_lines-gui.nbline;
481 #else
482 if(pos>p->max_lines-1) pos=p->max_lines-1;
483 #endif
485 if(pos!=p->top_line)
487 if(!p->ccp.select) inv_curs(p,FALSE);
488 scroll_xy(p, curs_visible(p,pos), pos, TRUE);
489 if(p->ccp.select) move_selection(p, p->nbrwc, p->nbl);
490 inv_curs(p,TRUE);
494 /*** Like previous, but with required simplifications ***/
495 BOOL autoscroll(Project p, WORD y)
497 LONG pos=p->top_line+y;
498 /* Clamp values to the boundary */
499 #if 1
500 if(pos>(LONG)p->max_lines-(LONG)gui.nbline) pos=p->max_lines-gui.nbline;
501 #else
502 if(pos>p->max_lines-1) pos=p->max_lines-1;
503 #endif
504 if(pos<0) pos=0;
506 if(pos!=p->top_line)
508 if(!p->ccp.select) inv_curs(p,FALSE);
509 scroll_xy(p, p->left_pos, pos, TRUE);
510 if(!p->ccp.select)
511 curs_visible(p, p->top_line),
512 inv_curs(p,TRUE);
514 return TRUE;
515 } else return FALSE;
518 /*** Redraw part of a display ***/
519 void redraw_content(Project p, LINE *ln, WORD startpos, WORD nb)
521 for(SetAPen(RP,pen.fg); nb-- && ln; startpos += YSIZE,ln = ln->next)
522 Move(RP, gui.left, startpos),
523 write_text(p, ln);
525 /* Empty lines visible? */
526 if(nb >= 0)
527 SetAPen(RP,pen.bg),
528 RectFill(RP,gui.left, startpos-BASEL, gui.right, gui.bottom),
529 SetAPen(RP,pen.fg);
532 /*** Scroll to a determinate absolute position ***/
533 void scroll_xy(Project p, LONG xp, LONG yp, BYTE adj)
535 LONG skipy = yp-p->top_line,
536 skipx = xp-p->left_pos, svg;
538 if(skipy<0) skipy = -skipy;
539 if(skipx<0) skipx = -skipx;
540 /* Can the process be optimized? */
541 if(skipy < gui.nbline && skipx < gui.nbcol)
543 /* Yes, don't redraw whole display */
544 WORD ystart,xstart; LINE *disp = p->show;
546 xstart = skipx * XSIZE;
547 ystart = skipy * YSIZE;
549 /* 1. Scroll the display */
550 ScrollRaster(RP, xp<p->left_pos? -xstart:xstart, yp<p->top_line? -ystart:ystart,
551 gui.left, gui.top, gui.rcurs-1, gui.bottom);
553 /* Only performs changes if required */
554 if(skipy)
556 /* 2. Update internal variables if vertical scroll */
557 if(yp < p->top_line)
559 /* Scroll display down */
560 register LONG nb=skipy;
561 for(; nb--; disp=disp->prev);
562 p->top_line -= skipy;
563 ystart = gui.topcurs;
564 p->show = disp;
565 } else {
566 /* Scroll display up */
567 register LONG nb=skipy;
568 p->top_line += skipy;
569 ystart = gui.botcurs - ystart + YSIZE;
570 for(; nb--; disp=disp->next);
571 p->show = disp;
573 for(nb=gui.nbline - skipy; disp && nb--; disp=disp->next);
577 /* Same fight */
578 if(skipx)
580 /* 3. Update variables if horizontal scroll */
581 svg = gui.nbcol;
582 gui.nbcol = skipx;
584 if(xp < p->left_pos)
586 /* Scroll display right */
587 p->xcurs += xstart;
588 clear = FALSE;
589 p->left_pos = xp;
590 redraw_content(p, p->show, gui.topcurs, gui.nbline);
591 clear = TRUE;
592 } else {
593 /* Scroll display left */
594 register LONG left=gui.left;
595 p->xcurs -= xstart;
596 gui.left = gui.rcurs-xstart;
597 p->left_pos += svg;
598 redraw_content(p, p->show, gui.topcurs, gui.nbline);
599 gui.left = left;
600 p->left_pos = xp;
602 gui.nbcol = svg;
604 /* 4. Redraw display (needs previous changes before) */
605 if(skipy) redraw_content(p, disp, ystart, skipy);
607 } else {
608 /* We've jump too far, whole redraw */
609 register LONG nb = yp-p->top_line;
610 register LINE *disp = p->show;
611 p->top_line += nb;
612 if(nb < 0) for(; nb++; disp=disp->prev);
613 else for(; nb--; disp=disp->next);
614 p->show = disp;
615 p->left_pos = xp;
616 p->xcurs = (p->nbrc-p->left_pos) * XSIZE + gui.left;
617 redraw_content(p, disp, gui.topcurs, gui.nbline);
619 /* Adjust position of the prop gadget */
620 if(adj) prop_adj(p);
623 /*** Scroll lines up, at a specified position (used for deleting) ***/
624 void scroll_up(Project p, LINE *ln, WORD ystart, LONG leftpos)
626 register WORD nbl;
627 /* If some of redrawn lines are still visible */
628 if( leftpos == p->left_pos )
629 /* Optimize lines to refresh */
630 nbl = p->top_line + gui.nbline - p->nbl - 1,
631 ystart += YSIZE;
632 else
633 /* Redraw whole content */
634 nbl = gui.nbline, ystart = gui.topcurs, ln=p->show,
635 p->left_pos = leftpos;
637 redraw_content(p, ln, ystart, nbl);
638 /* Adjust cursor position */
639 p->xcurs = (p->nbrc-p->left_pos) * XSIZE + gui.left;