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.