mirror of
https://github.com/darlinghq/darling-cocotron.git
synced 2024-11-23 04:00:00 +00:00
477 lines
16 KiB
Objective-C
477 lines
16 KiB
Objective-C
/* Copyright (c) 2006-2009 Christopher J. W. Lloyd
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
this software and associated documentation files (the "Software"), to deal in
|
|
the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <AppKit/AppKitExport.h>
|
|
#import <AppKit/NSAttributedString.h>
|
|
#import <AppKit/NSColor.h>
|
|
#import <AppKit/NSFont.h>
|
|
#import <AppKit/NSGraphics.h>
|
|
#import <AppKit/NSImage.h>
|
|
#import <AppKit/NSMenu.h>
|
|
#import <AppKit/NSMenuView.h>
|
|
#import <AppKit/NSMenuWindow.h>
|
|
#import <AppKit/NSParagraphStyle.h>
|
|
#import <AppKit/NSStringDrawing.h>
|
|
#import <AppKit/NSText.h>
|
|
#import <AppKit/NSToolbar.h>
|
|
#import <AppKit/NSToolbarCustomizationView.h>
|
|
#import <AppKit/NSToolbarItem.h>
|
|
#import <AppKit/NSToolbarView.h>
|
|
#import <AppKit/NSWindow.h>
|
|
|
|
NSString *const _NSToolbarItemDragType = @"NSToolbarItemDragType";
|
|
NSString *const _NSToolbarItemIdentifierPboardType = @"NSToolbarItemIdentifierPboardType";
|
|
|
|
@interface NSToolbar (Private)
|
|
- (NSArray *) _itemsWithIdentifiers: (NSArray *) identifiers;
|
|
- (NSArray *) _allowedToolbarItems;
|
|
- (NSArray *) _defaultToolbarItems;
|
|
- (NSToolbarItem *) _itemForItemIdentifier: (NSToolbarItemIdentifier) identifier
|
|
willBeInsertedIntoToolbar: (BOOL) toolbar;
|
|
- (void) _setItemsWithIdentifiersFromArray: (NSArray *) identifiers;
|
|
- (NSArray *) itemIdentifiers;
|
|
@end
|
|
|
|
@interface NSToolbarItem (private)
|
|
- (NSSize) constrainedSize;
|
|
- (void) _setItemViewFrame: (NSRect) rect;
|
|
- (CGFloat) _expandWidth: (CGFloat) try;
|
|
- (NSView *) _enclosingView;
|
|
- (void) drawInRect: (NSRect) rect;
|
|
@end
|
|
|
|
@implementation NSToolbarView
|
|
|
|
- (instancetype) initWithFrame: (NSRect) frame {
|
|
[super initWithFrame: frame];
|
|
[self setAutoresizesSubviews: YES];
|
|
[self setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin];
|
|
_minXMargin = 6.0;
|
|
_minYMargin = 2.0;
|
|
_visibleItems = [[NSMutableArray alloc] init];
|
|
[self registerForDraggedTypes: @[ NSToolbarItemIdentifierPboardType ]];
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc {
|
|
[[NSNotificationCenter defaultCenter] removeObserver: self];
|
|
_toolbar = nil;
|
|
[_visibleItems release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSToolbar *) toolbar {
|
|
return _toolbar;
|
|
}
|
|
|
|
- (void) setToolbar: (NSToolbar *) toolbar {
|
|
_toolbar = toolbar;
|
|
}
|
|
|
|
- (NSArray *) visibleItems {
|
|
return _visibleItems;
|
|
}
|
|
|
|
- (NSImage *) overflowImage {
|
|
return [NSImage imageNamed: @"NSToolbarOverflowArrow"];
|
|
}
|
|
|
|
- (NSSize) overflowImageSizeWithMargins {
|
|
NSSize size = [[self overflowImage] size];
|
|
|
|
size.width += 8;
|
|
size.height += 8;
|
|
|
|
return size;
|
|
}
|
|
|
|
- (NSRect) overflowRect {
|
|
NSRect bounds = [self bounds];
|
|
NSRect result;
|
|
|
|
result.size = [self overflowImageSizeWithMargins];
|
|
|
|
result.size.height = MAX(result.size.height, bounds.size.height);
|
|
|
|
result.origin.y = bounds.origin.y;
|
|
result.origin.x = NSMaxX(bounds) - result.size.width;
|
|
return result;
|
|
}
|
|
|
|
- (void) layoutViewsWithWidth: (CGFloat) desiredWidth
|
|
setFrame: (BOOL) setFrame
|
|
{
|
|
NSArray *items = [_toolbar items];
|
|
NSUInteger count = [items count];
|
|
NSRect frames[count];
|
|
BOOL isFlexible[count];
|
|
BOOL notSpace[count];
|
|
BOOL visible[count];
|
|
NSSize overflowSize = [self overflowImageSizeWithMargins];
|
|
CGFloat leftMargin = 5, rightMargin = 5;
|
|
CGFloat consumedWidth = leftMargin + rightMargin,
|
|
resultHeight = overflowSize.height;
|
|
NSInteger totalNonSpace = 0, totalVisibleNonSpace = 0;
|
|
NSInteger totalVisible = 0;
|
|
|
|
NSInteger priorities[4] = {
|
|
NSToolbarItemVisibilityPriorityUser,
|
|
NSToolbarItemVisibilityPriorityHigh,
|
|
NSToolbarItemVisibilityPriorityStandard,
|
|
NSToolbarItemVisibilityPriorityLow,
|
|
};
|
|
int priorityCount = 4;
|
|
|
|
[_visibleItems removeAllObjects];
|
|
|
|
for (NSUInteger i = 0; i < count; i++) {
|
|
NSToolbarItem *item = items[i];
|
|
NSToolbarItemIdentifier identifier = [item itemIdentifier];
|
|
NSSize size = [item constrainedSize];
|
|
|
|
frames[i].origin = NSZeroPoint;
|
|
frames[i].size = size;
|
|
resultHeight = MAX(resultHeight, size.height);
|
|
visible[i] = NO;
|
|
isFlexible[i] = NO;
|
|
notSpace[i] = NO;
|
|
|
|
if ([identifier
|
|
isEqualToString: NSToolbarFlexibleSpaceItemIdentifier]) {
|
|
isFlexible[i] = YES;
|
|
} else if (![identifier
|
|
isEqualToString: NSToolbarSeparatorItemIdentifier] &&
|
|
![identifier
|
|
isEqualToString: NSToolbarSpaceItemIdentifier]) {
|
|
notSpace[i] = YES;
|
|
totalNonSpace++;
|
|
}
|
|
}
|
|
|
|
// Consume available space based on priority.
|
|
for (int p = 0; p < priorityCount; p++) {
|
|
NSInteger priority = priorities[p];
|
|
|
|
for (NSUInteger i = 0; i < count; i++) {
|
|
NSToolbarItem *item = items[i];
|
|
|
|
if ([item visibilityPriority] == priority) {
|
|
CGFloat availableWidth = desiredWidth - consumedWidth;
|
|
|
|
// If there are still items which can go in the overflow menu we
|
|
// need to accomodate the menu.
|
|
if (totalVisibleNonSpace + 1 < totalNonSpace)
|
|
availableWidth -= overflowSize.width;
|
|
|
|
if (frames[i].size.width < availableWidth) {
|
|
[_visibleItems addObject: item];
|
|
visible[i] = YES;
|
|
totalVisible++;
|
|
consumedWidth += frames[i].size.width;
|
|
if (notSpace[i])
|
|
totalVisibleNonSpace++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (totalVisibleNonSpace < totalNonSpace) {
|
|
consumedWidth += overflowSize.width;
|
|
_overflow = YES;
|
|
} else {
|
|
_overflow = NO;
|
|
}
|
|
|
|
// Distribute leftover.
|
|
NSInteger totalConsumers = totalVisible;
|
|
BOOL useFlexible = NO;
|
|
|
|
while (consumedWidth < desiredWidth && totalConsumers > 0) {
|
|
CGFloat availablePerItem =
|
|
floor((desiredWidth - consumedWidth) / totalConsumers);
|
|
|
|
if (availablePerItem < 1)
|
|
break;
|
|
|
|
totalConsumers = 0;
|
|
for (NSUInteger i = 0; i < count && consumedWidth < desiredWidth; i++) {
|
|
if (visible[i] && (!isFlexible[i] || useFlexible)) {
|
|
NSToolbarItem *item = items[i];
|
|
CGFloat attempt = frames[i].size.width + availablePerItem;
|
|
CGFloat final = [item _expandWidth: attempt];
|
|
CGFloat consumed = final - frames[i].size.width;
|
|
|
|
if (consumed >= 1) {
|
|
frames[i].size.width += consumed;
|
|
consumedWidth += consumed;
|
|
totalConsumers++;
|
|
}
|
|
}
|
|
}
|
|
if (totalConsumers == 0 && !useFlexible) {
|
|
useFlexible = YES;
|
|
totalConsumers = totalVisible;
|
|
}
|
|
}
|
|
|
|
if (setFrame) {
|
|
NSRect frame = [self frame];
|
|
|
|
[self setFrame: NSMakeRect(frame.origin.x, frame.origin.y, desiredWidth,
|
|
resultHeight)];
|
|
}
|
|
|
|
CGFloat x = leftMargin;
|
|
|
|
for (NSUInteger i = 0; i < count; i++) {
|
|
NSToolbarItem *item = items[i];
|
|
if (!visible[i]) {
|
|
[[item _enclosingView] removeFromSuperview];
|
|
} else {
|
|
frames[i].origin.x = x;
|
|
[item _setItemViewFrame: frames[i]];
|
|
if ([item _enclosingView] != self) {
|
|
[self addSubview: [item _enclosingView]];
|
|
}
|
|
[[item _enclosingView] setToolTip: [item toolTip]];
|
|
|
|
x += frames[i].size.width;
|
|
}
|
|
}
|
|
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
- (void) drawRect: (NSRect) rect {
|
|
if (_overflow) {
|
|
NSSize imageSize = [[self overflowImage] size];
|
|
NSRect rect = [self overflowRect];
|
|
|
|
rect.origin.x += (rect.size.width - imageSize.width) / 2;
|
|
rect.origin.y += (rect.size.height - imageSize.height) / 2;
|
|
rect.size = imageSize;
|
|
|
|
[[self overflowImage] drawInRect: rect
|
|
fromRect: NSZeroRect
|
|
operation: NSCompositeSourceOver
|
|
fraction: 1.0];
|
|
}
|
|
}
|
|
|
|
- (void) layoutViews {
|
|
[self layoutViewsWithWidth: [self bounds].size.width setFrame: NO];
|
|
}
|
|
|
|
- (void) _insertItem: (NSToolbarItem *) item atIndex: (NSInteger) index {
|
|
[self layoutViews];
|
|
}
|
|
|
|
- (void) _removeItemAtIndex: (NSInteger) index {
|
|
[[[[self subviews] copy] autorelease]
|
|
makeObjectsPerformSelector: @selector(removeFromSuperview)];
|
|
[self layoutViews];
|
|
}
|
|
|
|
- (void) resizeWithOldSuperviewSize: (NSSize) oldSize {
|
|
[super resizeWithOldSuperviewSize: oldSize];
|
|
[self layoutViews];
|
|
}
|
|
|
|
- (void) popUpOverflowMenu: (id) sender {
|
|
}
|
|
|
|
// NSView dragging destination settings.
|
|
// - as drop target: if decoded object is an item, then insert; if NSArray, then
|
|
// replace all
|
|
- (NSDragOperation) draggingEntered: (id<NSDraggingInfo>) sender {
|
|
if ([_toolbar customizationPaletteIsRunning]) {
|
|
NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard];
|
|
NSData *data = [pboard dataForType: NSToolbarItemIdentifierPboardType];
|
|
id droppedObject = [NSUnarchiver unarchiveObjectWithData: data];
|
|
|
|
if ([droppedObject isKindOfClass: [NSString class]]) {
|
|
NSToolbarItem *item;
|
|
item = [_toolbar _itemForItemIdentifier: droppedObject
|
|
willBeInsertedIntoToolbar: NO];
|
|
|
|
if ([[_toolbar itemIdentifiers] containsObject: droppedObject] &&
|
|
![item allowsDuplicatesInToolbar]) {
|
|
return NSDragOperationNone;
|
|
}
|
|
}
|
|
|
|
return NSDragOperationCopy;
|
|
}
|
|
return NSDragOperationNone;
|
|
}
|
|
|
|
// It'd be nice to do the OSX-style visual toolbar item insertion stuff
|
|
- (NSDragOperation) draggingUpdated: (id<NSDraggingInfo>) sender {
|
|
return NSDragOperationCopy;
|
|
}
|
|
|
|
- (BOOL) prepareForDragOperation: (id<NSDraggingInfo>) sender {
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) performDragOperation: (id<NSDraggingInfo>) sender {
|
|
NSPasteboard *pboard = [sender draggingPasteboard];
|
|
if ([_toolbar customizationPaletteIsRunning]) {
|
|
if ([[pboard types]
|
|
containsObject: NSToolbarItemIdentifierPboardType]) {
|
|
NSData *data =
|
|
[pboard dataForType: NSToolbarItemIdentifierPboardType];
|
|
id droppedObject = [NSUnarchiver unarchiveObjectWithData: data];
|
|
|
|
if ([droppedObject isKindOfClass: [NSArray class]]) {
|
|
[_toolbar _setItemsWithIdentifiersFromArray: droppedObject];
|
|
} else {
|
|
NSPoint location = [self convertPoint: [sender draggingLocation]
|
|
fromView: nil];
|
|
NSArray *subviews = [self subviews];
|
|
NSUInteger i, count = [subviews count];
|
|
|
|
// Figure out what this drop "means". I figure:
|
|
// [ Item 0 ] [ Item 1 ] [ Item 2 ]
|
|
// 0 ] [ Insert 1 ] [ Insert 2 ] ... etc
|
|
for (i = 0; i < count; i++) {
|
|
NSRect frame = [subviews[i] frame];
|
|
frame.origin.x -= floor(frame.size.width / 2);
|
|
if (NSPointInRect(location, frame))
|
|
break;
|
|
}
|
|
[_toolbar insertItemWithItemIdentifier: droppedObject
|
|
atIndex: i];
|
|
}
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL) isLocal {
|
|
return NSDragOperationCopy;
|
|
}
|
|
|
|
- (void) mouseDown: (NSEvent *) event {
|
|
if ([_toolbar customizationPaletteIsRunning]) {
|
|
NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard];
|
|
NSView *subview = [self hitTest: [event locationInWindow]];
|
|
int index = [[self subviews] indexOfObject: subview];
|
|
NSToolbarItem *item = [[_toolbar items][index] retain];
|
|
NSRect frame = NSMakeRect(0, 0, [subview frame].size.width + 4,
|
|
[subview frame].size.height + 4);
|
|
NSImage *image =
|
|
[[[NSImage alloc] initWithSize: frame.size] autorelease];
|
|
NSData *data =
|
|
[NSArchiver archivedDataWithRootObject: [item itemIdentifier]];
|
|
|
|
[image setCachedSeparately: YES];
|
|
[image lockFocus];
|
|
[item drawInRect: [self bounds]];
|
|
[[NSColor blackColor] setStroke];
|
|
NSFrameRect(frame);
|
|
[image unlockFocus];
|
|
|
|
[pboard declareTypes: @[ NSToolbarItemIdentifierPboardType ]
|
|
owner: nil];
|
|
[pboard setData: data forType: NSToolbarItemIdentifierPboardType];
|
|
|
|
[_toolbar removeItemAtIndex: index];
|
|
|
|
[self dragImage: image
|
|
at: NSMakePoint([[item image] size].width / 2,
|
|
[[item image] size].height / 2)
|
|
offset: NSZeroSize
|
|
event: event
|
|
pasteboard: pboard
|
|
source: self
|
|
slideBack: YES];
|
|
|
|
[item release];
|
|
return;
|
|
}
|
|
|
|
if (!_overflow)
|
|
return;
|
|
|
|
if (!NSPointInRect([self convertPoint: [event locationInWindow]
|
|
fromView: nil],
|
|
[self overflowRect]))
|
|
return;
|
|
|
|
NSArray *items = [_toolbar items];
|
|
NSString *menuTitle = NSLocalizedStringFromTableInBundle(
|
|
@"Overflow", nil, [NSBundle bundleForClass: [NSToolbarView class]],
|
|
@"Describes the overflow area of the toolbar");
|
|
NSMenu *menu = [[NSMenu alloc] initWithTitle: menuTitle];
|
|
NSRect menuFrame = [self frame];
|
|
|
|
for (NSToolbarItem *item in items) {
|
|
NSToolbarItemIdentifier identifier = [item itemIdentifier];
|
|
|
|
if ([identifier
|
|
isEqualToString: NSToolbarFlexibleSpaceItemIdentifier] ||
|
|
[identifier isEqualToString: NSToolbarSeparatorItemIdentifier] ||
|
|
[identifier isEqualToString: NSToolbarSpaceItemIdentifier]) {
|
|
continue;
|
|
}
|
|
|
|
if ([[item _enclosingView] superview] == nil) {
|
|
[menu addItem: [item menuFormRepresentation]];
|
|
}
|
|
}
|
|
|
|
for (NSUInteger i = 0; i < [menu numberOfItems]; ++i) {
|
|
NSToolbarItem *item = [[menu itemAtIndex: i] representedObject];
|
|
|
|
if ([[item label] sizeWithAttributes: nil].width > menuFrame.size.width)
|
|
menuFrame.size.width =
|
|
[[item label] sizeWithAttributes: nil].width + 20.0; // argh
|
|
}
|
|
|
|
NSMenuWindow *window = [[NSMenuWindow alloc] initWithMenu: menu];
|
|
|
|
menuFrame.origin.x = NSMaxX([self frame]);
|
|
menuFrame.origin.y = [self frame].origin.y + [self frame].size.height / 2;
|
|
menuFrame.origin = [[self window] convertBaseToScreen: menuFrame.origin];
|
|
|
|
menuFrame.origin.y -= [[window menuView] frame].size.height;
|
|
|
|
[window setFrameOrigin: menuFrame.origin];
|
|
[window orderFront: nil];
|
|
|
|
BOOL didAccept = [window acceptsMouseMovedEvents];
|
|
|
|
[window setAcceptsMouseMovedEvents: YES];
|
|
NSMenuItem *menuItem = [[window menuView] trackForEvent: event];
|
|
[window setAcceptsMouseMovedEvents: didAccept];
|
|
|
|
[window close];
|
|
|
|
if (menuItem != nil) {
|
|
[NSApp sendAction: [menuItem action]
|
|
to: [menuItem target]
|
|
from: [menuItem representedObject]];
|
|
}
|
|
|
|
[menu release];
|
|
}
|
|
|
|
@end
|