This is ScrollText.m in view mode; [Download] [Up]
// ------------------------------------------------------------------------------------- // ScrollText.m // author: Martin D. Flynn // (see ScrollText.h for usage information) // ------------------------------------------------------------------------------------- // Permission is granted to freely redistribute this source code, and to use fragments // of this code in your own applications if you find them to be useful. This class, // along with the source code, come with no warranty of any kind, and the user assumes // all responsibility for its use. // ------------------------------------------------------------------------------------- #import <appkit/appkit.h> #import <libc.h> #import <mach/cthreads.h> #import <stdlib.h> #import <stdarg.h> #import <string.h> #import <pwd.h> #import <sys/types.h> #import <sys/wait.h> #import "ScrollText.h" // ------------------------------------------------------------------------------------- // mutex defines #define mutexAlloc mutex_alloc #define mutexFree mutex_free #define mutexLock mutex_lock #define mutexUnlock mutex_unlock // ------------------------------------------------------------------------------------- // null text attribute structure static NXColor nullColor = { 0 }; static textAttr_t nullAttr = { (id)nil, 0 }; #define isNullAttr(X) (!X.fontId && !X.colorMode) // ------------------------------------------------------------------------------------- // main thread handle #define isMainTHREAD (mainThread == cthread_self()) static cthread_t mainThread = (cthread_t)nil; // ------------------------------------------------------------------------------------- // Should be proto-typed elsewhere @interface Object(ShouldBeElsewhere) - (const char*)printerName; // should be in PrintInfo @end // ------------------------------------------------------------------------------------- // Object thread support @interface Object(ThreadPerform) - mainThreadPerform:(SEL)aSelector with:anArg wait:(BOOL)wait; @end // ------------------------------------------------------------------------------------- // Object notified when command completes @interface Object(ScrollTextDelegate) - commandDidComplete:shellId withError:(int)errorCode; @end // ------------------------------------------------------------------------------------- // private methods @interface ScrollText(Private) - _setTextAttrFont:fontId color:(int)mode:(NXColor)color; - (BOOL)_appendTextToView:(const char*)buffer len:(int)len attr:(textAttr_t)tAttr; - _appendTextAndMakeVisible:(const char*)buffer attr:(textAttr_t)tAttr; - _appendTextToQueue:(const char*)buffer attr:(textAttr_t)tAttr; - _appendQueue:sender; - _delayedAppendQueue; - (int)_queueLength:(textQueue_t*)queue; - (int)_maxRunLength:(textQueue_t*)queue; - _stopCommand; - (int)_popen:(const char*)cmd cmdPath:(const char*)cmdPath; - (int)_pclose; - (void)_gotData; @end // ------------------------------------------------------------------------------------- /* convert color to gray */ static float cvtColor2Gray(NXColor color) { float gray; NXConvertColorToGray(color, &gray); return gray; } // ------------------------------------------------------------------------------------- @implementation ScrollText // ------------------------------------------------------------------------------------- /* initializes the outlet (to be executed by main thread only!) */ + newScrollText:anObject { /* check thread */ if (!mainThread) mainThread = cthread_self(); /* init */ self = [super new]; scrollView = anObject; textView = [scrollView docView]; wasEditable = [textView isEditable]; autoLf = NO; queueMutex = mutexAlloc(); queueData = (textQueue_t*)nil; queueBack = (textQueue_t*)nil; runAttr = nullAttr; cmdChild = 0; /* set textView attributes */ //[textView setEditable:NO]; //[textView setMonoFont:NO]; return self; } /* return textView id (docView) */ - docView { return textView; } /* return scroll view */ - scrollView { return scrollView; } /* set delegate */ - setDelegate:theDelegate { delegate = theDelegate; return self; } /* set auto linefeed mode */ - setAutoLineFeed:(BOOL)mode { autoLf = mode; return self; } /* free object */ - free { /* kill any running child process */ [self killCommand]; /* free queueData */ for (;queueData;) { textQueue_t *next = queueData->next; if (queueData->record) free(queueData->record); free(queueData); queueData = next; } mutexFree(queueMutex); /* free super */ return [super free]; } // -------------------------------------------------------------------------------- /* set font */ - _setTextAttrFont:fontId color:(int)mode:(NXColor)color { textAttr_t tAttr = nullAttr; /* init text attributes */ tAttr.fontId = fontId; tAttr.colorMode = mode; tAttr.color = color; [self _appendTextAndMakeVisible:(char*)nil attr:tAttr]; return self; } /* set font */ - setTextAttributeFont:fontId { return [self _setTextAttrFont:fontId color:0:nullColor]; } /* set gray */ - setTextAttributeGray:(float)aGray { return [self _setTextAttrFont:(id)nil color:1:NXConvertGrayToColor(aGray)]; } /* set gray */ - setTextAttributeColor:(NXColor)aColor { return [self _setTextAttrFont:(id)nil color:2:aColor]; } /* set default tabs */ // THIS NEEDS TO BE REDONE - setTabStops:(float*)tabArray count:(int)c { NXTextStyle style = *((NXTextStyle*)[textView defaultParaStyle]); style.numTabs = (short)c; style.tabs = (NXTabStop*)malloc(sizeof(NXTabStop) * style.numTabs); while (--c >= 0) { style.tabs[c].kind = NX_LEFTTAB; style.tabs[c].x = tabArray[c]; } [textView setParaStyle:(void*)&style]; return self; } /* repeat given tab multiple times */ - setTab:(float)tabSize count:(int)c { int i; NXTextStyle style = *((NXTextStyle*)[textView defaultParaStyle]); style.numTabs = (short)c; style.tabs = (NXTabStop*)malloc(sizeof(NXTabStop) * style.numTabs); for (i = 0; i < c; i++) { style.tabs[i].kind = NX_LEFTTAB; style.tabs[i].x = (float)(i + 1) * tabSize; } [textView setParaStyle:(void*)&style]; return self; } // -------------------------------------------------------------------------------- /* clear text scroll view area */ - clearScrollText { [textView setEditable:YES]; [textView setText:""]; if (!wasEditable) [textView setEditable:NO]; [scrollView display]; return self; } - clear:sender { return [self clearScrollText]; } /* delete lines */ - deleteLinesFrom:(int)fLine to:(int)tLine { [textView setEditable:YES]; [textView setSel:[textView positionFromLine:fLine]:[textView positionFromLine:tLine+1]]; [textView replaceSel:""]; if (!wasEditable) [textView setEditable:NO]; [scrollView display]; return self; } /* return current number of lines */ - (int)textLines { return [textView lineFromPosition:[textView textLength]]; } // -------------------------------------------------------------------------------- // append text to scroll view /* append buffer to view: return YES if text was visible */ - (BOOL)_appendTextToView:(const char*)buffer len:(int)len attr:(textAttr_t)tAttr { int txtLen; NXSelPt startPt, endPt; NXRect rect; /* check for font/gray change (save state) */ if (tAttr.fontId) { runAttr.fontId = tAttr.fontId; } if (tAttr.colorMode) { runAttr.colorMode = tAttr.colorMode; runAttr.color = tAttr.color; } if (!buffer || !*buffer || (len == 0)) return NO; /* get ready to print text */ [textView getVisibleRect:&rect]; // visible rectangle [textView setEditable:YES]; txtLen = [textView textLength]; [textView setSel:txtLen :txtLen]; [textView getSel:&startPt :&endPt]; // selected coordinates /* set text run attributes if specified */ if (!isNullAttr(runAttr)) { if ([textView isMonoFont]) [textView setMonoFont:NO]; if (!txtLen) { [textView replaceSel:" "]; [textView setSel:0 :1]; } if (runAttr.fontId) [textView setSelFont:runAttr.fontId]; if (runAttr.colorMode == 1) [textView setSelGray:cvtColor2Gray(runAttr.color)]; else if (runAttr.colorMode == 2) [textView setSelColor:runAttr.color]; runAttr = nullAttr; } /* print text */ if (len > 0) [textView replaceSel:buffer length:len]; else [textView replaceSel:buffer]; if (!wasEditable) [textView setEditable:NO]; return (rect.origin.y + rect.size.height > endPt.y); // was visible? } /* append text to view and scroll to visible */ - _appendTextAndMakeVisible:(const char*)buffer attr:(textAttr_t)tAttr { BOOL wasVisible; /* if not main thread, append buffer to queue */ if (!isMainTHREAD) return [self _appendTextToQueue:buffer attr:tAttr]; /* print queue contents */ [self _appendQueue:self]; /* print buffer */ wasVisible = [self _appendTextToView:buffer len:-1 attr:tAttr]; if (autoLf && buffer) [self _appendTextToView:"\n" len:-1 attr:nullAttr]; if (wasVisible) [textView scrollSelToVisible]; return self; } // -------------------------------------------------------------------------------- // queue up text to print / print text queue /* return queue length */ - (int)_queueLength:(textQueue_t*)queue { int len; textQueue_t *qPtr; qPtr = (queue)? queue: queueData; for (len = 0; qPtr; qPtr = qPtr->next) { if (qPtr->record) len += strlen(qPtr->record) + 1; } return len; } /* return queue length */ - (int)_maxRunLength:(textQueue_t*)queue { int len, maxLen; textQueue_t *qPtr; qPtr = (queue)? queue: queueData; for (maxLen = len = 0; qPtr; qPtr = qPtr->next) { if (!isNullAttr(qPtr->attr)) { if (len > maxLen) maxLen = len; len = 0; } if (qPtr->record) len += strlen(qPtr->record) + 1; } return MAX(maxLen, len); } /* delayed append queue */ - _delayedAppendQueue { if ([self respondsTo:@selector(mainThreadPerform:with:wait:)]) [self mainThreadPerform:@selector(_appendQueue:) with:self wait:NO]; return self; } /* add text string to queue (to be executed by non-main thread only) */ - _appendTextToQueue:(const char*)buffer attr:(textAttr_t)tAttr { textQueue_t *newData; /* load temporarily permanent buffer */ newData = (textQueue_t*)malloc(sizeof(textQueue_t)); newData->record = buffer? NXCopyStringBuffer(buffer) : (char*)nil; newData->attr = tAttr; newData->next = (textQueue_t*)nil; /* lock the queue */ mutexLock(queueMutex); /* add it to the queue */ if (!queueData) { queueData = newData; [self _delayedAppendQueue]; } else queueBack->next = newData; queueBack = newData; /* unlock the queue */ mutexUnlock(queueMutex); return self; } /* print text queue contents (executed by main thread only!) */ // needs to be fixed to support fonts/grays - _appendQueue:sender { int i; char *text; textQueue_t *queue, *next; /* get pointer to queue list */ mutexLock(queueMutex); queue = queueData; queueData = queueBack = (textQueue_t*)nil; mutexUnlock(queueMutex); if (!queue) return self; /* concatenate text */ text = (char*)malloc([self _maxRunLength:queue] + 1); for (i = 0; queue;) { /* check for attribute change */ if (!isNullAttr(queue->attr)) { // print contents of 'text' if ((i > 0) && [self _appendTextToView:text len:-1 attr:nullAttr]) [textView scrollSelToVisible]; [self _appendTextToView:"" len:0 attr:queue->attr]; text[i = 0] = 0; } /* append record to string buffer */ if (queue->record) { i += sprintf(&text[i], "%s%s", queue->record, ((autoLf)? "\n": "")); free(queue->record); } /* next node */ next = queue->next; free(queue); queue = next; } /* place remaining text into scroll view */ if ([self _appendTextToView:text len:-1 attr:nullAttr]) [textView scrollSelToVisible]; free(text); return self; } // -------------------------------------------------------------------------------- // append a formatted text string message into a text view // input from ktaylor /* append text unformatted */ - (int)textPrint:(const char*)buffer { [self _appendTextAndMakeVisible:buffer attr:nullAttr]; return strlen(buffer); } /* append text with variable args into textView */ - (int)textPrintf:(const char*)fmt args:(va_list)args { char tempString[textStringSIZE] = { 0 }; int retVal = vsprintf(tempString, fmt, args); [self _appendTextAndMakeVisible:tempString attr:nullAttr]; return retVal; } /* append text with variable args into textView */ - (int)textPrintf:(const char*)fmt, ... { va_list args; int retVal; va_start(args, fmt); retVal = [self textPrintf:fmt args:args]; va_end(args); return retVal; } /* append text with fprintf(...) style format */ int textPrintf(id self, const char *fmt, ...) { va_list args; int retVal; va_start(args, fmt); retVal = [self textPrintf:fmt args:args]; va_end(args); return retVal; } // -------------------------------------------------------------------------------- // view printing /* prints the textView */ - print:sender { id oldInfo, pInfo = [PrintInfo new]; [pInfo setVertCentered:NO]; oldInfo = [NXApp setPrintInfo:pInfo]; [textView lockFocus]; [textView printPSCode:self]; [textView unlockFocus]; [NXApp setPrintInfo:oldInfo]; [pInfo free]; return self; } /* print textView using enscript */ #define tempFILE ".textPrint" #define previewFILE ".previewPrint.ps" #define CAT(S) strcat(command, (S)); - enscriptPrint:(char*)title option:(char*)option printPanel:(BOOL)prtPnl { NXStream *stream; char temp[256], command[512], *panelStatus; int rtn; id oldInfo; id printPanel = (id)nil; id printInfo = (id)nil; /* create temp objects */ printInfo = [PrintInfo new]; oldInfo = [NXApp setPrintInfo:printInfo]; /* run print panel */ if (prtPnl) { printPanel = [PrintPanel new]; rtn = [printPanel runModal]; } else { printPanel = (id)nil; rtn = NX_OKTAG; } /* results of print panel */ if (rtn != NX_CANCELTAG) { /* save file to disk */ stream = NXOpenMemory((char*)nil, 0, NX_WRITEONLY); [textView writeText:stream]; NXSaveToFile(stream, tempFILE); NXClose(stream); /* init default options */ sprintf(command, "enscript -qhG%d", [printInfo pagesPerSheet]); CAT(([printInfo orientation] == NX_LANDSCAPE)? "r": "R"); /* printer option */ if (rtn == NX_OKTAG) { // print sprintf(temp, " -#%d", [printInfo copies]); CAT(temp); // # copies sprintf(temp, " -P%s", [printInfo printerName]); CAT(temp);// printer name panelStatus = "Printing..."; } else if (rtn == NX_SAVETAG) { // save to file sprintf(temp, " -p%s", [printInfo outputFile]); CAT(temp); panelStatus = "Saving..."; } else if (rtn == NX_PREVIEWTAG) { // prieview CAT(" -p" previewFILE); panelStatus = "Messaging Preview..."; } /* font */ sprintf(temp, " -f%s%d", [[textView font] name], (int)[[textView font] pointSize]); CAT(temp); /* options */ if (title) {sprintf(temp, " -b\"%s\"", title); CAT(temp);} // title if (option) { CAT(" "); CAT(option); } // other options CAT(" "); CAT(tempFILE); // file name /* execute print command */ // if (printPanel) [printPanel->status setStringValue:panelStatus]; system(command); /* preview check */ if (rtn == NX_PREVIEWTAG) system("open " previewFILE); } /* restore state and free objects */ [NXApp setPrintInfo:oldInfo]; [printInfo free]; if (printPanel) [printPanel orderOut:(id)nil]; return self; } // -------------------------------------------------------------------------------- // executing commands within a shell, using the scroll text as output /* stop command */ - _stopCommand { int error; DPSRemoveFD(inputDescriptor); error = [self _pclose]; close(inputDescriptor); [self commandDidCompleteWithError:error]; return self; } /* set environment variable */ static int _setenv(char **ep, char *eVal, char *fmt, ...) { va_list args; register char *cp, *dp; va_start(args, fmt); vsprintf(eVal, fmt, args); va_end(args); for (;dp = *ep; ep++) { for (cp = eVal; (*cp == *dp) && (*cp != '=') && *cp; cp++, dp++) continue; if (((*cp == '=') || !*cp) && ((*dp == '=') || !*dp)) { *ep = eVal; return 0; } } return -1; } /* open pipe to shell and execute command */ - (int)_popen:(const char*)cmd cmdPath:(const char*)cmdPath { int inputP[2], hisOutput, myInput; const char **locEnv = environ; /* open pipe */ pipe(inputP); myInput = inputP[0]; hisOutput = inputP[1]; /* fork and execute shell */ if ((cmdChild = vfork()) == 0) { int i; char **env, *path, *ePath; /* make local copy of environment table */ for (i = 0; locEnv[i]; i++); env = (char**)alloca(sizeof(char*) * (i + 1)); // allocate on stack memcpy(env, locEnv, sizeof(char*) * (i + 1)); /* check for additional path requirements */ if (cmdPath && (ePath = getenv("PATH"))) { path = (char*)alloca(strlen(ePath) + strlen(cmdPath) + 8); _setenv(env, path, "PATH=%s:%s", cmdPath, ePath); } /* set up pipe handles */ close(myInput); if (hisOutput != 1) dup2(hisOutput, 1); if (hisOutput != 2) dup2(hisOutput, 2); if ((hisOutput != 1) && (hisOutput != 2)) close(hisOutput); /* execute command */ setpgrp(0, getpid()); execle("/bin/csh", "csh", "-f", "-c", cmd, (char *)nil, env); _exit(RUNCMD_EXEC); } /* set io */ if (cmdChild == -1) { close(myInput); myInput = -1; } close(hisOutput); return myInput; } /* close shell command pipe */ - (int)_pclose { int pid, omask, err; union wait status; omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); while ((pid = wait(&status)) != cmdChild && pid != -1); (void)sigsetmask(omask); return (err = (status.w_status & 0xFF))? -1 : (status.w_status >> 8) & 0xFF; } /* filter for data piped from command shell */ - (void)_gotData { char data[1024]; int n, cnt = 0; BOOL doScroll = NO; /* read available text */ do { if ((n = read(inputDescriptor, data, sizeof(data))) >= 0) { cnt += n; if (n && [self _appendTextToView:data len:n attr:nullAttr]) doScroll = YES; if (delegate && [delegate respondsTo:@selector(runCommandOutput:len:)]) { [delegate perform:@selector(runCommandOutput:len:) with:(id)data with:(id)n]; } } } while (n == sizeof(data)); /* show text */ if (doScroll) [textView scrollSelToVisible]; if (!cnt) [self _stopCommand]; } /* fd routine for receiving data */ static void gotData(int fd, void *self) { [(ScrollText*)self _gotData]; } /* execute a command */ - runCommand:(const char*)command withPath:(const char*)cmdPath { inputDescriptor = [self _popen:command cmdPath:cmdPath]; if (inputDescriptor >= 0) { DPSAddFD(inputDescriptor, gotData, self, NX_BASETHRESHOLD); return self; } return (id)nil; } /* execute a command */ - runCommand:(const char*)command { return [self runCommand:command withPath:(char*)nil]; } /* terminate command */ - terminateCommand { if (cmdChild > 0) { killpg(cmdChild, SIGTERM); kill(cmdChild, SIGTERM); } return self; } /* kill command */ - killCommand { if (cmdChild > 0) { killpg(cmdChild, SIGKILL); kill(cmdChild, SIGKILL); } return self; } // -------------------------------------------------------------------------------- // support for remote shell server output /* copy text to scrollView (MAIN THREAD ONLY!) */ - (oneway void)commandOutput:(const char*)buffer len:(int)len { if (len && buffer) { if (!isMainTHREAD) { NXLogError("(ScrollText)commandOutput:len: Not main thread!"); return; } if ([self _appendTextToView:buffer len:len attr:nullAttr]) [textView scrollSelToVisible]; free((char*)buffer); } } /* indicate that the shell has completed */ - (oneway void)commandDidCompleteWithError:(int)errorCode; { if (delegate && [delegate respondsTo:@selector(commandDidComplete:withError:)]) [delegate commandDidComplete:self withError:errorCode]; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.