home *** CD-ROM | disk | FTP | other *** search
- // -------------------------------------------------------------------------------------
- // 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
-