If the display name for the mail application includes .app, as happens if the finder...
[adiumx.git] / Source / KNShelfSplitView.m
blob468a06ff7bc7eabe9b86f2b27c87973406832753
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>
43 #import <AIUtilities/AIToolbarUtilities.h>
44 #import <AIAdium.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
63 + (void)initialize
65         if ((self == [KNShelfSplitView class])) {
66                 [self exposeBinding:@"contextButtonMenu"];
67         }
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];
84         if( self ){
85                 
86                 currentShelfWidth = DEFAULT_SHELF_WIDTH; //change this
87                 isShelfVisible = YES;
88                 shouldHilite = NO;
89                 activeControlPart = CONTROL_PART_NONE;
90                 contextButtonMenu = nil;
91                 [self recalculateSizes];
92                 
93                 autosaveName = nil;
94                 shelfBackgroundColor = nil;
95                 actionButtonImage = nil;
96                 contextButtonImage = nil;
97                 
98                 background = [[NSImage imageNamed:@"sourceListBackground" forClass:[self class]] retain];
99                 backgroundSize = [background size];
101                 [self setDelegate: nil];
102                 target = nil;
103                 action = nil;
104         
105                 [self setShelfView: aShelfView];
106                 [self setContentView: aContentView];
107         }
108         return self;
112 -(void)dealloc{
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         
119         [self unbind:@"contextButtonMenu"];
121         [super dealloc];
124 -(void)setDelegate:(id)aDelegate{
125         delegate = aDelegate;
126         
127         delegateHasValidateWidth = NO;
128         
129         if( delegate ){
130                 if( [delegate respondsToSelector:@selector(shelfSplitView:validateWidth:)] ){
131                         delegateHasValidateWidth = YES;
132                 }
133         }
136 -(id)delegate{
137         return delegate;
140 -(void)setTarget:(id)aTarget{
141         target = aTarget;
142         [self recalculateSizes];
145 -(id)target{
146         return target;
149 -(void)setAction:(SEL)aSelector{
150         action = aSelector;
151         [self recalculateSizes];
154 -(SEL)action{
155         return action;
158 -(void)setContextButtonMenu:(NSMenu *)aMenu{
159         if( contextButtonMenu ){
160                 [contextButtonMenu autorelease];
161                 [[NSNotificationCenter defaultCenter] removeObserver: self];
162         }
163         
164         contextButtonMenu = [aMenu retain];
165         
166         if( contextButtonMenu ){
167                 [contextButtonMenu setDelegate: self];
168                 [[NSNotificationCenter defaultCenter] addObserver: self
169                         selector: @selector(didEndContextMenuTracking)
170                         name: NSMenuDidEndTrackingNotification
171                         object: contextButtonMenu
172                 ];
173         }
175         [self recalculateSizes];
178 -(void)didEndContextMenuTracking{
179         shouldHilite = NO;
180         [self setNeedsDisplayInRect: controlRect];
183 -(NSMenu *)contextButtonMenu{
184         return contextButtonMenu;
187 -(void)setShelfView:(NSView *)aView{
188         if( shelfView ){
189                 [shelfView removeFromSuperview];
190         }
191         
192         shelfView = aView;
193         
194         if( shelfView ){
195                 [self addSubview: shelfView];
196         }
197         [self recalculateSizes];
200 -(NSView *)shelfView{
201         return shelfView;
204 -(void)setContentView:(NSView *)aView{
205         if( contentView ){
206                 [contentView removeFromSuperview];
207         }
208         
209         contentView = aView;
210         
211         if( contentView ){
212                 [self addSubview: contentView];
213         }
214         
215         [self recalculateSizes];
218 -(NSView *)contentView{
219         return contentView;
222 -(void)setShelfWidth:(float)aWidth{
223         float                           newWidth = aWidth;
224         
225         
226         // The shelf can never be completely closed. We always have at least enough to show our resize thumb, otherwise
227         // if the delegate responds to shelfSplitView:validateWidth:, we use that width as our minimum shelf size
228         float                           minShelf = THUMB_WIDTH;
229         if( delegateHasValidateWidth ){
230                 float                           requestedWidth = [delegate shelfSplitView:self validateWidth: aWidth];
231                 if( requestedWidth > minShelf ){
232                         minShelf = requestedWidth;
233                 }
234         }
235         if( minShelf > newWidth ){
236                 newWidth = minShelf;
237         }
238         
239         // The shelf can never be wider than half the entire view
240         float maxShelf = [self frame].size.width / 2;
241         
242         if( newWidth > maxShelf ){
243                 newWidth = maxShelf;
244         }
245         
246         currentShelfWidth = newWidth;
247         
248         [self recalculateSizes];
251 -(float)shelfWidth{
252         return currentShelfWidth;
255 -(void)setAutosaveName:(NSString *)aName{
256         if( autosaveName ){
257                 [autosaveName autorelease];
258         }
259         autosaveName = [aName retain];
262 -(NSString *)autosaveName{
263         return autosaveName;
266 -(void)recalculateSizes{        
267         if( isShelfVisible ){
268                 controlRect = NSMakeRect( 0, 0, currentShelfWidth, CONTROL_HEIGHT );
269                 
270                 resizeThumbRect = NSMakeRect( (controlRect.size.width - THUMB_WIDTH), 0, THUMB_WIDTH, CONTROL_HEIGHT );
271                 resizeBarRect = NSMakeRect( currentShelfWidth - (RESIZE_BAR_EFFECTIVE_WIDTH / 2), 0, RESIZE_BAR_EFFECTIVE_WIDTH, [self frame].size.height );
272                 
273                 float availableSpace = controlRect.size.width - THUMB_WIDTH;
274                 
275                 if( target && action && (availableSpace > BUTTON_WIDTH) ){
276                         shouldDrawActionButton = YES;
277                         actionButtonRect = NSMakeRect( 0, 0, BUTTON_WIDTH, CONTROL_HEIGHT );
278                         availableSpace -= BUTTON_WIDTH;
279                 }
280                 
281                 if( contextButtonMenu && [contextButtonMenu numberOfItems] && (availableSpace > BUTTON_WIDTH) ){
282                         shouldDrawContextButton = YES;
283                         contextButtonRect = NSMakeRect(controlRect.size.width - (THUMB_WIDTH + availableSpace), 0, BUTTON_WIDTH, CONTROL_HEIGHT);
284                 }
285         }
286         
287         if( shelfView ){
288                 [shelfView setFrame: NSMakeRect( 0, CONTROL_HEIGHT + 1, currentShelfWidth, [self bounds].size.height - (CONTROL_HEIGHT + 1) )];
289         }
290         
291         if( contentView ){
292                 float contentViewX = (isShelfVisible ? (currentShelfWidth + 1) : 0);
293                 NSRect newRect = NSMakeRect(contentViewX, 0, NSWidth([self bounds]) - contentViewX, NSHeight([self bounds]));
294                 if (!NSEqualRects(newRect, [contentView frame])){
295                         [contentView setFrame:newRect];
296                 }
297         }
298         
299         [self setNeedsDisplay: YES];
300         [[self window] invalidateCursorRectsForView: self];
301         
304 -(BOOL)isShelfVisible{
305         return isShelfVisible;
308 -(void)setShelfIsVisible:(BOOL)visible{
309         if( shelfView ){
310                 if( isShelfVisible && !visible ){
311                         [shelfView retain];
312                         [shelfView removeFromSuperview];
313                 } else if( !isShelfVisible && visible ){
314                         [self addSubview: shelfView];
315                         [shelfView release];
316                 }
317         }
319         isShelfVisible = visible;
320         [self recalculateSizes];
323 -(void)setActionButtonImage:(NSImage *)anImage{
324         if( actionButtonImage ){
325                 [actionButtonImage autorelease];
326         }
327         
328         actionButtonImage = [anImage retain];
329         
330         [self setNeedsDisplayInRect: controlRect];
333 -(NSImage *)actionButtonImage{
334                 return actionButtonImage;
337 -(void)setContextButtonImage:(NSImage *)anImage{
338         if( contextButtonImage ){
339                 [contextButtonImage autorelease];
340         }
341         
342         contextButtonImage = [anImage retain];
343         
344         [self setNeedsDisplayInRect: controlRect];
347 -(NSImage *)contextButtonImage{
348         return contextButtonImage;
351 -(void)setShelfBackgroundColor:(NSColor *)aColor{
352         if( shelfBackgroundColor ){
353                 [shelfBackgroundColor autorelease];
354         }
355         
356         shelfBackgroundColor = [aColor retain];
357         [self setNeedsDisplay: YES];
360 -(NSColor *)shelfBackgroundColor{
361         return shelfBackgroundColor;
364 -(void)resetCursorRects{
365         [super resetCursorRects];
366         if( isShelfVisible ){
367                 [self addCursorRect: resizeThumbRect cursor: [NSCursor resizeLeftRightCursor]];
368                 [self addCursorRect: resizeBarRect cursor: [NSCursor resizeLeftRightCursor]];
369         }
372 -(void)mouseDown:(NSEvent *)anEvent{
373         BOOL                                    stillMouseDown = YES;
374         NSPoint                                 currentLocation;
376         // determine if we're in a control part we care about
377         currentLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];
378         
379         if( shouldDrawActionButton && NSPointInRect( currentLocation, actionButtonRect ) ){
380                 activeControlPart = CONTROL_PART_ACTION_BUTTON;
381                 shouldHilite = YES;
382         }else if( shouldDrawContextButton && NSPointInRect( currentLocation, contextButtonRect ) ){
383                 activeControlPart = CONTROL_PART_CONTEXT_BUTTON;
384                 shouldHilite = YES;
385                 
386                 NSEvent *                       contextEvent = [NSEvent mouseEventWithType: [anEvent type]
387                                                                                                 location: NSMakePoint( contextButtonRect.origin.x + (contextButtonRect.size.width / 2) , contextButtonRect.origin.y + (contextButtonRect.size.height / 2) )
388                                                                                                 modifierFlags: [anEvent modifierFlags]
389                                                                                                 timestamp: [anEvent timestamp]
390                                                                                                 windowNumber: [anEvent windowNumber]
391                                                                                                 context: [anEvent context]
392                                                                                                 eventNumber: [anEvent eventNumber]
393                                                                                                 clickCount: [anEvent clickCount]
394                                                                                                 pressure: [anEvent pressure]
395                                                                                         ];
396                 [self setNeedsDisplayInRect: controlRect];
397                 [NSMenu popUpContextMenu: contextButtonMenu withEvent: contextEvent forView: self];
398                 [super mouseDown: contextEvent];
399                 return;
400                 
401         }else if( NSPointInRect( currentLocation, resizeThumbRect ) ){
402                 activeControlPart = CONTROL_PART_RESIZE_THUMB;
403         }else if( NSPointInRect( currentLocation, resizeBarRect ) ){
404                 activeControlPart = CONTROL_PART_RESIZE_BAR;
405         }else{
406                 activeControlPart = CONTROL_PART_NONE;
407         }
408         
409         [self setNeedsDisplayInRect: controlRect];
411         if( activeControlPart != CONTROL_PART_NONE ){
412                 if([anEvent clickCount] == 2){
413                         [self setShelfIsVisible: NO];
414                 } else {
415                 while( stillMouseDown ){
416                         anEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask];
417                         currentLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];
418                         shouldHilite = NO;
419                         
420                         if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
421                                 shouldHilite = YES;
422                         }else if( (activeControlPart == CONTROL_PART_CONTEXT_BUTTON) && NSPointInRect( currentLocation, contextButtonRect ) ){
423                                 shouldHilite = YES;
424                         }
425                         
426                         switch( [anEvent type] ){
427                                 case NSLeftMouseDragged:
428                                         if( (activeControlPart == CONTROL_PART_RESIZE_THUMB) || (activeControlPart == CONTROL_PART_RESIZE_BAR) ){
429                                                 [self setShelfWidth: currentLocation.x];
430                                         }else{
431                                                 [self setNeedsDisplayInRect: controlRect];
432                                         }
433                                         break;
434                                         
435                                 case NSLeftMouseUp:
436                                         shouldHilite = NO;
437                                         [self setNeedsDisplayInRect: controlRect];
438                                         
439                                         if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
440                                                 // trigger an action
441                                                 if( target && action && [target respondsToSelector:action]){
442                                                         [target performSelector: action withObject: self];
443                                                 }
444                                         }                                       
445                                         stillMouseDown = NO;
446                                         
447                                         break;
448                                         
449                                 default:
450                                         break;
451                         }
452                 }
453         }
454         }else{
455                 [super mouseDown:anEvent];
456         }
459 - (void)drawRect:(NSRect)rect {
460 #pragma unused( rect )
461         
462                 if( isShelfVisible ){
463                 //NSLog(@"Drawing Control( %f, %f) (%f, %f)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
464                 
465                 float remainderStart = 0.0;
466                 
467                 // action button
468                 if( shouldDrawActionButton  == YES){
469                         [self drawControlBackgroundInRect: actionButtonRect
470                                 active: (activeControlPart == CONTROL_PART_ACTION_BUTTON) && shouldHilite
471                         ];
472                         [[NSColor windowFrameColor] set];
473                 //      NSRectFill( NSMakeRect( (actionButtonRect.origin.x + actionButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
474                         remainderStart += actionButtonRect.size.width;
475                         
476                         if( actionButtonImage ){
477                                 
478                                 NSRect                  targetRect = NSMakeRect(actionButtonRect.origin.x,
479                                                                                                                 actionButtonRect.origin.y,
480                                                                                                                 [actionButtonImage size].width, 
481                                                                                                                 [actionButtonImage size].height
482                                                                                         );
483                                 
484                                 if( targetRect.size.width > actionButtonRect.size.width ){
485                                         targetRect.size.width = actionButtonRect.size.width;
486                                 }
487                                 if( targetRect.size.width < actionButtonRect.size.width ){
488                                         targetRect.origin.x += (actionButtonRect.size.width - targetRect.size.width) / 2.0;
489                                 }
490                                 if( targetRect.size.height > actionButtonRect.size.height ){
491                                         targetRect.size.height = actionButtonRect.size.height;
492                                 }
493                                 if( targetRect.size.height < actionButtonRect.size.height ){
494                                         targetRect.origin.y += (actionButtonRect.size.height - targetRect.size.height) / 2.0;
495                                 }
496                                 
497                                 [actionButtonImage compositeToPoint:NSMakePoint(actionButtonRect.origin.x,
498                                                                                                                 actionButtonRect.origin.y) operation:NSCompositeDestinationAtop];
500         
501                         }
502                 }
503                 
504                 // context button
505                 
506                 if( shouldDrawContextButton ){
507                         [self drawControlBackgroundInRect: contextButtonRect
508                                 active: (activeControlPart == CONTROL_PART_CONTEXT_BUTTON ) && shouldHilite
509                         ];
510                         [[NSColor windowFrameColor] set];
511                         NSRectFill( NSMakeRect( (contextButtonRect.origin.x + contextButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
512                         remainderStart += contextButtonRect.size.width;
513                         
514                         if( contextButtonImage ){
515                 
516                                 NSRect                  targetRect = NSMakeRect(contextButtonRect.origin.x,
517                                                                                                                 contextButtonRect.origin.y,
518                                                                                                                 [contextButtonImage size].width, 
519                                                                                                                 [contextButtonImage size].height
520                                                                                         );
521                                 
522                                 if( targetRect.size.width > contextButtonRect.size.width ){
523                                         targetRect.size.width = contextButtonRect.size.width;
524                                 }
525                                 if( targetRect.size.width < contextButtonRect.size.width ){
526                                         targetRect.origin.x += (contextButtonRect.size.width - targetRect.size.width) / 2.0;
527                                 }
528                                 if( targetRect.size.height > contextButtonRect.size.height ){
529                                         targetRect.size.height = contextButtonRect.size.height;
530                                 }
531                                 if( targetRect.size.height < contextButtonRect.size.height ){
532                                         targetRect.origin.y += (contextButtonRect.size.height - targetRect.size.height) / 2.0;
533                                 }
534                                 [contextButtonImage drawInRect: targetRect
535                                         fromRect: NSMakeRect( 0, 0, [contextButtonImage size].width, [contextButtonImage size].height )
536                                         operation: NSCompositeSourceOver
537                                         fraction: 1.0f
538                                 ]; 
539                         }
540                 }
541                 
542                 //remainder and thumb
543                 [self drawControlBackgroundInRect:NSMakeRect( remainderStart, 0, (controlRect.size.width - remainderStart), controlRect.size.height )
544                                                                    active:NO];
546                 [[NSColor windowFrameColor] set];
547                 NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT, currentShelfWidth, 1 ) );
548                 
549                 // Draw our split line
550                 [[NSColor windowFrameColor] set];
551                 NSRectFill( NSMakeRect( currentShelfWidth, 0, 1, [self frame].size.height ) );
552                 
553                 // Draw our thumb lines
554                 [[NSColor disabledControlTextColor] set];
555                 NSRect                  thumbLineRect = NSMakeRect( 
556                                                                                         resizeThumbRect.origin.x + (resizeThumbRect.size.width - ((2*THUMB_LINE_SPACING) + 3.0)) / 2.0, 
557                                                                                         resizeThumbRect.size.height / 4.0, 
558                                                                                         1.0, 
559                                                                                         resizeThumbRect.size.height / 2.0
560                                                                                 );
561                 int i;
562                 for( i=0; i<3; i++ ){
563                         NSRectFill( thumbLineRect );
564                         thumbLineRect.origin.x += (1+THUMB_LINE_SPACING);
565                 }
566                 
567                 if( shelfBackgroundColor ){
568                         [shelfBackgroundColor set];
569                         NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT+1, currentShelfWidth, [self frame].size.height ) );
570                 }
571                 
572                 //Draw the string
573                 if (attributedStringValue && !shouldDrawContextButton && !shouldDrawActionButton) {
574                         NSRect  textRect = NSMakeRect(6, (NSHeight(controlRect) - stringHeight)/2, NSMinX(resizeThumbRect) - 8, stringHeight);
575                         
576                         [attributedStringValue drawInRect:textRect];
577                 } 
578         }
581 -(void)drawControlBackgroundInRect:(NSRect)aRect active:(BOOL)isActive{ 
582         //Draw the background, tiling across
583     NSRect sourceRect = NSMakeRect(0, 0, backgroundSize.width, backgroundSize.height);
584     NSRect destRect = NSMakeRect(aRect.origin.x, aRect.origin.y, sourceRect.size.width, aRect.size.height);
585         
586     while ((destRect.origin.x < NSMaxX(aRect)) && destRect.size.width > 0) {
587         //Crop
588         if (NSMaxX(destRect) > NSMaxX(aRect)) {
589                         destRect.size.width = NSMaxX(aRect) - NSMinX(destRect);
590             sourceRect.size.width = NSWidth(destRect);
591         }
592                 
593         [background drawInRect:destRect
594                                           fromRect:sourceRect
595                                          operation:NSCompositeSourceOver
596                                           fraction:1.0];
597         destRect.origin.x += destRect.size.width;
598     }
601 -(void)setFrame:(NSRect)aRect{
602         [super setFrame: aRect];
603         [self recalculateSizes];
606 #pragma mark Status string
607 - (void)setResizeThumbStringValue:(NSString *)inString
609         if (![inString isEqualToString:stringValue]) {
610                 [stringValue release];
611                 stringValue = [inString copy];
612                 
613                 [attributedStringValue release];
614                 if (stringValue) {
615                         NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
616                                 [NSParagraphStyle styleWithAlignment:NSLeftTextAlignment
617                                                                            lineBreakMode:NSLineBreakByTruncatingTail], NSParagraphStyleAttributeName,
618                                 [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, 
619                                 nil];
620                         
621                         stringHeight = [NSAttributedString stringHeightForAttributes:attributes];
622                         attributedStringValue = [[NSAttributedString alloc] initWithString:stringValue
623                                                                                                                                         attributes:attributes];
624                 } else {
625                         attributedStringValue = nil;
626                 }
627                 [self setNeedsDisplay:YES];
628         }
631 @end