/* * Copyright (C) 1998,1999 David Stes. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #define Object XtObject #define String XtString #include #include #include #if 0 #include #endif #include #include #include #include #include #include "rtfwidget.h" #undef Object #undef String #include "document.h" /* subclass of Object */ #include "main.h" #include "menu.h" #include "richtext.h" #include "panic.h" #include #include #include "charblk.h" #include #include "plain.h" #include "boldface.h" #include "italic.h" #include "underlined.h" #include "fontchange.h" #include "fontsizechange.h" #include "richfont.h" #include "richtext.xbm" #define Object XtObject /* XtWindow() and XtDisplay are casting to Object */ static Window getxwindow(void* w) { return XtWindow((Widget)w); } static Display* getxdisplay(void* w) { return XtDisplay((Widget)w); } #undef Object static id documents; @implementation Document + initialize { rtfwidgetinitialize(); documents = [OrdCltn new]; papersize = PSIZE_A4; #ifndef LPCMD #define LPCMD "lpr -h -#1 -P lp" #endif printcmd = [String str:LPCMD]; return self; } + documents { return documents; } + rearrange { int i; int n = [documents size]; for(i=0;ivalue; [(id)clientdata scroll:newvalue]; } - setibeamcursor { Cursor cursor = XCreateFontCursor(getxdisplay(textwidget),XC_xterm); XDefineCursor(getxdisplay(textwidget),getxwindow(textwidget),cursor); return self; } - setwaitcursor { Cursor cursor = XCreateFontCursor(getxdisplay(textwidget),XC_watch); XDefineCursor(getxdisplay(textwidget),getxwindow(textwidget),cursor); return self; } static void deleteproc(Widget w,caddr_t clientdata,caddr_t calldata) { [(id)clientdata close]; } - setwmcallbacks { Atom xa = XmInternAtom(maindisplay,"WM_DELETE_WINDOW",True); XmAddWMProtocolCallback(shell,xa,(XtCallbackProc)deleteproc,(caddr_t)self); return self; } - realize { int ac = 0; Arg al[20]; Pixmap icon; char *nm = [filename str]; XtSetArg(al[ac], XmNtitle, nm); ac++; XtSetArg(al[ac], XmNdeleteResponse, XmDO_NOTHING); ac++; XtSetArg(al[ac], XmNiconName, nm); ac++; shell = XtAppCreateShell(NULL, APP_CLASS,applicationShellWidgetClass, maindisplay, al, ac); /* store self in userData (see +fromwidget:) */ XtSetArg(al[ac], XmNuserData, (XtPointer)self); ac++; XtSetArg(al[ac], XmNspacing, 0); ac++; /* set callback for WM close button */ [self setwmcallbacks]; mainw = XmCreateMainWindow(shell, "main", al, ac); XtManageChild(mainw); menubar = newmenubar(mainw,self); XtManageChild(menubar); scrollbar = XtVaCreateManagedWidget("scrollbar",xmScrollBarWidgetClass,mainw,XmNorientation,XmVERTICAL,XmNrepeatDelay,10,0); XtAddCallback(scrollbar, XmNdragCallback, scrollcb, (XtPointer)self); XtAddCallback(scrollbar, XmNvalueChangedCallback, scrollcb, (XtPointer)self); textwidget = XtVaCreateManagedWidget("richtext",rtfWidgetClass,mainw,rtfNrichtext,richtext,rtfNscrollbar,scrollbar,0); XmMainWindowSetAreas(mainw,menubar,NULL,NULL,scrollbar,textwidget); lastfocus = textwidget; [documents add:self]; XtRealizeWidget(shell); [self setmodified:NO]; /* because resize() may have set modified to YES */ icon = XCreateBitmapFromData(getxdisplay(shell),getxwindow(shell),(char*)richtext_bits,richtext_width,richtext_height); ac = 0; XtSetArg(al[ac], XtNiconPixmap, icon); ac++; XtSetValues(shell, al, ac); [self setibeamcursor]; return self; } + new { id doc = [super new]; id rt = [Richtext new]; [rt adobefonts]; [rt addnewline]; [doc richtext:rt]; [doc setuntitledname]; return [doc realize]; } static void delayedopen(XtPointer clientdata,XtIntervalId *timer) { [documents elementsPerform:@selector(setwaitcursor)]; [Document open:[selectedname str]]; selectedname = [selectedname free]; [documents elementsPerform:@selector(setibeamcursor)]; } - open { if (DLGOK == runfiledialog(shell,"Open File:")) { assert(selectedname != nil); XtAppAddTimeOut(maincontext,1,delayedopen,NULL); } return self; } - richtext:obj { richtext=obj; return self; } - richtext { return richtext; } - open:(char*)fn { FILE *f; char *what; f = fopen(fn,"r"); if (!f) { what = strerror(errno); goto err; } [self richtext:[Richtext new]]; if (![richtext readrtf:f]) { what = [richtext errormsg]; goto err; } fclose(f); [self setfilename:fn]; [self realize]; return self; err: [self setuntitledname]; [self realize]; warndialog([self lastfocus],"Can't open %s.\n\n%s.",fn,what); if (f) fclose(f); return self; } + open:(char*)fn { return [[super new] open:fn]; } + fromwidget:(Widget)w { id doc; while (1) { Widget parent = XtParent(w); if (parent == NULL) return nil; if (XtClass(parent) == applicationShellWidgetClass) break; w = parent; } XtVaGetValues(w, XmNuserData, &doc, 0); return doc; } + fromfilename:(char*)fn { int i,n; for(i=0,n=[documents size];i= 0 && y <= totalh - min) { [richtext top:y]; scrollwidget((RtfWidget)textwidget,y - oldtop); } else if (y < 0) { if (oldtop != 0) { [richtext top:0]; scrollwidget((RtfWidget)textwidget,-oldtop); } } else { if (oldtop != totalh - min) { [richtext top:totalh - min]; scrollwidget((RtfWidget)textwidget,totalh - min - oldtop); } } return self; } } - updatescrollbar { int ac = 0; Arg al[20]; int oldtop = [richtext top]; int totalh = [richtext totalheight]; int paperh = twips2points([richtext paperh]); int min = (totalh h) [self scroll:oldtop + mousey - h]; if ([richtext top] != oldtop) [self updatescrollbar]; autoscrolltimer = XtAppAddTimeOut(maincontext,getautoscroll(textwidget),autoscroll,self); return self; } - startAutoscroll { if ([self insertionpoint]) [self stopBlink]; if (!autoscrolltimer) [self autoscroll]; return self; } - stopAutoscroll { if (autoscrolltimer) { XtRemoveTimeOut(autoscrolltimer); autoscrolltimer = (XtIntervalId)NULL; } if ([self insertionpoint]) [self startBlink]; return self; } static void blink(XtPointer clientdata,XtIntervalId *timer) { [(id)clientdata blink]; } - blink { if (blinktimer) { assert([self insertionpoint]); selectionshowing = !selectionshowing; reverseinsertpoint((RtfWidget)textwidget,startblock); blinktimer = XtAppAddTimeOut(maincontext,getblinkrate(textwidget),blink,self); } return self; } - startBlink { if (!blinktimer && [self insertionpoint]) { assert(selectionshowing == NO); blinktimer = XtAppAddTimeOut(maincontext,getblinkrate(textwidget),blink,self); } return self; } - stopBlink { if (blinktimer && selectionshowing) { reverseinsertpoint((RtfWidget)textwidget,startblock); selectionshowing = NO; } if (blinktimer) { XtRemoveTimeOut(blinktimer); blinktimer = (XtIntervalId)NULL; } return self; } - zapSelection:(char*)s count:(int)n { if ([self insertionpoint]) { int oldlinecount,newlinecount; [self deselect]; oldlinecount = [[[richtext paralines] at:[startblock parindex]] size]; [richtext atBlock:startblock insert:s count:n]; newlinecount = [[[richtext paralines] at:[startblock parindex]] size]; [self selectInvisiblyAt:[startblock parindex]:[startblock stringindex]+n]; [self setmodified:YES]; if (newlinecount == oldlinecount) { /* cheap but not always correct */ refreshparagraph((RtfWidget)textwidget,[startblock parindex]); } else { [self update]; } return [self select]; } if ([self properselection]) { id attrs; int i = [startblock parindex]; int j = [startblock stringindex]; [self deselect]; attrs = [[richtext attributesAtBlock:startblock] deepCopy]; [richtext replaceFrom:startblock to:stopblock with:s count:n]; [self selectInvisiblyFrom:i:j to:i:j+n]; [richtext addAttributes:attrs fromblock:startblock toblock:stopblock]; [attrs free]; [self selectInvisiblyAt:i:j+n]; [self update]; [self setmodified:YES]; return [self select]; } XBell(maindisplay,100); return self; } - keydown:(XEvent*)event { KeySym k; k = XtGetActionKeysym(event,NULL); if (k == NoSymbol) { XBell(maindisplay,100); } else { /* if (k == XK_hyphen) k = XK_minus; */ if (k & 0xff00) { } else { char c = (char)(k); [self zapSelection:&c count:1]; } } return self; } - newline { [self zapSelection:"\n" count:1]; return self; } - tabkey { [self zapSelection:"\t" count:1]; return self; } - movecursor:aBlock { if (aBlock) { [self deselect]; [self startblock:aBlock stopblock:nil]; [self selectAndScroll]; } else { XBell(maindisplay,100); } return self; } - cursorright { id blk = nil; if ([self insertionpoint]) blk=[richtext blockAfter:startblock]; if ([self properselection]) blk=[richtext blockAfter:stopblock]; return [self movecursor:blk]; } - cursorleft { id blk = nil; if ([self insertionpoint]) blk=[richtext blockBefore:startblock]; if ([self properselection]) blk=[richtext blockBefore:stopblock]; return [self movecursor:blk]; } - cursorup { id blk = nil; if ([self insertionpoint]) blk=[richtext blockAbove:startblock]; if ([self properselection]) blk=[richtext blockAbove:startblock]; return [self movecursor:blk]; } - cursordown { id blk = nil; if ([self insertionpoint]) blk=[richtext blockBelow:startblock]; if ([self properselection]) blk=[richtext blockBelow:stopblock]; return [self movecursor:blk]; } - backspace { if ([self properselection]) { return [self delete]; } if ([self insertionpoint]) { id b = [richtext blockBefore:startblock]; [self startblock:b stopblock:[b copy]]; return [self delete]; } XBell(maindisplay,100); return self; } static int numclicks; static Time lasttime; - mousedown:(XButtonEvent*)event { id b; [self raise]; b = [richtext charBlockAtPoint:event->x:event->y + [richtext top]]; assert(b); [self deselect]; if (event->time > lasttime + XtGetMultiClickTime(maindisplay)) { numclicks=1; } else { numclicks++; } lasttime = event->time; switch (numclicks) { case 2 : [self selectWordAt:b]; break; case 3 : [self selectLineAt:b]; break; default : [self selectAt:b]; break; } [self select]; return self; } - shiftclick:(XButtonEvent*)event { numclicks = 1; [self raise]; return [self mousemoved:event]; } - mousemoved:(XButtonEvent*)event { id b; int h = twips2points([richtext paperh]); mousex = event->x; mousey = event->y; if (mousey < 0 || mousey > h) { [self startAutoscroll]; } else { [self stopAutoscroll]; } b = [richtext charBlockAtPoint:mousex:mousey + [richtext top]]; assert(b); switch (numclicks) { case 2 : [self extendWordSelection:b];break; case 3 : [self extendLineSelection:b];break; default : [self extendSelection:b];break; } return self; } - mouseup:(XButtonEvent*)event { if (pivotblock) pivotblock = [pivotblock free]; [self stopAutoscroll]; return self; } - wordbeginAt:aBlock { int p,n; char *t,*delims; int pi = [aBlock parindex]; int ci = [aBlock stringindex]; delims = getworddelimiters(textwidget); n = [[[[richtext paragraphs] at:pi] text] size]; t = [[[[richtext paragraphs] at:pi] text] str]; if (n && strchr(delims,t[ci]) == NULL) { p = ci; while (p>0) if (strchr(delims,t[p])) {p++;break;} else p--; return [richtext charBlockForIndex:pi:p]; } return nil; } - wordendAt:aBlock { int q,n; char *t,*delims; int pi = [aBlock parindex]; int ci = [aBlock stringindex]; delims = getworddelimiters(textwidget); n = [[[[richtext paragraphs] at:pi] text] size]; t = [[[[richtext paragraphs] at:pi] text] str]; if (n && strchr(delims,t[ci]) == NULL) { q = ci; while (q= s) { if (strncmp(f,fs,len)==0) { return [self selectFrom:i:f-s to:i:f-s+len-1]; } } } if (--i >= 0) { s = [[[paragraphs at:i] text] str]; t = s + strlen(s); } else { break; } } } XBell(maindisplay,100); return self; } - recomputeSelection { if ([self insertionpoint]) { [self selectInvisiblyAt:[startblock parindex]:[startblock stringindex]]; } if ([self properselection]) { [self selectInvisiblyFrom:[startblock parindex]:[startblock stringindex] to:[stopblock parindex]:[stopblock stringindex]]; [richtext selrectsfrom:startblock to:stopblock]; } return self; } - selectFrom:(int)pi:(int)ci to:(int)pj:(int)cj { if ((startblock != nil && stopblock != nil) && ([startblock parindex] == pi && [startblock stringindex] == ci) && ([stopblock parindex] == pj && [stopblock stringindex] == cj)) { return [self selectAndScroll]; } else { [self deselect]; [self selectInvisiblyFrom:pi:ci to:pj:cj]; return [self selectAndScroll]; } } - selectAll { int n; id pgs = [richtext paragraphs]; if ((n = [pgs size])) { int m = [[[pgs at:n-1] text] size]; if (m) [self selectFrom:0:0 to:n-1:m-1]; else [self selectFrom:0:0 to:n-1:0]; } return self; } - makeRoman { id x = [Fontchange new]; id f = [richtext fontwithfamily:FFROMAN]; [x setfontnum:[f fontnum]]; [richtext addAttribute:x fromblock:startblock toblock:stopblock]; [self setmodified:YES]; return [self update]; } - makeSwiss { id x = [Fontchange new]; id f = [richtext fontwithfamily:FFSWISS]; [x setfontnum:[f fontnum]]; [richtext addAttribute:x fromblock:startblock toblock:stopblock]; [self setmodified:YES]; return [self update]; } - makeModern { id x = [Fontchange new]; id f = [richtext fontwithfamily:FFMODERN]; [x setfontnum:[f fontnum]]; [richtext addAttribute:x fromblock:startblock toblock:stopblock]; [self setmodified:YES]; return [self update]; } - makeFontsize:(int)points { id x = [Fontsizechange new]; [x setfontsize:points * 2]; /* RTF double points */ [richtext addAttribute:x fromblock:startblock toblock:stopblock]; [self setmodified:YES]; return [self update]; } - makePlain { id x = [Plain new]; [richtext addAttribute:x fromblock:startblock toblock:stopblock]; [self setmodified:YES]; return [self update]; } - makeBold { id x = [Boldface new]; [richtext addAttribute:x fromblock:startblock toblock:stopblock]; [self setmodified:YES]; return [self update]; } - makeItalic { id x = [Italic new]; [richtext addAttribute:x fromblock:startblock toblock:stopblock]; [self setmodified:YES]; return [self update]; } - makeUnderlined { id x = [Underlined new]; [richtext addAttribute:x fromblock:startblock toblock:stopblock]; [self setmodified:YES]; return [self update]; } - forkprint { if (printcmd != nil && [printcmd size] > 1) { FILE *ps = popen([printcmd str],"w"); if (ps) { [richtext writepostscript:ps]; pclose(ps); } else { warndialog([self lastfocus],"Can't open pipe to %s.\n",[printcmd str]); } } else { warndialog([self lastfocus],"Can't open pipe for printing (no cmd).\n"); } return self; } static void delayedprint(XtPointer clientdata,XtIntervalId *timer) { [(id)clientdata forkprint]; } - print { if (printdialog(shell) == DLGOK) { XtAppAddTimeOut(maincontext,1,delayedprint,self); } return self; } @end