Patch from Jesse Rusak which prevents the message window and contact list from steali...
[adiumx.git] / Source / KNShelfSplitView.m
blobc96f0996d3a21b5a1f8bdd0ca1da64c195189208
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.
34 #import "KNShelfSplitView.h"
36 #import <AIUtilities/AIAttributedStringAdditions.h>
37 #import <AIUtilities/AIParagraphStyleAdditions.h>
38 #import <AIUtilities/AIImageAdditions.h>
40 #define DEFAULT_SHELF_WIDTH 200
41 #define CONTROL_HEIGHT 22
42 #define BUTTON_WIDTH 30
43 #define THUMB_WIDTH 15
44 #define THUMB_LINE_SPACING 2.0
45 #define RESIZE_BAR_EFFECTIVE_WIDTH 1.0
47 #define CONTROL_PART_NONE 0
48 #define CONTROL_PART_ACTION_BUTTON 1
49 #define CONTROL_PART_CONTEXT_BUTTON 2
50 #define CONTROL_PART_RESIZE_THUMB 3
51 #define CONTROL_PART_RESIZE_BAR 4
53 @implementation KNShelfSplitView
55 -(IBAction)toggleShelf:(id)sender{
56 #pragma unused(sender)
57         [self setShelfIsVisible: ![self isShelfVisible]];
58         [self setNeedsDisplay: YES];
61 - (id)initWithFrame:(NSRect)aFrame {
62         return [self initWithFrame: aFrame shelfView: nil contentView: nil];
65 -(id)initWithFrame:(NSRect)aFrame shelfView:(NSView *)aShelfView contentView:(NSView *)aContentView{
66         self = [super initWithFrame: aFrame];
67         if( self ){
68                 
69                 currentShelfWidth = DEFAULT_SHELF_WIDTH;
70                 isShelfVisible = YES;
71                 shouldHilite = NO;
72                 activeControlPart = CONTROL_PART_NONE;
73                 contextButtonMenu = nil;
74                 [self recalculateSizes];
75                 
76                 autosaveName = nil;
77                 shelfBackgroundColor = nil;
78                 actionButtonImage = nil;
79                 contextButtonImage = nil;
80                 
81                 background = [[NSImage imageNamed:@"sourceListBackground" forClass:[self class]] retain];
82                 backgroundSize = [background size];
84                 [self setDelegate: nil];
85                 target = nil;
86                 action = nil;
87                 
88                 [self setShelfView: aShelfView];
89                 [self setContentView: aContentView];
90         }
91         return self;
94 -(void)dealloc{
95         if( autosaveName ){ [autosaveName release]; }
96         if( contextButtonImage ){ [contextButtonImage release]; }
97         if( actionButtonImage ){ [actionButtonImage release]; }
98         if( shelfBackgroundColor ){ [shelfBackgroundColor release]; }
99         if( contextButtonMenu ){ [contextButtonMenu release]; }
100         [super dealloc];
103 -(void)setDelegate:(id)aDelegate{
104         delegate = aDelegate;
105         
106         delegateHasValidateWidth = NO;
107         
108         if( delegate ){
109                 if( [delegate respondsToSelector:@selector(shelfSplitView:validateWidth:)] ){
110                         delegateHasValidateWidth = YES;
111                 }
112         }
115 -(id)delegate{
116         return delegate;
119 -(void)setTarget:(id)aTarget{
120         target = aTarget;
121         [self recalculateSizes];
124 -(id)target{
125         return target;
128 -(void)setAction:(SEL)aSelector{
129         action = aSelector;
130         [self recalculateSizes];
133 -(SEL)action{
134         return action;
137 -(void)setContextButtonMenu:(NSMenu *)aMenu{
138         if( contextButtonMenu ){
139                 [contextButtonMenu autorelease];
140                 [[NSNotificationCenter defaultCenter] removeObserver: self];
141         }
142         
143         contextButtonMenu = [aMenu retain];
144         
145         if( contextButtonMenu ){
146                 [contextButtonMenu setDelegate: self];
147                 [[NSNotificationCenter defaultCenter] addObserver: self
148                         selector: @selector(didEndContextMenuTracking)
149                         name: NSMenuDidEndTrackingNotification
150                         object: contextButtonMenu
151                 ];
152         }
153         [self recalculateSizes];
156 -(void)didEndContextMenuTracking{
157         shouldHilite = NO;
158         [self setNeedsDisplayInRect: controlRect];
161 -(NSMenu *)contextButtonMenu{
162         return contextButtonMenu;
165 -(void)setShelfView:(NSView *)aView{
166         if( shelfView ){
167                 [shelfView removeFromSuperview];
168         }
169         
170         shelfView = aView;
171         
172         if( shelfView ){
173                 [self addSubview: shelfView];
174         }
175         [self recalculateSizes];
178 -(NSView *)shelfView{
179         return shelfView;
182 -(void)setContentView:(NSView *)aView{
183         if( contentView ){
184                 [contentView removeFromSuperview];
185         }
186         
187         contentView = aView;
188         
189         if( contentView ){
190                 [self addSubview: contentView];
191         }
192         
193         [self recalculateSizes];
196 -(NSView *)contentView{
197         return contentView;
200 -(void)setShelfWidth:(float)aWidth{
201         float                           newWidth = aWidth;
202         
203         
204         // The shelf can never be completely closed. We always have at least enough to show our resize thumb, otherwise
205         // if the delegate responds to shelfSplitView:validateWidth:, we use that width as our minimum shelf size
206         float                           minShelf = THUMB_WIDTH;
207         if( delegateHasValidateWidth ){
208                 float                           requestedWidth = [delegate shelfSplitView:self validateWidth: aWidth];
209                 if( requestedWidth > minShelf ){
210                         minShelf = requestedWidth;
211                 }
212         }
213         if( minShelf > newWidth ){
214                 newWidth = minShelf;
215         }
216         
217         // The shelf can never be wider than half the entire view
218         float                           maxShelf = [self frame].size.width / 2;
219         
220         if( newWidth > maxShelf ){
221                 newWidth = maxShelf;
222         }
223         
224         currentShelfWidth = newWidth;
225         
226         [self recalculateSizes];
229 -(float)shelfWidth{
230         return currentShelfWidth;
233 -(void)setAutosaveName:(NSString *)aName{
234         if( autosaveName ){
235                 [autosaveName autorelease];
236         }
237         autosaveName = [aName retain];
240 -(NSString *)autosaveName{
241         return autosaveName;
244 -(void)recalculateSizes{
245         shouldDrawActionButton = NO;
246         shouldDrawContextButton = NO;
247         
248         if( isShelfVisible ){
249                 controlRect = NSMakeRect( 0, 0, currentShelfWidth, CONTROL_HEIGHT );
250                 
251                 resizeThumbRect = NSMakeRect( (controlRect.size.width - THUMB_WIDTH), 0, THUMB_WIDTH, CONTROL_HEIGHT );
252                 resizeBarRect = NSMakeRect( currentShelfWidth - (RESIZE_BAR_EFFECTIVE_WIDTH / 2), 0, RESIZE_BAR_EFFECTIVE_WIDTH, [self frame].size.height );
253                 
254                 float availableSpace = controlRect.size.width - THUMB_WIDTH;
255                 
256                 if( target && action && (availableSpace > BUTTON_WIDTH) ){
257                         shouldDrawActionButton = YES;
258                         actionButtonRect = NSMakeRect( 0, 0, BUTTON_WIDTH, CONTROL_HEIGHT );
259                         availableSpace -= BUTTON_WIDTH;
260                 }
261                 
262                 if( contextButtonMenu && (availableSpace > BUTTON_WIDTH) ){
263                         shouldDrawContextButton = YES;
264                         contextButtonRect = NSMakeRect(controlRect.size.width - (THUMB_WIDTH + availableSpace), 0, BUTTON_WIDTH, CONTROL_HEIGHT);
265                 }
266         }
267         
268         if( shelfView ){
269                 [shelfView setFrame: NSMakeRect( 0, CONTROL_HEIGHT + 1, currentShelfWidth, [self bounds].size.height - (CONTROL_HEIGHT + 1) )];
270         }
271         
272         if( contentView ){
273                 float contentViewX = (isShelfVisible ? currentShelfWidth : 0);
274                 NSRect newRect = NSMakeRect( contentViewX + 1, 0, [self bounds].size.width - (contentViewX + 1), [self bounds].size.height);
275                 if( ! NSEqualRects(newRect, [contentView frame]) ){
276                         [contentView setFrame: newRect];
277                 }
278         }
279         
280         [self setNeedsDisplay: YES];
281         [[self window] invalidateCursorRectsForView: self];
282         
285 -(BOOL)isShelfVisible{
286         return isShelfVisible;
289 -(void)setShelfIsVisible:(BOOL)visible{
290         if( shelfView ){
291                 if( isShelfVisible ){
292                         [shelfView retain];
293                         [shelfView removeFromSuperview];
294                 }else{
295                         [self addSubview: shelfView];
296                         [shelfView release];
297                 }
298         }
300         isShelfVisible = visible;
301         [self recalculateSizes];
304 -(void)setActionButtonImage:(NSImage *)anImage{
305         if( actionButtonImage ){
306                 [actionButtonImage autorelease];
307         }
308         
309         actionButtonImage = [anImage retain];
310         
311         [self setNeedsDisplayInRect: controlRect];
314 -(NSImage *)actionButtonImage{
315                 return actionButtonImage;
318 -(void)setContextButtonImage:(NSImage *)anImage{
319         if( contextButtonImage ){
320                 [contextButtonImage autorelease];
321         }
322         
323         contextButtonImage = [anImage retain];
324         
325         [self setNeedsDisplayInRect: controlRect];
328 -(NSImage *)contextButtonImage{
329         return contextButtonImage;
332 -(void)setShelfBackgroundColor:(NSColor *)aColor{
333         if( shelfBackgroundColor ){
334                 [shelfBackgroundColor autorelease];
335         }
336         
337         shelfBackgroundColor = [aColor retain];
338         [self setNeedsDisplay: YES];
341 -(NSColor *)shelfBackgroundColor{
342         return shelfBackgroundColor;
345 -(void)resetCursorRects{
346         [super resetCursorRects];
347         if( isShelfVisible ){
348                 [self addCursorRect: resizeThumbRect cursor: [NSCursor resizeLeftRightCursor]];
349                 [self addCursorRect: resizeBarRect cursor: [NSCursor resizeLeftRightCursor]];
350         }
353 -(void)mouseDown:(NSEvent *)anEvent{
354         BOOL                                    stillMouseDown = YES;
355         NSPoint                                 currentLocation;
357         // determine if we're in a control part we care about
358         currentLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];
359         
360         if( shouldDrawActionButton && NSPointInRect( currentLocation, actionButtonRect ) ){
361                 activeControlPart = CONTROL_PART_ACTION_BUTTON;
362                 shouldHilite = YES;
363         }else if( shouldDrawContextButton && NSPointInRect( currentLocation, contextButtonRect ) ){
364                 activeControlPart = CONTROL_PART_CONTEXT_BUTTON;
365                 shouldHilite = YES;
366                 
367                 NSEvent *                       contextEvent = [NSEvent mouseEventWithType: [anEvent type]
368                                                                                                 location: NSMakePoint( contextButtonRect.origin.x + (contextButtonRect.size.width / 2) , contextButtonRect.origin.y + (contextButtonRect.size.height / 2) )
369                                                                                                 modifierFlags: [anEvent modifierFlags]
370                                                                                                 timestamp: [anEvent timestamp]
371                                                                                                 windowNumber: [anEvent windowNumber]
372                                                                                                 context: [anEvent context]
373                                                                                                 eventNumber: [anEvent eventNumber]
374                                                                                                 clickCount: [anEvent clickCount]
375                                                                                                 pressure: [anEvent pressure]
376                                                                                         ];
377                 [self setNeedsDisplayInRect: controlRect];
378                 [NSMenu popUpContextMenu: contextButtonMenu withEvent: contextEvent forView: self];
379                 [super mouseDown: contextEvent];
380                 return;
381                 
382         }else if( NSPointInRect( currentLocation, resizeThumbRect ) ){
383                 activeControlPart = CONTROL_PART_RESIZE_THUMB;
384         }else if( NSPointInRect( currentLocation, resizeBarRect ) ){
385                 activeControlPart = CONTROL_PART_RESIZE_BAR;
386         }else{
387                 activeControlPart = CONTROL_PART_NONE;
388         }
389         
390         [self setNeedsDisplayInRect: controlRect];
392         if( activeControlPart != CONTROL_PART_NONE ){
393                 while( stillMouseDown ){
394                         anEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask];
395                         currentLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];
396                         shouldHilite = NO;
397                         
398                         if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
399                                 shouldHilite = YES;
400                         }else if( (activeControlPart == CONTROL_PART_CONTEXT_BUTTON) && NSPointInRect( currentLocation, contextButtonRect ) ){
401                                 shouldHilite = YES;
402                         }
403                         
404                         switch( [anEvent type] ){
405                                 case NSLeftMouseDragged:
406                                         if( (activeControlPart == CONTROL_PART_RESIZE_THUMB) || (activeControlPart == CONTROL_PART_RESIZE_BAR) ){
407                                                 [self setShelfWidth: currentLocation.x];
408                                         }else{
409                                                 [self setNeedsDisplayInRect: controlRect];
410                                         }
411                                         break;
412                                         
413                                 case NSLeftMouseUp:
414                                         shouldHilite = NO;
415                                         [self setNeedsDisplayInRect: controlRect];
416                                         
417                                         if( (activeControlPart == CONTROL_PART_ACTION_BUTTON) && NSPointInRect( currentLocation, actionButtonRect ) ){
418                                                 // trigger an action
419                                                 if( target && action && [target respondsToSelector:action]){
420                                                         [target performSelector: action withObject: self];
421                                                 }
422                                         }                                       
423                                         stillMouseDown = NO;
424                                         
425                                         break;
426                                         
427                                 default:
428                                         break;
429                         }
430                 }
431         }else{
432                 [super mouseDown:anEvent];
433         }
436 - (void)drawRect:(NSRect)rect {
437 #pragma unused( rect )
438                 
439         if( isShelfVisible ){
440                 //NSLog(@"Drawing Control( %f, %f) (%f, %f)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
441                 
442                 float remainderStart = 0.0;
443                 
444                 // action button
445                 if( shouldDrawActionButton ){
446                         [self drawControlBackgroundInRect: actionButtonRect
447                                 active: (activeControlPart == CONTROL_PART_ACTION_BUTTON) && shouldHilite
448                         ];
449                         [[NSColor windowFrameColor] set];
450                         NSRectFill( NSMakeRect( (actionButtonRect.origin.x + actionButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
451                         remainderStart += actionButtonRect.size.width;
452                         
453                         if( actionButtonImage ){
454                                 NSRect                  targetRect = NSMakeRect(actionButtonRect.origin.x,
455                                                                                                                 actionButtonRect.origin.y,
456                                                                                                                 [actionButtonImage size].width, 
457                                                                                                                 [actionButtonImage size].height
458                                                                                         );
459                                 
460                                 if( targetRect.size.width > actionButtonRect.size.width ){
461                                         targetRect.size.width = actionButtonRect.size.width;
462                                 }
463                                 if( targetRect.size.width < actionButtonRect.size.width ){
464                                         targetRect.origin.x += (actionButtonRect.size.width - targetRect.size.width) / 2.0;
465                                 }
466                                 if( targetRect.size.height > actionButtonRect.size.height ){
467                                         targetRect.size.height = actionButtonRect.size.height;
468                                 }
469                                 if( targetRect.size.height < actionButtonRect.size.height ){
470                                         targetRect.origin.y += (actionButtonRect.size.height - targetRect.size.height) / 2.0;
471                                 }
472                                 
473                                 [actionButtonImage drawInRect: targetRect 
474                                         fromRect: NSMakeRect( 0, 0, [actionButtonImage size].width, [actionButtonImage size].height )
475                                         operation: NSCompositeSourceOver
476                                         fraction: 1.0f
477                                 ];
478                         }
479                 }
480                 
481                 // context button
482                 if( shouldDrawContextButton ){
483                         [self drawControlBackgroundInRect: contextButtonRect
484                                 active: (activeControlPart == CONTROL_PART_CONTEXT_BUTTON ) && shouldHilite
485                         ];
486                         [[NSColor windowFrameColor] set];
487                         NSRectFill( NSMakeRect( (contextButtonRect.origin.x + contextButtonRect.size.width) - 1, 0, 1, controlRect.size.height ) );
488                         remainderStart += contextButtonRect.size.width;
489                         
490                         if( contextButtonImage ){
491                                 NSRect                  targetRect = NSMakeRect(contextButtonRect.origin.x,
492                                                                                                                 contextButtonRect.origin.y,
493                                                                                                                 [contextButtonImage size].width, 
494                                                                                                                 [contextButtonImage size].height
495                                                                                         );
496                                 
497                                 if( targetRect.size.width > contextButtonRect.size.width ){
498                                         targetRect.size.width = contextButtonRect.size.width;
499                                 }
500                                 if( targetRect.size.width < contextButtonRect.size.width ){
501                                         targetRect.origin.x += (contextButtonRect.size.width - targetRect.size.width) / 2.0;
502                                 }
503                                 if( targetRect.size.height > contextButtonRect.size.height ){
504                                         targetRect.size.height = contextButtonRect.size.height;
505                                 }
506                                 if( targetRect.size.height < contextButtonRect.size.height ){
507                                         targetRect.origin.y += (contextButtonRect.size.height - targetRect.size.height) / 2.0;
508                                 }
509                                 [contextButtonImage drawInRect: targetRect
510                                         fromRect: NSMakeRect( 0, 0, [contextButtonImage size].width, [contextButtonImage size].height )
511                                         operation: NSCompositeSourceOver
512                                         fraction: 1.0f
513                                 ];
514                         }
515                 }
516                 
517                 //remainder and thumb
518                 [self drawControlBackgroundInRect: NSMakeRect( remainderStart, 0, (controlRect.size.width - remainderStart), controlRect.size.height )
519                         active: NO
520                 ];
521                 
522                 [[NSColor windowFrameColor] set];
523                 NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT, currentShelfWidth, 1 ) );
524                 
525                 // Draw our split line
526                 [[NSColor windowFrameColor] set];
527                 NSRectFill( NSMakeRect( currentShelfWidth, 0, 1, [self frame].size.height ) );
528                 
529                 // Draw our thumb lines
530                 [[NSColor disabledControlTextColor] set];
531                 NSRect                  thumbLineRect = NSMakeRect( 
532                                                                                         resizeThumbRect.origin.x + (resizeThumbRect.size.width - ((2*THUMB_LINE_SPACING) + 3.0)) / 2.0, 
533                                                                                         resizeThumbRect.size.height / 4.0, 
534                                                                                         1.0, 
535                                                                                         resizeThumbRect.size.height / 2.0
536                                                                                 );
537                 int i;
538                 for( i=0; i<3; i++ ){
539                         NSRectFill( thumbLineRect );
540                         thumbLineRect.origin.x += (1+THUMB_LINE_SPACING);
541                 }
542                 
543                 if( shelfBackgroundColor ){
544                         [shelfBackgroundColor set];
545                         NSRectFill( NSMakeRect( 0, CONTROL_HEIGHT+1, currentShelfWidth, [self frame].size.height ) );
546                 }
547                 
548                 //Draw the string
549                 if (attributedStringValue && !shouldDrawContextButton && !shouldDrawActionButton) {
550                         NSRect  textRect = NSMakeRect(6, (NSHeight(controlRect) - stringHeight)/2, NSMinX(resizeThumbRect) - 8, stringHeight);
551                         
552                         [attributedStringValue drawInRect:textRect];
553                 }               
554         }
557 //This glass implementation is actually broken but works for Adium -- a proper one would take the width of aRect into account.
558 -(void)drawControlBackgroundInRect:(NSRect)aRect active:(BOOL)isActive{
559         NSRect  frame = [self frame];
560         
561         //Draw the background, tiling across
562     NSRect sourceRect = NSMakeRect(0, 0, backgroundSize.width, backgroundSize.height);
563     NSRect destRect = NSMakeRect(frame.origin.x, frame.origin.y, sourceRect.size.width, aRect.size.height);
564         
565     while ((destRect.origin.x < NSMaxX(frame)) && destRect.size.width > 0) {
566         //Crop
567         if (NSMaxX(destRect) > NSMaxX(frame)) {
568             sourceRect.size.width = NSWidth(destRect);
569         }
570                 
571         [background drawInRect:destRect
572                                           fromRect:sourceRect
573                                          operation:NSCompositeSourceOver
574                                           fraction:1.0];
575         destRect.origin.x += destRect.size.width;
576     }   
579 -(void)setFrame:(NSRect)aRect{
580         [super setFrame: aRect];
581         [self recalculateSizes];
584 #pragma mark Status string
585 - (void)setResizeThumbStringValue:(NSString *)inString
587         if (![inString isEqualToString:stringValue]) {
588                 [stringValue release];
589                 stringValue = [inString copy];
590                 
591                 [attributedStringValue release];
592                 if (stringValue) {
593                         NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
594                                 [NSParagraphStyle styleWithAlignment:NSLeftTextAlignment
595                                                                            lineBreakMode:NSLineBreakByTruncatingTail], NSParagraphStyleAttributeName,
596                                 [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, 
597                                 nil];
598                         
599                         stringHeight = [NSAttributedString stringHeightForAttributes:attributes];
600                         attributedStringValue = [[NSAttributedString alloc] initWithString:stringValue
601                                                                                                                                         attributes:attributes];
602                 } else {
603                         attributedStringValue = nil;
604                 }
605                 [self setNeedsDisplay:YES];
606         }
609 @end