This is Query.m in view mode; [Download] [Up]
/*--------------------------------------------------------------------------- Query.m -- Copyright (c) 1991 Rex Pruess 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 1, 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, or send electronic mail to the the author. A new Query object is created each time the user asks for a new query window. This code manages the query window and is the primary interface to the Qi server for query requests. The methods are grouped by function and roughly in the order they are called by previous methods. Rex Pruess <Rex-Pruess@uiowa.edu> $Header: /rpruess/apps/Ph/query.subproj/RCS/Query.m,v 3.0 93/06/26 07:59:17 rpruess Exp $ ----------------------------------------------------------------------------- $Log: Query.m,v $ Revision 3.0 93/06/26 07:59:17 rpruess Brought Ph code up to NeXTSTEP 3.0 specifications. Revision 2.4 91/12/19 09:27:23 rpruess Fixed timing bug whereby "hide" preference option caused Ph to crash in the situation where the socket had timed out before the user unhid Ph. Then, Ph would crash if the user selected the "fields" view. Revision 2.3 91/12/11 08:48:27 rpruess Added code to verify the search string was non-null when doing name searches. If it is null, an alert panel is displayed. The user can still beat this simple check by entering a blank search string. Oh well. Revision 2.2 91/12/10 16:30:51 rpruess Moved the timer code from QueryManager to Query. Each Query object is now responsible for determining when it has received the server "fields" data. This code was added prior to the initial Ph Version 2.0 release. Revision 2.1 91/11/20 08:47:36 rpruess Added code to clear the ScrollView of old data just before a new search is begun. This provides a clear signal to the user as to when the search has really finished. Prior to this code, consecutive searches which returned the same data did not provide as clear a signal as to when it had finished a search. Revision 2.0 91/11/19 08:19:29 rpruess Revision 2.0 is the initial production release of Ph. -----------------------------------------------------------------------------*/ #define FIELDBOXTITLE "Field Search" /* Title in PopUpList of fields box */ #define ALLFIELDS 0 /* Returned Fields radio switch tags */ #define DEFFIELDS 1 #define SPCFIELDS 2 #define EMAILTO "email to" /* Title to use for refomatted mail addrs */ #define MAILLEN 128 /* Maximum length of reformatted mail addr */ #define MINBOXHEIGHT 67.0 /* Name/Field box minimum size */ #define MINQUERYHEIGHT 200.0 /* Minimum sizes for Query window */ #define MINQUERYWIDTH 400.0 #define SEARCHING "Searching..." #define TIMERSECS 0.5 /* Animator wakeup interval */ enum returnType { ALL, DEFAULT, SPECIFIC }; /* Application class header files */ #import "Query.h" #import "BoxViewSwitcher.h" #import "QueryFieldView.h" #import "Specify.h" #import "../PhShare.h" #import "../info.h" #import "../qiServers.h" @implementation Query /*--------------------------------------------------------------------------- A new Query object must load its nib file, bring the BoxViewSwitcher to life, and intialize several variables. -----------------------------------------------------------------------------*/ - init { [super init]; [NXApp loadNibSection:"Query.nib" owner:self withNames:NO]; [queryWindow setMiniwindowIcon:"app"]; [self createBoxViewSwitcher:self]; /*** This set of fields needed for the rewriting of email addresses */ alias[0] = '\0'; copiedEmail = NO; domain = NULL; email[0] = '\0'; reformatEmail = NO; /*** Finish initializing variables to default values. */ curPerson = 0; isQueryReady = NO; returnedFields = DEFAULT; qiObject = nil; myFieldForm = nil; myIconMatrix = nil; myQueryFieldView = nil; mySpecify = nil; queryAnimator = nil; theQiManager = nil; /*** Initialize the searching string & search button. */ [self displaySearchingWord:NO]; /*** Create the fonts, set the line wrapping, and initialize the hyphens/equals divider lines. */ [self createFonts:self]; [self initLineWrap:self]; /*** Add the two subviews to the splitView. */ [splitView addSubview:boxView]; [splitView addSubview:phoneView]; return self; } /*--------------------------------------------------------------------------- The BoxViewSwitcher must be brought to life. It is responsible for switching the name box with the fields box based on the user's popuplist selection. -----------------------------------------------------------------------------*/ - createBoxViewSwitcher:sender { myBVS = [[BoxViewSwitcher alloc] init]; [myBVS setBoxView:boxView]; [myBVS setBoxViewWindow:boxViewWindow]; [myBVS setControlButton:searchTypeButton]; [myBVS removeBoxTitles:self]; [myBVS setDelegate:self]; return self; } /*--------------------------------------------------------------------------- Initialize the font based on the defaults data base values. -----------------------------------------------------------------------------*/ - createFonts:sender { const char *fontName; float fontSize; id defaultFont; fontName = NXGetDefaultValue ([NXApp appName], FONT); fontSize = atof (NXGetDefaultValue ([NXApp appName], FONTSIZE)); defaultFont = [Font newFont:fontName size:fontSize style:0 matrix:NX_FLIPPEDMATRIX]; [[phoneView docView] setFont:defaultFont]; return self; } /*--------------------------------------------------------------------------- Set the docView line wrapping capability based on the defaults data base setting. -----------------------------------------------------------------------------*/ - initLineWrap:sender { int i; int nChars; const char *lineWrap; BOOL wrapLines; lineWrap = NXGetDefaultValue ([NXApp appName], WRAPLINES); if (strcmp (lineWrap, "YES") == 0) wrapLines = YES; else { wrapLines = NO; [[phoneView docView] setNoWrap]; } /*** Set width of separator lines based on line wrap value */ if (wrapLines) nChars = WRAPWIDTH; else nChars = NWRAPWIDTH; /*** Set hyphens and equals separator lines */ hyphens[0] = '\0'; for (i = 0; i < nChars - 3; i += 2) strcat (hyphens, "- "); strcat (hyphens, "\n"); equals[0] = '\0'; for (i = 0; i < nChars - 2; i++) strcat (equals, "="); strcat (equals, "\n"); return self; } /*--------------------------------------------------------------------------- This method must be called before calling the openSession method. This method sets important variables. -----------------------------------------------------------------------------*/ - setQueryVars:aQiManager domain:(const char *)aDomain { theQiManager = aQiManager; if (aDomain == NULL) domain = NULL; else { domain = malloc (strlen (aDomain) + 1); strcpy (domain, aDomain); } return self; } /*--------------------------------------------------------------------------- This method should be called before calling the openSession method. It shifts each Query window a bit from the previous Query window. This makes it clear to the user that there are multiple Query windows. -----------------------------------------------------------------------------*/ - setQueryWindow:(const char *)aTitle offset:(float)anOffset { NXRect aRect; [queryWindow setTitle:aTitle]; [queryWindow getFrame:&aRect]; [queryWindow moveTo:(aRect.origin.x + anOffset) :(aRect.origin.y - anOffset)]; return self; } /*--------------------------------------------------------------------------- Try to form a connection with the Qi server. If it can not make a connection, it will return nil. If it is successful, start the animator so it can wait for all the "fields" to come back from the server. -----------------------------------------------------------------------------*/ - (BOOL) openSession:(const char *)aServer { if ((qiObject = [theQiManager open:aServer]) == nil) return NO; /*** If the Qi object was obtained, then create the nifty matrix view & establish views appropriately. */ [self createQueryFieldView:self]; [[fieldScrollView setDocView:myQueryFieldView] free]; /*** Start the animator. It will wait for the "fields" to come back from the Qi server. */ queryAnimator = [[Animator alloc] initChronon:TIMERSECS adaptation:0.0 target:self action:@selector (queryTimeCheck) autoStart:YES eventMask:0]; return YES; } /*--------------------------------------------------------------------------- The animator is responsible for periodically invoking this method. This code checks to see if the Query object has been initialized. Query objects need to get "fields" data from the Qi server so there is some delay before they come to life. -----------------------------------------------------------------------------*/ - queryTimeCheck { /*** If the "fields" have not been read, then we must wait some more. Check the server socket to see if it has closed. If so, stop the timer since we won't get the data anyway. */ if ([qiObject hasQiFields] == NO) { if ([qiObject hasClosed]) [self stopTimer:self]; return self; } /*** Success! Stop the animator, fill the "fields" matrix, and show the window. */ [self stopTimer:self]; [self fillFieldMatrix:self]; [self showWindow:self]; return self; } /*--------------------------------------------------------------------------- The Query object has initialized itself. Stop the animator & free it. -----------------------------------------------------------------------------*/ - stopTimer:sender { [queryAnimator stopEntry]; free (queryAnimator); return self; } /*--------------------------------------------------------------------------- This method is called after the Qi object has been obtained (i.e., the connection was successfully established with the server). The purpose of this method is to create the QueryFieldView object. That object will contain two matrixes. One matrix will contain tiny cells capable of holding the bullet icon. The second matrix will be a form which will eventally hold all the "fields" from the server. -----------------------------------------------------------------------------*/ - createQueryFieldView:sender { NXRect mRect; mRect.origin.x = 0.0; mRect.origin.y = 0.0; [fieldScrollView getContentSize:&mRect.size]; myQueryFieldView = [[QueryFieldView alloc] initFrame:&mRect]; myIconMatrix = [myQueryFieldView iconMatrix]; myFieldForm = [myQueryFieldView fieldForm]; return self; } /*--------------------------------------------------------------------------- This method is invoked from within the queryTimeCheck method after all of the "fields" have been retrieved from the server. This code loops through the list of fields to determine which ones are eligible for selection criteria and which ones are indexed. Indexed fields are marked with a bullet icon so the user knows which fields are actually indexed. -----------------------------------------------------------------------------*/ - fillFieldMatrix:sender { id aCell; id aField; int i; int row; id myFieldList; row = 0; myFieldList = [qiObject fieldList]; for (i = 0; i < [myFieldList count]; i++) { aField = [myFieldList objectAt:i]; if (strstr ([aField keywords], "Lookup") != NULL && strstr ([aField keywords], "Public") != NULL) { aCell = [myFieldForm insertEntry:[aField name] at:row]; [aCell setScrollable:YES]; [aCell setTarget:fieldSearchButton]; [aCell setAction:@selector (performClick:)]; [myIconMatrix addRow]; [[myIconMatrix cellAt:row :0] setBordered:NO]; [[myIconMatrix cellAt:row :0] setBezeled:NO]; [[myIconMatrix cellAt:row :0] setEnabled:NO]; if (strstr ([aField keywords], "Indexed") != NULL) [[myIconMatrix cellAt:row :0] setIcon:"Bullet"]; row++; } } [myFieldForm sizeToCells]; [myFieldForm display]; [myIconMatrix sizeToCells]; [myIconMatrix display]; [myQueryFieldView adjHeight:self]; [fieldScrollView display]; isQueryReady = YES; return self; } /*--------------------------------------------------------------------------- When the user clicks on one of the radio buttons in the "return fields" box, we go to work. For the button ALL & DEFAULT, we discard the Specify window (if any). In the case of of SPECIFIC, we must display a window with all of the returnable fields displayed as switches. -----------------------------------------------------------------------------*/ - setReturnedFields:sender { switch ([[sender selectedCell] tag]) { case ALLFIELDS: returnedFields = ALL; [self hideSpecify:self]; break; case SPCFIELDS: returnedFields = SPECIFIC; [self showSpecify:self]; break; default: returnedFields = DEFAULT; [self hideSpecify:self]; break; } return self; } /*--------------------------------------------------------------------------- The first time the user requests that specific fields be returned, we must do a lot of work. The Specify object is created. It creates a window with a scrollview to hold the switches. Specify is told how many items will created as well as a good origin for the Specify window. This presents a consistent, pleasant interface to the user. Once the window has been setup, then we walk through all of the fields to determine which ones are valid for return. -----------------------------------------------------------------------------*/ - createSpecify:sender { id aField; int i; int nItems; id myFieldList; float xLoc; float yLoc; nItems = 0; mySpecify = [[Specify alloc] init]; myFieldList = [qiObject fieldList]; /*** Find the number of items which will be placed into the Specify matrix. This value is needed so we can tell the Specify object how many items there will be. This allows Specify to create the proper sized window. */ for (i = 0; i < [myFieldList count]; i++) { aField = [myFieldList objectAt:i]; if (strstr ([aField keywords], "Lookup") != NULL && strstr ([aField keywords], "Public") != NULL) nItems++; } /*** Go calculate the origin for the Specify window & then create it. */ [self specOrigin:&xLoc:&yLoc]; [mySpecify initSpecify:nItems x:xLoc y:yLoc title:[queryWindow title]]; /*** Make another pass through the fields. This time we add a switch to the Specify matrix for each field we wish to include. */ for (i = 0; i < [myFieldList count]; i++) { aField = [myFieldList objectAt:i]; if (strstr ([aField keywords], "Lookup") != NULL && strstr ([aField keywords], "Public") != NULL) [mySpecify addSwitch:[aField name]]; } [mySpecify updateDisplay:self]; return self; } /*--------------------------------------------------------------------------- Calculate a good origin for the window created by Specify. It should be placed under the returnFieldsBox (radio matrix). -----------------------------------------------------------------------------*/ - specOrigin:(float *)xLoc :(float *)yLoc { NXRect aFrame; [queryWindow getFrame:&aFrame]; *xLoc = aFrame.origin.x; *yLoc = aFrame.origin.y; [returnFieldsBox getFrame:&aFrame]; *xLoc += aFrame.origin.x; *yLoc += aFrame.origin.y; return self; } /*--------------------------------------------------------------------------- Create the Specify object & order it to show its window. -----------------------------------------------------------------------------*/ - showSpecify:sender { if (mySpecify == nil) [self createSpecify:self]; [mySpecify showWindow:self]; return self; } /*--------------------------------------------------------------------------- Order the specify window out. -----------------------------------------------------------------------------*/ - hideSpecify:sender { if (mySpecify != nil) [mySpecify orderWindowOut:self]; return self; } /*--------------------------------------------------------------------------- This code is executed by another application using the Speaker interface. It accepts a search command of the form "name=pruess department=weeg", fills in the appropriate search fields, & then performs the search. -----------------------------------------------------------------------------*/ - speakerSearch:(const char *)aCommand { char *command; char *field; int i; BOOL foundOne; int nCols; int nRows; char *string; if (aCommand == NULL || strlen (aCommand) <= 0) return self; /*** Switch to the field box view, blank out all field forms. */ [queryWindow disableFlushWindow]; [myBVS selectBoxViewTitle:FIELDBOXTITLE]; [myFieldForm getNumRows:&nRows numCols:&nCols]; for (i = 0; i < nRows; i++) [myFieldForm setStringValue:"" at:i]; /*** Parse the query command and fill in the query fields appropriately. */ foundOne = NO; command = malloc (strlen (aCommand) + 1); strcpy (command, aCommand); field = strtok (command, "="); while (field != NULL) { if ((string = strtok (NULL, "\t")) == NULL) break; for (i = 0; i < nRows; i++) { if (strcmp (field, [myFieldForm titleAt:i]) == 0) { foundOne = YES; [myFieldForm setStringValue:string at:i]; break; } } field = strtok (NULL, "="); } free (command); [[queryWindow reenableFlushWindow] flushWindow]; /*** Invoke the search method if at least one of the fields was valid. */ if (foundOne) [self search:self]; return self; } /*--------------------------------------------------------------------------- Typically, this method is invoked when the user has clicked on the search button. However, it can be invoked by the speakerSearch method due to another application using the Speaker interface. In any event, this method is responsible for building the search command and sending it to the server. -----------------------------------------------------------------------------*/ - search:sender { char command[COMSIZE]; strcpy (command, "query"); /*** Get search criteria based on the popuplist value */ if (strstr ([searchTypeButton title], "Name") != NULL) { if (strlen ([nameForm stringValueAt:0]) <= 0) { NXRunAlertPanel (NULL, "You must specify a search string.", NULL, NULL, NULL); return self; } strcat (command, " "); strcat (command,[nameForm stringValueAt:0]); } else { if ([self getFieldForms:command] == NO) return self; } /*** Specify which fields should be returned based on the user's request */ switch (returnedFields) { case ALL: reformatEmail = NO; if ([self catData:command data:" return all"] == NO) return self; break; case SPECIFIC: reformatEmail = NO; if ([self catData:command data:" return"] == NO) return self; [mySpecify getSelectedFields:selFields]; if ([self catData:command data:selFields] == NO) return self; break; default: if (domain != NULL && strlen (domain) > 0) reformatEmail = YES; break; } /*** Tack on a newline to the command. Initialize the values for a new person. Display the searching word. */ if ([self catData:command data:"\n"] == NO) return self; [self newPerson:0]; [self clearQueryView]; [self displaySearchingWord:YES]; /*** Go tell it to the server & then fetch the output from the server */ queryStream = NXOpenMemory (NULL, 0, NX_READWRITE); [qiObject qiSend:command delegate:self]; return self; } /*--------------------------------------------------------------------------- Verify that the command does not get too long. Highly doubtful it ever will. -----------------------------------------------------------------------------*/ - (BOOL) catData:(char *)aSource data:(const char *)aData { if (strlen (aSource) + strlen (aData) + 1 > COMSIZE) { strcpy (errMsg, "The query command is too long. Reduce your selection "); strcat (errMsg, "criteria or reduce the number of fields you wish to "); strcat (errMsg, "return."); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL); return NO; } strcat (aSource, aData); return YES; } /*--------------------------------------------------------------------------- Go through all of the field forms and capture the user requested search criteria. At least one of the search fields must be indexed. -----------------------------------------------------------------------------*/ - (BOOL) getFieldForms:(char *)aCommand { BOOL indexFlag; int i; int nCols; int nRows; [myFieldForm getNumRows:&nRows numCols:&nCols]; indexFlag = NO; for (i = 0; i < nRows; i++) { if (strlen ([myFieldForm stringValueAt:i]) > 0) { if ([[myIconMatrix cellAt:i :0] icon] != NULL) indexFlag = YES; if ([self catData:aCommand data:" "] == NO || [self catData:aCommand data:[myFieldForm titleAt:i]] == NO || [self catData:aCommand data:"="] == NO || [self catData:aCommand data:[myFieldForm stringValueAt:i]] == NO) return NO; } } if (indexFlag == NO) { strcpy (errMsg, "You must search by at least one indexed field. "); strcat (errMsg, "(Indexed fields are marked with a bullet.)"); NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL); return NO; } return YES; } /*--------------------------------------------------------------------------- Brand new person. Reset the default values. -----------------------------------------------------------------------------*/ - newPerson:(const int)aPerson { alias[0] = '\0'; email[0] = '\0'; copiedEmail = NO; curPerson = aPerson; return self; } /*--------------------------------------------------------------------------- Process a single line of Qi server output. Each line identifies the person by number. We rely on this number to determine when pretty separator lines should be displayed. -----------------------------------------------------------------------------*/ - qiOutput:(char *)aBuf { int theCode; int thePerson; theCode = atoi (aBuf); /*** Display the number of matches. */ if (theCode == LR_NUMRET) { [self outputString:(strchr (aBuf, ':') + 1)]; return self; } /*** Upon receiving the final line, do some final processing for the entry. This includes reformatting the email address, flushing the outputStream to the ScrollView, and telling the Qi object to stop watching the file descriptor. */ if (theCode >= LR_OK) { if (theCode != LR_OK) { if (theCode == LR_NOMATCH) [self outputString:(strchr (aBuf, ':') + 1)]; else [self outputString:aBuf]; } if (reformatEmail) [self displayEmail:self]; [self outputString:equals]; [self displaySearchingWord:NO]; [self outputStream:self]; [qiObject stopFd:self]; return self; } /*** If this is a new person, finish off the previous person. This includes reformatting the email address & displaying a pretty separator line. The newPerson method is invoked to initialize variables for the new person. */ if (theCode == -LR_OK || theCode == -LR_AINFO || theCode == -LR_ABSENT || theCode == -LR_ISCRYPT) { thePerson = atoi (index (aBuf, ':') + 1); if (curPerson != thePerson) { if (curPerson != 0) { if (reformatEmail) [self displayEmail:self]; [self outputString:hyphens]; } [self newPerson:thePerson]; } [self processLine:aBuf]; } else [self outputString:aBuf]; return self; } /*--------------------------------------------------------------------------- If we are in the "reformatMail" mode, then we must grab the "alias" and "email" fields for later processing. This code is based on Steve Dorner's UNIX Ph code. The code has been altered to fit into my implementation. I've added very crude length checks; EMAILSIZE should be plenty large, but I don't want to crash due to an array bounds problem. -----------------------------------------------------------------------------*/ - processLine:(const char *)aBuf { char *cp; /* Character pointer */ char *lp; /* Line pointer */ lp = (strchr (strchr (aBuf, ':') + 1, ':') + 1); if (reformatEmail) { cp = lp; while (*cp && *cp==' ') cp++; if (!strncmp ("alias", cp, 5)) { copiedEmail = NO; strncpy (alias, lp, EMAILSIZE - 2); if (strlen (alias) > EMAILSIZE - 2) alias[strlen (alias)] = '\n'; return self; } else if (!strncmp ("email", cp, 5)) { strncpy (email, lp, EMAILSIZE - 2); if (strlen (email) > EMAILSIZE - 2) email[strlen (email)] = '\n'; copiedEmail = YES; return self; } else if (*cp==':' && copiedEmail) return self; else copiedEmail = NO; } /*** Output the line */ [self outputString:lp]; return self; } /*--------------------------------------------------------------------------- Print the e-mail line in a nice, pretty format. This code is based on Steve Dorner's UNIX Ph code. The code has been altered to fit into my implementation. I've added very crude length checks; EMAILSIZE should be plenty large, but I don't want to crash due to an array bounds problem. -----------------------------------------------------------------------------*/ - emailLine:sender { char scratch[EMAILSIZE]; char *emSpot; /* Beginning of email account */ char *alSpot; /* Beginning of nameserver alias */ if (*alias) { alSpot = index (alias, ':') + 2; emSpot = index (email, ':') + 2; *index (alSpot, '\n') = 0; *index (emSpot, '\n') = 0; /*** Overwrite the email label. */ if (strlen (alias) + strlen (domain) + 1 < EMAILSIZE && strlen (email) + 2 < EMAILSIZE ) { strcpy (alSpot - 2 - strlen (EMAILTO), EMAILTO); alSpot[-2] = ':'; /* strcpy clobbered the colon; repair */ sprintf (scratch, "@%s\n", domain); strcat (alias, scratch); [self outputString:alias]; strcpy (emSpot - 2 - strlen ("email"), " "); emSpot[-2] = ':'; /* strcpy clobbered the colon; repair */ sprintf (scratch, "(%s)\n", emSpot); strcpy (emSpot, scratch); /* Leave it in the "email" line */ } [self outputString:email]; } return self; } /*--------------------------------------------------------------------------- Print a "not registered" e-mail line in a nice, pretty format. This code is based on Steve Dorner's UNIX Ph code. The code has been altered to fit into my implementation. I've added very crude length checks; EMAILSIZE should be plenty large, but I don't want to crash due to an array bounds problem. -----------------------------------------------------------------------------*/ - notRegisteredLine:sender { char *cp; if (*alias) { cp = index (alias, ':'); strcpy (cp - strlen (EMAILTO), EMAILTO); *cp = ':'; if (strlen (alias) + strlen (domain) + 30 < EMAILSIZE) { *index (cp, '\n') = 0; strcat (alias, "@"); strcat (alias, domain); strcat (alias, " (no account registered)\n"); } [self outputString:alias]; } return self; } /*--------------------------------------------------------------------------- The user has clicked the Clear Fields menu item. Only clear the fields if the window is the key window. -----------------------------------------------------------------------------*/ - clearQueryFields { int i; int nCols; int nRows; if ([queryWindow isKeyWindow] == NO) return self; /*** Determine which boxView is visible and clear the fields in it. */ if (strstr ([searchTypeButton title], "Name") != NULL) [nameForm setStringValue:"" at:0]; else { [myFieldForm getNumRows:&nRows numCols:&nCols]; for (i = 0; i < nRows; i++) [myFieldForm setStringValue:"" at:i]; } return self; } /*--------------------------------------------------------------------------- This method is called in a couple of ways. It is called internally by the search method so that the view is cleared just before searching begins. It is also invoked whenever the user clicks the Clear Buffer menu item. The view is cleared only if the window is the key window. This is necessary so that the Clear Buffer menu item will only clear the key window's view. There is probably a better way to do this. -----------------------------------------------------------------------------*/ - clearQueryView { id phoneText; if ([queryWindow isKeyWindow] == NO) return self; [queryWindow disableFlushWindow]; phoneText = [phoneView docView]; [phoneText setSel:0 :[phoneText textLength]]; [phoneText replaceSel:""]; [[queryWindow reenableFlushWindow] flushWindow]; return self; } /*--------------------------------------------------------------------------- Let the user know we're busy searching. -----------------------------------------------------------------------------*/ - displaySearchingWord:(BOOL) searching { BOOL enable; char *sWord; if (searching) { enable = NO; sWord = SEARCHING; } else { enable = YES; sWord = NULL; [self selCell:self]; /* Select a cell so it is highlighted. */ } [fieldSearchButton setEnabled:enable]; [nameSearchButton setEnabled:enable]; [fieldSearchingWord setStringValue:sWord]; [nameSearchingWord setStringValue:sWord]; return self; } /*--------------------------------------------------------------------------- Display a normal line of output in the scrollview. -----------------------------------------------------------------------------*/ - outputString:(char *)aString { NXPrintf (queryStream, "%s", aString); return self; } /*--------------------------------------------------------------------------- We have received all of the data for this query. Display the data. -----------------------------------------------------------------------------*/ - outputStream:sender { static NXPoint origin = {0.0,0.0}; id phoneText; /*** Flush the buffer; reset pointer to beginning. */ NXFlush (queryStream); NXSeek (queryStream, 0, NX_FROMSTART); /*** Display the queryStream data to the user. */ [queryWindow disableFlushWindow]; phoneText = [phoneView docView]; [phoneText readText:queryStream]; [phoneText scrollPoint:&origin]; [[queryWindow reenableFlushWindow] flushWindow]; /*** Gotta free that memory. */ NXCloseMemory (queryStream, NX_FREEBUFFER); return self; } /*--------------------------------------------------------------------------- When there is a valid domain for the site and we are returning the "default" fields, then we reformat the e-mail address to reflect the fancy format. -----------------------------------------------------------------------------*/ - displayEmail:sender { if (*alias && !*email) [self notRegisteredLine:sender]; else if (*email) { [self emailLine:sender]; email[0] = '\0'; } copiedEmail = NO; return self; } /*--------------------------------------------------------------------------- When the box view switches, select the appropriate help file and select an appropriate cell. -----------------------------------------------------------------------------*/ - boxViewDidSwitch:sender { if (strstr ([searchTypeButton title], "Name") != NULL) [NXHelpPanel attachHelpFile:"Tasks/Basics/QueryByName.rtfd" markerName:NULL to:queryWindow]; else [NXHelpPanel attachHelpFile:"Tasks/Basics/QueryByFields.rtfd" markerName:NULL to:queryWindow]; [self selCell:self]; return self; } /*--------------------------------------------------------------------------- Select an appropriate cell for the user. If the Name box is visibile select the nameForm text. Otherwise, select the the fieldForm which was previously selected by the user. -----------------------------------------------------------------------------*/ - selCell:sender { id aCell; if (strstr ([searchTypeButton title], "Name") != NULL) [nameForm selectTextAt:0]; else { if ([myFieldForm cellCount] <= 0) return self; aCell = [myFieldForm selectedCell]; if (aCell == nil) return self; [myFieldForm selectCell:aCell]; } return self; } /*--------------------------------------------------------------------------- The user has grabbed the divider bar. Enforce some sizing constraints. -----------------------------------------------------------------------------*/ - splitView:sender getMinY:(NXCoord *) minY maxY:(NXCoord *) maxY ofSubviewAt:(int)offset { *minY = MINBOXHEIGHT; return self; } /*--------------------------------------------------------------------------- If the splitView is going to resize, then try to do it in a sensible way. The boxView width is allowed to change, but its height will remain constant. The phoneView height & width will change to fit the remaining space. -----------------------------------------------------------------------------*/ - splitView:sender resizeSubviews:(const NXSize *) oldSize { int bvInd; id subViews; NXRect bvRect; NXRect pvRect; NXRect splitRect; /*** There should be a boxView and a phoneView in this splitView. */ subViews = [sender subviews]; if ([subViews count] != 2) { fprintf (stderr, "%s: Number of Query SplitView subviews is %u. Should be 2.\n", [NXApp appName],[subViews count]); return self; } /*** bvInd will contain the index of the boxView object. */ if ([subViews objectAt:1] == phoneView) bvInd = 0; else { if ([subViews objectAt:0] != phoneView) { fprintf (stderr, "%s: Query SplitView does not have phoneView\n", [NXApp appName]); return self; } bvInd = 1; } [splitView getFrame:&splitRect]; /* Grab the new size of the splitView */ /*** Set the boxView width to the same width as the splitView. */ [[subViews objectAt:bvInd] getFrame:&bvRect]; bvRect.origin.x = 0.0; bvRect.origin.y = 0.0; bvRect.size.width = splitRect.size.width; [[subViews objectAt:bvInd] setFrame:&bvRect]; /*** Set the phoneView origin, height, & width so it fills the remainder of splitView. */ [phoneView getFrame:&pvRect]; pvRect.origin.x = 0.0; pvRect.origin.y = bvRect.size.height + [splitView dividerHeight]; pvRect.size.width = splitRect.size.width; pvRect.size.height = splitRect.size.height - pvRect.origin.y; [phoneView setFrame:&pvRect]; return self; } /*--------------------------------------------------------------------------- Each new Query window should be shifted a bit from the placement of the previous Query window. Otherwise, it is difficult to distinguish the new window from previous ones. -----------------------------------------------------------------------------*/ - shiftWindow:(float)anOffset { return self; } /*--------------------------------------------------------------------------- Put the window on the screen. -----------------------------------------------------------------------------*/ - showWindow:sender { [nameForm selectTextAt:0]; [queryWindow makeKeyAndOrderFront:self]; return self; } /*--------------------------------------------------------------------------- Get rid of the Specify window if the query window goes away. -----------------------------------------------------------------------------*/ - windowDidMiniaturize:sender { [self hideSpecify:self]; return self; } - windowWillClose:sender { [self hideSpecify:self]; return self; } /*--------------------------------------------------------------------------- Don't let the query window be too small. -----------------------------------------------------------------------------*/ - windowWillResize:sender toSize:(NXSize *) frameSize { if (frameSize -> width < MINQUERYWIDTH) frameSize -> width = MINQUERYWIDTH; if (frameSize -> height < MINQUERYHEIGHT) frameSize -> height = MINQUERYHEIGHT; return self; } /*--------------------------------------------------------------------------- Methods to return the requested data. -----------------------------------------------------------------------------*/ - (const char *)getServerName { return[qiObject server]; } - (BOOL) isQueryReady { return isQueryReady; } - qiObject { return qiObject; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.