mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-25 07:50:53 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@4499 72102866-910b-0410-8b05-ffd578937521
626 lines
15 KiB
Objective-C
626 lines
15 KiB
Objective-C
#include <AppKit/NSApplication.h>
|
|
#include <AppKit/NSEvent.h>
|
|
#include <AppKit/NSMenuView.h>
|
|
#include <AppKit/NSWindow.h>
|
|
#include <AppKit/PSOperators.h>
|
|
|
|
static float GSMenuBarHeight = 25.0; // a guess.
|
|
|
|
@implementation NSMenuView
|
|
|
|
// Class methods.
|
|
|
|
+ (float)menuBarHeight
|
|
{
|
|
return GSMenuBarHeight;
|
|
}
|
|
|
|
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
// Init methods.
|
|
|
|
- (id)init
|
|
{
|
|
return [self initWithFrame:NSZeroRect];
|
|
}
|
|
|
|
- (id)initWithFrame:(NSRect)aFrame
|
|
{
|
|
cellSize = NSMakeSize(110,20);
|
|
menuv_highlightedItemIndex = -1;
|
|
|
|
return [super initWithFrame:aFrame];
|
|
}
|
|
|
|
// Our menu.
|
|
|
|
- (void)setMenu:(NSMenu *)menu
|
|
{
|
|
ASSIGN(menuv_menu, menu);
|
|
}
|
|
|
|
- (NSMenu *)menu
|
|
{
|
|
return menuv_menu;
|
|
}
|
|
|
|
- (void)setHorizontal:(BOOL)flag
|
|
{
|
|
menuv_horizontal = flag;
|
|
}
|
|
|
|
- (BOOL)isHorizontal
|
|
{
|
|
return menuv_horizontal;
|
|
}
|
|
|
|
- (void)setFont:(NSFont *)font
|
|
{
|
|
ASSIGN(menuv_font, font);
|
|
}
|
|
|
|
- (NSFont *)font
|
|
{
|
|
return menuv_font;
|
|
}
|
|
|
|
/*
|
|
* - (void)setHighlightedItemIndex:(int)index
|
|
*
|
|
* MacOS-X defines this function as the central way of switching to a new
|
|
* highlighted item. The index value is == to the item you want
|
|
* highlighted. When used this method unhighlights the last item (if
|
|
* applicable) and selects the new item. If index == -1 highlighting is
|
|
* turned off.
|
|
*
|
|
* NOTES (Michael Hanni):
|
|
*
|
|
* I modified this method for GNUstep to take submenus into account. This
|
|
* way we get maximum performance while still using a method outside the
|
|
* loop.
|
|
*
|
|
*/
|
|
|
|
- (void)setHighlightedItemIndex:(int)index
|
|
{
|
|
NSArray *menu_items = [menuv_menu itemArray];
|
|
id anItem;
|
|
BOOL _closeASubmenu = NO;
|
|
|
|
[self lockFocus];
|
|
|
|
if (index == -1) {
|
|
if (menuv_highlightedItemIndex != -1) {
|
|
anItem = [menu_items objectAtIndex:menuv_highlightedItemIndex];
|
|
|
|
[anItem highlight:NO
|
|
withFrame:[self rectOfItemAtIndex:menuv_highlightedItemIndex]
|
|
inView:self];
|
|
[anItem setState:0];
|
|
menuv_highlightedItemIndex = -1;
|
|
}
|
|
} else if (index >= 0) {
|
|
if ( menuv_highlightedItemIndex != -1 ) {
|
|
anItem = [menu_items objectAtIndex:menuv_highlightedItemIndex];
|
|
|
|
[anItem highlight:NO
|
|
withFrame:[self rectOfItemAtIndex:menuv_highlightedItemIndex]
|
|
inView:self];
|
|
|
|
if ([anItem hasSubmenu] && ![[anItem target] isTornOff])
|
|
[[anItem target] close];
|
|
|
|
[anItem setState:0];
|
|
}
|
|
|
|
if (menuv_highlightedItemIndex != index) {
|
|
anItem = [menu_items objectAtIndex:index];
|
|
|
|
[anItem highlight:YES
|
|
withFrame:[self rectOfItemAtIndex:index]
|
|
inView:self];
|
|
|
|
[anItem setState:1];
|
|
|
|
if ([anItem hasSubmenu])
|
|
[[anItem target] display];
|
|
|
|
// set view needs to be redrawn
|
|
[window flushWindow];
|
|
|
|
// set ivar to new index
|
|
menuv_highlightedItemIndex = index;
|
|
} else {
|
|
menuv_highlightedItemIndex = -1;
|
|
}
|
|
}
|
|
[self unlockFocus];
|
|
[window flushWindow];
|
|
}
|
|
|
|
- (int)highlightedItemIndex
|
|
{
|
|
return menuv_highlightedItemIndex;
|
|
}
|
|
|
|
- (void)setMenuItemCell:(NSMenuItemCell *)cell
|
|
forItemAtIndex:(int)index
|
|
{
|
|
// [menuv_items insertObject:cell atIndex:index];
|
|
|
|
// resize the cell
|
|
[cell setNeedsSizing:YES];
|
|
|
|
// resize menuview
|
|
[self setNeedsSizing:YES];
|
|
}
|
|
|
|
- (NSMenuItemCell *)menuItemCellForItemAtIndex:(int)index
|
|
{
|
|
return [[menuv_menu itemArray] objectAtIndex:index];
|
|
}
|
|
|
|
- (NSMenuView *)attachedMenuView
|
|
{
|
|
return [[menuv_menu attachedMenu] menuView];
|
|
}
|
|
|
|
- (NSMenu *)attachedMenu
|
|
{
|
|
return [menuv_menu attachedMenu];
|
|
}
|
|
|
|
- (BOOL)isAttached
|
|
{
|
|
return [menuv_menu isAttached];
|
|
}
|
|
|
|
- (BOOL)isTornOff
|
|
{
|
|
return [menuv_menu isTornOff];
|
|
}
|
|
|
|
- (void)setHorizontalEdgePadding:(float)pad
|
|
{
|
|
menuv_hEdgePad = pad;
|
|
}
|
|
|
|
- (float)horizontalEdgePadding
|
|
{
|
|
return menuv_hEdgePad;
|
|
}
|
|
|
|
- (void)itemChanged:(NSNotification *)notification
|
|
{
|
|
}
|
|
|
|
- (void)itemAdded:(NSNotification *)notification
|
|
{
|
|
}
|
|
|
|
- (void)itemRemoved:(NSNotification *)notification
|
|
{
|
|
}
|
|
|
|
// Submenus.
|
|
|
|
- (void)detachSubmenu
|
|
{
|
|
}
|
|
|
|
- (void)attachSubmenuForItemAtIndex:(int)index
|
|
{
|
|
// create rect to display submenu in.
|
|
|
|
// order window with submenu in it to front.
|
|
}
|
|
|
|
- (void)update
|
|
{
|
|
// [menuv_menu update];
|
|
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
}
|
|
|
|
- (void)setNeedsSizing:(BOOL)flag
|
|
{
|
|
menuv_needsSizing = flag;
|
|
}
|
|
|
|
- (BOOL)needsSizing
|
|
{
|
|
return menuv_needsSizing;
|
|
}
|
|
|
|
- (void)sizeToFit
|
|
{
|
|
int i;
|
|
int howMany = [[menuv_menu itemArray] count];
|
|
int howHigh = (howMany * cellSize.height) + 21;
|
|
float neededWidth = 0;
|
|
|
|
for (i=0;i<[[menuv_menu itemArray] count];i++)
|
|
{
|
|
float aWidth;
|
|
|
|
NSMenuItemCell *anItem = [[menuv_menu itemArray] objectAtIndex:i];
|
|
aWidth = [anItem titleWidth];
|
|
|
|
if (aWidth > neededWidth)
|
|
neededWidth = aWidth;
|
|
}
|
|
|
|
cellSize.width = 7 + neededWidth + 7 + 7 + 5;
|
|
|
|
[[self window] setFrame:NSMakeRect(300,300,cellSize.width,howHigh) display:YES];
|
|
[self setFrame:NSMakeRect(0,0,cellSize.width,howHigh-21)];
|
|
}
|
|
|
|
- (float)stateImageOffset
|
|
{
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
|
|
return menuv_stateImageOffset;
|
|
}
|
|
|
|
- (float)stateImageWidth
|
|
{
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
|
|
return menuv_stateImageWidth;
|
|
}
|
|
|
|
- (float)imageAndTitleOffset
|
|
{
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
|
|
return menuv_imageAndTitleOffset;
|
|
}
|
|
|
|
- (float)imageAndTitleWidth
|
|
{
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
|
|
return menuv_imageAndTitleWidth;
|
|
}
|
|
|
|
- (float)keyEquivalentOffset
|
|
{
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
|
|
return menuv_keyEqOffset;
|
|
}
|
|
|
|
- (float)keyEquivalentWidth
|
|
{
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
|
|
return menuv_keyEqWidth;
|
|
}
|
|
|
|
- (NSRect)innerRect
|
|
{
|
|
return [self bounds];
|
|
|
|
// this could change if we drew menuitemcells as
|
|
// plain rects with no bezel like in macOSX. Talk to Michael Hanni if
|
|
// you would like to see this configurable.
|
|
}
|
|
|
|
- (NSRect)rectOfItemAtIndex:(int)index
|
|
{
|
|
NSRect theRect;
|
|
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
|
|
if (index == 0)
|
|
theRect.origin.y = [self frame].size.height - cellSize.height;
|
|
else
|
|
theRect.origin.y = [self frame].size.height - (cellSize.height * (index + 1));
|
|
theRect.origin.x = 0;
|
|
theRect.size = cellSize;
|
|
|
|
return theRect;
|
|
}
|
|
|
|
- (int)indexOfItemAtPoint:(NSPoint)point
|
|
{
|
|
// The MacOSX API says that this method calls - rectOfItemAtIndex for
|
|
// *every* cell to figure this out. Well, instead we will just do some
|
|
// simple math.
|
|
NSRect aRect = [self rectOfItemAtIndex:0];
|
|
|
|
// this will need some finnessing but should be close.
|
|
return ([self frame].size.height - point.y) / aRect.size.height;
|
|
}
|
|
|
|
- (void)setNeedsDisplayForItemAtIndex:(int)index
|
|
{
|
|
[[[menuv_menu itemArray] objectAtIndex:index] setNeedsDisplay:YES];
|
|
}
|
|
|
|
- (NSPoint)locationForSubmenu:(NSMenu *)aSubmenu
|
|
{
|
|
if (menuv_needsSizing)
|
|
[self sizeToFit];
|
|
|
|
// find aSubmenu's parent
|
|
|
|
// position aSubmenu's window to be adjacent to its parent.
|
|
|
|
// return new origin of window.
|
|
return NSZeroPoint;
|
|
}
|
|
|
|
- (void)resizeWindowWithMaxHeight:(float)maxHeight
|
|
{
|
|
// set the menuview's window to max height in order to keep on screen?
|
|
}
|
|
|
|
- (void)setWindowFrameForAttachingToRect:(NSRect)screenRect
|
|
onScreen:(NSScreen *)screen
|
|
preferredEdge:(NSRectEdge)edge
|
|
popUpSelectedItem:(int)selectedItemIndex
|
|
{
|
|
// huh.
|
|
}
|
|
|
|
// Drawing.
|
|
|
|
- (void)drawRect:(NSRect)rect
|
|
{
|
|
int i;
|
|
NSArray *menuCells = [menuv_menu itemArray];
|
|
NSRect aRect = [self frame];
|
|
|
|
// This code currently doesn't take intercell spacing into account. I'll
|
|
// need to fix that.
|
|
|
|
aRect.origin.y = cellSize.height * ([menuCells count] - 1);
|
|
aRect.size = cellSize;
|
|
|
|
for (i=0;i<[menuCells count];i++)
|
|
{
|
|
id aCell = [menuCells objectAtIndex:i];
|
|
|
|
[aCell drawWithFrame:aRect inView:self];
|
|
aRect.origin.y -= cellSize.height;
|
|
}
|
|
}
|
|
|
|
// Event.
|
|
|
|
- (void)performActionWithHighlightingForItemAtIndex:(int)index
|
|
{
|
|
// for use with key equivalents.
|
|
}
|
|
|
|
- (BOOL)trackWithEvent:(NSEvent *)event
|
|
{
|
|
NSPoint lastLocation = [event locationInWindow];
|
|
float height = [self frame].size.height;
|
|
int index;
|
|
int lastIndex = 0;
|
|
unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDownMask
|
|
| NSRightMouseUpMask | NSRightMouseDraggedMask
|
|
| NSLeftMouseDraggedMask;
|
|
BOOL done = NO;
|
|
NSApplication *theApp = [NSApplication sharedApplication];
|
|
NSDate *theDistantFuture = [NSDate distantFuture];
|
|
int theCount = [[menuv_menu itemArray] count];
|
|
id selectedCell;
|
|
|
|
// These 3 BOOLs are misnomers. I'll rename them later. -Michael. FIXME.
|
|
|
|
BOOL weWereOut = NO;
|
|
BOOL weLeftMenu = NO;
|
|
BOOL weRightMenu = NO;
|
|
|
|
// Get our mouse location, regardless of where it may be it the event
|
|
// stream.
|
|
|
|
lastLocation = [[self window] mouseLocationOutsideOfEventStream];
|
|
|
|
index = (height - lastLocation.y) / cellSize.height;
|
|
|
|
if (index >= 0 && index < theCount) {
|
|
[self setHighlightedItemIndex:index];
|
|
lastIndex = index;
|
|
}
|
|
|
|
while (!done) {
|
|
|
|
event = [theApp nextEventMatchingMask: eventMask
|
|
untilDate: theDistantFuture
|
|
inMode: NSEventTrackingRunLoopMode
|
|
dequeue: YES];
|
|
|
|
switch ([event type])
|
|
{
|
|
case NSRightMouseUp:
|
|
case NSLeftMouseUp:
|
|
/* right mouse up or left mouse up means we're done */
|
|
done = YES;
|
|
break;
|
|
case NSRightMouseDragged:
|
|
case NSLeftMouseDragged:
|
|
lastLocation = [[self window] mouseLocationOutsideOfEventStream];
|
|
|
|
#ifdef 0
|
|
NSLog (@"location = (%f, %f, %f)", lastLocation.x, [[self window]
|
|
frame].origin.x, [[self window] frame].size.width);
|
|
#endif
|
|
|
|
if (lastLocation.x > 0
|
|
&& lastLocation.x < [[self window] frame].size.width) {
|
|
lastLocation = [self convertPoint: lastLocation fromView:nil];
|
|
|
|
index = (height - lastLocation.y) / cellSize.height;
|
|
#ifdef 0
|
|
NSLog (@"location = (%f, %f)", lastLocation.x, lastLocation.y);
|
|
NSLog (@"index = %d\n", index);
|
|
#endif
|
|
if (index >= 0 && index < theCount) {
|
|
if (index != lastIndex) {
|
|
[self setHighlightedItemIndex:index];
|
|
lastIndex = index;
|
|
} else {
|
|
if (weWereOut) {
|
|
[self setHighlightedItemIndex:index];
|
|
lastIndex = index;
|
|
weWereOut = NO;
|
|
}
|
|
}
|
|
}
|
|
} else if (lastLocation.x > [[self window] frame].size.width) {
|
|
NSRect aRect = [self rectOfItemAtIndex:lastIndex];
|
|
if (lastLocation.y > aRect.origin.y && lastLocation.y <
|
|
aRect.origin.y + aRect.size.height && [[[menuv_menu itemArray] objectAtIndex:lastIndex] hasSubmenu]) {
|
|
weLeftMenu = YES;
|
|
done = YES;
|
|
} else {
|
|
[self setHighlightedItemIndex:-1];
|
|
lastIndex = index;
|
|
weWereOut = YES;
|
|
[window flushWindow];
|
|
}
|
|
} else if (lastLocation.x < 0) {
|
|
if ([menuv_menu supermenu]) {
|
|
weRightMenu = YES;
|
|
done = YES;
|
|
} else {
|
|
[self setHighlightedItemIndex:-1];
|
|
lastIndex = index;
|
|
weWereOut = YES;
|
|
[window flushWindow];
|
|
}
|
|
} else {
|
|
// FIXME, Michael. This might be needed... or not?
|
|
NSLog(@"This is the final else... its evil\n");
|
|
if (lastIndex >= 0 && lastIndex < theCount) {
|
|
[self setHighlightedItemIndex:-1];
|
|
lastIndex = index;
|
|
weWereOut = YES;
|
|
[window flushWindow];
|
|
}
|
|
}
|
|
[window flushWindow];
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!weLeftMenu && !weRightMenu && !weWereOut && menuv_highlightedItemIndex != -1) {
|
|
if (![[[menuv_menu itemArray] objectAtIndex:menuv_highlightedItemIndex] hasSubmenu]) {
|
|
BOOL finished = NO;
|
|
NSMenu *aMenu = menuv_menu;
|
|
selectedCell = [[menuv_menu itemArray] objectAtIndex:index];
|
|
|
|
[self setHighlightedItemIndex:-1];
|
|
|
|
if ([selectedCell action])
|
|
[menuv_menu performActionForItem:[[menuv_menu itemArray] objectAtIndex:lastIndex]];
|
|
|
|
if ([selectedCell hasSubmenu])
|
|
[[selectedCell target] close];
|
|
|
|
while (!finished) { // "forward"cursive menu find.
|
|
if ([aMenu attachedMenu]) {
|
|
aMenu = [aMenu attachedMenu];
|
|
}
|
|
else
|
|
finished = YES;
|
|
}
|
|
|
|
finished = NO;
|
|
|
|
while (!finished) { // Recursive menu close & deselect.
|
|
if ([aMenu supermenu] && ![aMenu isTornOff]) {
|
|
[[[aMenu supermenu] menuView] setHighlightedItemIndex:-1];
|
|
[aMenu close];
|
|
aMenu = [aMenu supermenu];
|
|
}
|
|
else
|
|
finished = YES;
|
|
|
|
[window flushWindow];
|
|
}
|
|
}
|
|
} else if (weRightMenu) {
|
|
NSPoint cP = [[self window] convertBaseToScreen:lastLocation];
|
|
|
|
[self setHighlightedItemIndex:-1];
|
|
|
|
if ([menuv_menu supermenu] && ![menuv_menu isTornOff]) {
|
|
[self mouseUp:
|
|
[NSEvent mouseEventWithType:NSLeftMouseUp
|
|
location:cP
|
|
modifierFlags:[event modifierFlags]
|
|
timestamp:[event timestamp]
|
|
windowNumber:[[self window] windowNumber]
|
|
context:[event context]
|
|
eventNumber:[event eventNumber]
|
|
clickCount:[event clickCount]
|
|
pressure:[event pressure]]];
|
|
|
|
[[[menuv_menu supermenu] menuView] mouseDown:
|
|
[NSEvent mouseEventWithType:NSLeftMouseDragged
|
|
location:cP
|
|
modifierFlags:[event modifierFlags]
|
|
timestamp:[event timestamp]
|
|
windowNumber:[[[[menuv_menu supermenu] menuView] window] windowNumber]
|
|
context:[event context]
|
|
eventNumber:[event eventNumber]
|
|
clickCount:[event clickCount]
|
|
pressure:[event pressure]]];
|
|
}
|
|
} else if (weLeftMenu) { /* The weLeftMenu case */
|
|
NSPoint cP = [[self window] convertBaseToScreen:lastLocation];
|
|
|
|
NSLog(@"Urph.\n");
|
|
|
|
selectedCell = [[menuv_menu itemArray] objectAtIndex:lastIndex];
|
|
if ([selectedCell hasSubmenu]) {
|
|
[self mouseUp:
|
|
[NSEvent mouseEventWithType:NSLeftMouseUp
|
|
location:cP
|
|
modifierFlags:[event modifierFlags]
|
|
timestamp:[event timestamp]
|
|
windowNumber:[[self window] windowNumber]
|
|
context:[event context]
|
|
eventNumber:[event eventNumber]
|
|
clickCount:[event clickCount]
|
|
pressure:[event pressure]]];
|
|
|
|
[[[selectedCell target] menuView] mouseDown:
|
|
[NSEvent mouseEventWithType:NSLeftMouseDragged
|
|
location:cP
|
|
modifierFlags:[event modifierFlags]
|
|
timestamp:[event timestamp]
|
|
windowNumber:[[[[selectedCell target] menuView] window] windowNumber]
|
|
context:[event context]
|
|
eventNumber:[event eventNumber]
|
|
clickCount:[event clickCount]
|
|
pressure:[event pressure]]];
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)mouseDown:(NSEvent *)theEvent
|
|
{
|
|
[self trackWithEvent:theEvent];
|
|
}
|
|
@end
|