mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-05-30 05:20:38 +00:00
Menu patch by Wim
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@16143 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
836df5adbe
commit
00b16254bb
5 changed files with 1340 additions and 526 deletions
596
Source/NSMenu.m
596
Source/NSMenu.m
|
@ -55,39 +55,78 @@
|
|||
#include <AppKit/NSScreen.h>
|
||||
#include <AppKit/NSAttributedString.h>
|
||||
|
||||
|
||||
/*
|
||||
Drawing related:
|
||||
|
||||
NSMenu superMenu (if not root menu, the parent meu)
|
||||
^
|
||||
|
|
||||
| +------------------> NSMenuView view (content, draws the menu items)
|
||||
| |
|
||||
NSMenu +----------+-------> NSMenuPanel A (regular window, torn off window)
|
||||
| | `-------> NSMenuPanel B (transient window)
|
||||
| |
|
||||
| +------------------> NSString title (title)
|
||||
|
|
||||
v
|
||||
NSMenu attachedMenu (the menu that is attached to this one, during navigation)
|
||||
|
||||
|
||||
|
||||
+--[NSMenuPanel]------+
|
||||
| +-[NSMenuView]----+ |
|
||||
| | title if applic | |
|
||||
| | +-------------+ | |
|
||||
| | | NSMenuItem- | | |
|
||||
| | | Cell | | |
|
||||
| | +-------------+ | |
|
||||
| | . | |
|
||||
| | . | |
|
||||
| +-----------------+ |
|
||||
+---------------------+
|
||||
|
||||
The two windows
|
||||
---------------
|
||||
|
||||
Basically we have for a menu two windows, window A and window B.
|
||||
Window A is the regular window and Window B is used for transient windows.
|
||||
|
||||
At any one time, the views, like title view, NSMenuView are put either in
|
||||
window A or in window B. They are moved over from one window to the oter
|
||||
when needed.
|
||||
|
||||
the code is supposed to know when it is using window A or B.
|
||||
But it will probably only work correctly when
|
||||
|
||||
window A correspond to _transient == NO
|
||||
window B correspond to _transient == YES
|
||||
*/
|
||||
|
||||
|
||||
/* Subclass of NSPanel since menus cannot become key */
|
||||
@interface NSMenuPanel : NSPanel
|
||||
@end
|
||||
|
||||
/* A menu's title is an instance of this class */
|
||||
@interface NSMenuWindowTitleView : NSView
|
||||
{
|
||||
id menu;
|
||||
NSButton *button;
|
||||
}
|
||||
|
||||
- (void) addCloseButton;
|
||||
- (void) releaseCloseButton;
|
||||
- (void) createButton;
|
||||
- (void) setMenu: (NSMenu*)menu;
|
||||
- (NSMenu*) menu;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSMenuView (GNUstepPrivate)
|
||||
- (NSArray *)_itemCells;
|
||||
@end
|
||||
|
||||
|
||||
static NSZone *menuZone = NULL;
|
||||
static NSString *NSMenuLocationsKey = @"NSMenuLocations";
|
||||
static NSString *NSMenuLocationsKey = @"NSMenuLocations";
|
||||
static NSString *NSEnqueuedMenuMoveName = @"EnqueuedMoveNotificationName";
|
||||
static NSNotificationCenter *nc;
|
||||
|
||||
@interface NSMenu (GNUstepPrivate)
|
||||
|
||||
- (NSString *) _locationKey;
|
||||
- (NSMenuPanel *) _createWindow;
|
||||
- (void) _updateUserDefaults: (id) notification;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSMenuPanel
|
||||
- (BOOL) canBecomeKeyWindow
|
||||
{
|
||||
|
@ -139,6 +178,68 @@ static NSNotificationCenter *nc;
|
|||
return win;
|
||||
}
|
||||
|
||||
/**
|
||||
Will track the mouse movement. It will trigger the updating of the user
|
||||
defaults in due time.
|
||||
*/
|
||||
- (void) _menuMoved: (id) notification
|
||||
{
|
||||
NSNotification *resend;
|
||||
|
||||
resend = [NSNotification notificationWithName: NSEnqueuedMenuMoveName
|
||||
object: self];
|
||||
|
||||
[[NSNotificationQueue defaultQueue]
|
||||
enqueueNotification: resend
|
||||
postingStyle: NSPostASAP
|
||||
coalesceMask: NSNotificationCoalescingOnSender
|
||||
forModes: [NSArray arrayWithObject: NSDefaultRunLoopMode]];
|
||||
}
|
||||
|
||||
/**
|
||||
Save the current menu position in the standard user defaults
|
||||
*/
|
||||
- (void) _updateUserDefaults: (id) notification
|
||||
{
|
||||
NSString *key;
|
||||
|
||||
NSDebugLLog (@"NSMenu", @"Synchronizing user defaults");
|
||||
key = [self _locationKey];
|
||||
if (key != nil)
|
||||
{
|
||||
NSUserDefaults *defaults;
|
||||
NSMutableDictionary *menuLocations;
|
||||
NSString *locString;
|
||||
|
||||
defaults = [NSUserDefaults standardUserDefaults];
|
||||
menuLocations = [[defaults objectForKey: NSMenuLocationsKey] mutableCopy];
|
||||
|
||||
if ([_aWindow isVisible] && [self isTornOff])
|
||||
{
|
||||
if (menuLocations == nil)
|
||||
{
|
||||
menuLocations = AUTORELEASE([[NSMutableDictionary alloc] initWithCapacity: 2]);
|
||||
}
|
||||
locString = [[self window] stringWithSavedFrame];
|
||||
[menuLocations setObject: locString forKey: key];
|
||||
}
|
||||
else
|
||||
{
|
||||
[menuLocations removeObjectForKey: key];
|
||||
}
|
||||
|
||||
if ([menuLocations count] > 0)
|
||||
{
|
||||
[defaults setObject: menuLocations forKey: NSMenuLocationsKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
[defaults removeObjectForKey: NSMenuLocationsKey];
|
||||
}
|
||||
[defaults synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
@ -168,7 +269,7 @@ static NSNotificationCenter *nc;
|
|||
}
|
||||
|
||||
/*
|
||||
* Initializing a New NSMenu
|
||||
*
|
||||
*/
|
||||
- (id) init
|
||||
{
|
||||
|
@ -185,14 +286,15 @@ static NSNotificationCenter *nc;
|
|||
RELEASE(_view);
|
||||
RELEASE(_aWindow);
|
||||
RELEASE(_bWindow);
|
||||
RELEASE(_titleView);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
- (id) initWithTitle: (NSString*)aTitle
|
||||
{
|
||||
float height;
|
||||
NSView *contentView;
|
||||
|
||||
[super init];
|
||||
|
@ -203,19 +305,12 @@ static NSNotificationCenter *nc;
|
|||
// Create an array to store out menu items.
|
||||
_items = [[NSMutableArray alloc] init];
|
||||
|
||||
// We have no supermenu.
|
||||
// _superMenu = nil;
|
||||
// _is_tornoff = NO;
|
||||
// _follow_transient = NO;
|
||||
|
||||
_changedMessagesEnabled = YES;
|
||||
_notifications = [[NSMutableArray alloc] init];
|
||||
_changed = YES;
|
||||
// According to the spec, menus do autoenable by default.
|
||||
_autoenable = YES;
|
||||
|
||||
// Transient windows private stuff.
|
||||
// _oldAttachedMenu = nil;
|
||||
|
||||
/* Please note that we own all this menu network of objects. So,
|
||||
none of these objects should be retaining us. When we are deallocated,
|
||||
|
@ -231,15 +326,8 @@ static NSNotificationCenter *nc;
|
|||
_view = [[NSMenuView alloc] initWithFrame: NSMakeRect(0,0,50,50)];
|
||||
[_view setMenu: self];
|
||||
|
||||
// Create the title view
|
||||
height = [[_view class] menuBarHeight];
|
||||
_titleView = [[NSMenuWindowTitleView alloc] initWithFrame:
|
||||
NSMakeRect(0, 0, 50, height)];
|
||||
[_titleView setMenu: self];
|
||||
|
||||
contentView = [_aWindow contentView];
|
||||
[contentView addSubview: _view];
|
||||
[contentView addSubview: _titleView];
|
||||
|
||||
/* Set up the notification to start the process of redisplaying
|
||||
the menus where the user left them the last time.
|
||||
|
@ -258,6 +346,16 @@ static NSNotificationCenter *nc;
|
|||
name: NSApplicationWillBecomeActiveNotification
|
||||
object: NSApp];
|
||||
|
||||
[nc addObserver: self
|
||||
selector: @selector (_menuMoved:)
|
||||
name: NSWindowDidMoveNotification
|
||||
object: _aWindow];
|
||||
|
||||
[nc addObserver: self
|
||||
selector: @selector (_updateUserDefaults:)
|
||||
name: NSEnqueuedMenuMoveName
|
||||
object: self];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -558,18 +656,34 @@ static NSNotificationCenter *nc;
|
|||
|
||||
- (void) submenuAction: (id)sender
|
||||
{
|
||||
[_view detachSubmenu];
|
||||
}
|
||||
|
||||
|
||||
- (NSMenu *) attachedMenu
|
||||
{
|
||||
if (_attachedMenu && _follow_transient
|
||||
&& !_attachedMenu->_follow_transient)
|
||||
if (_attachedMenu && _transient
|
||||
&& !_attachedMenu->_transient)
|
||||
return nil;
|
||||
|
||||
return _attachedMenu;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Look for the semantics in the header. Note that
|
||||
this implementation works because there are ... cases:
|
||||
<enum>
|
||||
<item>
|
||||
This menu is transient, its supermenu is also transient.
|
||||
In this case we just do the check between the transient windows
|
||||
and everything is fine
|
||||
</item>
|
||||
<item>
|
||||
The menu is transient, its supermenu is not transient.
|
||||
This can go WRONG
|
||||
</item>
|
||||
</enum>
|
||||
*/
|
||||
- (BOOL) isAttached
|
||||
{
|
||||
return _superMenu && [_superMenu attachedMenu] == self;
|
||||
|
@ -835,6 +949,9 @@ static NSNotificationCenter *nc;
|
|||
//
|
||||
// Updating the Menu Layout
|
||||
//
|
||||
// Wim 20030301: Question, what happens when the notification trigger
|
||||
// new notifications? I think it is not allowed to add items
|
||||
// to the _notifications array while enumerating it.
|
||||
- (void) setMenuChangedMessagesEnabled: (BOOL)flag
|
||||
{
|
||||
if (_changedMessagesEnabled != flag)
|
||||
|
@ -869,39 +986,25 @@ static NSNotificationCenter *nc;
|
|||
NSRect menuFrame;
|
||||
NSSize size;
|
||||
|
||||
//if ([_view needsSizing])
|
||||
[_view sizeToFit];
|
||||
[_view sizeToFit];
|
||||
|
||||
menuFrame = [_view frame];
|
||||
size = menuFrame.size;
|
||||
|
||||
windowFrame = [_aWindow frame];
|
||||
[_aWindow setContentSize: size];
|
||||
[_aWindow setFrameTopLeftPoint:
|
||||
NSMakePoint(NSMinX(windowFrame),NSMaxY(windowFrame))];
|
||||
|
||||
windowFrame = [_bWindow frame];
|
||||
[_bWindow setContentSize: size];
|
||||
[_bWindow setFrameTopLeftPoint:
|
||||
NSMakePoint(NSMinX(windowFrame),NSMaxY(windowFrame))];
|
||||
|
||||
if (_popUpButtonCell == nil)
|
||||
{
|
||||
float height = [[_view class] menuBarHeight];
|
||||
|
||||
size.height += height;
|
||||
[_aWindow setContentSize: size];
|
||||
[_aWindow setFrameTopLeftPoint:
|
||||
NSMakePoint(NSMinX(windowFrame),NSMaxY(windowFrame))];
|
||||
|
||||
windowFrame = [_bWindow frame];
|
||||
[_bWindow setContentSize: size];
|
||||
[_bWindow setFrameTopLeftPoint:
|
||||
NSMakePoint(NSMinX(windowFrame),NSMaxY(windowFrame))];
|
||||
|
||||
[_view setFrameOrigin: NSMakePoint (0, 0)];
|
||||
[_titleView setFrame: NSMakeRect (0, size.height - height,
|
||||
size.width, height)];
|
||||
[_titleView setNeedsDisplay: YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_aWindow setContentSize: size];
|
||||
[_aWindow setFrameTopLeftPoint:
|
||||
NSMakePoint(NSMinX(windowFrame),NSMaxY(windowFrame))];
|
||||
}
|
||||
}
|
||||
|
||||
[_view setNeedsDisplay: YES];
|
||||
|
||||
|
@ -1013,23 +1116,22 @@ static NSNotificationCenter *nc;
|
|||
#define IS_OFFSCREEN(WINDOW) \
|
||||
!(NSContainsRect([[NSScreen mainScreen] frame], [WINDOW frame]))
|
||||
|
||||
- (void) _setTornOff: (BOOL)flag
|
||||
- (void) setTornOff: (BOOL)flag
|
||||
{
|
||||
NSMenu *supermenu;
|
||||
|
||||
_is_tornoff = flag;
|
||||
_is_tornoff = flag;
|
||||
|
||||
if (flag)
|
||||
[_titleView addCloseButton];
|
||||
else
|
||||
[_titleView releaseCloseButton];
|
||||
|
||||
supermenu = [self supermenu];
|
||||
if (supermenu != nil)
|
||||
{
|
||||
[[supermenu menuRepresentation] setHighlightedItemIndex: -1];
|
||||
supermenu->_attachedMenu = nil;
|
||||
supermenu = [self supermenu];
|
||||
if (supermenu != nil)
|
||||
{
|
||||
[[supermenu menuRepresentation] setHighlightedItemIndex: -1];
|
||||
supermenu->_attachedMenu = nil;
|
||||
}
|
||||
}
|
||||
[_view update];
|
||||
}
|
||||
|
||||
- (void) _showTornOffMenuIfAny: (NSNotification*)notification
|
||||
|
@ -1051,7 +1153,7 @@ static NSNotificationCenter *nc;
|
|||
location = [menuLocations objectForKey: key];
|
||||
if (location && [location isKindOfClass: [NSString class]])
|
||||
{
|
||||
[self _setTornOff: YES];
|
||||
[self setTornOff: YES];
|
||||
[self display];
|
||||
}
|
||||
}
|
||||
|
@ -1068,52 +1170,25 @@ static NSNotificationCenter *nc;
|
|||
}
|
||||
}
|
||||
|
||||
- (BOOL) isFollowTransient
|
||||
- (BOOL) isTransient
|
||||
{
|
||||
return _follow_transient;
|
||||
return _transient;
|
||||
}
|
||||
|
||||
- (BOOL) isPartlyOffScreen
|
||||
{
|
||||
return _isPartlyOffScreen;
|
||||
}
|
||||
|
||||
- (void) nestedCheckOffScreen
|
||||
{
|
||||
// This method is used when the menu is moved.
|
||||
if (_attachedMenu)
|
||||
[_attachedMenu nestedCheckOffScreen];
|
||||
|
||||
_isPartlyOffScreen = IS_OFFSCREEN(_aWindow);
|
||||
return IS_OFFSCREEN ([self window]);
|
||||
}
|
||||
|
||||
- (void) _performMenuClose: (id)sender
|
||||
{
|
||||
NSString *key;
|
||||
|
||||
if (_attachedMenu)
|
||||
[_view detachSubmenu];
|
||||
|
||||
key = [self _locationKey];
|
||||
if (key != nil)
|
||||
{
|
||||
NSUserDefaults *defaults;
|
||||
NSMutableDictionary *menuLocations;
|
||||
|
||||
defaults = [NSUserDefaults standardUserDefaults];
|
||||
menuLocations = [[defaults objectForKey: NSMenuLocationsKey] mutableCopy];
|
||||
[menuLocations removeObjectForKey: key];
|
||||
if ([menuLocations count] > 0)
|
||||
[defaults setObject: menuLocations forKey: NSMenuLocationsKey];
|
||||
else
|
||||
[defaults removeObjectForKey: NSMenuLocationsKey];
|
||||
RELEASE(menuLocations);
|
||||
[defaults synchronize];
|
||||
}
|
||||
|
||||
|
||||
[_view setHighlightedItemIndex: -1];
|
||||
[self _setTornOff: NO];
|
||||
[self close];
|
||||
[self setTornOff: NO];
|
||||
[self _updateUserDefaults: nil];
|
||||
}
|
||||
|
||||
- (void) _rightMouseDisplay: (NSEvent*)theEvent
|
||||
|
@ -1125,6 +1200,11 @@ static NSNotificationCenter *nc;
|
|||
|
||||
- (void) display
|
||||
{
|
||||
if (_transient)
|
||||
{
|
||||
NSDebugLLog (@"NSMenu", @"trying to display while alreay displayed transient");
|
||||
}
|
||||
|
||||
if (_changed)
|
||||
[self sizeToFit];
|
||||
|
||||
|
@ -1142,10 +1222,7 @@ static NSNotificationCenter *nc;
|
|||
[self setGeometry];
|
||||
}
|
||||
}
|
||||
|
||||
[_aWindow orderFrontRegardless];
|
||||
|
||||
_isPartlyOffScreen = IS_OFFSCREEN(_aWindow);
|
||||
}
|
||||
|
||||
- (void) displayTransient
|
||||
|
@ -1153,8 +1230,15 @@ static NSNotificationCenter *nc;
|
|||
NSPoint location;
|
||||
NSView *contentView;
|
||||
|
||||
_follow_transient = YES;
|
||||
|
||||
if (_transient)
|
||||
{
|
||||
NSDebugLLog (@"NSMenu", @"displaying transient while it is transient");
|
||||
return;
|
||||
}
|
||||
|
||||
_oldHiglightedIndex = [[self menuRepresentation] highlightedItemIndex];
|
||||
_transient = YES;
|
||||
|
||||
/*
|
||||
* Cache the old submenu if any and query the supermenu our position.
|
||||
* Otherwise, raise menu under the mouse.
|
||||
|
@ -1180,20 +1264,14 @@ static NSNotificationCenter *nc;
|
|||
[_bWindow setFrameOrigin: location];
|
||||
|
||||
[_view removeFromSuperviewWithoutNeedingDisplay];
|
||||
[_titleView removeFromSuperviewWithoutNeedingDisplay];
|
||||
|
||||
if (_is_tornoff)
|
||||
[_titleView releaseCloseButton];
|
||||
|
||||
contentView = [_bWindow contentView];
|
||||
[contentView addSubview: _view];
|
||||
[contentView addSubview: _titleView];
|
||||
|
||||
[_view update];
|
||||
[_bWindow orderFront: self];
|
||||
|
||||
_isPartlyOffScreen = IS_OFFSCREEN(_bWindow);
|
||||
}
|
||||
|
||||
|
||||
- (void) setGeometry
|
||||
{
|
||||
NSString *key;
|
||||
|
@ -1232,6 +1310,12 @@ static NSNotificationCenter *nc;
|
|||
{
|
||||
NSMenu *sub = [self attachedMenu];
|
||||
|
||||
|
||||
if (_transient)
|
||||
{
|
||||
NSDebugLLog (@"NSMenu", @"We should not close ordinary menu while transient version is still open");
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have an attached submenu, we must close that too - but then make
|
||||
* sure we still have a record of it so that it can be re-displayed if we
|
||||
|
@ -1245,39 +1329,48 @@ static NSNotificationCenter *nc;
|
|||
[_aWindow orderOut: self];
|
||||
[_aWindow setFrameOrigin: NSMakePoint (0, 0)];
|
||||
|
||||
if (_superMenu)
|
||||
_superMenu->_attachedMenu = nil;
|
||||
if (_superMenu && ![self isTornOff])
|
||||
{
|
||||
_superMenu->_attachedMenu = nil;
|
||||
[[_superMenu menuRepresentation] setHighlightedItemIndex: -1];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) closeTransient
|
||||
{
|
||||
NSView *contentView;
|
||||
|
||||
if (_transient == NO)
|
||||
{
|
||||
NSDebugLLog (@"NSMenu", @"Closing transient: %@ while it is NOT transient now", _title);
|
||||
return;
|
||||
}
|
||||
|
||||
[_bWindow orderOut: self];
|
||||
[_view removeFromSuperviewWithoutNeedingDisplay];
|
||||
[_titleView removeFromSuperviewWithoutNeedingDisplay];
|
||||
|
||||
contentView = [_aWindow contentView];
|
||||
[contentView addSubview: _view];
|
||||
|
||||
if (_is_tornoff)
|
||||
[_titleView addCloseButton];
|
||||
|
||||
[contentView addSubview: _titleView];
|
||||
[contentView setNeedsDisplay: YES];
|
||||
[contentView setNeedsDisplay: YES];
|
||||
|
||||
// Restore the old submenu (if any).
|
||||
if (_superMenu != nil)
|
||||
_superMenu->_attachedMenu = _oldAttachedMenu;
|
||||
{
|
||||
_superMenu->_attachedMenu = _oldAttachedMenu;
|
||||
[[_superMenu menuRepresentation] setHighlightedItemIndex:
|
||||
[_superMenu indexOfItemWithSubmenu: _superMenu->_attachedMenu]];
|
||||
}
|
||||
|
||||
_follow_transient = NO;
|
||||
[[self menuRepresentation] setHighlightedItemIndex: _oldHiglightedIndex];
|
||||
|
||||
_transient = NO;
|
||||
|
||||
_isPartlyOffScreen = IS_OFFSCREEN(_aWindow);
|
||||
[_view update];
|
||||
}
|
||||
|
||||
- (NSWindow*) window
|
||||
{
|
||||
if (_follow_transient)
|
||||
if (_transient)
|
||||
return (NSWindow *)_bWindow;
|
||||
else
|
||||
return (NSWindow *)_aWindow;
|
||||
|
@ -1290,7 +1383,7 @@ static NSNotificationCenter *nc;
|
|||
*/
|
||||
- (void) nestedSetFrameOrigin: (NSPoint) aPoint
|
||||
{
|
||||
NSWindow *theWindow = _follow_transient ? _bWindow : _aWindow;
|
||||
NSWindow *theWindow = [self window];
|
||||
|
||||
// Move ourself and get our width.
|
||||
[theWindow setFrameOrigin: aPoint];
|
||||
|
@ -1307,39 +1400,37 @@ static NSNotificationCenter *nc;
|
|||
|
||||
- (void) shiftOnScreen
|
||||
{
|
||||
NSWindow *theWindow = _follow_transient ? _bWindow : _aWindow;
|
||||
NSWindow *theWindow = _transient ? _bWindow : _aWindow;
|
||||
NSRect frameRect = [theWindow frame];
|
||||
NSRect screenRect = [[NSScreen mainScreen] frame];
|
||||
NSPoint vector = {0.0, 0.0};
|
||||
BOOL moveIt = YES;
|
||||
|
||||
if (frameRect.origin.y < 0)
|
||||
BOOL moveIt = NO;
|
||||
|
||||
// 1 - determine the amount we need to shift in the y direction.
|
||||
if (NSMinY (frameRect) < 0)
|
||||
{
|
||||
if (frameRect.origin.y + SHIFT_DELTA <= 0)
|
||||
vector.y = SHIFT_DELTA;
|
||||
else
|
||||
vector.y = -frameRect.origin.y;
|
||||
vector.y = MIN (SHIFT_DELTA, -NSMinY (frameRect));
|
||||
moveIt = YES;
|
||||
}
|
||||
else if (frameRect.origin.x < 0)
|
||||
else if (NSMaxY (frameRect) > NSMaxY (screenRect))
|
||||
{
|
||||
if (frameRect.origin.x + SHIFT_DELTA <= 0)
|
||||
vector.x = SHIFT_DELTA;
|
||||
else
|
||||
vector.x = -frameRect.origin.x;
|
||||
vector.y = -MIN (SHIFT_DELTA, NSMaxY (frameRect) - NSMaxY (screenRect));
|
||||
moveIt = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
vector.x = frameRect.origin.x + frameRect.size.width;
|
||||
vector.x -= [[NSScreen mainScreen] frame].size.width;
|
||||
|
||||
if (vector.x > 0)
|
||||
{
|
||||
if (vector.x - SHIFT_DELTA <= 0)
|
||||
vector.x = -SHIFT_DELTA;
|
||||
else
|
||||
vector.x = -vector.x - 2;
|
||||
}
|
||||
else
|
||||
moveIt = NO;
|
||||
// 2 - determine the amount we need to shift in the x direction.
|
||||
if (NSMinX (frameRect) < 0)
|
||||
{
|
||||
vector.x = MIN (SHIFT_DELTA, -NSMinX (frameRect));
|
||||
moveIt = YES;
|
||||
}
|
||||
// Note the -3. This is done so the menu, after shifting completely
|
||||
// has some spare room on the right hand side. This is needed otherwise
|
||||
// the user can never access submenus of this menu.
|
||||
else if (NSMaxX (frameRect) > NSMaxX (screenRect) - 3)
|
||||
{
|
||||
vector.x = -MIN (SHIFT_DELTA, NSMaxX (frameRect) - NSMaxX (screenRect) + 3);
|
||||
moveIt = YES;
|
||||
}
|
||||
|
||||
if (moveIt)
|
||||
|
@ -1353,7 +1444,7 @@ static NSNotificationCenter *nc;
|
|||
for (candidateMenu = masterMenu = self;
|
||||
(candidateMenu = masterMenu->_superMenu)
|
||||
&& (!masterMenu->_is_tornoff
|
||||
|| masterMenu->_follow_transient);
|
||||
|| masterMenu->_transient);
|
||||
masterMenu = candidateMenu);
|
||||
|
||||
masterLocation = [[masterMenu window] frame].origin;
|
||||
|
@ -1362,8 +1453,6 @@ static NSNotificationCenter *nc;
|
|||
|
||||
[masterMenu nestedSetFrameOrigin: destinationPoint];
|
||||
}
|
||||
else
|
||||
_isPartlyOffScreen = NO;
|
||||
}
|
||||
|
||||
- (BOOL)_ownedByPopUp
|
||||
|
@ -1378,191 +1467,18 @@ static NSNotificationCenter *nc;
|
|||
_popUpButtonCell = popUp;
|
||||
if (popUp != nil)
|
||||
{
|
||||
[_titleView removeFromSuperviewWithoutNeedingDisplay];
|
||||
[_aWindow setLevel: NSPopUpMenuWindowLevel];
|
||||
[_bWindow setLevel: NSPopUpMenuWindowLevel];
|
||||
}
|
||||
|
||||
{
|
||||
NSArray *itemCells = [_view _itemCells];
|
||||
int i;
|
||||
int count = [itemCells count];
|
||||
|
||||
for ( i = 0; i < count; i++ )
|
||||
{
|
||||
[[itemCells objectAtIndex: i] setMenuView: _view];
|
||||
}
|
||||
}
|
||||
}
|
||||
[self update];
|
||||
}
|
||||
|
||||
- (NSString*) description
|
||||
{
|
||||
return [NSString stringWithFormat: @"NSMenu: %@ (%@)",
|
||||
_title, _transient ? @"Transient": @"Normal"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSMenuWindowTitleView
|
||||
|
||||
- (BOOL) acceptsFirstMouse: (NSEvent *)theEvent
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) setMenu: (NSMenu*)aMenu
|
||||
{
|
||||
menu = aMenu;
|
||||
}
|
||||
|
||||
- (NSMenu*) menu
|
||||
{
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (void) drawRect: (NSRect)rect
|
||||
{
|
||||
NSRect workRect = [self bounds];
|
||||
NSRectEdge sides[] = {NSMinXEdge, NSMaxYEdge};
|
||||
float grays[] = {NSDarkGray, NSDarkGray};
|
||||
/* Cache the title attributes */
|
||||
static NSDictionary *attr = nil;
|
||||
|
||||
// Draw the dark gray upper left lines.
|
||||
workRect = NSDrawTiledRects(workRect, workRect, sides, grays, 2);
|
||||
|
||||
// Draw the title box's button.
|
||||
NSDrawButton(workRect, workRect);
|
||||
|
||||
// Paint it Black!
|
||||
workRect.origin.x += 1;
|
||||
workRect.origin.y += 2;
|
||||
workRect.size.height -= 3;
|
||||
workRect.size.width -= 3;
|
||||
[[NSColor windowFrameColor] set];
|
||||
NSRectFill(workRect);
|
||||
|
||||
// Draw the title
|
||||
if (attr == nil)
|
||||
{
|
||||
attr = [[NSDictionary alloc]
|
||||
initWithObjectsAndKeys:
|
||||
[NSFont boldSystemFontOfSize: 0], NSFontAttributeName,
|
||||
[NSColor windowFrameTextColor], NSForegroundColorAttributeName,
|
||||
nil];
|
||||
}
|
||||
|
||||
// This gives the correct position
|
||||
workRect.origin.x += 5;
|
||||
workRect.size.width -= 5;
|
||||
workRect.size.height -= 2;
|
||||
[[menu title] drawInRect: workRect withAttributes: attr];
|
||||
}
|
||||
|
||||
- (void) mouseDown: (NSEvent*)theEvent
|
||||
{
|
||||
NSString *key;
|
||||
NSPoint lastLocation;
|
||||
NSPoint location;
|
||||
unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
|
||||
BOOL done = NO;
|
||||
NSDate *theDistantFuture = [NSDate distantFuture];
|
||||
|
||||
lastLocation = [theEvent locationInWindow];
|
||||
|
||||
if (![menu isTornOff] && [menu supermenu])
|
||||
{
|
||||
[menu _setTornOff: YES];
|
||||
}
|
||||
|
||||
while (!done)
|
||||
{
|
||||
theEvent = [NSApp nextEventMatchingMask: eventMask
|
||||
untilDate: theDistantFuture
|
||||
inMode: NSEventTrackingRunLoopMode
|
||||
dequeue: YES];
|
||||
|
||||
switch ([theEvent type])
|
||||
{
|
||||
case NSLeftMouseUp:
|
||||
done = YES;
|
||||
break;
|
||||
case NSLeftMouseDragged:
|
||||
location = [_window mouseLocationOutsideOfEventStream];
|
||||
if (NSEqualPoints(location, lastLocation) == NO)
|
||||
{
|
||||
NSPoint origin = [_window frame].origin;
|
||||
|
||||
origin.x += (location.x - lastLocation.x);
|
||||
origin.y += (location.y - lastLocation.y);
|
||||
[menu nestedSetFrameOrigin: origin];
|
||||
[menu nestedCheckOffScreen];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Same current menu frame in defaults database.
|
||||
*/
|
||||
key = [menu _locationKey];
|
||||
if (key != nil)
|
||||
{
|
||||
NSUserDefaults *defaults;
|
||||
NSMutableDictionary *menuLocations;
|
||||
NSString *locString;
|
||||
|
||||
defaults = [NSUserDefaults standardUserDefaults];
|
||||
menuLocations = [[defaults objectForKey: NSMenuLocationsKey] mutableCopy];
|
||||
if (menuLocations == nil)
|
||||
{
|
||||
menuLocations = AUTORELEASE([[NSMutableDictionary alloc] initWithCapacity: 2]);
|
||||
}
|
||||
locString = [[menu window] stringWithSavedFrame];
|
||||
[menuLocations setObject: locString forKey: key];
|
||||
[defaults setObject: menuLocations forKey: NSMenuLocationsKey];
|
||||
[defaults synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) createButton
|
||||
{
|
||||
// create the menu's close button
|
||||
NSImage* closeImage = [NSImage imageNamed: @"common_Close"];
|
||||
NSImage* closeHImage = [NSImage imageNamed: @"common_CloseH"];
|
||||
NSSize imageSize = [closeImage size];
|
||||
NSRect rect = { { _frame.size.width - imageSize.width - 4,
|
||||
(_frame.size.height - imageSize.height) / 2},
|
||||
{ imageSize.height, imageSize.width } };
|
||||
|
||||
button = [[NSButton alloc] initWithFrame: rect];
|
||||
[button setButtonType: NSMomentaryLight];
|
||||
[button setImagePosition: NSImageOnly];
|
||||
[button setImage: closeImage];
|
||||
[button setAlternateImage: closeHImage];
|
||||
[button setBordered: NO];
|
||||
[button setTarget: menu];
|
||||
[button setAction: @selector(_performMenuClose:)];
|
||||
[button setAutoresizingMask: NSViewMinXMargin];
|
||||
|
||||
[self setAutoresizingMask: NSViewMinXMargin | NSViewMinYMargin | NSViewMaxYMargin];
|
||||
}
|
||||
|
||||
- (void) releaseCloseButton
|
||||
{
|
||||
[button removeFromSuperview];
|
||||
}
|
||||
|
||||
- (void) addCloseButton
|
||||
{
|
||||
if (button == nil)
|
||||
[self createButton];
|
||||
[self addSubview: button];
|
||||
[self setNeedsDisplay: YES];
|
||||
}
|
||||
|
||||
- (void) rightMouseDown: (NSEvent*)theEvent
|
||||
{
|
||||
// Dont show our menu
|
||||
}
|
||||
|
||||
@end /* NSMenuWindowTitleView */
|
||||
|
|
|
@ -25,8 +25,7 @@
|
|||
License along with this library; see the file COPYING.LIB.
|
||||
If not, write to the Free Software Foundation,
|
||||
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
*/
|
||||
|
||||
#include <Foundation/NSRunLoop.h>
|
||||
|
||||
|
@ -34,9 +33,40 @@
|
|||
#include <AppKit/NSEvent.h>
|
||||
#include <AppKit/NSFont.h>
|
||||
#include <AppKit/NSMenuView.h>
|
||||
#include <AppKit/NSMenu.h>
|
||||
#include <AppKit/NSButton.h>
|
||||
#include <AppKit/NSWindow.h>
|
||||
#include <AppKit/PSOperators.h>
|
||||
|
||||
#include <Foundation/NSDebug.h>
|
||||
|
||||
#include <AppKit/NSImage.h>
|
||||
|
||||
/*
|
||||
NSMenuView contains:
|
||||
|
||||
a) Title, if needed, this is a subview
|
||||
b) menu items
|
||||
|
||||
|
||||
*/
|
||||
/* A menu's title is an instance of this class */
|
||||
@class NSButton;
|
||||
|
||||
@interface NSMenuWindowTitleView : NSView
|
||||
{
|
||||
NSMenu *menu;
|
||||
NSButton *button;
|
||||
}
|
||||
|
||||
- (void) addCloseButton;
|
||||
- (void) removeCloseButton;
|
||||
- (void) createButton;
|
||||
- (void) setMenu: (NSMenu*)menu;
|
||||
- (NSMenu*) menu;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSMenuView
|
||||
|
||||
static NSRect
|
||||
|
@ -80,6 +110,13 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
{
|
||||
return YES;
|
||||
}
|
||||
// We do not want to popup menus in this menu.
|
||||
- (id) menuForEvent: (NSEvent*) theEvent
|
||||
{
|
||||
NSDebugLLog (@"NSMenu", @"Query for menu in view");
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Init methods.
|
||||
|
@ -108,6 +145,11 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
// Create an array to store our menu item cells.
|
||||
_itemCells = [NSMutableArray new];
|
||||
|
||||
// Create title view and add it. CHECKME, should we do this here?
|
||||
_titleView = [[NSMenuWindowTitleView alloc] init];
|
||||
|
||||
[self addSubview: _titleView];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -171,6 +213,7 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
name: NSMenuDidRemoveItemNotification
|
||||
object: _menu];
|
||||
|
||||
[_titleView setMenu: _menu]; // WO CHECKME does this needs reorganizing?
|
||||
// Force menu view's layout to be recalculated.
|
||||
[self setNeedsSizing: YES];
|
||||
}
|
||||
|
@ -259,12 +302,7 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
|
||||
- (NSMenuView*) attachedMenuView
|
||||
{
|
||||
NSMenu *attachedMenu;
|
||||
|
||||
if ((attachedMenu = [_menu attachedMenu]))
|
||||
return [attachedMenu menuRepresentation];
|
||||
else
|
||||
return nil;
|
||||
return [[_menu attachedMenu] menuRepresentation];
|
||||
}
|
||||
|
||||
- (NSMenu*) attachedMenu
|
||||
|
@ -366,6 +404,7 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
/*
|
||||
* Working with Submenus.
|
||||
*/
|
||||
|
||||
- (void) detachSubmenu
|
||||
{
|
||||
NSMenu *attachedMenu = [_menu attachedMenu];
|
||||
|
@ -378,17 +417,24 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
|
||||
[attachedMenuView detachSubmenu];
|
||||
|
||||
[attachedMenuView setHighlightedItemIndex: -1];
|
||||
|
||||
if ([attachedMenu isFollowTransient])
|
||||
NSDebugLLog (@"NSMenu", @"detach submenu: %@ from: %@",
|
||||
attachedMenu, _menu);
|
||||
|
||||
if ([attachedMenu isTransient])
|
||||
{
|
||||
[attachedMenu closeTransient];
|
||||
[attachedMenuView setHighlightedItemIndex: _oldHighlightedItemIndex];
|
||||
}
|
||||
else
|
||||
[attachedMenu close];
|
||||
{
|
||||
[attachedMenu close];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Attach submenu if the item at index is a submenu.
|
||||
It will figure out if the new submenu should be transient
|
||||
or not.
|
||||
*/
|
||||
- (void) attachSubmenuForItemAtIndex: (int)index
|
||||
{
|
||||
/*
|
||||
|
@ -397,17 +443,26 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
* soon as we release the mouse the user will be able to leave submenus
|
||||
* open on the screen and interact with other menus at the same time.
|
||||
*/
|
||||
NSMenu *attachableMenu = [[_items_link objectAtIndex: index] submenu];
|
||||
NSMenu *attachableMenu;
|
||||
|
||||
if ([attachableMenu isTornOff] || [_menu isFollowTransient])
|
||||
if (index < 0)
|
||||
{
|
||||
_oldHighlightedItemIndex = [[attachableMenu menuRepresentation]
|
||||
highlightedItemIndex];
|
||||
return;
|
||||
}
|
||||
|
||||
attachableMenu = [[_items_link objectAtIndex: index] submenu];
|
||||
|
||||
if ([attachableMenu isTornOff] || [_menu isTransient])
|
||||
{
|
||||
NSDebugLLog (@"NSMenu", @"Will open transient: %@", attachableMenu);
|
||||
[attachableMenu displayTransient];
|
||||
[[attachableMenu menuRepresentation] setHighlightedItemIndex: -1];
|
||||
[[attachableMenu menuRepresentation] setHighlightedItemIndex: -1];
|
||||
}
|
||||
else
|
||||
[attachableMenu display];
|
||||
{
|
||||
NSDebugLLog (@"NSMenu", @"Will open normal: %@", attachableMenu);
|
||||
[attachableMenu display];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -417,6 +472,15 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
{
|
||||
[_menu update];
|
||||
|
||||
if ([_menu isTornOff] && ![_menu isTransient])
|
||||
{
|
||||
[_titleView addCloseButton];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_titleView removeCloseButton];
|
||||
}
|
||||
|
||||
if (_needsSizing)
|
||||
[self sizeToFit];
|
||||
}
|
||||
|
@ -527,8 +591,12 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
|
||||
if (_horizontal == NO)
|
||||
{
|
||||
float menuBarHeight = [[self class] menuBarHeight];
|
||||
|
||||
[self setFrameSize: NSMakeSize(_cellSize.width + _leftBorderOffset,
|
||||
(howMany * _cellSize.height))];
|
||||
(howMany * _cellSize.height) + menuBarHeight)];
|
||||
[_titleView setFrame: NSMakeRect (0, howMany * _cellSize.height,
|
||||
NSWidth (_bounds), menuBarHeight)];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -617,7 +685,7 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
*/
|
||||
if (_horizontal == NO)
|
||||
{
|
||||
theRect.origin.y = _bounds.size.height - (_cellSize.height * (index + 1));
|
||||
theRect.origin.y = _cellSize.height * ([_itemCells count] - index - 1);
|
||||
theRect.origin.x = _leftBorderOffset;
|
||||
}
|
||||
else
|
||||
|
@ -635,6 +703,11 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
return theRect;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the index of the item below point.
|
||||
Returns -1 if mouse is not above
|
||||
a menu item.
|
||||
*/
|
||||
- (int) indexOfItemAtPoint: (NSPoint)point
|
||||
{
|
||||
unsigned howMany = [_itemCells count];
|
||||
|
@ -774,7 +847,7 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
// TODO
|
||||
|
||||
// Compute position for popups, if needed
|
||||
if (selectedItemIndex > -1)
|
||||
if (selectedItemIndex != -1)
|
||||
{
|
||||
if (_horizontal == NO)
|
||||
{
|
||||
|
@ -852,7 +925,8 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
NSMenu *candidateMenu = _menu;
|
||||
NSMenuView *targetMenuView;
|
||||
int indexToHighlight = index;
|
||||
|
||||
int oldHighlightedIndex;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
NSMenu *superMenu = [candidateMenu supermenu];
|
||||
|
@ -872,9 +946,7 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
}
|
||||
}
|
||||
|
||||
if ([targetMenuView attachedMenu])
|
||||
[targetMenuView detachSubmenu];
|
||||
|
||||
oldHighlightedIndex = [targetMenuView highlightedItemIndex];
|
||||
[targetMenuView setHighlightedItemIndex: indexToHighlight];
|
||||
|
||||
/* We need to let the run loop run a little so that the fact that
|
||||
|
@ -887,29 +959,77 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
|
||||
if (![_menu _ownedByPopUp])
|
||||
{
|
||||
[targetMenuView setHighlightedItemIndex: -1];
|
||||
[targetMenuView setHighlightedItemIndex: oldHighlightedIndex];
|
||||
}
|
||||
}
|
||||
|
||||
#define MOVE_THRESHOLD_DELTA 2.0
|
||||
#define DELAY_MULTIPLIER 10
|
||||
|
||||
/**
|
||||
This method is responsible for tracking the mouse while this menu
|
||||
is on the screen and the user is busy navigating the menu or one
|
||||
of it submenus. Responsible does not mean that this method does it
|
||||
all. For submenus for example it will call, indirectly, itself for
|
||||
submenu under consideration.
|
||||
|
||||
It will return YES if user released mouse, not above a submenu item.
|
||||
NO in all other circumstances.
|
||||
|
||||
Implementation detail:
|
||||
|
||||
<list>
|
||||
<item> It use periodic events to update the highlight state
|
||||
and attach / detach submenus.
|
||||
</item>
|
||||
<item> The flag justAttachedNewSubmenu is set to YES when
|
||||
a new submenu is attached. The effect is that the
|
||||
highlightin / attaching / detaching is surpressed
|
||||
for this menu. This is done so the user is given
|
||||
a change to move the mouse pointer into the newly
|
||||
attached submenu. Otherwise it would immediately
|
||||
be removed as the mouse pointer move over another
|
||||
item.
|
||||
|
||||
The logic for resetting the flag is rather adhoc.
|
||||
|
||||
<item> the flag subMenusNeedRemoving means that we
|
||||
will remove all the submenus after we are done.
|
||||
|
||||
This flag is used to clean up the submenus
|
||||
when the user has opened a submenu by clicking
|
||||
and wants to close it again by clicking on the
|
||||
hihglighted item.
|
||||
</item>
|
||||
<item> When the user released the mouse this method
|
||||
will cleanup all the transient menus.
|
||||
|
||||
Not only its own, but also its attached menu
|
||||
and all its transient super menus.
|
||||
</item>
|
||||
<item> The clean up is done BEFORE the action is executed.
|
||||
This is needed otherwise `hiding' the application
|
||||
leaves a dangling menu. If this is not acceptable,
|
||||
there should be another mechanism of handling
|
||||
the hiding. BTW besides the `hiding' the application,
|
||||
model panels are also a problem when the menu
|
||||
is not cleared before executing the action.
|
||||
</item>
|
||||
</list>
|
||||
*/
|
||||
- (BOOL) trackWithEvent: (NSEvent*)event
|
||||
{
|
||||
unsigned eventMask = NSPeriodicMask;
|
||||
NSDate *theDistantFuture = [NSDate distantFuture];
|
||||
int index;
|
||||
NSPoint location;
|
||||
NSPoint lastLocation = {0,0};
|
||||
NSMenu *alreadyAttachedMenu = NO;
|
||||
BOOL mouseMoved = NO;
|
||||
BOOL delayedSelect = NO;
|
||||
BOOL justAttachedNewSubmenu = NO;
|
||||
BOOL subMenusNeedRemoving = YES;
|
||||
int delayCount = 0;
|
||||
float xDelta = MOVE_THRESHOLD_DELTA;
|
||||
int indexOfActionToExecute = -1;
|
||||
NSEvent *original;
|
||||
NSEventType type = [event type];
|
||||
NSEventType type;
|
||||
NSEventType end;
|
||||
|
||||
|
||||
/*
|
||||
* The original event is unused except to determine whether the method
|
||||
* was invoked in response to a right or left mouse down.
|
||||
|
@ -917,122 +1037,144 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
* submenu.
|
||||
*/
|
||||
original = AUTORELEASE(RETAIN(event));
|
||||
if (type == NSRightMouseDown)
|
||||
|
||||
type = [event type];
|
||||
|
||||
if (type == NSRightMouseDown || type == NSRightMouseDragged)
|
||||
{
|
||||
end = NSRightMouseUp;
|
||||
eventMask |= NSRightMouseUpMask | NSRightMouseDraggedMask;
|
||||
}
|
||||
else if (type == NSOtherMouseDown)
|
||||
else if (type == NSOtherMouseDown || type == NSOtherMouseDragged)
|
||||
{
|
||||
end = NSOtherMouseUp;
|
||||
eventMask |= NSOtherMouseUpMask | NSOtherMouseDraggedMask;
|
||||
}
|
||||
else
|
||||
else if (type == NSLeftMouseDown || type == NSLeftMouseDragged)
|
||||
{
|
||||
end = NSLeftMouseUp;
|
||||
eventMask |= NSLeftMouseUpMask | NSLeftMouseDraggedMask;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
NSLog (@"Unexpected event: %d during event tracking in NSMenuView", type);
|
||||
end = NSLeftMouseUp;
|
||||
eventMask |= NSLeftMouseUpMask | NSLeftMouseDraggedMask;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
location = [_window mouseLocationOutsideOfEventStream];
|
||||
index = [self indexOfItemAtPoint: location];
|
||||
if (type == NSPeriodic || event == original)
|
||||
{
|
||||
NSPoint location;
|
||||
int index;
|
||||
|
||||
location = [_window mouseLocationOutsideOfEventStream];
|
||||
index = [self indexOfItemAtPoint: location];
|
||||
|
||||
if (index != _highlightedItemIndex)
|
||||
{
|
||||
mouseMoved = YES; /* Ok - had an initial movement. */
|
||||
}
|
||||
if (type == NSPeriodic)
|
||||
{
|
||||
// 1 - if menus is only partly visible and the mouse is at the
|
||||
// edge of the screen we move the menu so it will be visible.
|
||||
|
||||
if ([_menu isPartlyOffScreen])
|
||||
{
|
||||
NSPoint pointerLoc = [_window convertBaseToScreen: location];
|
||||
|
||||
// TODO: Why 1 in the Y axis?
|
||||
// The +/-1 in the y - direction is because the flipping between X-coordinates
|
||||
// and GNUstep coordinates let the GNUstep screen coordinates start with 1.
|
||||
if (pointerLoc.x == 0 || pointerLoc.y == 1
|
||||
|| pointerLoc.x == [[_window screen] frame].size.width - 1)
|
||||
|| pointerLoc.x == [[_window screen] frame].size.width - 1
|
||||
|| pointerLoc.y == [[_window screen] frame].size.height)
|
||||
[_menu shiftOnScreen];
|
||||
}
|
||||
|
||||
if (delayedSelect && mouseMoved && [event type] == NSPeriodic)
|
||||
{
|
||||
float xDiff = location.x - lastLocation.x;
|
||||
|
||||
if (xDiff > xDelta)
|
||||
{
|
||||
delayCount++;
|
||||
if (delayCount >= DELAY_MULTIPLIER)
|
||||
delayedSelect = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
delayedSelect = NO;
|
||||
}
|
||||
// 2 - Check if we have to reset the justAttachedNewSubmenu flag to NO.
|
||||
if (justAttachedNewSubmenu && index != -1 && index != _highlightedItemIndex)
|
||||
{
|
||||
if (location.x - lastLocation.x > MOVE_THRESHOLD_DELTA)
|
||||
{
|
||||
delayCount ++;
|
||||
if (delayCount >= DELAY_MULTIPLIER)
|
||||
{
|
||||
justAttachedNewSubmenu = NO;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
justAttachedNewSubmenu = NO;
|
||||
}
|
||||
}
|
||||
|
||||
lastLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
NSWindow *w;
|
||||
// 3 - If we have moved outside this menu, take appropriate action
|
||||
if (index == -1)
|
||||
{
|
||||
NSPoint locationInScreenCoordinates;
|
||||
NSWindow *windowUnderMouse;
|
||||
NSMenu *candidateMenu;
|
||||
|
||||
subMenusNeedRemoving = NO;
|
||||
|
||||
locationInScreenCoordinates = [_window convertBaseToScreen: location];
|
||||
|
||||
location = [_window convertBaseToScreen: location];
|
||||
// 3a - Check if moved into one of the ancester menus.
|
||||
// This is tricky, there are a few possibilities:
|
||||
// We are a transient attached menu of a non-transient menu
|
||||
// We are a non-transient attached menu
|
||||
// We are a root: isTornOff of AppMenu
|
||||
candidateMenu = [_menu supermenu];
|
||||
while (candidateMenu
|
||||
&& !NSMouseInRect (locationInScreenCoordinates, [[candidateMenu window] frame], NO) // not found yet
|
||||
&& (! ([candidateMenu isTornOff] && ![candidateMenu isTransient])) // no root of display tree
|
||||
&& [candidateMenu isAttached]) // has displayed parent
|
||||
{
|
||||
candidateMenu = [candidateMenu supermenu];
|
||||
}
|
||||
|
||||
if (candidateMenu != nil && NSMouseInRect (locationInScreenCoordinates,
|
||||
[[candidateMenu window] frame], NO))
|
||||
{
|
||||
// The call to fetch attachedMenu is not needed. But putting
|
||||
// it here avoids flicker when we go back to an ancester meu
|
||||
// and the attached menu is alreay correct.
|
||||
[[[candidateMenu attachedMenu] menuRepresentation] detachSubmenu];
|
||||
return [[candidateMenu menuRepresentation]
|
||||
trackWithEvent: original];
|
||||
}
|
||||
|
||||
/*
|
||||
* If the mouse is back in the supermenu, we return NO so that
|
||||
* our caller knows the button was not released.
|
||||
*/
|
||||
w = [[_menu supermenu] window];
|
||||
if (w != nil && NSMouseInRect(location, [w frame], NO) == YES)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
/*
|
||||
* if the mouse is in our attached menu - get that menu to track it.
|
||||
*/
|
||||
w = [[_menu attachedMenu] window];
|
||||
if (w != nil && NSMouseInRect(location, [w frame], NO) == YES)
|
||||
{
|
||||
if ([[self attachedMenuView] trackWithEvent: original])
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index != _highlightedItemIndex)
|
||||
[self setHighlightedItemIndex: index];
|
||||
}
|
||||
#if 0
|
||||
if (([_menu supermenu] && ![_menu isTornOff])
|
||||
|| [_menu isFollowTransient])
|
||||
return NO;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index != _highlightedItemIndex)
|
||||
{
|
||||
if (![_menu attachedMenu] || !delayedSelect)
|
||||
{
|
||||
[self setHighlightedItemIndex: index];
|
||||
// 3b - Check if we enter the attached submenu
|
||||
windowUnderMouse = [[_menu attachedMenu] window];
|
||||
if (windowUnderMouse != nil && NSMouseInRect (locationInScreenCoordinates,
|
||||
[windowUnderMouse frame], NO))
|
||||
{
|
||||
BOOL wasTransient = [_menu isTransient];
|
||||
BOOL subMenuResult = [[self attachedMenuView] trackWithEvent: original];
|
||||
|
||||
if (subMenuResult && wasTransient == [_menu isTransient])
|
||||
{
|
||||
[self detachSubmenu];
|
||||
}
|
||||
return subMenuResult;
|
||||
}
|
||||
}
|
||||
|
||||
// 4 - We changed the selected item and should update.
|
||||
if (!justAttachedNewSubmenu && index != _highlightedItemIndex)
|
||||
{
|
||||
subMenusNeedRemoving = NO;
|
||||
[self detachSubmenu];
|
||||
[self setHighlightedItemIndex: index];
|
||||
|
||||
if ([_menu attachedMenu])
|
||||
[self detachSubmenu];
|
||||
|
||||
if ((alreadyAttachedMenu =
|
||||
[[_items_link objectAtIndex: index] submenu]))
|
||||
{
|
||||
[self attachSubmenuForItemAtIndex: index];
|
||||
mouseMoved = NO;
|
||||
delayedSelect = YES;
|
||||
delayCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
delayedSelect = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
// WO: Question? Why the ivar _items_link
|
||||
if (index >= 0 && [[_items_link objectAtIndex: index] submenu])
|
||||
{
|
||||
[self attachSubmenuForItemAtIndex: index];
|
||||
justAttachedNewSubmenu = YES;
|
||||
delayCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Update last seen location for the justAttachedNewSubmenu logic.
|
||||
lastLocation = location;
|
||||
}
|
||||
|
||||
event = [NSApp nextEventMatchingMask: eventMask
|
||||
|
@ -1043,71 +1185,147 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
}
|
||||
while (type != end);
|
||||
|
||||
// Perform actions as needed.
|
||||
if (index != -1 && !alreadyAttachedMenu)
|
||||
{
|
||||
// Stop the periodic events before performing the action
|
||||
[NSEvent stopPeriodicEvents];
|
||||
[_menu performActionForItemAtIndex: index];
|
||||
|
||||
if (![_menu isFollowTransient] && ![_menu _ownedByPopUp])
|
||||
[self setHighlightedItemIndex: -1];
|
||||
// Ok, we released the mouse
|
||||
// There are now a few possibilities:
|
||||
// A - We released the mouse outside the menu.
|
||||
// Then we want the situation as it was before
|
||||
// we entered everything.
|
||||
// B - We released the mouse on a submenu item
|
||||
// (i) - this was highlighted before we started clicking:
|
||||
// Remove attached menus
|
||||
// (ii) - this was not highlighted before pressed the mouse button;
|
||||
// Keep attached menus.
|
||||
// C - We released the mouse above an ordinary action:
|
||||
// Execute the action.
|
||||
//
|
||||
// In case A, B and C we want the transient menus to be removed
|
||||
// In case A and C we want to remove the menus that were created
|
||||
// during the dragging.
|
||||
|
||||
// So we should do the following things:
|
||||
//
|
||||
// 1 - Stop periodic events,
|
||||
// 2 - Determine the action.
|
||||
// 3 - Remove the Transient menus from the screen.
|
||||
// 4 - Perform the action if there is one.
|
||||
|
||||
[NSEvent stopPeriodicEvents];
|
||||
|
||||
// We need to store this, because _highlightedItemIndex
|
||||
// will not be valid after we removed this menu from the screen.
|
||||
indexOfActionToExecute = _highlightedItemIndex;
|
||||
|
||||
// remove transient menus. --------------------------------------------
|
||||
{
|
||||
NSMenu *currentMenu = _menu;
|
||||
|
||||
while (currentMenu && ![currentMenu isTransient])
|
||||
{
|
||||
currentMenu = [currentMenu attachedMenu];
|
||||
}
|
||||
|
||||
while ([currentMenu isTransient] &&
|
||||
[currentMenu supermenu])
|
||||
{
|
||||
currentMenu = [currentMenu supermenu];
|
||||
}
|
||||
|
||||
[[currentMenu menuRepresentation] detachSubmenu];
|
||||
|
||||
if ([currentMenu isTransient])
|
||||
{
|
||||
[currentMenu closeTransient];
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
if (indexOfActionToExecute == -1)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (indexOfActionToExecute >= 0
|
||||
&& [_menu attachedMenu] != nil && [_menu attachedMenu] ==
|
||||
[[_items_link objectAtIndex: indexOfActionToExecute] submenu])
|
||||
{
|
||||
if (subMenusNeedRemoving)
|
||||
{
|
||||
[self detachSubmenu];
|
||||
}
|
||||
// Clicked on a submenu.
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Close menus if needed.
|
||||
if (!_keepAttachedMenus || index == -1
|
||||
|| (alreadyAttachedMenu && [alreadyAttachedMenu isFollowTransient]))
|
||||
[_menu performActionForItemAtIndex: indexOfActionToExecute];
|
||||
|
||||
// Remove highlighting.
|
||||
// We first check if it still highlighted because it could be the
|
||||
// case that we choose an action in a transient window which
|
||||
// has already dissappeared.
|
||||
if (indexOfActionToExecute == _highlightedItemIndex)
|
||||
{
|
||||
NSMenu *parentMenu;
|
||||
NSMenu *masterMenu;
|
||||
|
||||
for (parentMenu = masterMenu = _menu;
|
||||
(parentMenu = [masterMenu supermenu])
|
||||
&& (![masterMenu isTornOff] || [masterMenu isFollowTransient]);
|
||||
masterMenu = parentMenu);
|
||||
|
||||
if ([masterMenu attachedMenu])
|
||||
{
|
||||
NSMenuView *masterMenuView = [masterMenu menuRepresentation];
|
||||
|
||||
[masterMenuView detachSubmenu];
|
||||
[masterMenuView setHighlightedItemIndex: -1];
|
||||
}
|
||||
[self setHighlightedItemIndex: -1];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/**
|
||||
This method is called when the user clicks on a button
|
||||
in the menu. Or, if a right click happens and the
|
||||
app menu is brought up.
|
||||
|
||||
The original position is stored, so we can restore
|
||||
the position of menu. The position of the menu
|
||||
can change during the event tracking because
|
||||
the menu will automatillay move when parts
|
||||
are outside the screen and the user move the
|
||||
mouse pointer to the edge of the screen.
|
||||
*/
|
||||
- (void) mouseDown: (NSEvent*)theEvent
|
||||
{
|
||||
NSRect currentFrame;
|
||||
NSRect originalFrame;
|
||||
NSPoint currentTopLeft;
|
||||
NSPoint originalTopLeft;
|
||||
BOOL restorePosition;
|
||||
/*
|
||||
* Only for non transient menus do we want
|
||||
* to remember the position.
|
||||
*/
|
||||
restorePosition = ![_menu isTransient];
|
||||
|
||||
_keepAttachedMenus = YES;
|
||||
originalFrame = [_window frame];
|
||||
originalTopLeft = originalFrame.origin;
|
||||
originalTopLeft.y += originalFrame.size.height;
|
||||
|
||||
if (restorePosition)
|
||||
{ // store old position;
|
||||
originalFrame = [_window frame];
|
||||
originalTopLeft = originalFrame.origin;
|
||||
originalTopLeft.y += originalFrame.size.height;
|
||||
}
|
||||
|
||||
[NSEvent startPeriodicEventsAfterDelay: 0.1 withPeriod: 0.05];
|
||||
[self trackWithEvent: theEvent];
|
||||
[NSEvent stopPeriodicEvents];
|
||||
|
||||
currentFrame = [_window frame];
|
||||
currentTopLeft = currentFrame.origin;
|
||||
currentTopLeft.y += currentFrame.size.height;
|
||||
|
||||
if (NSEqualPoints(currentTopLeft, originalTopLeft) == NO)
|
||||
if (restorePosition)
|
||||
{
|
||||
NSPoint origin = currentFrame.origin;
|
||||
currentFrame = [_window frame];
|
||||
currentTopLeft = currentFrame.origin;
|
||||
currentTopLeft.y += currentFrame.size.height;
|
||||
|
||||
origin.x += (originalTopLeft.x - currentTopLeft.x);
|
||||
origin.y += (originalTopLeft.y - currentTopLeft.y);
|
||||
[_menu nestedSetFrameOrigin: origin];
|
||||
[_menu nestedCheckOffScreen];
|
||||
if (NSEqualPoints(currentTopLeft, originalTopLeft) == NO)
|
||||
{
|
||||
NSPoint origin = currentFrame.origin;
|
||||
|
||||
origin.x += (originalTopLeft.x - currentTopLeft.x);
|
||||
origin.y += (originalTopLeft.y - currentTopLeft.y);
|
||||
[_menu nestedSetFrameOrigin: origin];
|
||||
}
|
||||
}
|
||||
_keepAttachedMenus = NO;
|
||||
}
|
||||
|
||||
- (void) rightMouseDown: (NSEvent*) theEvent
|
||||
{
|
||||
[self mouseDown: theEvent];
|
||||
}
|
||||
|
||||
- (BOOL) performKeyEquivalent: (NSEvent *)theEvent
|
||||
|
@ -1115,6 +1333,7 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
return [_menu performKeyEquivalent: theEvent];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* NSCoding Protocol
|
||||
*
|
||||
|
@ -1155,8 +1374,165 @@ _addLeftBorderOffsetToRect(NSRect aRect, BOOL isHorizontal)
|
|||
@end
|
||||
|
||||
@implementation NSMenuView (GNUstepPrivate)
|
||||
|
||||
- (NSArray *)_itemCells
|
||||
{
|
||||
return _itemCells;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSMenuWindowTitleView
|
||||
|
||||
- (BOOL) acceptsFirstMouse: (NSEvent *)theEvent
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) setMenu: (NSMenu*)aMenu
|
||||
{
|
||||
menu = aMenu;
|
||||
}
|
||||
|
||||
- (NSMenu*) menu
|
||||
{
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (void) drawRect: (NSRect)rect
|
||||
{
|
||||
NSRect workRect = [self bounds];
|
||||
NSRectEdge sides[] = {NSMinXEdge, NSMaxYEdge}; float grays[] = {NSDarkGray, NSDarkGray};
|
||||
/* Cache the title attributes */
|
||||
static NSDictionary *attr = nil;
|
||||
|
||||
// Draw the dark gray upper left lines.
|
||||
workRect = NSDrawTiledRects(workRect, workRect, sides, grays, 2);
|
||||
|
||||
// Draw the title box's button.
|
||||
NSDrawButton(workRect, workRect);
|
||||
|
||||
// Paint it Black!
|
||||
workRect.origin.x += 1;
|
||||
workRect.origin.y += 2;
|
||||
workRect.size.height -= 3;
|
||||
workRect.size.width -= 3;
|
||||
[[NSColor windowFrameColor] set];
|
||||
NSRectFill(workRect);
|
||||
|
||||
// Draw the title
|
||||
if (attr == nil)
|
||||
{
|
||||
attr = [[NSDictionary alloc]
|
||||
initWithObjectsAndKeys:
|
||||
[NSFont boldSystemFontOfSize: 0], NSFontAttributeName,
|
||||
[NSColor windowFrameTextColor], NSForegroundColorAttributeName,
|
||||
nil];
|
||||
}
|
||||
|
||||
// This gives the correct position
|
||||
// WO: No it doesn't!
|
||||
workRect.origin.x += 5;
|
||||
workRect.size.width -= 5;
|
||||
workRect.size.height -= 2;
|
||||
[[menu title] drawInRect: workRect withAttributes: attr];
|
||||
}
|
||||
|
||||
- (void) mouseDown: (NSEvent*)theEvent
|
||||
{
|
||||
NSPoint lastLocation;
|
||||
NSPoint location;
|
||||
unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
|
||||
BOOL done = NO;
|
||||
NSDate *theDistantFuture = [NSDate distantFuture];
|
||||
|
||||
NSDebugLLog (@"NSMenu", @"Mouse down in title!");
|
||||
|
||||
lastLocation = [theEvent locationInWindow];
|
||||
|
||||
if (![menu isTornOff] && [menu supermenu])
|
||||
{
|
||||
[menu setTornOff: YES];
|
||||
}
|
||||
|
||||
while (!done)
|
||||
{
|
||||
theEvent = [NSApp nextEventMatchingMask: eventMask
|
||||
untilDate: theDistantFuture
|
||||
inMode: NSEventTrackingRunLoopMode
|
||||
dequeue: YES];
|
||||
|
||||
switch ([theEvent type])
|
||||
{
|
||||
case NSRightMouseUp:
|
||||
case NSLeftMouseUp:
|
||||
done = YES;
|
||||
break;
|
||||
case NSRightMouseDragged:
|
||||
case NSLeftMouseDragged:
|
||||
location = [_window mouseLocationOutsideOfEventStream];
|
||||
if (NSEqualPoints(location, lastLocation) == NO)
|
||||
{
|
||||
NSPoint origin = [_window frame].origin;
|
||||
|
||||
origin.x += (location.x - lastLocation.x);
|
||||
origin.y += (location.y - lastLocation.y);
|
||||
[menu nestedSetFrameOrigin: origin];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) createButton
|
||||
{
|
||||
// create the menu's close button
|
||||
NSImage* closeImage = [NSImage imageNamed: @"common_Close"];
|
||||
NSImage* closeHImage = [NSImage imageNamed: @"common_CloseH"];
|
||||
NSSize imageSize = [closeImage size];
|
||||
NSRect rect = { { _frame.size.width - imageSize.width - 4,
|
||||
(_frame.size.height - imageSize.height) / 2},
|
||||
{ imageSize.height, imageSize.width } };
|
||||
|
||||
button = [[NSButton alloc] initWithFrame: rect];
|
||||
[button setButtonType: NSMomentaryLight];
|
||||
[button setImagePosition: NSImageOnly];
|
||||
[button setImage: closeImage];
|
||||
[button setAlternateImage: closeHImage];
|
||||
[button setBordered: NO];
|
||||
[button setTarget: menu];
|
||||
[button setAction: @selector(_performMenuClose:)];
|
||||
[button setAutoresizingMask: NSViewMinXMargin];
|
||||
|
||||
[self setAutoresizingMask: NSViewMinXMargin | NSViewMinYMargin | NSViewMaxYMargin];
|
||||
}
|
||||
|
||||
- (void) removeCloseButton
|
||||
{
|
||||
[button removeFromSuperview];
|
||||
}
|
||||
|
||||
- (void) addCloseButton
|
||||
{
|
||||
if (button == nil)
|
||||
[self createButton];
|
||||
[self addSubview: button];
|
||||
[self setNeedsDisplay: YES];
|
||||
}
|
||||
|
||||
|
||||
- (void) rightMouseDown: (NSEvent*)theEvent
|
||||
{
|
||||
}
|
||||
|
||||
// We do not want to popup menus in this menu.
|
||||
- (id) menuForEvent: (NSEvent*) theEvent
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end /* NSMenuWindowTitleView */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue