This is DraggableMenu.m in view mode; [Download] [Up]
// Written by Gideon King // Caveats and options for improvement // - If a target or action changes due to a program update, the affected menu items won't work // - Could be extended to allow user re-ordering of menus - you'd need to save the whole menu structure though, // and watch out for program updates. // - You could ask the users to name their custom menus when you create new ones. // // Source file : DraggableMenu.m // Created by : gideon@berd // Created on : Fri Sep 8 16:18:54 NZST 1995 // RCS File : $Source: /Ramoth/Black.Albatross/CVS/CustomMenu/CustomMenu.subproj/DraggableMenu.m,v $ // Last modified : $Date: 1995/09/10 22:38:07 $ // Last modified by : $Author: gideon $ // Current Revision : $Revision: 1.1.1.1 $ // static const char RCSId[] = "$Id: DraggableMenu.m,v 1.1.1.1 1995/09/10 22:38:07 gideon Exp $"; #import "DraggableMenu.h" #import "CustomMenu.h" // My private pasteboard for menu cells const char *MHMenuCellPboardType = "MenuHack Menu Cell version 1.0"; // This has to be defined here so we don't have an instance variable, and therefore can't poseAs menu BOOL __saveAndRestoreFlag__ = YES; BOOL __haveUnarchivedMenu__ = NO; @implementation DraggableMenu - (const char *)version { return RCSId; } - setSaveAndRestore:(BOOL)yesNo { __saveAndRestoreFlag__ = yesNo; return self; } - (BOOL)getSaveAndRestore { return __saveAndRestoreFlag__; } - awakeFromNib { char fileName[MAXPATHLEN+1]; NXStream *stream; if (__saveAndRestoreFlag__ && !__haveUnarchivedMenu__) { __haveUnarchivedMenu__ = YES; // Load the custom menus from file sprintf(fileName, "%s/.NeXT/%s.customMenus", NXHomeDirectory(), [NXApp appName]); stream = NXMapFile(fileName, NX_READONLY); if (stream) { NX_DURING NXTypedStream *ts = NXOpenTypedStream(stream, NX_READONLY); if (ts) { id mainItemList = [[NXApp mainMenu] itemList]; int i, count; NXReadType(ts, "i", &count); for (i = 0; i < count; i++) { id newItem = NXReadObject(ts); [mainItemList insertRowAt:0]; [mainItemList putCell:newItem at:0 :0]; } [[NXApp mainMenu] setItemList:mainItemList]; [[NXApp mainMenu] display]; NXCloseTypedStream(ts); } else { fprintf(stderr, "Error opening %s for reading.\n", fileName); } NXCloseMemory(stream, NX_FREEBUFFER); NX_HANDLER fprintf(stderr, "Error reading file %s.\n", fileName); NX_ENDHANDLER } } return self; } // This is a nasty hack - because we are using poseAs to take over from Menu, we can't add any new methods // that you can connect to in IB, so I've taken over the performClose mathod. Oh well, I guess the whole thing // is a nasty hack anyway, so who cares??? - performClose:sender { char fileName[MAXPATHLEN+1]; NXTypedStream *ts; if (strcmp([[sender selectedCell] title], "Quit") == 0) { if (__saveAndRestoreFlag__) { // Write the custom menus to file sprintf(fileName, "%s/.NeXT/%s.customMenus", NXHomeDirectory(), [NXApp appName]); ts = NXOpenTypedStreamForFile(fileName, NX_WRITEONLY); if (ts) { NX_DURING int i, count = 0; id mainItemList = [[NXApp mainMenu] itemList]; // First go through and find out how many menu cells we have to archive for (i = 0; i < [mainItemList cellCount]; i++) { id thisCell = [mainItemList cellAt:i :0]; // We have given the menus we have added a tag of 42131 to identify them if(strstr([thisCell title], "Custom Menu") && ([thisCell tag] == 42131)) { count++; } } NXWriteType(ts, "i", &count); // Now archive the cells in reverse to make them easier to unarchive for (i = count-1; i >= 0; i--) { NXWriteRootObject(ts, [mainItemList cellAt:i :0]); } NXCloseTypedStream(ts); NX_HANDLER fprintf(stderr, "Error writing file %s.\n", fileName); NX_ENDHANDLER } else { fprintf(stderr, "Error opening %s for writing.\n", fileName); } } return [NXApp terminate:sender]; } return [super performClose:sender]; } // Pick up an Alternate-drag of a menu item, and start a dragging session - mouseDown:(NXEvent *)theEvent { // Only do the dragging if an Alternate key is down if (theEvent->flags & NX_ALTERNATEMASK) { NXImage *theImage; id theCell = [matrix selectedCell]; int theRow = [matrix selectedRow]; NXBitmapImageRep *newRep = nil; NXPoint zero = {0.0, 0.0}; NXPoint offset; id thePboard; NXRect myRect; NXRect matrixFrame; NXStream *stream; NXTypedStream *ts; static NXCursor *cursor = nil; NXPoint spot; // Work out the size and position of the menu cell. [matrix getCellFrame:&myRect at:theRow :0]; theImage = [[NXImage alloc] initSize:&(myRect.size)]; // Draw the button into the imagerep [matrix lockFocus]; newRep = [[NXBitmapImageRep alloc] initData:NULL fromRect:&myRect]; [matrix unlockFocus]; [matrix getFrame:&matrixFrame]; [theImage useRepresentation:newRep]; offset.x = 0; offset.y = theEvent->location.y + ((NX_HEIGHT(&matrixFrame) - NX_Y(&myRect)) - theEvent->location.y) - NX_HEIGHT(&myRect); // get the Pboard and put the menu cell on to it. thePboard = [Pasteboard newName:NXDragPboard]; [thePboard declareTypes:&MHMenuCellPboardType num:1 owner:self]; // Write the cell to the pasteboard if (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) { if (ts = NXOpenTypedStream(stream, NX_WRITEONLY)) { NXWriteRootObject(ts, theCell); NXCloseTypedStream(ts); } [thePboard writeType:MHMenuCellPboardType fromStream:stream]; NXCloseMemory(stream, NX_FREEBUFFER); } if (!cursor) { NXImage *cursorImage = [NXImage findImageNamed:"lightningCursor"]; if (cursorImage) { cursor = [[NXCursor alloc] initFromImage:cursorImage]; spot.x = 8.0; spot.y = 8.0; [cursor setHotSpot:&spot]; } } if (cursor) [cursor push]; // Start the drag [self dragImage:theImage // visible on screen during drag at:&offset // offset of the mouse point for the drag offset:&zero // offset of the inital mouse pt event:theEvent // theEvent structure pasteboard:thePboard // a Pasteboard with data on it source:self // source object slideBack:NO]; // if no destination don't animate back to source - make a new menu if (cursor) [cursor pop]; [theImage free]; } else { [super mouseDown:theEvent]; } return self; } // Methods to be the source of a dragging operation - draggedImage:(NXImage *)image beganAt:(NXPoint *)screenPoint { return self; } - (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag { return (NX_DragOperationCopy | NX_DragOperationGeneric); } // Make a new menu if the user dropped the menu item anywhere not on a CustomMenu - draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint deposited:(BOOL)flag { if (!flag) // They just dragged it out into thin air { int retval; retval = NXRunAlertPanel("Custom Menu", "Would you like to create a new custom menu?", "Yes", "No", NULL); if (retval == NX_ALERTDEFAULT) // Yes make a new one { // Check whether there are any other custom menus already defined // This will have to be changed if you are asking the users for a menu name int i, count = 1; id draggedCell = [matrix selectedCell]; char titleString[128]; id newMenu; id newItem; id mainItemList = [[NXApp mainMenu] itemList]; id itemList; List *menuAndItemList = [[List alloc] init]; for (i = 0; i < [mainItemList cellCount]; i++) { id thisCell = [mainItemList cellAt:i :0]; // We have given the menus we have added a tag of 42131 to identify them if(strstr([thisCell title], "Custom Menu") && ([thisCell tag] == 42131)) { count++; } } sprintf(titleString, "Custom Menu %d", count); // Create a new menu in the main menu newItem = [[MenuCell alloc] initTextCell:titleString]; [newItem setTag:42131]; [mainItemList insertRowAt:count-1]; [mainItemList putCell:newItem at:count-1 :0]; [menuAndItemList addObject:mainItemList]; [menuAndItemList addObject:newItem]; // Create the new submenu newMenu = [[CustomMenu alloc] initTitle:titleString]; // Add the dragged menu item to the new menu itemList = [newMenu itemList]; [itemList insertRowAt:0]; [itemList putCell:draggedCell at:0 :0]; [newMenu setItemList:itemList]; // Attach the new submenu to the main menu [menuAndItemList addObject:newMenu]; // This is done in this way to avoid removing the view from the view hierarchy while lockfocused. [self perform:@selector(attachSubmenuUsing:) with:menuAndItemList afterDelay:0 cancelPrevious:YES]; } } return self; } - (BOOL)ignoreModifierKeysWhileDragging { return YES; } - (BOOL)isDraggingSourceLocal { return YES; } // Method to avoid getting DPS errors - see draggedImage:endedAt:deposited: - attachSubmenuUsing:aList { id itemList = [aList objectAt:0]; id newItem = [aList objectAt:1]; id newMenu = [aList objectAt:2]; [[NXApp mainMenu] setItemList:itemList]; [[NXApp mainMenu] display]; // Attach the new submenu to the main menu [[NXApp mainMenu] setSubmenu:newMenu forItem:newItem]; [aList free]; return self; } - write:(NXTypedStream *)stream { [super write:stream]; NXWriteType(stream, "c", &__saveAndRestoreFlag__); return self; } - read:(NXTypedStream *)stream { [super read:stream]; NXReadType(stream, "c", &__saveAndRestoreFlag__); // The poseas has to happen at this point so the read and write methods work, with the right super-class if (![NXApp respondsTo:@selector(isTestingInterface)]) // We are not in IB [[DraggableMenu class] poseAs:[Menu class]]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.