This is MiscFormField.m in view mode; [Download] [Up]
#include <string.h> #import <appkit/Cursor.h> #import <appkit/NXCType.h> #import "MiscFormField.h" #import "MiscFormFieldCell.h" #define FORM_FIELD_CURRENT_VERSION 4 // caveat: 4 reserved for stefan (skip to 5) /* ToDo * im .r mode multiple jumpchars erlauben * Wenn sprungzeichen getippt (zB das in mask sichtbare), in value durch jumpchar substituieren * error when inserting into a full r-field when cursor is not adjacent to jumpchar-> Beep() and ignore * outoinserting of a point when crossing validfields is possible * unpleasant removal of pixels under blinking caret * "rrrr" needs to be "rrrr." to work * setStringValue should check strlen and reset cursor if necess. * displayProblems when no bordertype specified * pasteing into full r-run should clip text * prevent deleting of jumpchar when in deleted selection * NXEditableFieldFormatter support * resignFirsResponder returnvalue must not be ignored * keybindings, firstresponderproblem (no nextText: resignFirstResponder) * * test control=" .rrrr" * enter jumpchar-> select 'cell' (esp. in r-mode) * valid-support * */ /******************************************** Spacer ********************************************************************/ /* Documentation: * * mask: this string determines the visual appearance of the content string: * every blank gives home to a corresponding char in the value string. * Example: mask:" . . " and value="123456" prints and copys as "12.34.56" * Note that every non-blank gets onscreen at corresponding his char-position * modify: this string gives you control over the cursorbehaviour (ignore this if you want simple * left to right typing behaviour). * Every run of 'r's lets the corresponding typed chars behave as in a calculator * eg 'rrrr. ' with '1.50' typed prints as ' 1.50'. Note that the actual '.'-char printed onscreen is * specified in the template string which would be ' . ' in the above example. * Also note that the very character you have to type that determines the jump from the section before and * behind the '.' is the one in mask (at the corresponding char position). for german use * mask could spell: ' , '; In modifiy it has always to be '.'. * If no special cursorbehaviour is needed, modiy can be omitted. * valid: gives the characterclass allowed for every corresponding position in the value string. note that *(not readyly) nonblank positions in the mask do not count. Example: mask=' . ', valid='NNNNN'. * avail. Defined classes will some day be: * 'C': [A-Z] (If [a-z] is typed, toupper is automatically invoked) * 'c': [a-z] (If [A-Z] is typed, tolower is automatically invoked) * '.': every character (same as no valid) * 'p': every printable character * '9': [0-9] * 'U': Userdefined */ #define jumpchar '.' char* mergeText(const char *t,const char *control,const char *c,char *buffer,char bc) { char a,*b,a1,*cntrl,*contstrt=(char*)c; BOOL hasCntrl=strlen(control); for(cntrl=(char*)control,a1=(char)*c,b=buffer;a=(char)*t;t++,cntrl++) { if(hasCntrl && cntrl[0] == 'r') { char *cp=cntrl,*cont,*lspc=strchr(c,jumpchar); int rspc,orspc,len=strlen(c); if(lspc) len=MIN(lspc-c,len); for(rspc=0;*cp && *cp == 'r';cp++) rspc++; cntrl=cp,orspc=rspc,c+=MIN(len,rspc),cont=len?((char*)c-1):0L; while (rspc > 0) b[--rspc]=(cont >= contstrt)? *cont--:bc; b+=orspc,t+=orspc-1,cntrl=cp-1; if(*c == jumpchar) c++, contstrt=(char*)c; //c=oldcont+orspc; continue; } if(a == bc) { if(!a1) *b++=bc; else *b++=(a1=*c++)? a1:bc; } else *b++=a; } *b++=0; return buffer; } void filterText(const char *val,char *cp,const char *template,const char *control) { int i; for(i=0;*val;i++,val++) { if(*val == template[i] && !(control && control[i]== jumpchar && control[MAX(i-1,0)] == 'r')) continue; *cp++=*val; } *cp=0; } int nCharsInStr(const char *control,char bc) { int erg,i; for(erg=i=0;control[i];i++) if(control[i] == bc) erg++; return erg; } int addrOfChars(const char *control,int cnt,char bc) { int i; for(i=0;control[i];i++) if (control[i] == bc) if(--cnt< 0) return i; return i; } @implementation MiscFormField static id FormFieldCellClass; // class variable + initialize { FormFieldCellClass=[MiscFormFieldCell class]; if(self == [MiscFormField class]) { [self setVersion:FORM_FIELD_CURRENT_VERSION]; } return self; } + setCellClass:classId { FormFieldCellClass=classId; return self; } - (const char *) getInspectorClassName { return "MiscFormFieldInspector"; } -(BOOL) charIsJumpChar:(char) theChar atPosition:(int) position { if(NXIsAlNum(theChar)) return NO; if(control && strstr(control,"r.")) //pocketcalculatormode { if(strchr(content,jumpchar)) return YES; // better: addrOfChars(content,cnt,jumpchar) } else if(strchr(template,theChar) && theChar!=blankchar) return YES; return NO; } int getRwidth(const char *control,int cnt) { int jcharPos=addrOfChars(control,cnt,jumpchar),rCnt; char om=control [jcharPos],*right; ((char*)control)[jcharPos]=0,rCnt=nCharsInStr((right=strrchr(control,jumpchar))?right:control,'r'); ((char*)control)[jcharPos]=om; return rCnt; } -(int) physPosForLogPos:(int)logpos { int physpos,olp=logpos; char c; const char *p=template; logpos=MIN(logpos,strlen(content)); for(physpos=0;logpos && (c=*p);physpos++,p++) if(c == blankchar) logpos--; if(control && strstr(control,"r.")) //pocketcalculatormode { int cnt,mcnt; char om=content [olp]; ((char*)content)[olp]=0,cnt=nCharsInStr(content,jumpchar), ((char*)content)[olp]=om; om=control [physpos]; ((char*)control)[physpos]=0,mcnt=nCharsInStr(control,jumpchar), ((char*)control)[physpos]=om; if(strchr(content,jumpchar) || !mcnt) { int erg=addrOfChars(control,cnt,jumpchar); if(control[MAX(erg-1,0)] != 'r') erg=addrOfChars(control,cnt-1,jumpchar)+ ((content+olp)-strchr(content,jumpchar)); else { int contWidth=strlen(content); // cnt-1???? if(strchr(content,jumpchar)) contWidth=addrOfChars(content,cnt,jumpchar); erg=getRwidth(control,mcnt)-(contWidth-olp); } return MIN(erg,strlen(template)); } } return physpos; } -(int) spaceAfterPosition:(int)logpos { int physpos,olp; char c; const char *p=template; logpos=olp=MIN(logpos,strlen(content)); for(physpos=0;logpos && (c=*p);physpos++,p++) if(c == blankchar) logpos--; if(control && strstr(control,"r.")) //pocketcalculatormode { int cnt,mcnt; char om=content [olp]; ((char*)content)[olp]=0,cnt=nCharsInStr(content,jumpchar), ((char*)content)[olp]=om; om=control [physpos]; ((char*)control)[physpos]=0,mcnt=nCharsInStr(control,jumpchar), ((char*)control)[physpos]=om; if(strchr(content,jumpchar) || !mcnt) { if(control[MAX(addrOfChars(control,cnt,jumpchar)-1,0)] != 'r') physpos=addrOfChars(control,cnt-1,jumpchar)+ ((content+olp)-strchr(content,jumpchar)); physpos=MIN(physpos,strlen(template)); } } return nCharsInStr(template+physpos, blankchar); } -(int) logPosForPhysPos:(int)physpos { int i; char c; const char *p=template; if(control && strstr(control,"r.")) //pocketcalculatormode { int cnt; char om=control [physpos],*ptr; ((char*)control)[physpos]=0,cnt=nCharsInStr(control,jumpchar); ((char*)control)[physpos]=om; if(control[MAX(physpos-1,0)] == 'r') { int contWidth=strlen(content); if(strchr(content,jumpchar)) contWidth=addrOfChars(content,cnt-1,jumpchar); physpos-=getRwidth(control,cnt-1)-contWidth; physpos=MAX(physpos,0); } else if(ptr=strchr(content,jumpchar)) return MIN((ptr-content)+physpos-addrOfChars(control,cnt-1,jumpchar),strlen(content)); else return strlen(content); // legal due to auto-insertion of jumpchar } for(i=physpos;i && (c=*p);i--,p++) if(c != blankchar) physpos--; return MIN(physpos,strlen(content)); } char* TextFilter(MiscFormField *self,uchar *insertText,int *insertLength,int position) { const char *mask=self->template; char *content=self->content, *p=content+position; int l,sWidth,il=*insertLength; if(sWidth=self->spN.cp-self->sp0.cp) //remove selected text { sWidth=MIN(sWidth,strlen(p)); memmove(p,p+sWidth,strlen(p)+1-sWidth); } if(insertText[0] == NX_BS && !(self->spN.cp != self->sp0.cp) && position) // backspace without selection memmove(p-1,p,strlen(p)+1); // delete one char else if(il) { if((l=([self spaceAfterPosition:position]-il)) > 0) memmove(p+il,p,MIN(strlen(p)+1,l+nCharsInStr(p,jumpchar))); // shuffle space for insertion else if(l < 0) p--; // bumped against the right margin memmove(p,insertText,MIN(il,MAX(strlen(mask)-position,1))); // insert new text take care of width } return insertText; } unsigned short FieldFilter(unsigned short theChar, int flags, unsigned short charSet) { if(theChar == NX_ESC) return NX_ESC; return NXFieldFilter(theChar,flags,charSet); } -(NXCoord) borderWidth { return BEZEL_WIDTH; } - getBounds:(NXRect *)lr { NXCoord borderWidth=[self borderWidth]; [super getBounds:lr]; lr->origin.x+=borderWidth,lr->origin.y+=borderWidth; lr->size.width-=(borderWidth*2),lr->size.height-=(borderWidth*2); return self; } - setFont:f { font=f; return [super setFont:f]; } - setFromCell:(MiscFormFieldCell*)theCell { [[[[self setMask:[theCell mask]] setModifyString:[theCell modifyString]] setStringValue:[theCell stringValue]] // calls cell's setStringValue with slightly altered string. (problm?) setBlankchar:[theCell blank]]; [self setValidString:[theCell validString]]; backgroundGray=[theCell backgroundGray]; textGray=[theCell textGray]; return self; } - discardSelection { sp0.x=-1.0; sp0.cp=spN.cp=0,sp0.op=spN.op=-1; return [self display]; } - setUpPointers { textFilterFunc=TextFilter; charFilterFunc=FieldFilter; return self; } /* * Designated Initializer */ - initFrame:(const NXRect *)frameRect withString:(const char *)value andMask:(const char *)mask andControlString:(const char *)theControlString andValidString:(const char *)theValidString { Font *f=[Font userFixedPitchFontOfSize:14 matrix:NX_FLIPPEDMATRIX]; blankchar=' '; mergeText(mask,theControlString,value,buffer,blankchar); [super initFrame:frameRect]; [f set]; charHeight=[f pointSize]; //-[f metrics]->baseline; [[self setCell:[[FormFieldCellClass alloc] initFormCell:value andMask:mask andControlString:theControlString andValidString:theValidString andBlank:blankchar]] free]; [[[self setFromCell:[self cell]] setFont:f] setEditable:YES]; [self setUpPointers]; [self discardSelection]; [self showCaret]; return self; } - initFrame:(const NXRect *)frameRect andMask:(const char *)theMask { [self initFrame:frameRect withString:"" andMask:theMask andControlString:"" andValidString:"99999999"]; return self; } - initFrame:(const NXRect *)frameRect { [self initFrame:frameRect withString:"1234.1" andMask:" . " andControlString:"rrrr. " andValidString:"99999999"]; return self; } - setBackgroundGray:(float)value { backgroundGray=value; return self; } - setTextGray:(float)value { textGray=value; return self; } - init { NXRect dummyFrame={0,0,10,10}; [self initFrame:&dummyFrame]; return self; } - awake //FromNib { timer=NULL; [self setUpPointers]; [self setFromCell:[self cell]]; [self discardSelection]; if(!blankchar) blankchar=' '; return self; } -(NXTextFilterFunc)textFilter { return textFilterFunc; } #define BASELINE_HEIGHT 3 static void DrawALine(MiscFormField *self,const NXRect *lr) { PSgsave(); NXRectClip(lr); [self->font set]; if(self->backgroundGray >=0) // transparent background does not work anymore! PSsetgray(self->backgroundGray),NXRectFill(lr); PSsetgray(self->textGray); PSmoveto(lr->origin.x,lr->origin.y+(self->charHeight-BASELINE_HEIGHT)+(lr->size.height-self->charHeight)/2); if(self->textGray >=0) PSsetgray([self isEnabled]? self->textGray:NX_DKGRAY); PSshow(mergeText(self->template,self->control,self->content,self->buffer,self->blankchar)); PSgrestore(); } void runOneCaret(DPSTimedEntry timedEntry, double timeNow, void *data) { NXRect lr; float greyV=NX_BLACK; MiscFormField *self=data; [self getBounds:&lr]; if(self->sp0.x <0) return; // do not draw invalidated cartet if(self->sp0._lcVisible=!self->sp0._lcVisible) greyV=self->backgroundGray; [self lockFocus]; NXRectClip(&lr); if(self->sp0.ox != self->sp0.x) // cursor moving? { DrawALine(self,&lr); // remove last cursor self->sp0.ox=self->sp0.x; greyV=NX_BLACK; // no blink when cursor moves } else if(!timedEntry) // prevent flickering when bumping margins { [self unlockFocus]; return; } lr.origin.x=self->sp0.x,lr.size.width=0, lr.origin.y+=(lr.size.height-self->charHeight)/2,lr.size.height=self->charHeight; PSsetgray(greyV),PSrectfill(NX_X(&lr),NX_Y(&lr),NX_WIDTH(&lr),NX_HEIGHT(&lr)); // draw cursor [self unlockFocus]; [[self window] flushWindow]; } -(BOOL) hasSelection:(int)l:(int)r { return !(l == r || l <0 || r <0); } -(BOOL) hasSelection { return [self hasSelection:sp0.cp:spN.cp]; } - showCaret { if ([self hasSelection]) return self; if(!timer) { timer=DPSAddTimedEntry(0.8,&runOneCaret,self,NX_BASETHRESHOLD); spN.cp=sp0.cp; //Caret to left margin of sel sp0.ox=-1; //ensure refresh } runOneCaret(NULL,0,self); // Force drawing return self; } - hideCaret { if(timer) DPSRemoveTimedEntry(timer),timer=NULL, [self display]; return self; } - (BOOL)acceptsFirstMouse { return YES; } - becomeFirstResponder { [self showCaret]; endChar=0; if(textDelegate && [textDelegate respondsTo:@selector(willBecomeFirstResponder:)]) [textDelegate willBecomeFirstResponder:self]; return self; } - becomeKeyWindow { [self becomeFirstResponder]; return self; } - resignFirstResponder { id erg=self; if(textDelegate && [textDelegate respondsTo:@selector(textWillEnd:)]) { if([textDelegate textWillEnd:self]) erg=nil; } if(erg) { [self discardSelection],[self hideCaret]; if(textDelegate && [textDelegate respondsTo:@selector(textDidEnd:endChar:)]) [textDelegate textDidEnd:self endChar:endChar]; [self discardSelection],[self hideCaret]; } return erg; } - resignKeyWindow { [self hideCaret]; return self; } - windowChanged:newWindow { return [self hideCaret]; } - setSel:(int)start :(int)end //logical postions { sp0.op=sp0.cp,spN.op=spN.cp; //remember prev sp0.cp=start,spN.cp=end; //establish new sel. if(start == end && [self isEditable]) // no selection but caret { NXRect lr; int nPos=[self physPosForLogPos:MAX(MIN(start,strlen(content)),0)]; sp0.x=[self widthForCp:nPos]; // caret coordinate [self getBounds:&lr],sp0.x+=lr.origin.x; [self showCaret]; } else { [self hideCaret]; [self display]; } return self; } - selectAll:sender { return [self setSel:0 :strlen(content)]; } //for textclass compatibility only - setText:(const char *)theText { [self setStringValue:theText]; [self selectAll:self]; return self; } - selectText:sender { [self selectAll:self]; [window makeFirstResponder:self]; return self; } -(float)widthForCp:(int)cp //pixeloffset for characterposition { float erg; char om=buffer [cp]; ((char*)buffer)[cp]=0,erg=[font getWidthOf:buffer], ((char*)buffer)[cp]=om; return erg; } -(int)cpForEvent:(NXEvent *)event wasInside:(BOOL*)was // returns a physical position { NXPoint p=event->location; NXRect lr; char om; int i; [self getBounds:&lr]; [self convertPoint:&p fromView:nil]; *was=NXPointInRect(&p,&lr); if(p.x <= 0 || p.y <0) return 0; if(p.y> lr.size.height) return strlen(template); for(i=0 ;om=buffer[i],((char*)buffer)[i]=0,YES ;((char*)buffer)[i++]=om) { if([font getWidthOf:buffer] >= p.x) { ((char*)buffer)[i]=om; return MAX(0,i-1); } if(!om) break; } return strlen(template); } - drawSelection:(int)l :(int)r deleteIfEmptySel:(BOOL) delSel //physical positions { NXRect lr; float x; [self getBounds:&lr]; if (![self hasSelection:l:r]) { if(delSel) { [self lockFocus]; DrawALine(self,&lr); [self unlockFocus]; } return nil; // no selection present } else [self hideCaret]; while(template[l] != blankchar) l++; //(only for cosmetical reasons) x=lr.origin.x; [self lockFocus]; DrawALine(self,&lr); // remove Background lr.origin.x=[self widthForCp:l], //clc selection coordinates lr.size.width=[self widthForCp:r]-lr.origin.x-1; lr.size.height-=2,lr.origin.x+=x; lr.origin.y+=1; NXHighlightRect(&lr); // display selection [window flushWindow]; [self unlockFocus]; return self; } - drawSelection:(int)l :(int)r { return [self drawSelection:l:r deleteIfEmptySel:NO]; } - drawSelection { return [self drawSelection:[self physPosForLogPos:sp0.cp] :[self physPosForLogPos:spN.cp]]; } #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK - mouseDown:(NXEvent *)theEvent { NXEvent *event=theEvent; BOOL inside; int eventMask,peekPos=[self cpForEvent:theEvent wasInside:&inside]; if(!inside || ![self isSelectable]) return self; if(theEvent->flags & NX_CONTROLMASK) //control-cliq { //so what should i do here? } else if(theEvent->flags & NX_ALTERNATEMASK) //Alt-cliq { } else { peekPos=[self physPosForLogPos:[self logPosForPhysPos:peekPos]]; sp0.cp=spN.cp=peekPos; //no sel. present, so [self display]; //remove phenotype of prev. selection } switch(theEvent->data.mouse.click) { case 2: //Doubleclick case 3: return [self selectAll:self]; //Trippleclick-> select all } for(eventMask=[window addToEventMask:NX_MOUSEDRAGGEDMASK] //This for runs the modalsession ;event->type != NX_MOUSEUP ;event=[NXApp getNextEvent:MOVE_MASK]) { peekPos=[self cpForEvent:event wasInside:&inside]; peekPos=[self physPosForLogPos:[self logPosForPhysPos:peekPos]]; // for peekPos supervising [self drawSelection:MIN(peekPos,sp0.cp):MAX(peekPos,sp0.cp) deleteIfEmptySel:YES]; // draw but not set the selection } [window setEventMask:eventMask]; [self setSel:[self logPosForPhysPos:MIN(peekPos,sp0.cp)] //at least establish the selection :[self logPosForPhysPos:MAX(peekPos,sp0.cp)]]; return self; } - setCaretFromLogPos:(int) pos { [self setSel:pos:pos]; return self; } -(int)checkForNewPos:(int)npos prev:(int)prev //as log pos { int bonus=0; if(control && strstr(control,"r.")) { bonus=nCharsInStr(content,jumpchar); if(prev < 0) //check whether typed char bumps the right margin { if ((control[MAX([self physPosForLogPos:sp0.cp]-1,0)] != 'r') && ([self physPosForLogPos:npos] == [self physPosForLogPos:sp0.cp])) return sp0.cp; } else if([self physPosForLogPos:prev] == [self physPosForLogPos:npos]) return prev; } return MIN(MIN(MAX(0,npos),strlen(content)),nCharsInStr(template, blankchar)+bonus); } -(BOOL) checkCrossingPoint:(int) position { int physpos=strchr(control,jumpchar)-control,mcnt; // calculate offset from control because char om=template [physpos]; //jumpchar only makes sense here ((char*)template)[physpos]=0,mcnt=nCharsInStr(template, blankchar), //"logpos" ((char*)template)[physpos]=om; return (position >= mcnt); } -(BOOL) shouldRejectChar:(int) theChar atPosition:(int) position { int cnt=nCharsInStr(content,jumpchar); const char *p; if (theChar !=NX_BS && control && strstr(control,"r.") && (p=strchr(content+position,jumpchar)) && p-content>= getRwidth(control,cnt-1)) return YES; else // check the valid string¼ { if(theChar !=NX_BS && valid && strlen(valid)) { switch(valid[position]) { case '9': return !NXIsDigit(theChar); // only numbers allowed case 'A': break; // capital charactes (unimplemented) } } return NO; } } -(BOOL) shouldSkipJumpchar:(int) position :(int) startingPosition { if(!(control && strstr(control,"r.")) || !(strchr(content,jumpchar))) return NO; else { if(position <=strlen(content) && content[MIN(position,startingPosition)] == jumpchar) { if(startingPosition< position) //crsr right { if([self checkCrossingPoint:startingPosition]) return YES; } else return YES; } } return NO; } - (char *)doTextFilterFunction:(unsigned char *) c length:(int *)insertLength position:(int)insertPosition { char *ret=0; BOOL editNotAllowed=NO; if (textDelegate && [textDelegate respondsTo:@selector(textWillChange:)]) editNotAllowed=[textDelegate textWillChange:self]; if (!editNotAllowed) { ret=textFilterFunc(self,c,insertLength,insertPosition); #if 0 if(textDelegate && [textDelegate respondsTo:@selector(textDidGetKeys:isEmpty:)]) [textDelegate textDidGetKeys:self isEmpty:content[0]]; #endif if(textDelegate && [textDelegate respondsTo:@selector(textDidChange:)]) [textDelegate textDidChange:self]; } return ret; } - keyDown:(NXEvent *)theEvent { BOOL hasSel=spN.cp != sp0.cp; int position=sp0.cp,startingPosition=position,insertPosition=position; ushort theChar; id target=[self target]; if (![self isEditable]) return self; switch(theChar=charFilterFunc(theEvent->data.key.charCode, theEvent->flags, theEvent->data.key.charSet)) { case NX_RETURN: // Enter+ Return #if 1 if(target) [NXApp sendAction:[self action] to:target from:self]; // fall thru (tab follows cr) #else return [super keyDown:theEvent]; #endif case NX_DOWN:// if(theChar== NX_DOWN && !fFlags.crscUPDWN) break; case NX_TAB: { if(nextText && [nextText respondsTo:@selector(selectText:)]) { endChar=theChar; [self resignFirstResponder], [[nextText selectText:self] setPreviousText:self]; } else [self selectText:self]; } #if 0 if(theChar == NX_RETURN) { endChar=theChar; [window makeFirstResponder:nextResponder]; } #endif return self; case NX_UP: // if(theChar== NX_DOWN && !fFlags.crscUPDWN) break; case NX_BACKTAB: { if(previousText && [previousText respondsTo:@selector(selectText:)]) { endChar=theChar; [self resignFirstResponder],[previousText selectText:self]; } } return self; case NX_LEFT: if(hasSel) position=sp0.cp; else position--; position=[self checkForNewPos:position prev:sp0.cp]; break; case NX_RIGHT: if(hasSel) position=spN.cp; else position++; position=[self checkForNewPos:position prev:spN.cp]; break; case NX_ESC: if(fFlags.escClearLine && strlen(content)) [self setStringValue:""],position=0; if(textDelegate && [textDelegate respondsTo:@selector(escPressed:)]) [textDelegate escPressed:self]; break; default: { if(theChar == NX_BS && (!sp0.cp && !spN.cp)) // BS at left margin? { NXBeep(); return self; } else { int pos=MAX(sp0.cp,spN.cp),cposition; const char *cellL,*cellR,*nCell; if(!pos || (pos ==strlen(content) && theChar != NX_BS)) cellL=template-1,cellR=NULL; else { char om=template[pos]; ((char*)template)[pos]=0,cellL=strrchr(template,theChar),((char*)template)[pos]=om; cellR=strchr(template+pos,theChar); } cposition=MAX(cellL,cellR)-template+1; if([self charIsJumpChar:theChar atPosition:position] && (cellL || cellR)) //is typed char a jumpchar? { nCell=(nCell=strchr(template+cposition,theChar))? nCell:template+strlen(template); [self setSel: [self logPosForPhysPos:cposition] :MIN([self logPosForPhysPos:nCell-template], strlen(content))]; return self; } else // displaying is done by automatic caret refresh { uchar c[2]={(uchar) theChar,(uchar) theChar}; int insertLength=1; if(theChar == NX_BS) { insertLength=0; if(hasSel) return [self delete:self]; else position--; } else position++; if([self shouldSkipJumpchar:position:startingPosition]) { if (position > startingPosition) //protect jumpchar from edititng insertPosition++, position++; else insertPosition--, position--; } else if([self shouldRejectChar:theChar atPosition:startingPosition] && ![self hasSelection]) { NXBeep(); return self; } else if(control && strstr(control,"r.") && [self checkCrossingPoint:startingPosition] && !strchr(content,jumpchar)) // in calculatormode add jumpchchar if absent (crude calculation) { c[0]=jumpchar,insertLength++,position++; } [self doTextFilterFunction:c length:&insertLength position:insertPosition]; position=[self checkForNewPos:position prev:-1]; if(startingPosition == position) { if(fFlags.beepWhenRight) NXBeep(); [self display]; // force drawing when caret did not move } if(self->sp0.ox == self->sp0.x) [self display]; // force drawing in some condition } } } } [self setCaretFromLogPos:position]; return self; } - displayInRect:(const NXRect *)rect // gets called from the cell { if(![self drawSelection]) DrawALine(self,rect); return self; } - (int)getSubstring:(char *)buf start:(int)startPos length:(int)length // as physpos { char *c=buffer; int copyLen=MIN(startPos+length,strlen(c))-startPos; if(startPos > strlen(c)) return -1; if(copyLen) strncpy(buf,c+startPos,copyLen)[copyLen]=0; return copyLen; } - delete:sender { char data=NX_BS; int length=0; if(![self hasSelection]) return self; [self doTextFilterFunction:&data length:&length position:sp0.cp]; [[self setCaretFromLogPos:sp0.cp] display]; return self; } - copy:sender { id gpb=[Pasteboard new]; int l=[self physPosForLogPos:sp0.cp], r=[self physPosForLogPos:spN.cp]; [self getSubstring:lb start:l length:r-l]; [gpb declareTypes:&NXAsciiPboardType num:1 owner:self]; [gpb writeType:NXAsciiPboardType data:lb length:strlen(lb)]; return self; } - cut:sender { [[self copy:self] delete:self]; return self; } - paste:sender { id gpb=[Pasteboard new]; char *data; int length; [gpb types]; if([gpb readType:NXAsciiPboardType data:&data length:&length]) { int nl; data[length]=0; // truncation filterText(data,lb,template+sp0.cp,control+sp0.cp),nl=strlen(lb); [self doTextFilterFunction:lb length:&nl position:sp0.cp]; [[self setCaretFromLogPos:sp0.cp+strlen(lb)] display]; [gpb deallocatePasteboardData:data length:length]; } return self; } #if 1 - setEnabled:(BOOL) flag { [super setEnabled:flag]; //[self setSelectable:flag]; [self setEditable:flag]; if(flag) [self selectText:self]; else [self discardSelection]; fFlags.disabled= !flag; [self display]; return self; } -(BOOL) isEnabled { return !fFlags.disabled; } #endif #define PT_CHR jumpchar #define MX_FBF 32 -(const char *)stringValue { char *erg=mergeText(template,control,content,buffer,blankchar); #if 1 if(completionChar) { int contLen=strlen(content),fillLen=nCharsInStr(template,blankchar)-contLen; if(fillLen > 0) { char *dstPtr=(char *)erg+contLen; memset(dstPtr,completionChar,fillLen); dstPtr[fillLen]=0; } } #endif return erg; } - setStringValue:(const char *)val atOffset:(int)o { filterText((char *)val,content+o,template,control); [cell setStringValue:content]; if([window firstResponder] == self) [self setSel:MIN(sp0.cp,strlen(content)):MIN(spN.cp,strlen(content))]; [self display]; return self; } #define DIGITS "0123456789" BOOL isFloat(const char *val) { int l1; if (*val=='-') val++; if (val[l1=strspn(val,DIGITS)]!=PT_CHR) return NO; return (strspn(val+l1+1,DIGITS)+l1+1)==strlen(val)? YES: NO; } - setStringValue:(const char *)val { strcpy(lb,val); if (isFloat(val)) { const char *cStr=strchr(control, PT_CHR); if (cStr && !strchr(cStr+1,PT_CHR)) //floting-point-like formatting { int length=strlen(control); sprintf(lb,"%*.*f",length,length-(int)(cStr-control)-1,atof(val)); } } if (delegate && [delegate respondsTo:@selector(modifyStringValue:for:)]) [delegate modifyStringValue:content for:self]; //<!>?? [self setStringValue:lb atOffset:0]; //[cell setStringValue:content]; return self; } - setDoubleValue:(double)val { char floatingStr[MX_FBF]; sprintf(floatingStr,"%f",val); return [self setStringValue:floatingStr]; } - takeDoubleValueFrom:sender { return [self setDoubleValue:[sender doubleValue]]; } - (double)doubleValue { return atof([self pureStringValue]); } - takeFloatValueFrom:sender { return [self setDoubleValue:[sender floatValue]]; } - (float)floatValue { return (float)atof([self pureStringValue]); } - setIntValue:(int)val { char intStr[MX_FBF]; sprintf(intStr,"%*d",nCharsInStr(content, blankchar),val); [self setStringValue:intStr]; return self; } - takeIntValueFrom:sender { return [self setIntValue:[sender intValue]]; } - (int)intValue { return atoi([self pureStringValue]); } - setValue:(const char *)val { strcpy(content,val); return self; } - setMask:(const char *)mask { template=mask; return self; } -(const char*) mask { return template; } -(const char*) validString { return valid; } - setModifyString:(const char *)mask { control=mask; return self; } -(const char*) modifyString { return control; } -(const char*) pureStringValue { return content; } - setGrid:(BOOL)flag { fFlags.drawGrid=flag; return [self display]; } - setOverwrite:(BOOL)flag { fFlags.overwrite=flag; return self; } -(BOOL)grid { return fFlags.drawGrid; } -(BOOL)overwrite { return fFlags.overwrite; } -(BOOL) specialKeybindings { return fFlags.specialKeybindings; } - setSpecialKeybindings:(BOOL)flag { fFlags.specialKeybindings=flag; return self; } -(char)blankchar { return blankchar; } - setBlankchar:(char)bc { blankchar=bc; return self; } - (const char*)valid { return valid; } - setValidString:(const char*)aValid { valid=aValid; return self; } - (BOOL)beepWhenRight { return fFlags.beepWhenRight; } - setBeepWhenRight:(BOOL)aBeepWhenRight { fFlags.beepWhenRight=aBeepWhenRight; return self; } - (BOOL)escClearLine { return fFlags.escClearLine; } - setEscClearLine:(BOOL)aEscClearLine { fFlags.escClearLine=aEscClearLine; return self; } - read: (NXTypedStream*)stream { int version; [super read:stream]; switch(version=NXTypedStreamClassVersion(stream,"MiscFormField")) { case 1: case 2: NXReadTypes(stream,"@fi",&font,&charHeight,&fFlags); break; case 3: NXReadTypes(stream,"@fi",&font,&charHeight,&fFlags); NXReadObject(stream); break; case FORM_FIELD_CURRENT_VERSION: { NXReadTypes(stream,"@fic",&font,&charHeight,&fFlags,&completionChar); delegate=NXReadObject(stream); } break; default: NXRunAlertPanel("Error","Unsupported MiscFormField version %d","Exit",NULL,NULL,version); } return self; } - write: (NXTypedStream*)stream { [super write:stream]; NXWriteTypes(stream,"@fic",&font,&charHeight,&fFlags,&completionChar); NXWriteObjectReference(stream,delegate); return self; } - delegate { return delegate; } - setDelegate:aDelegate { delegate=aDelegate; return self; } - (char)completionChar { return completionChar; } - setCompletionChar:(char)aCompletionChar { completionChar=aCompletionChar; return self; } @end @interface FormField:MiscFormField { } @end @implementation FormField @end @interface FormFieldCell:MiscFormFieldCell { } @end @implementation FormFieldCell @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.