5 Copyright (c) 2006, Keith Anderson
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice,
12 this list of conditions and the following disclaimer.
13 * Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
16 * Neither the name of keeto.net or Keith Anderson nor the names of its
17 contributors may be used to endorse or promote products derived
18 from this software without specific prior written permission.
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
24 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
25 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
26 OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 * Modified to have a glass-style background; this requires the sourceListBackground image.
38 #import "KNShelfSplitView.h"
40 #import <AIUtilities/AIAttributedStringAdditions.h>
41 #import <AIUtilities/AIParagraphStyleAdditions.h>
42 #import <AIUtilities/AIImageAdditions.h>
43 #import <AIUtilities/AIToolbarUtilities.h>
46 #define DEFAULT_SHELF_WIDTH 200
47 #define CONTROL_HEIGHT 22
48 #define BUTTON_WIDTH 30
49 #define THUMB_WIDTH 15
50 #define THUMB_LINE_SPACING 2.0
51 #define RESIZE_BAR_EFFECTIVE_WIDTH 1.0
53 #define CONTROL_PART_NONE 0
54 #define CONTROL_PART_ACTION_BUTTON 1
55 #define CONTROL_PART_CONTEXT_BUTTON 2
56 #define CONTROL_PART_RESIZE_THUMB 3
57 #define CONTROL_PART_RESIZE_BAR 4
59 #define TOOLBAR_TOGGLESHELF_IDENTIFIER @"Toggle Shelf"
60 #define TOGGLESHELF @"Toggle Shelf"
61 @implementation KNShelfSplitView
65 if ((self == [KNShelfSplitView class])) {
66 [self exposeBinding:@"contextButtonMenu"];
71 -(IBAction)toggleShelf:(id)sender
73 #pragma unused(sender)
74 [self setShelfIsVisible: ![self isShelfVisible]];
75 [self setNeedsDisplay: YES];
78 - (id)initWithFrame:(NSRect)aFrame {
79 return [self initWithFrame: aFrame shelfView: nil contentView: nil];
82 -(id)initWithFrame:(NSRect)aFrame shelfView:(NSView *)aShelfView contentView:(NSView *)aContentView{
83 self = [super initWithFrame: aFrame];
86 currentShelfWidth = DEFAULT_SHELF_WIDTH; //change this
89 activeControlPart = CONTROL_PART_NONE;
90 contextButtonMenu = nil;
91 [self recalculateSizes];
94 shelfBackgroundColor = nil;
95 actionButtonImage = nil;
96 contextButtonImage = nil;
98 background = [[NSImage imageNamed:@"sourceListBackground" forClass:[self class]] retain];
99 backgroundSize = [background size];
101 [self setDelegate: nil];
105 [self setShelfView: aShelfView];
106 [self setContentView: aContentView];
113 if( autosaveName ){ [autosaveName release]; }
114 if( contextButtonImage ){ [contextButtonImage release]; }
115 if( actionButtonImage ){ [actionButtonImage release]; }
116 if( shelfBackgroundColor ){ [shelfBackgroundColor release]; }
117 if( contextButtonMenu ){ [contextButtonMenu release]; }
118 if( background ){ [background release]; }
120 [self unbind:@"contextButtonMenu"];
125 -(void)setDelegate:(id)aDelegate{
126 delegate = aDelegate;
128 delegateHasValidateWidth = NO;
131 if( [delegate respondsToSelector:@selector(shelfSplitView:validateWidth:)] ){
132 delegateHasValidateWidth = YES;
141 -(void)setTarget:(id)aTarget{
143 [self recalculateSizes];
150 -(void)setAction:(SEL)aSelector{
152 [self recalculateSizes];
159 -(void)setContextButtonMenu:(NSMenu *)aMenu{
160 if( contextButtonMenu ){
161 [contextButtonMenu autorelease];
162 [[NSNotificationCenter defaultCenter] removeObserver: self];
165 contextButtonMenu = [aMenu retain];
167 if( contextButtonMenu ){
168 [contextButtonMenu setDelegate: self];
169 [[NSNotificationCenter defaultCenter] addObserver: self
170 selector: @selector(didEndContextMenuTracking)
171 name: NSMenuDidEndTrackingNotification
172 object: contextButtonMenu
176 [self recalculateSizes];
179 -(void)didEndContextMenuTracking{
181 [self setNeedsDisplayInRect: controlRect];
184 -(NSMenu *)contextButtonMenu{
185 return contextButtonMenu;
188 -(void)setShelfView:(NSView *)aView{
190 [shelfView removeFromSuperview];
196 [self addSubview: shelfView];
198 [self recalculateSizes];
201 -(NSView *)shelfView{
205 -(void)setContentView:(NSView *)aView{
207 [contentView removeFromSuperview];
213 [self addSubview: contentView];
216 [self recalculateSizes];
219 -(NSView *)contentView{
223 -(void)setShelfWidth:(float)aWidth{
224 float newWidth = aWidth;
227 // The shelf can never be completely closed. We always have at least enough to show our resize thumb, otherwise
228 // if the delegate responds to shelfSplitView:validateWidth:, we use that width as our minimum shelf size
229 float minShelf = THUMB_WIDTH;
230 if( delegateHasValidateWidth ){
231 float requestedWidth = [delegate shelfSplitView:self validateWidth: aWidth];
232 if( requestedWidth > minShelf ){
233 minShelf = requestedWidth;
236 if( minShelf > newWidth ){
240 // The shelf can never be wider than half the entire view
241 float maxShelf = [self frame].size.width / 2;
243 if( newWidth > maxShelf ){
247 currentShelfWidth = newWidth;
249 [self recalculateSizes];
253 return currentShelfWidth;
256 -(void)setAutosaveName:(NSString *)aName{
258 [autosaveName autorelease];
260 autosaveName = [aName retain];
263 -(NSString *)autosaveName{
267 -(void)recalculateSizes{
268 if( isShelfVisible ){
269 controlRect = NSMakeRect( 0, 0, currentShelfWidth, CONTROL_HEIGHT );
271 resizeThumbRect = NSMakeRect( (controlRect.size.width - THUMB_WIDTH), 0, THUMB_WIDTH, CONTROL_HEIGHT );
272 resizeBarRect = NSMakeRect( currentShelfWidth - (RESIZE_BAR_EFFECTIVE_WIDTH / 2), 0, RESIZE_BAR_EFFECTIVE_WIDTH, [self frame].size.height );
274 float availableSpace = controlRect.size.width - THUMB_WIDTH;
276 if( target && action && (availableSpace > BUTTON_WIDTH) ){
277 shouldDrawActionButton = YES;
278 actionButtonRect = NSMakeRect( 0, 0, BUTTON_WIDTH, CONTROL_HEIGHT );
279 availableSpace -= BUTTON_WIDTH;
282 if( contextButtonMenu && [contextButtonMenu numberOfItems] && (availableSpace > BUTTON_WIDTH) ){
283 shouldDrawContextButton = YES;
284 contextButtonRect = NSMakeRect(controlRect.size.width - (THUMB_WIDTH + availableSpace), 0, BUTTON_WIDTH, CONTROL_HEIGHT);
289 [shelfView setFrame: NSMakeRect( 0, CONTROL_HEIGHT + 1, currentShelfWidth, [self bounds].size.height - (CONTROL_HEIGHT + 1) )];
293 float contentViewX = (isShelfVisible ? (currentShelfWidth + 1) : 0);
294 NSRect newRect = NSMakeRect(contentViewX, 0, NSWidth([self bounds]) - contentViewX, NSHeight([self bounds]));
295 if (!NSEqualRects(newRect, [contentView frame])){
296 [contentView setFrame:newRect];
300 [self setNeedsDisplay: YES];
301 [[self window] invalidateCursorRectsForView: self];
305 -(BOOL)isShelfVisible{
306 return isShelfVisible;
309 -(void)setShelfIsVisible:(BOOL)visible{
311 if( isShelfVisible && !visible ){
313 [shelfView removeFromSuperview];
314 } else if( !isShelfVisible && visible ){
315 [self addSubview: shelfView];
320 isShelfVisible = visible;
321 [self recalculateSizes];
324 -(void)setActionButtonImage:(NSImage *)anImage{
325 if( actionButtonImage ){
326 [actionButtonImage autorelease];
329 actionButtonImage = [anImage retain];
331 [self setNeedsDisplayInRect: controlRect];
334 -(NSImage *)actionButtonImage{
335 return actionButtonImage;
338 -(void)setContextButtonImage:(NSImage *)anImage{
339 if( contextButtonImage ){
340 [contextButtonImage autorelease];
343 contextButtonImage = [anImage retain];
345 [self setNeedsDisplayInRect: controlRect];
348 -(NSImage *)contextButtonImage{
349 return contextButtonImage;
352 -(void)setShelfBackgroundColor:(NSColor *)aColor{
353 if( shelfBackgroundColor ){
354 [shelfBackgroundColor autorelease];
357 shelfBackgroundColor = [aColor retain];
358 [self setNeedsDisplay: YES];
361 -(NSColor *)shelfBackgroundColor{
362 return shelfBackgroundColor;
365 -(void)resetCursorRects{
366 [super resetCursorRects];
367 if( isShelfVisible ){
368 [self addCursorRect: resizeThumbRect cursor: [NSCursor resizeLeftRightCursor]];
369 [self addCursorRect: resizeBarRect cursor: [NSCursor resizeLeftRightCursor]];
373 -(void)mouseDown:(NSEvent *)anEvent{
374 BOOL stillMouseDown = YES;
375 NSPoint currentLocation;
377 // determine if we're in a control part we care about
378 currentLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];
380 if( shouldDrawActionButton && NSPointInRect( currentLocation, actionButtonRect ) ){
381 activeControlPart = CONTROL_PART_ACTION_BUTTON;
383 }else if( shouldDrawContextButton && NSPointInRect( currentLocation, contextButtonRect ) ){
384 activeControlPart = CONTROL_PART_CONTEXT_BUTTON;
387 NSEvent * contextEvent = [NSEvent mouseEventWithType: [anEvent type]
388 location: NSMakePoint( contextButtonRect.origin.x + (contextButtonRect.size.width / 2) , contextButtonRect.origin.y + (contextButtonRect.size.height / 2) )
389 modifierFlags: [anEvent modifierFlags]
390 timestamp: [anEvent timestamp]
391 windowNumber: [anEvent windowNumber]
392 context: [anEvent context]
393 eventNumber: [anEvent eventNumber]
394 clickCount: [anEvent clickCount]
395 pressure: [anEvent pressure]
397 [self setNeedsDisplayInRect: controlRect];
398 [NSMenu popUpContextMenu: contextButtonMenu withEvent: contextEvent forView: self];
399 [super mouseDown: contextEvent];
402 }else if( NSPointInRect( currentLocation, resizeThumbRect ) ){
403 activeControlPart = CONTROL_PART_RESIZE_THUMB;
404 }else if( NSPointInRect( currentLocation, resizeBarRect ) ){
405 activeControlPart = CONTROL_PART_RESIZE_BAR;
407 activeControlPart = CONTROL_PART_NONE;
410 [self setNeedsDisplayInRect: controlRect];
412 if( activeControlPart != CONTROL_PART_NONE ){
413 if([anEvent clickCount] == 2){
414 [self setShelfIsVisible: NO];
416 while( stillMouseDown ){
417 anEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask];
418 currentLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];
421 if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
423 }else if( (activeControlPart == CONTROL_PART_CONTEXT_BUTTON) && NSPointInRect( currentLocation, contextButtonRect ) ){
427 switch( [anEvent type] ){
428 case NSLeftMouseDragged:
429 if( (activeControlPart == CONTROL_PART_RESIZE_THUMB) || (activeControlPart == CONTROL_PART_RESIZE_BAR) ){
430 [self setShelfWidth: currentLocation.x];
432 [self setNeedsDisplayInRect: controlRect];
438 [self setNeedsDisplayInRect: controlRect];
440 if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
442 if( target && action && [target respondsToSelector:action]){
443 [target performSelector: action withObject: self];
456 [super mouseDown:anEvent];
460 - (void)drawRect:(NSRect)rect {
461 #pragma unused( rect )
463 if( isShelfVisible ){
464 //NSLog(@"Drawing Control( %f, %f) (%f, %f)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
466 float remainderStart = 0.0;
469 if( shouldDrawActionButton == YES){
470 [self drawControlBackgroundInRect: actionButtonRect
471 active: (activeControlPart == CONTROL_PART_ACTION_BUTTON) && shouldHilite
473 [[NSColor windowFrameColor] set];
474 // NSRectFill( NSMakeRect( (actionButtonRect.origin.x + actionButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
475 remainderStart += actionButtonRect.size.width;
477 if( actionButtonImage ){
479 NSRect targetRect = NSMakeRect(actionButtonRect.origin.x,
480 actionButtonRect.origin.y,
481 [actionButtonImage size].width,
482 [actionButtonImage size].height
485 if( targetRect.size.width > actionButtonRect.size.width ){
486 targetRect.size.width = actionButtonRect.size.width;
488 if( targetRect.size.width < actionButtonRect.size.width ){
489 targetRect.origin.x += (actionButtonRect.size.width - targetRect.size.width) / 2.0;
491 if( targetRect.size.height > actionButtonRect.size.height ){
492 targetRect.size.height = actionButtonRect.size.height;
494 if( targetRect.size.height < actionButtonRect.size.height ){
495 targetRect.origin.y += (actionButtonRect.size.height - targetRect.size.height) / 2.0;
498 [actionButtonImage compositeToPoint:NSMakePoint(actionButtonRect.origin.x,
499 actionButtonRect.origin.y) operation:NSCompositeDestinationAtop];
507 if( shouldDrawContextButton ){
508 [self drawControlBackgroundInRect: contextButtonRect
509 active: (activeControlPart == CONTROL_PART_CONTEXT_BUTTON ) && shouldHilite
511 [[NSColor windowFrameColor] set];
512 NSRectFill( NSMakeRect( (contextButtonRect.origin.x + contextButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
513 remainderStart += contextButtonRect.size.width;
515 if( contextButtonImage ){
517 NSRect targetRect = NSMakeRect(contextButtonRect.origin.x,
518 contextButtonRect.origin.y,
519 [contextButtonImage size].width,
520 [contextButtonImage size].height
523 if( targetRect.size.width > contextButtonRect.size.width ){
524 targetRect.size.width = contextButtonRect.size.width;
526 if( targetRect.size.width < contextButtonRect.size.width ){
527 targetRect.origin.x += (contextButtonRect.size.width - targetRect.size.width) / 2.0;
529 if( targetRect.size.height > contextButtonRect.size.height ){
530 targetRect.size.height = contextButtonRect.size.height;
532 if( targetRect.size.height < contextButtonRect.size.height ){
533 targetRect.origin.y += (contextButtonRect.size.height - targetRect.size.height) / 2.0;
535 [contextButtonImage drawInRect: targetRect
536 fromRect: NSMakeRect( 0, 0, [contextButtonImage size].width, [contextButtonImage size].height )
537 operation: NSCompositeSourceOver
543 //remainder and thumb
544 [self drawControlBackgroundInRect:NSMakeRect( remainderStart, 0, (controlRect.size.width - remainderStart), controlRect.size.height )
547 [[NSColor windowFrameColor] set];
548 NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT, currentShelfWidth, 1 ) );
550 // Draw our split line
551 [[NSColor windowFrameColor] set];
552 NSRectFill( NSMakeRect( currentShelfWidth, 0, 1, [self frame].size.height ) );
554 // Draw our thumb lines
555 [[NSColor disabledControlTextColor] set];
556 NSRect thumbLineRect = NSMakeRect(
557 resizeThumbRect.origin.x + (resizeThumbRect.size.width - ((2*THUMB_LINE_SPACING) + 3.0)) / 2.0,
558 resizeThumbRect.size.height / 4.0,
560 resizeThumbRect.size.height / 2.0
563 for( i=0; i<3; i++ ){
564 NSRectFill( thumbLineRect );
565 thumbLineRect.origin.x += (1+THUMB_LINE_SPACING);
568 if( shelfBackgroundColor ){
569 [shelfBackgroundColor set];
570 NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT+1, currentShelfWidth, [self frame].size.height ) );
574 if (attributedStringValue && !shouldDrawContextButton && !shouldDrawActionButton) {
575 NSRect textRect = NSMakeRect(6, (NSHeight(controlRect) - stringHeight)/2, NSMinX(resizeThumbRect) - 8, stringHeight);
577 [attributedStringValue drawInRect:textRect];
582 -(void)drawControlBackgroundInRect:(NSRect)aRect active:(BOOL)isActive{
583 //Draw the background, tiling across
584 NSRect sourceRect = NSMakeRect(0, 0, backgroundSize.width, backgroundSize.height);
585 NSRect destRect = NSMakeRect(aRect.origin.x, aRect.origin.y, sourceRect.size.width, aRect.size.height);
587 while ((destRect.origin.x < NSMaxX(aRect)) && destRect.size.width > 0) {
589 if (NSMaxX(destRect) > NSMaxX(aRect)) {
590 destRect.size.width = NSMaxX(aRect) - NSMinX(destRect);
591 sourceRect.size.width = NSWidth(destRect);
594 [background drawInRect:destRect
596 operation:NSCompositeSourceOver
598 destRect.origin.x += destRect.size.width;
602 -(void)setFrame:(NSRect)aRect{
603 [super setFrame: aRect];
604 [self recalculateSizes];
607 #pragma mark Status string
608 - (void)setResizeThumbStringValue:(NSString *)inString
610 if (![inString isEqualToString:stringValue]) {
611 [stringValue release];
612 stringValue = [inString copy];
614 [attributedStringValue release];
616 NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
617 [NSParagraphStyle styleWithAlignment:NSLeftTextAlignment
618 lineBreakMode:NSLineBreakByTruncatingTail], NSParagraphStyleAttributeName,
619 [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName,
622 stringHeight = [NSAttributedString stringHeightForAttributes:attributes];
623 attributedStringValue = [[NSAttributedString alloc] initWithString:stringValue
624 attributes:attributes];
626 attributedStringValue = nil;
628 [self setNeedsDisplay:YES];