(self != [self window]), so the observer was never removed. This was a crash caused...
[adiumx.git] / Source / KNShelfSplitView.m
blob5b7cb1b6656c5995ba823059e95c98831b5e369d
1 /*
3 BSD License
5 Copyright (c) 2006, Keith Anderson
6 All rights reserved.
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.
36  */
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];
71         if( self ){
72                 
73                 currentShelfWidth = DEFAULT_SHELF_WIDTH;
74                 isShelfVisible = YES;
75                 shouldHilite = NO;
76                 activeControlPart = CONTROL_PART_NONE;
77                 contextButtonMenu = nil;
78                 [self recalculateSizes];
79                 
80                 autosaveName = nil;
81                 shelfBackgroundColor = nil;
82                 actionButtonImage = nil;
83                 contextButtonImage = nil;
84                 
85                 background = [[NSImage imageNamed:@"sourceListBackground" forClass:[self class]] retain];
86                 backgroundSize = [background size];
88                 [self setDelegate: nil];
89                 target = nil;
90                 action = nil;
91                 
92                 [self setShelfView: aShelfView];
93                 [self setContentView: aContentView];
94                 
95                 [[NSNotificationCenter defaultCenter] addObserver:self 
96                                                                                          selector:@selector(windowDidResize:) 
97                                                                                                  name:NSWindowDidResizeNotification 
98                                                                                            object:[self window]];
99         }
100         return self;
103 -(void)dealloc{
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];
112         [super dealloc];
115 - (void)windowDidResize:(NSNotification *)aNotification
116 {       
117         [self setShelfWidth: [self shelfWidth]];
118         [self recalculateSizes];
121 -(void)setDelegate:(id)aDelegate{
122         delegate = aDelegate;
123         
124         delegateHasValidateWidth = NO;
125         
126         if( delegate ){
127                 if( [delegate respondsToSelector:@selector(shelfSplitView:validateWidth:)] ){
128                         delegateHasValidateWidth = YES;
129                 }
130         }
133 -(id)delegate{
134         return delegate;
137 -(void)setTarget:(id)aTarget{
138         target = aTarget;
139         [self recalculateSizes];
142 -(id)target{
143         return target;
146 -(void)setAction:(SEL)aSelector{
147         action = aSelector;
148         [self recalculateSizes];
151 -(SEL)action{
152         return action;
155 -(void)setContextButtonMenu:(NSMenu *)aMenu{
156         if( contextButtonMenu ){
157                 [contextButtonMenu autorelease];
158                 [[NSNotificationCenter defaultCenter] removeObserver: self];
159         }
160         
161         contextButtonMenu = [aMenu retain];
162         
163         if( contextButtonMenu ){
164                 [contextButtonMenu setDelegate: self];
165                 [[NSNotificationCenter defaultCenter] addObserver: self
166                         selector: @selector(didEndContextMenuTracking)
167                         name: NSMenuDidEndTrackingNotification
168                         object: contextButtonMenu
169                 ];
170         }
171         [self recalculateSizes];
174 -(void)didEndContextMenuTracking{
175         shouldHilite = NO;
176         [self setNeedsDisplayInRect: controlRect];
179 -(NSMenu *)contextButtonMenu{
180         return contextButtonMenu;
183 -(void)setShelfView:(NSView *)aView{
184         if( shelfView ){
185                 [shelfView removeFromSuperview];
186         }
187         
188         shelfView = aView;
189         
190         if( shelfView ){
191                 [self addSubview: shelfView];
192         }
193         [self recalculateSizes];
196 -(NSView *)shelfView{
197         return shelfView;
200 -(void)setContentView:(NSView *)aView{
201         if( contentView ){
202                 [contentView removeFromSuperview];
203         }
204         
205         contentView = aView;
206         
207         if( contentView ){
208                 [self addSubview: contentView];
209         }
210         
211         [self recalculateSizes];
214 -(NSView *)contentView{
215         return contentView;
218 -(void)setShelfWidth:(float)aWidth{
219         float                           newWidth = aWidth;
220         
221         
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;
229                 }
230         }
231         if( minShelf > newWidth ){
232                 newWidth = minShelf;
233         }
234         
235         // The shelf can never be wider than half the entire view
236         float                           maxShelf = [self frame].size.width / 2;
237         
238         if( newWidth > maxShelf ){
239                 newWidth = maxShelf;
240         }
241         
242         currentShelfWidth = newWidth;
243         
244         [self recalculateSizes];
247 -(float)shelfWidth{
248         return currentShelfWidth;
251 -(void)setAutosaveName:(NSString *)aName{
252         if( autosaveName ){
253                 [autosaveName autorelease];
254         }
255         autosaveName = [aName retain];
258 -(NSString *)autosaveName{
259         return autosaveName;
262 -(void)recalculateSizes{
263         shouldDrawActionButton = NO;
264         shouldDrawContextButton = NO;
265         
266         if( isShelfVisible ){
267                 controlRect = NSMakeRect( 0, 0, currentShelfWidth, CONTROL_HEIGHT );
268                 
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 );
271                 
272                 float availableSpace = controlRect.size.width - THUMB_WIDTH;
273                 
274                 if( target && action && (availableSpace > BUTTON_WIDTH) ){
275                         shouldDrawActionButton = YES;
276                         actionButtonRect = NSMakeRect( 0, 0, BUTTON_WIDTH, CONTROL_HEIGHT );
277                         availableSpace -= BUTTON_WIDTH;
278                 }
279                 
280                 if( contextButtonMenu && (availableSpace > BUTTON_WIDTH) ){
281                         shouldDrawContextButton = YES;
282                         contextButtonRect = NSMakeRect(controlRect.size.width - (THUMB_WIDTH + availableSpace), 0, BUTTON_WIDTH, CONTROL_HEIGHT);
283                 }
284         }
285         
286         if( shelfView ){
287                 [shelfView setFrame: NSMakeRect( 0, CONTROL_HEIGHT + 1, currentShelfWidth, [self bounds].size.height - (CONTROL_HEIGHT + 1) )];
288         }
289         
290         if( contentView ){
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];
295                 }
296         }
297         
298         [self setNeedsDisplay: YES];
299         [[self window] invalidateCursorRectsForView: self];
300         
303 -(BOOL)isShelfVisible{
304         return isShelfVisible;
307 -(void)setShelfIsVisible:(BOOL)visible{
308         if( shelfView ){
309                 if( isShelfVisible ){
310                         [shelfView retain];
311                         [shelfView removeFromSuperview];
312                 }else{
313                         [self addSubview: shelfView];
314                         [shelfView release];
315                 }
316         }
318         isShelfVisible = visible;
319         [self recalculateSizes];
322 -(void)setActionButtonImage:(NSImage *)anImage{
323         if( actionButtonImage ){
324                 [actionButtonImage autorelease];
325         }
326         
327         actionButtonImage = [anImage retain];
328         
329         [self setNeedsDisplayInRect: controlRect];
332 -(NSImage *)actionButtonImage{
333                 return actionButtonImage;
336 -(void)setContextButtonImage:(NSImage *)anImage{
337         if( contextButtonImage ){
338                 [contextButtonImage autorelease];
339         }
340         
341         contextButtonImage = [anImage retain];
342         
343         [self setNeedsDisplayInRect: controlRect];
346 -(NSImage *)contextButtonImage{
347         return contextButtonImage;
350 -(void)setShelfBackgroundColor:(NSColor *)aColor{
351         if( shelfBackgroundColor ){
352                 [shelfBackgroundColor autorelease];
353         }
354         
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]];
368         }
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];
377         
378         if( shouldDrawActionButton && NSPointInRect( currentLocation, actionButtonRect ) ){
379                 activeControlPart = CONTROL_PART_ACTION_BUTTON;
380                 shouldHilite = YES;
381         }else if( shouldDrawContextButton && NSPointInRect( currentLocation, contextButtonRect ) ){
382                 activeControlPart = CONTROL_PART_CONTEXT_BUTTON;
383                 shouldHilite = YES;
384                 
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]
394                                                                                         ];
395                 [self setNeedsDisplayInRect: controlRect];
396                 [NSMenu popUpContextMenu: contextButtonMenu withEvent: contextEvent forView: self];
397                 [super mouseDown: contextEvent];
398                 return;
399                 
400         }else if( NSPointInRect( currentLocation, resizeThumbRect ) ){
401                 activeControlPart = CONTROL_PART_RESIZE_THUMB;
402         }else if( NSPointInRect( currentLocation, resizeBarRect ) ){
403                 activeControlPart = CONTROL_PART_RESIZE_BAR;
404         }else{
405                 activeControlPart = CONTROL_PART_NONE;
406         }
407         
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];
414                         shouldHilite = NO;
415                         
416                         if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
417                                 shouldHilite = YES;
418                         }else if( (activeControlPart == CONTROL_PART_CONTEXT_BUTTON) && NSPointInRect( currentLocation, contextButtonRect ) ){
419                                 shouldHilite = YES;
420                         }
421                         
422                         switch( [anEvent type] ){
423                                 case NSLeftMouseDragged:
424                                         if( (activeControlPart == CONTROL_PART_RESIZE_THUMB) || (activeControlPart == CONTROL_PART_RESIZE_BAR) ){
425                                                 [self setShelfWidth: currentLocation.x];
426                                         }else{
427                                                 [self setNeedsDisplayInRect: controlRect];
428                                         }
429                                         break;
430                                         
431                                 case NSLeftMouseUp:
432                                         shouldHilite = NO;
433                                         [self setNeedsDisplayInRect: controlRect];
434                                         
435                                         if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
436                                                 // trigger an action
437                                                 if( target && action && [target respondsToSelector:action]){
438                                                         [target performSelector: action withObject: self];
439                                                 }
440                                         }                                       
441                                         stillMouseDown = NO;
442                                         
443                                         break;
444                                         
445                                 default:
446                                         break;
447                         }
448                 }
449         }else{
450                 [super mouseDown:anEvent];
451         }
454 - (void)drawRect:(NSRect)rect {
455 #pragma unused( rect )
456                 
457         if( isShelfVisible ){
458                 //NSLog(@"Drawing Control( %f, %f) (%f, %f)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
459                 
460                 float remainderStart = 0.0;
461                 
462                 // action button
463                 if( shouldDrawActionButton ){
464                         [self drawControlBackgroundInRect: actionButtonRect
465                                 active: (activeControlPart == CONTROL_PART_ACTION_BUTTON) && shouldHilite
466                         ];
467                         [[NSColor windowFrameColor] set];
468                         NSRectFill( NSMakeRect( (actionButtonRect.origin.x + actionButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
469                         remainderStart += actionButtonRect.size.width;
470                         
471                         if( actionButtonImage ){
472                                 NSRect                  targetRect = NSMakeRect(actionButtonRect.origin.x,
473                                                                                                                 actionButtonRect.origin.y,
474                                                                                                                 [actionButtonImage size].width, 
475                                                                                                                 [actionButtonImage size].height
476                                                                                         );
477                                 
478                                 if( targetRect.size.width > actionButtonRect.size.width ){
479                                         targetRect.size.width = actionButtonRect.size.width;
480                                 }
481                                 if( targetRect.size.width < actionButtonRect.size.width ){
482                                         targetRect.origin.x += (actionButtonRect.size.width - targetRect.size.width) / 2.0;
483                                 }
484                                 if( targetRect.size.height > actionButtonRect.size.height ){
485                                         targetRect.size.height = actionButtonRect.size.height;
486                                 }
487                                 if( targetRect.size.height < actionButtonRect.size.height ){
488                                         targetRect.origin.y += (actionButtonRect.size.height - targetRect.size.height) / 2.0;
489                                 }
490                                 
491                                 [actionButtonImage drawInRect: targetRect 
492                                         fromRect: NSMakeRect( 0, 0, [actionButtonImage size].width, [actionButtonImage size].height )
493                                         operation: NSCompositeSourceOver
494                                         fraction: 1.0f
495                                 ];
496                         }
497                 }
498                 
499                 // context button
500                 if( shouldDrawContextButton ){
501                         [self drawControlBackgroundInRect: contextButtonRect
502                                 active: (activeControlPart == CONTROL_PART_CONTEXT_BUTTON ) && shouldHilite
503                         ];
504                         [[NSColor windowFrameColor] set];
505                         NSRectFill( NSMakeRect( (contextButtonRect.origin.x + contextButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
506                         remainderStart += contextButtonRect.size.width;
507                         
508                         if( contextButtonImage ){
509                                 NSRect                  targetRect = NSMakeRect(contextButtonRect.origin.x,
510                                                                                                                 contextButtonRect.origin.y,
511                                                                                                                 [contextButtonImage size].width, 
512                                                                                                                 [contextButtonImage size].height
513                                                                                         );
514                                 
515                                 if( targetRect.size.width > contextButtonRect.size.width ){
516                                         targetRect.size.width = contextButtonRect.size.width;
517                                 }
518                                 if( targetRect.size.width < contextButtonRect.size.width ){
519                                         targetRect.origin.x += (contextButtonRect.size.width - targetRect.size.width) / 2.0;
520                                 }
521                                 if( targetRect.size.height > contextButtonRect.size.height ){
522                                         targetRect.size.height = contextButtonRect.size.height;
523                                 }
524                                 if( targetRect.size.height < contextButtonRect.size.height ){
525                                         targetRect.origin.y += (contextButtonRect.size.height - targetRect.size.height) / 2.0;
526                                 }
527                                 [contextButtonImage drawInRect: targetRect
528                                         fromRect: NSMakeRect( 0, 0, [contextButtonImage size].width, [contextButtonImage size].height )
529                                         operation: NSCompositeSourceOver
530                                         fraction: 1.0f
531                                 ];
532                         }
533                 }
534                 
535                 //remainder and thumb
536                 [self drawControlBackgroundInRect: NSMakeRect( remainderStart, 0, (controlRect.size.width - remainderStart), controlRect.size.height )
537                         active: NO
538                 ];
539                 
540                 [[NSColor windowFrameColor] set];
541                 NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT, currentShelfWidth, 1 ) );
542                 
543                 // Draw our split line
544                 [[NSColor windowFrameColor] set];
545                 NSRectFill( NSMakeRect( currentShelfWidth, 0, 1, [self frame].size.height ) );
546                 
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, 
552                                                                                         1.0, 
553                                                                                         resizeThumbRect.size.height / 2.0
554                                                                                 );
555                 int i;
556                 for( i=0; i<3; i++ ){
557                         NSRectFill( thumbLineRect );
558                         thumbLineRect.origin.x += (1+THUMB_LINE_SPACING);
559                 }
560                 
561                 if( shelfBackgroundColor ){
562                         [shelfBackgroundColor set];
563                         NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT+1, currentShelfWidth, [self frame].size.height ) );
564                 }
565                 
566                 //Draw the string
567                 if (attributedStringValue && !shouldDrawContextButton && !shouldDrawActionButton) {
568                         NSRect  textRect = NSMakeRect(6, (NSHeight(controlRect) - stringHeight)/2, NSMinX(resizeThumbRect) - 8, stringHeight);
569                         
570                         [attributedStringValue drawInRect:textRect];
571                 }               
572         }
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) {
581         //Crop
582         if (NSMaxX(destRect) > NSMaxX(aRect)) {
583             sourceRect.size.width = NSWidth(destRect);
584         }
586         [background drawInRect:destRect
587                                   fromRect:sourceRect
588                                  operation:NSCompositeSourceOver
589                                   fraction:1.0];
590         destRect.origin.x += destRect.size.width;
591     }
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];
605                 
606                 [attributedStringValue release];
607                 if (stringValue) {
608                         NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
609                                 [NSParagraphStyle styleWithAlignment:NSLeftTextAlignment
610                                                                            lineBreakMode:NSLineBreakByTruncatingTail], NSParagraphStyleAttributeName,
611                                 [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, 
612                                 nil];
613                         
614                         stringHeight = [NSAttributedString stringHeightForAttributes:attributes];
615                         attributedStringValue = [[NSAttributedString alloc] initWithString:stringValue
616                                                                                                                                         attributes:attributes];
617                 } else {
618                         attributedStringValue = nil;
619                 }
620                 [self setNeedsDisplay:YES];
621         }
624 @end