This is MODocManager.m in view mode; [Download] [Up]
// MODocManager.m // // by Mike Ferris // Part of MOKit // Copyright 1993, all rights reserved. // ABOUT MOKit // // MOKit is a collection of useful and general objects. Permission is // granted by the author to use MOKit in your own programs in any way // you see fit. All other rights to the kit are reserved by the author // including the right to sell these objects as part of a LIBRARY or as // SOURCE CODE. In plain English, I wish to retain rights to these // objects as objects, but allow the use of the objects as pieces in a // fully functional program. NO WARRANTY is expressed or implied. The author // will under no circumstances be held responsible for ANY consequences to // you from the use of these objects. Since you don't have to pay for // them, and full source is provided, I think this is perfectly fair. #import "MOKit/MODocManager.h" #import "MOKit/MODocController.h" #import "MOKit/MODocType.h" #import "MOKit/MOString.h" #import "MOKit/MOPathString.h" #import <objc/objc-runtime.h> #define CLASS_VERSION 0 #define CLASS_NAME "MODocManager" #define MOSTRING_CLASS_NAME "MOString" #define MOPATHSTRING_CLASS_NAME "MOPathString" #define MODOCTYPE_CLASS_NAME "MODocType" #define BUNDLE_TYPE "bundle" #define MOKIT_BUNDLE "MOKitBundle" #define QUITPANEL_NIBNAME "MODocQuitPanel.nib" #define QUITPANEL_REVIEW 0 #define QUITPANEL_SAVEALL 1 #define QUITPANEL_QUIT 2 #define QUITPANEL_CANCEL 3 #define MAX_FRAME_CYCLE 10 #define FRAME_ADJUST 25.0 #define FRAME_START_LOC_X 150.0 #define FRAME_START_LOC_Y 30.0 // top of screen to top of window #define NEW_MENU_TITLE "New" #define OPEN_MENU_TITLE "Open" @interface MODocManager(Private) + (Class)MO_loadClassBundle:(const char *)className; + (BOOL)MO_loadMOKitBundle; - MO_resetOpenMenus; @end @implementation MODocManager static Class MOStringClass; static Class MOPathStringClass; static Class MODocTypeClass; static NXBundle *MOKitBundle; + (Class)MO_loadClassBundle:(const char *)className // Finds the bundle of the same name as the class, grabs it and loads the // class from it and returns the named class. { char pathBuff[MAXPATHLEN+1]; id classBundle = nil; Class class = nil; // Load the bundle if ((class = objc_lookUpClass(className)) == nil) { // class is not already loaded... load it. // Look for the bundle in the main bundle first, // else try in this class's bundle. if (![[NXBundle mainBundle] getPath:pathBuff forResource:className ofType:BUNDLE_TYPE]) { if (![[NXBundle bundleForClass:[self class]] getPath:pathBuff forResource:className ofType:BUNDLE_TYPE]) { NXLogError("[%s loadClassBundle] failed to " "find %s class bundle.", [self name], className); return nil; } } classBundle = [[NXBundle allocFromZone:[self zone]] initForDirectory:pathBuff]; if (!classBundle) { NXLogError("[%s loadClassBundle] failed to " "create bundle for class %s.", [self name], className); return nil; } if ((class = [classBundle classNamed:className]) == nil) { NXLogError("[%s loadClassBundle] failed to " "load %s class from bundle.", [self name], className); return nil; } } return class; } + (BOOL)MO_loadMOKitBundle; // Locates and loads (hopefully) the MOKit bundle which contains a nib we need. { char pathBuff[MAXPATHLEN+1]; // Look for the bundle in the main bundle first, // else try in this class's bundle. if (![[NXBundle mainBundle] getPath:pathBuff forResource:MOKIT_BUNDLE ofType:BUNDLE_TYPE]) { if (![[NXBundle bundleForClass:[self class]] getPath:pathBuff forResource:MOKIT_BUNDLE ofType:BUNDLE_TYPE]) { NXLogError("[%s MO_loadMOKitBundle] failed to " "find MOKit bundle.", [self name]); return NO; } } MOKitBundle = [[NXBundle allocFromZone:[self zone]] initForDirectory:pathBuff]; if (!MOKitBundle) { NXLogError("[%s MO_loadMOKitBundle] failed to " "create bundle for MOKit bundle.", [self name]); return NO; } return YES; } + initialize // Set the version. Load classes. Load the MOKit bundle. { if (self == [MODocManager class]) { [self setVersion:CLASS_VERSION]; // Load all the classes we need MOStringClass = [self MO_loadClassBundle:MOSTRING_CLASS_NAME]; MOPathStringClass = [self MO_loadClassBundle:MOPATHSTRING_CLASS_NAME]; MODocTypeClass = [self MO_loadClassBundle:MODOCTYPE_CLASS_NAME]; // Find the MOKit bundle which contains MOKit's nibs and // other non-code resources. [self MO_loadMOKitBundle]; } return self; } - init // Call the DI. { return [self initDocumentClass:nil]; } - initDocumentClass:(Class)docControllerClass; // Deesignated initializer. // Init the manager with defaults. If docControllerClass is non-nil it is // added as the first of potentially several document controller classes this // manager will manage. { const NXScreen *screen = NULL; [super init]; docList = [[List allocFromZone:[self zone]] init]; currentDoc = nil; docClassList = [[List allocFromZone:[self zone]] init]; if (docControllerClass) [docClassList addObject:docControllerClass]; untitledCount = 0; frameCycle = 0; screen = [NXApp mainScreen]; windowStartingLocation.x = FRAME_START_LOC_X; windowStartingLocation.y = screen->screenBounds.size.height - FRAME_START_LOC_Y; return self; } - free // free stuff we allocated. { [[docList freeObjects] free]; [docClassList empty]; [self MO_resetOpenMenus]; if (newSubmenuCell) [newSubmenuCell free]; if (newMenu) [newMenu free]; if (openSubmenuCell) [openSubmenuCell free]; if (openMenu) [openMenu free]; [docClassList free]; return [super free]; } - awakeFromNib // Set the updateActions for the menu items we'll manage. { if (docMenu) { [NXApp setAutoupdate:YES]; [docMenu setDelegate:self]; [openCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu]; [newCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu]; [saveCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu]; [saveAsCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu]; [saveToCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu]; [saveAllCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu]; [revertCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu]; [closeCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu]; } [self MO_resetOpenMenus]; return self; } - new:sender // create a new untitled doc. This is only used when we have one // document class { id newDoc; if ([docClassList count] != 1) { NXBeep(); return nil; } newDoc = [[[docClassList objectAt:0] allocFromZone:[self zone]] initFromFile:NULL manager:self]; if (!newDoc) { return nil; } return self; } - newFromDocumentClass:sender // Create a new untitled doc from the class that's at the same position // in our doc class list as the selectedRow of the sender. This is only // used when there is more than one doc class. { id newDoc = [[[docClassList objectAt:[sender selectedTag]] allocFromZone:[self zone]] initFromFile:NULL manager:self]; if (!newDoc) { return nil; } return self; } static BOOL isExtensionInList(const char *ext, List *list, int *pos) // Return YES and the position of the type in the list if we find a match. // If there is no match, try matching a NULL extension. Returns YES only if // the extension is actually in the list. Returns NO if not. pos is set to // the position of the entry if the match is made. If no match is made, but // there is a null-extension type in the list, it's position is put in pos. // Otherwise pos is set to -1. { int i, c = [list count]; for (i=0; i<c; i++) { const char *curExt = [[list objectAt:i] typeExtension]; // They match if they are both empty or if they match if ((!ext || !*ext) && (!curExt || !*curExt)) { if (pos) *pos = i; return YES; } if (ext && curExt && (strcmp(ext, curExt)) == 0) { if (pos) *pos = i; return YES; } } if (ext && *ext) { if (!isExtensionInList(NULL, list, pos)) { if (pos) *pos = -1; } } else { if (pos) *pos = -1; } return NO; } - open:sender // Allow the user to open a document from disk. We must handle all // doc extensions supported by all doc controller classes. If two // classes support reading the same extension, the first class which // supports that extension is used to open files with that extension. { id openPanel = [OpenPanel new]; int currentClass, docClassCount, currentType, typeCount; List *openTypes = nil; // We'll fill this list with each open type supported by each doc // controller class. No duplicates, though. List *allTypes = [[List allocFromZone:[self zone]] init]; const char **types = NULL; const char *aType; BOOL useTypes = YES; // First, fill allTypes and classForTypes with info for all the extensions // we need to handle. docClassCount = [docClassList count]; if (docClassCount == 0) { [allTypes free]; return nil; } for (currentClass = 0; currentClass < docClassCount; currentClass++) { openTypes = [[docClassList objectAt:currentClass] getOpenTypesList:nil]; typeCount = [openTypes count]; for (currentType = 0; currentType < typeCount; currentType++) { MODocType *type = [openTypes objectAt:currentType]; if (!isExtensionInList([type typeExtension], allTypes, NULL)) { [allTypes addObject:type]; } } [openTypes free]; } // Now make a nasty array like the open panel wants. typeCount = [allTypes count]; if (typeCount == 0) { [allTypes free]; return nil; } NX_MALLOC(types, char *, typeCount+1); for (currentType=0; currentType<typeCount; currentType++) { aType = [[allTypes objectAt:currentType] typeExtension]; useTypes = (useTypes && aType && *aType); types[currentType] = (aType?aType:""); } types[typeCount] = NULL; // Now run the damn thing. [openPanel allowMultipleFiles:NO]; [openPanel chooseDirectories:NO]; [openPanel setAccessoryView:nil]; [openPanel setDelegate:self]; [openPanel setTreatsFilePackagesAsDirectories:NO]; if ([openPanel runModalForTypes:(useTypes?types:NULL)]) { MOPathString *path = [[MOPathStringClass allocFromZone:[self zone]] initPath:[openPanel filename]]; MOPathString *extension = [path fileExtension]; // Now we need to open the damn thing with the right class. int index = -1; isExtensionInList([extension stringValue], allTypes, &index); if (index < 0) { // we shouldn't have been allowed to open the doc if we can't find // a type for it... why are we here [allTypes free]; if (types) NX_FREE(types); [path free]; [extension free]; return nil; } [self openDocument:[path stringValue] withClass:[[allTypes objectAt:index] controllerClass]]; [extension free]; [path free]; } [allTypes free]; if (types) NX_FREE(types); return self; } - openFromDocumentClass:sender // This runs the open panel for the types which are supported by the // document class at the same position in our doc class list as the // selectedRow of the sender. This is used when there is more than // one doc class. { id openPanel = [OpenPanel new]; id openTypes = [[docClassList objectAt:[sender selectedTag]] getOpenTypesList:nil]; int i, c; const char **types = NULL; const char * aType; BOOL useTypes = YES; c = [openTypes count]; if (c == 0) { [openTypes free]; return nil; } NX_MALLOC(types, char *, c+1); for (i=0; i<c; i++) { aType = [[openTypes objectAt:i] typeExtension]; useTypes = (useTypes && aType && *aType); types[i] = (aType?aType:""); } types[i] = NULL; [openPanel allowMultipleFiles:NO]; [openPanel chooseDirectories:NO]; [openPanel setAccessoryView:nil]; [openPanel setDelegate:self]; [openPanel setTreatsFilePackagesAsDirectories:NO]; if ([openPanel runModalForTypes:(useTypes?types:NULL)]) { [self openDocument:[openPanel filename] withClass:[docClassList objectAt:[sender selectedTag]]]; } NX_FREE(types); [openTypes free]; return self; } - openDocument:(const char *)path withClass:(Class)docClass; // Make a new documentof the given class and initialize it from the given file. { id newDoc; newDoc = [[docClass allocFromZone:[self zone]] initFromFile:path manager:self]; return self; } - save:sender // Tell the current doc to save. { if (currentDoc) { [currentDoc save:self]; } return self; } - saveAs:sender // Tell the current doc to save as. { if (currentDoc) { [currentDoc saveAs:self]; } return self; } - saveTo:sender // Tell the current doc to save to. { if (currentDoc) { [currentDoc saveTo:self]; } return self; } - saveAll:sender // Save all the open docs. { int i, c = [docList count]; id temp; for (i=0; i<c; i++) { temp = [docList objectAt:i]; [temp saveIfNeeded]; } return self; } - revert:sender // Tell the current doc to revert. { if (currentDoc) { if (NXRunAlertPanel("Revert", "Do you really want to revert to " "%s version? This cannot be undone.", "Revert", "Cancel", NULL, ([currentDoc isUntitled]?"blank":"previously saved")) == NX_ALERTDEFAULT) { [currentDoc revert:self]; } } return self; } - close:sender // Tell the current doc to close. { if (currentDoc) { [currentDoc close:self]; } return self; } - print:sender // Tell the current doc to print. { if (currentDoc) { [currentDoc print:self]; } return self; } - getNextWindowLocation:(NXPoint *)pt // Get the location for the next document window. { pt->x = windowStartingLocation.x + (FRAME_ADJUST * frameCycle); pt->y = windowStartingLocation.y - (FRAME_ADJUST * frameCycle); frameCycle++; if (frameCycle>=MAX_FRAME_CYCLE) frameCycle = 0; return self; } - (int)nextUntitledNum // Get the number for the next untitled doc. { untitledCount++; return untitledCount; } - setWindowStartingLocation:(const NXPoint *)pt // Set the starting window location from which the staggering of doc // windows will begin. This should be set before any windows are // created for best results. { windowStartingLocation.x = pt->x; windowStartingLocation.y = pt->y; return self; } - addDocument:(MODocController *)aDocument // Add a new document to our list. DocController calls this automatically. { [docList addObject:aDocument]; return self; } - removeDocument:(MODocController *)aDocument // Remove a document to our list. (DocController calls this when the // document is closing.) { [docList removeObject:aDocument]; if (currentDoc == aDocument) { currentDoc = nil; } return self; } - (MODocController *)findDocumentForWindow:aWindow // Find the controller that controls the given window (if any). { int i, c = [docList count]; id temp; for (i=0; i<c; i++) { temp = [docList objectAt:i]; if ([temp window] == aWindow) { return temp; } } return nil; } - (List *)documentList // Return the List object we use to store our docs. { return docList; } - makeDocumentsPerform:(SEL)aMethod with:anArg // Distribute a message to the documents. { [docList makeObjectsPerform:aMethod with:anArg]; return self; } - (BOOL)areDocumentsDirty // Returns YES if at least one open document is dirty. { int i, c = [docList count]; for (i=0; i<c; i++) { if ([[docList objectAt:i] isDirty]) { return YES; } } return NO; } - setCurrentDocument:(MODocController *)aDocument // Sets the current doc. Called from DocController's window delegate // methods. { currentDoc = aDocument; return self; } - currentDocument // Returns the current doc's controller. { return currentDoc; } - addDocumentClass:(Class)docControllerClass // Add the class to the list of classes we manage. Rebuilds the Document menu. { [docClassList addObject:docControllerClass]; [self MO_resetOpenMenus]; return self; } - removeDocumentClass:(Class)docControllerClass // Remove the class from the list of classes we manage. // Rebuilds the Document menu. { [docClassList removeObject:docControllerClass]; [self MO_resetOpenMenus]; return self; } - (List *)documentClassList // Return the list of classes we manage. { return docClassList; } - (int)docClassCount // Returns the number of classes that this manager is managing. { return [docClassList count]; } - appWillTerminate:sender // If there are unsaved docs, put up a panel asking if the user wants to // Review Unsaved, Save All, Quit Anyway, or Cancel. { int ret; int i, c; if ([self areDocumentsDirty]) { if (!quitPanel) { if (MOKitBundle) { char pathBuff[MAXPATHLEN+1]; if (![MOKitBundle getPath:pathBuff forResource:QUITPANEL_NIBNAME ofType:NULL]) { NXLogError("%s: failed to find nib file %s in " "MOKit bundle.", [[self class] name], QUITPANEL_NIBNAME); return nil; } if (![NXApp loadNibFile:pathBuff owner:self]) { NXLogError("%s: failed to load nib file %s.", [[self class] name], pathBuff); return nil; } } else { if (![NXApp loadNibSection:QUITPANEL_NIBNAME owner:self]) { NXLogError("%s: failed to load nib file %s.", [[self class] name], QUITPANEL_NIBNAME); return nil; } } } ret = [NXApp runModalFor:quitPanel]; [quitPanel orderOut:self]; if (ret == QUITPANEL_REVIEW) { c = [docList count]; for (i=c-1;i>=0;i--) { [[[docList objectAt:i] window] makeKeyAndOrderFront:self]; if ([[docList objectAt:i] windowWillClose:[[docList objectAt:i] window]] == nil) { return nil; } [[[docList objectAt:i] window] orderOut:self]; } return self; } else if (ret == QUITPANEL_SAVEALL) { [self saveAll:self]; return self; } else if (ret == QUITPANEL_QUIT) { return self; } else if (ret == QUITPANEL_CANCEL) { return nil; } } return self; // nil to abort termination } - quitPanelStopModalAction:sender // This is the action for the buttons in the quit panel. { [NXApp stopModal:[sender selectedTag]]; return self; } #define QUITPANEL_OK_BUTTON_ROW 1 #define QUITPANEL_OK_BUTTON_COL 2 - windowDidBecomeKey:sender // For the quit panel... make sure that the return icon is only there // if the window is key. { if (sender == quitPanel) { [[buttonMatrix cellAt:QUITPANEL_OK_BUTTON_ROW:QUITPANEL_OK_BUTTON_COL] setIconPosition:NX_ICONRIGHT]; [buttonMatrix setIcon:"NXreturnSign" at:QUITPANEL_OK_BUTTON_ROW:QUITPANEL_OK_BUTTON_COL]; } return self; } - windowDidResignKey:sender // For the quit panel... make sure that the return icon is only there // if the window is key. { if (sender == quitPanel) { [[buttonMatrix cellAt:QUITPANEL_OK_BUTTON_ROW:QUITPANEL_OK_BUTTON_COL] setIconPosition:NX_TITLEONLY]; [buttonMatrix setIcon:NULL at:QUITPANEL_OK_BUTTON_ROW:QUITPANEL_OK_BUTTON_COL]; } return self; } - (BOOL)menuUpdate:menuCell // This is the updateAction for all the Document menu stuff. { BOOL enabled = [menuCell isEnabled]; BOOL docsDirty; if ((menuCell == openCell) || (menuCell == newCell)) { if (([docClassList count] == 0) && (enabled)) { [menuCell setEnabled:NO]; return YES; } else if (!enabled) { [menuCell setEnabled:YES]; return YES; } } else if ((menuCell == saveCell) || (menuCell == saveAsCell) || (menuCell == saveToCell) || (menuCell == closeCell)) { if ((currentDoc == nil) && (enabled)) { [menuCell setEnabled:NO]; return YES; } else if ((currentDoc != nil) && (!enabled)) { [menuCell setEnabled:YES]; return YES; } } else if (menuCell == saveAllCell) { docsDirty = [self areDocumentsDirty]; if ((docsDirty) && (!enabled)) { [menuCell setEnabled:YES]; return YES; } else if ((!docsDirty) && (enabled)) { [menuCell setEnabled:NO]; return YES; } } else if (menuCell == revertCell) { if ((currentDoc == nil) && (enabled)) { [menuCell setEnabled:NO]; return YES; } else if (currentDoc != nil) { if (([currentDoc isDirty]) && (!enabled)) { [menuCell setEnabled:YES]; return YES; } else if ((![currentDoc isDirty]) && (enabled)) { [menuCell setEnabled:NO]; return YES; } } } return NO; } - awake // Initialize stuff we don't archive. { quitPanel = nil; buttonMatrix = nil; newMenu = nil; newSubmenuCell = nil; openMenu = nil; openSubmenuCell = nil; return self; } - read:(NXTypedStream *)strm // This method is probably not useful. But here it is. { int classVersion; const NXScreen *screen = NULL; [super read:strm]; classVersion = NXTypedStreamClassVersion(strm, CLASS_NAME); switch (classVersion) { case 0: // First version. docList = NXReadObject(strm); currentDoc = NXReadObject(strm); docClassList = NXReadObject(strm); NXReadType(strm, "i", &untitledCount); NXReadType(strm, "i", &frameCycle); NXReadPoint(strm, &windowStartingLocation); docMenu = NXReadObject(strm); openCell = NXReadObject(strm); newCell = NXReadObject(strm); saveCell = NXReadObject(strm); saveAsCell = NXReadObject(strm); saveToCell = NXReadObject(strm); saveAllCell = NXReadObject(strm); revertCell = NXReadObject(strm); closeCell = NXReadObject(strm); break; default: NXLogError("[%s read:] class version %d cannot read " "instances archived with version %d", CLASS_NAME, CLASS_VERSION, classVersion); docList = [[List allocFromZone:[self zone]] init]; currentDoc = nil; docClassList = [[List allocFromZone:[self zone]] init]; untitledCount = 0; frameCycle = 0; screen = [NXApp mainScreen]; windowStartingLocation.x = FRAME_START_LOC_X; windowStartingLocation.y = screen->screenBounds.size.height - FRAME_START_LOC_Y; docMenu = nil; openCell = nil; newCell = nil; saveCell = nil; saveAsCell = nil; saveToCell = nil; saveAllCell = nil; revertCell = nil; closeCell = nil; break; } return self; } - write:(NXTypedStream *)strm // This method is probably not useful. But here it is. { [super write:strm]; NXWriteObject(strm, docList); NXWriteObject(strm, currentDoc); NXWriteObject(strm, docClassList); NXWriteType(strm, "i", &untitledCount); NXWriteType(strm, "i", &frameCycle); NXWritePoint(strm, &windowStartingLocation); NXWriteObjectReference(strm, docMenu); NXWriteObjectReference(strm, openCell); NXWriteObjectReference(strm, newCell); NXWriteObjectReference(strm, saveCell); NXWriteObjectReference(strm, saveAsCell); NXWriteObjectReference(strm, saveToCell); NXWriteObjectReference(strm, saveAllCell); NXWriteObjectReference(strm, revertCell); NXWriteObjectReference(strm, closeCell); return self; } - MO_resetOpenMenus // Keeps the document menu in step with changes in the manager's class list. // This is the method that makes sure the Open and New stuff is correct for // the document classes being managed. { id itemList; int i, c; const char *name; id cell; int row, col; // See if the nib is loaded and we do menus if (!docMenu) { return nil; } if ([docClassList count] > 1) { // We should have submenus for new and open. // First do the newMenu if (!newMenu) { newMenu = [[Menu allocFromZone:[Menu menuZone]] initTitle:NEW_MENU_TITLE]; } itemList = [newMenu itemList]; [itemList renewRows:0 cols:1]; c = [docClassList count]; for (i=0; i<c; i++) { name = [[[docClassList objectAt:i] controllerName] stringValue]; [itemList addRow]; cell = [itemList cellAt:i:0]; [cell setTitle:name]; [cell setTag:i]; [cell setTarget:self]; [cell setAction:@selector(newFromDocumentClass:)]; } [newMenu sizeToFit]; // Now make a newSubmenuItem if (!newSubmenuCell) { newSubmenuCell = [[MenuCell allocFromZone:[Menu menuZone]] initTextCell:NEW_MENU_TITLE]; } // Put the submenu item in the doc menu in place of the new item itemList = [docMenu itemList]; if (![itemList getRow:&row andCol:&col ofCell:newSubmenuCell]) { [itemList getRow:&row andCol:&col ofCell:newCell]; [itemList putCell:newSubmenuCell at:row :col]; [docMenu setSubmenu:newMenu forItem:newSubmenuCell]; [docMenu sizeToFit]; } // Now do the open menu if (!openMenu) { openMenu = [[Menu allocFromZone:[Menu menuZone]] initTitle:OPEN_MENU_TITLE]; } itemList = [openMenu itemList]; [itemList renewRows:0 cols:1]; c = [docClassList count]; for (i=0; i<c; i++) { name = [[[docClassList objectAt:i] controllerName] stringValue]; [itemList addRow]; cell = [itemList cellAt:i:0]; [cell setTitle:name]; [cell setTag:i]; [cell setTarget:self]; [cell setAction:@selector(openFromDocumentClass:)]; } [openMenu sizeToFit]; // Now make an openSubmenuItem if (!openSubmenuCell) { openSubmenuCell = [[MenuCell allocFromZone:[Menu menuZone]] initTextCell:OPEN_MENU_TITLE]; } // Put the submenu item in the doc menu below the open item itemList = [docMenu itemList]; [itemList getRow:&row andCol:&col ofCell:openCell]; if ([itemList cellAt:row+1:col] != openSubmenuCell) { [itemList insertRowAt:row+1]; [[itemList putCell:openSubmenuCell at:row+1 :col] free]; [docMenu setSubmenu:openMenu forItem:openSubmenuCell]; [docMenu sizeToFit]; } } else { // We should not have submenus. itemList = [docMenu itemList]; // take out the new submenu item if its there and put back the new item if ([itemList getRow:&row andCol:&col ofCell:newSubmenuCell]) { [itemList putCell:newCell at:row :col]; } // take out the open submenu item if its there if ([itemList getRow:&row andCol:&col ofCell:openSubmenuCell]) { [itemList removeRowAt:row andFree:NO]; } [docMenu sizeToFit]; } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.