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>
44 #define DEFAULT_SHELF_WIDTH 200
45 #define CONTROL_HEIGHT 22
46 #define BUTTON_WIDTH 30
47 #define THUMB_WIDTH 15
48 #define THUMB_LINE_SPACING 2.0
49 #define RESIZE_BAR_EFFECTIVE_WIDTH 1.0
51 #define CONTROL_PART_NONE 0
52 #define CONTROL_PART_ACTION_BUTTON 1
53 #define CONTROL_PART_CONTEXT_BUTTON 2
54 #define CONTROL_PART_RESIZE_THUMB 3
55 #define CONTROL_PART_RESIZE_BAR 4
57 @implementation KNShelfSplitView
59 -(IBAction)toggleShelf:(id)sender{
60 #pragma unused(sender)
61 [self setShelfIsVisible: ![self isShelfVisible]];
62 [self setNeedsDisplay: YES];
65 - (id)initWithFrame:(NSRect)aFrame {
66 return [self initWithFrame: aFrame shelfView: nil contentView: nil];
69 -(id)initWithFrame:(NSRect)aFrame shelfView:(NSView *)aShelfView contentView:(NSView *)aContentView{
70 self = [super initWithFrame: aFrame];
73 currentShelfWidth = DEFAULT_SHELF_WIDTH;
76 activeControlPart = CONTROL_PART_NONE;
77 contextButtonMenu = nil;
78 [self recalculateSizes];
81 shelfBackgroundColor = nil;
82 actionButtonImage = nil;
83 contextButtonImage = nil;
85 background = [[NSImage imageNamed:@"sourceListBackground" forClass:[self class]] retain];
86 backgroundSize = [background size];
88 [self setDelegate: nil];
92 [self setShelfView: aShelfView];
93 [self setContentView: aContentView];
95 [[NSNotificationCenter defaultCenter] addObserver:self
96 selector:@selector(windowDidResize:)
97 name:NSWindowDidResizeNotification
98 object:[self window]];
104 if( autosaveName ){ [autosaveName release]; }
105 if( contextButtonImage ){ [contextButtonImage release]; }
106 if( actionButtonImage ){ [actionButtonImage release]; }
107 if( shelfBackgroundColor ){ [shelfBackgroundColor release]; }
108 if( contextButtonMenu ){ [contextButtonMenu release]; }
110 [[NSNotificationCenter defaultCenter] removeObserver:self];
115 - (void)windowDidResize:(NSNotification *)aNotification
117 [self setShelfWidth: [self shelfWidth]];
118 [self recalculateSizes];
121 -(void)setDelegate:(id)aDelegate{
122 delegate = aDelegate;
124 delegateHasValidateWidth = NO;
127 if( [delegate respondsToSelector:@selector(shelfSplitView:validateWidth:)] ){
128 delegateHasValidateWidth = YES;
137 -(void)setTarget:(id)aTarget{
139 [self recalculateSizes];
146 -(void)setAction:(SEL)aSelector{
148 [self recalculateSizes];
155 -(void)setContextButtonMenu:(NSMenu *)aMenu{
156 if( contextButtonMenu ){
157 [contextButtonMenu autorelease];
158 [[NSNotificationCenter defaultCenter] removeObserver: self];
161 contextButtonMenu = [aMenu retain];
163 if( contextButtonMenu ){
164 [contextButtonMenu setDelegate: self];
165 [[NSNotificationCenter defaultCenter] addObserver: self
166 selector: @selector(didEndContextMenuTracking)
167 name: NSMenuDidEndTrackingNotification
168 object: contextButtonMenu
171 [self recalculateSizes];
174 -(void)didEndContextMenuTracking{
176 [self setNeedsDisplayInRect: controlRect];
179 -(NSMenu *)contextButtonMenu{
180 return contextButtonMenu;
183 -(void)setShelfView:(NSView *)aView{
185 [shelfView removeFromSuperview];
191 [self addSubview: shelfView];
193 [self recalculateSizes];
196 -(NSView *)shelfView{
200 -(void)setContentView:(NSView *)aView{
202 [contentView removeFromSuperview];
208 [self addSubview: contentView];
211 [self recalculateSizes];
214 -(NSView *)contentView{
218 -(void)setShelfWidth:(float)aWidth{
219 float newWidth = aWidth;
222 // The shelf can never be completely closed. We always have at least enough to show our resize thumb, otherwise
223 // if the delegate responds to shelfSplitView:validateWidth:, we use that width as our minimum shelf size
224 float minShelf = THUMB_WIDTH;
225 if( delegateHasValidateWidth ){
226 float requestedWidth = [delegate shelfSplitView:self validateWidth: aWidth];
227 if( requestedWidth > minShelf ){
228 minShelf = requestedWidth;
231 if( minShelf > newWidth ){
235 // The shelf can never be wider than half the entire view
236 float maxShelf = [self frame].size.width / 2;
238 if( newWidth > maxShelf ){
242 currentShelfWidth = newWidth;
244 [self recalculateSizes];
248 return currentShelfWidth;
251 -(void)setAutosaveName:(NSString *)aName{
253 [autosaveName autorelease];
255 autosaveName = [aName retain];
258 -(NSString *)autosaveName{
262 -(void)recalculateSizes{
263 shouldDrawActionButton = NO;
264 shouldDrawContextButton = NO;
266 if( isShelfVisible ){
267 controlRect = NSMakeRect( 0, 0, currentShelfWidth, CONTROL_HEIGHT );
269 resizeThumbRect = NSMakeRect( (controlRect.size.width - THUMB_WIDTH), 0, THUMB_WIDTH, CONTROL_HEIGHT );
270 resizeBarRect = NSMakeRect( currentShelfWidth - (RESIZE_BAR_EFFECTIVE_WIDTH / 2), 0, RESIZE_BAR_EFFECTIVE_WIDTH, [self frame].size.height );
272 float availableSpace = controlRect.size.width - THUMB_WIDTH;
274 if( target && action && (availableSpace > BUTTON_WIDTH) ){
275 shouldDrawActionButton = YES;
276 actionButtonRect = NSMakeRect( 0, 0, BUTTON_WIDTH, CONTROL_HEIGHT );
277 availableSpace -= BUTTON_WIDTH;
280 if( contextButtonMenu && (availableSpace > BUTTON_WIDTH) ){
281 shouldDrawContextButton = YES;
282 contextButtonRect = NSMakeRect(controlRect.size.width - (THUMB_WIDTH + availableSpace), 0, BUTTON_WIDTH, CONTROL_HEIGHT);
287 [shelfView setFrame: NSMakeRect( 0, CONTROL_HEIGHT + 1, currentShelfWidth, [self bounds].size.height - (CONTROL_HEIGHT + 1) )];
291 float contentViewX = (isShelfVisible ? currentShelfWidth : 0);
292 NSRect newRect = NSMakeRect( contentViewX + 1, 0, [self bounds].size.width - (contentViewX + 1), [self bounds].size.height);
293 if( ! NSEqualRects(newRect, [contentView frame]) ){
294 [contentView setFrame: newRect];
298 [self setNeedsDisplay: YES];
299 [[self window] invalidateCursorRectsForView: self];
303 -(BOOL)isShelfVisible{
304 return isShelfVisible;
307 -(void)setShelfIsVisible:(BOOL)visible{
309 if( isShelfVisible ){
311 [shelfView removeFromSuperview];
313 [self addSubview: shelfView];
318 isShelfVisible = visible;
319 [self recalculateSizes];
322 -(void)setActionButtonImage:(NSImage *)anImage{
323 if( actionButtonImage ){
324 [actionButtonImage autorelease];
327 actionButtonImage = [anImage retain];
329 [self setNeedsDisplayInRect: controlRect];
332 -(NSImage *)actionButtonImage{
333 return actionButtonImage;
336 -(void)setContextButtonImage:(NSImage *)anImage{
337 if( contextButtonImage ){
338 [contextButtonImage autorelease];
341 contextButtonImage = [anImage retain];
343 [self setNeedsDisplayInRect: controlRect];
346 -(NSImage *)contextButtonImage{
347 return contextButtonImage;
350 -(void)setShelfBackgroundColor:(NSColor *)aColor{
351 if( shelfBackgroundColor ){
352 [shelfBackgroundColor autorelease];
355 shelfBackgroundColor = [aColor retain];
356 [self setNeedsDisplay: YES];
359 -(NSColor *)shelfBackgroundColor{
360 return shelfBackgroundColor;
363 -(void)resetCursorRects{
364 [super resetCursorRects];
365 if( isShelfVisible ){
366 [self addCursorRect: resizeThumbRect cursor: [NSCursor resizeLeftRightCursor]];
367 [self addCursorRect: resizeBarRect cursor: [NSCursor resizeLeftRightCursor]];
371 -(void)mouseDown:(NSEvent *)anEvent{
372 BOOL stillMouseDown = YES;
373 NSPoint currentLocation;
375 // determine if we're in a control part we care about
376 currentLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];
378 if( shouldDrawActionButton && NSPointInRect( currentLocation, actionButtonRect ) ){
379 activeControlPart = CONTROL_PART_ACTION_BUTTON;
381 }else if( shouldDrawContextButton && NSPointInRect( currentLocation, contextButtonRect ) ){
382 activeControlPart = CONTROL_PART_CONTEXT_BUTTON;
385 NSEvent * contextEvent = [NSEvent mouseEventWithType: [anEvent type]
386 location: NSMakePoint( contextButtonRect.origin.x + (contextButtonRect.size.width / 2) , contextButtonRect.origin.y + (contextButtonRect.size.height / 2) )
387 modifierFlags: [anEvent modifierFlags]
388 timestamp: [anEvent timestamp]
389 windowNumber: [anEvent windowNumber]
390 context: [anEvent context]
391 eventNumber: [anEvent eventNumber]
392 clickCount: [anEvent clickCount]
393 pressure: [anEvent pressure]
395 [self setNeedsDisplayInRect: controlRect];
396 [NSMenu popUpContextMenu: contextButtonMenu withEvent: contextEvent forView: self];
397 [super mouseDown: contextEvent];
400 }else if( NSPointInRect( currentLocation, resizeThumbRect ) ){
401 activeControlPart = CONTROL_PART_RESIZE_THUMB;
402 }else if( NSPointInRect( currentLocation, resizeBarRect ) ){
403 activeControlPart = CONTROL_PART_RESIZE_BAR;
405 activeControlPart = CONTROL_PART_NONE;
408 [self setNeedsDisplayInRect: controlRect];
410 if( activeControlPart != CONTROL_PART_NONE ){
411 while( stillMouseDown ){
412 anEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask];
413 currentLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];
416 if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
418 }else if( (activeControlPart == CONTROL_PART_CONTEXT_BUTTON) && NSPointInRect( currentLocation, contextButtonRect ) ){
422 switch( [anEvent type] ){
423 case NSLeftMouseDragged:
424 if( (activeControlPart == CONTROL_PART_RESIZE_THUMB) || (activeControlPart == CONTROL_PART_RESIZE_BAR) ){
425 [self setShelfWidth: currentLocation.x];
427 [self setNeedsDisplayInRect: controlRect];
433 [self setNeedsDisplayInRect: controlRect];
435 if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
437 if( target && action && [target respondsToSelector:action]){
438 [target performSelector: action withObject: self];
450 [super mouseDown:anEvent];
454 - (void)drawRect:(NSRect)rect {
455 #pragma unused( rect )
457 if( isShelfVisible ){
458 //NSLog(@"Drawing Control( %f, %f) (%f, %f)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
460 float remainderStart = 0.0;
463 if( shouldDrawActionButton ){
464 [self drawControlBackgroundInRect: actionButtonRect
465 active: (activeControlPart == CONTROL_PART_ACTION_BUTTON) && shouldHilite
467 [[NSColor windowFrameColor] set];
468 NSRectFill( NSMakeRect( (actionButtonRect.origin.x + actionButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
469 remainderStart += actionButtonRect.size.width;
471 if( actionButtonImage ){
472 NSRect targetRect = NSMakeRect(actionButtonRect.origin.x,
473 actionButtonRect.origin.y,
474 [actionButtonImage size].width,
475 [actionButtonImage size].height
478 if( targetRect.size.width > actionButtonRect.size.width ){
479 targetRect.size.width = actionButtonRect.size.width;
481 if( targetRect.size.width < actionButtonRect.size.width ){
482 targetRect.origin.x += (actionButtonRect.size.width - targetRect.size.width) / 2.0;
484 if( targetRect.size.height > actionButtonRect.size.height ){
485 targetRect.size.height = actionButtonRect.size.height;
487 if( targetRect.size.height < actionButtonRect.size.height ){
488 targetRect.origin.y += (actionButtonRect.size.height - targetRect.size.height) / 2.0;
491 [actionButtonImage drawInRect: targetRect
492 fromRect: NSMakeRect( 0, 0, [actionButtonImage size].width, [actionButtonImage size].height )
493 operation: NSCompositeSourceOver
500 if( shouldDrawContextButton ){
501 [self drawControlBackgroundInRect: contextButtonRect
502 active: (activeControlPart == CONTROL_PART_CONTEXT_BUTTON ) && shouldHilite
504 [[NSColor windowFrameColor] set];
505 NSRectFill( NSMakeRect( (contextButtonRect.origin.x + contextButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
506 remainderStart += contextButtonRect.size.width;
508 if( contextButtonImage ){
509 NSRect targetRect = NSMakeRect(contextButtonRect.origin.x,
510 contextButtonRect.origin.y,
511 [contextButtonImage size].width,
512 [contextButtonImage size].height
515 if( targetRect.size.width > contextButtonRect.size.width ){
516 targetRect.size.width = contextButtonRect.size.width;
518 if( targetRect.size.width < contextButtonRect.size.width ){
519 targetRect.origin.x += (contextButtonRect.size.width - targetRect.size.width) / 2.0;
521 if( targetRect.size.height > contextButtonRect.size.height ){
522 targetRect.size.height = contextButtonRect.size.height;
524 if( targetRect.size.height < contextButtonRect.size.height ){
525 targetRect.origin.y += (contextButtonRect.size.height - targetRect.size.height) / 2.0;
527 [contextButtonImage drawInRect: targetRect
528 fromRect: NSMakeRect( 0, 0, [contextButtonImage size].width, [contextButtonImage size].height )
529 operation: NSCompositeSourceOver
535 //remainder and thumb
536 [self drawControlBackgroundInRect: NSMakeRect( remainderStart, 0, (controlRect.size.width - remainderStart), controlRect.size.height )
540 [[NSColor windowFrameColor] set];
541 NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT, currentShelfWidth, 1 ) );
543 // Draw our split line
544 [[NSColor windowFrameColor] set];
545 NSRectFill( NSMakeRect( currentShelfWidth, 0, 1, [self frame].size.height ) );
547 // Draw our thumb lines
548 [[NSColor disabledControlTextColor] set];
549 NSRect thumbLineRect = NSMakeRect(
550 resizeThumbRect.origin.x + (resizeThumbRect.size.width - ((2*THUMB_LINE_SPACING) + 3.0)) / 2.0,
551 resizeThumbRect.size.height / 4.0,
553 resizeThumbRect.size.height / 2.0
556 for( i=0; i<3; i++ ){
557 NSRectFill( thumbLineRect );
558 thumbLineRect.origin.x += (1+THUMB_LINE_SPACING);
561 if( shelfBackgroundColor ){
562 [shelfBackgroundColor set];
563 NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT+1, currentShelfWidth, [self frame].size.height ) );
567 if (attributedStringValue && !shouldDrawContextButton && !shouldDrawActionButton) {
568 NSRect textRect = NSMakeRect(6, (NSHeight(controlRect) - stringHeight)/2, NSMinX(resizeThumbRect) - 8, stringHeight);
570 [attributedStringValue drawInRect:textRect];
575 -(void)drawControlBackgroundInRect:(NSRect)aRect active:(BOOL)isActive{
576 //Draw the background, tiling across
577 NSRect sourceRect = NSMakeRect(0, 0, backgroundSize.width, backgroundSize.height);
578 NSRect destRect = NSMakeRect(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height);
580 while ((destRect.origin.x < NSMaxX(aRect)) && destRect.size.width > 0) {
582 if (NSMaxX(destRect) > NSMaxX(aRect)) {
583 sourceRect.size.width = NSWidth(destRect);
586 [background drawInRect:destRect
588 operation:NSCompositeSourceOver
590 destRect.origin.x += destRect.size.width;
594 -(void)setFrame:(NSRect)aRect{
595 [super setFrame: aRect];
596 [self recalculateSizes];
599 #pragma mark Status string
600 - (void)setResizeThumbStringValue:(NSString *)inString
602 if (![inString isEqualToString:stringValue]) {
603 [stringValue release];
604 stringValue = [inString copy];
606 [attributedStringValue release];
608 NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
609 [NSParagraphStyle styleWithAlignment:NSLeftTextAlignment
610 lineBreakMode:NSLineBreakByTruncatingTail], NSParagraphStyleAttributeName,
611 [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName,
614 stringHeight = [NSAttributedString stringHeightForAttributes:attributes];
615 attributedStringValue = [[NSAttributedString alloc] initWithString:stringValue
616 attributes:attributes];
618 attributedStringValue = nil;
620 [self setNeedsDisplay:YES];