Plugins/Dual Window Interface/AIMessageWindowController.m
author Zachary West <zacw@adium.im>
Tue Jun 02 17:22:19 2009 -0400 (2009-06-02)
changeset 2450 08aa9c3a346c
parent 2130 1b9cd87159b9
child 2634 f8f70fff48f1
permissions -rw-r--r--
Create a new advanced preference called "Confirmations". Move Quit Confirmations there.

Adds a new confirmation type for closing message windows with more than 1 tab open. Fixes #12006.

This new preference has two options: always confirm, only confirm when there's unread content. Displays an alert sheet when trying to close the window.
     1 /* 
     2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
     3  * with this source distribution.
     4  * 
     5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
     6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
     7  * or (at your option) any later version.
     8  * 
     9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
    10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
    11  * Public License for more details.
    12  * 
    13  * You should have received a copy of the GNU General Public License along with this program; if not,
    14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    15  */
    16 
    17 #import "AIDualWindowInterfacePlugin.h"
    18 #import <Adium/AIInterfaceControllerProtocol.h>
    19 #import <Adium/AIMenuControllerProtocol.h>
    20 #import "AIMessageTabViewItem.h"
    21 #import "AIMessageViewController.h"
    22 #import "AIMessageWindowController.h"
    23 #import "AIDockController.h"
    24 #import <Adium/AIToolbarControllerProtocol.h>
    25 #import <Adium/AIAccountControllerProtocol.h>
    26 #import <AIUtilities/AIApplicationAdditions.h>
    27 #import <AIUtilities/AIAttributedStringAdditions.h>
    28 #import <AIUtilities/AIImageAdditions.h>
    29 #import <AIUtilities/AIStringAdditions.h>
    30 #import <AIUtilities/AIToolbarUtilities.h>
    31 #import <AIUtilities/AIArrayAdditions.h>
    32 #import <AIUtilities/AIWindowAdditions.h>
    33 #import <Adium/AIChat.h>
    34 #import <Adium/AIListContact.h>
    35 #import <Adium/AIListObject.h>
    36 #import <PSMTabBarControl/PSMTabBarControl.h>
    37 #import <PSMTabBarControl/PSMOverflowPopUpButton.h>
    38 #import <PSMTabBarControl/PSMAdiumTabStyle.h>
    39 #import <PSMTabBarControl/PSMTabStyle.h>
    40 #import "AIMessageTabSplitView.h"
    41 #import <Adium/AIStatusIcons.h>
    42 
    43 #define KEY_MESSAGE_WINDOW_POSITION 			@"Message Window"
    44 
    45 #define AIMessageTabDragBeganNotification		@"AIMessageTabDragBeganNotification"
    46 #define AIMessageTabDragEndedNotification    	@"AIMessageTabDragEndedNotification"
    47 #define	MESSAGE_WINDOW_NIB                      @"MessageWindow"			//Filename of the message window nib
    48 #define TAB_BAR_FPS                             20.0
    49 #define TAB_BAR_STEP                            0.6
    50 #define TOOLBAR_MESSAGE_WINDOW					@"AdiumMessageWindow"			//Toolbar identifier
    51 
    52 #define HORIZONTAL_TAB_BAR_TO_VIEW_SPACING		7
    53 
    54 #define KEY_VERTICAL_TABS_WIDTH					@"Vertical Tabs Width"
    55 #define VERTICAL_DIVIDER_THICKNESS				4
    56 #define VERTICAL_TAB_BAR_TO_VIEW_SPACING		3
    57 
    58 @interface AIMessageWindowController ()
    59 - (id)initWithWindowNibName:(NSString *)windowNibName interface:(AIDualWindowInterfacePlugin *)inInterface containerID:(NSString *)inContainerID containerName:(NSString *)inName;
    60 - (void)_configureToolbar;
    61 - (void)_updateWindowTitleAndIcon;
    62 - (NSString *)_frameSaveKey;
    63 - (void)_reloadContainedChats;
    64 @end
    65 
    66 //Used to squelch compiler warnings on this private call
    67 @interface NSWindow (AISecretWindowDocumentIconAdditions)
    68 - (void)addDocumentIconButton;
    69 @end
    70 
    71 @implementation AIMessageWindowController
    72 
    73 //Create a new message window controller
    74 + (AIMessageWindowController *)messageWindowControllerForInterface:(AIDualWindowInterfacePlugin *)inInterface
    75 															withID:(NSString *)inContainerID
    76 															  name:(NSString *)inName
    77 {
    78     return [[[self alloc] initWithWindowNibName:MESSAGE_WINDOW_NIB
    79 									  interface:inInterface
    80 									containerID:inContainerID
    81 										   containerName:inName] autorelease];
    82 }
    83 
    84 //init
    85 - (id)initWithWindowNibName:(NSString *)windowNibName
    86 				  interface:(AIDualWindowInterfacePlugin *)inInterface
    87 				containerID:(NSString *)inContainerID
    88 					   containerName:(NSString *)inName
    89 {
    90 	if ((self = [super initWithWindowNibName:windowNibName])) {
    91 		NSWindow	*myWindow;
    92 	
    93 		interface = [inInterface retain];
    94 		containerName = [inName retain];
    95 		containerID = [inContainerID retain];
    96 		m_containedChats = [[NSMutableArray alloc] init];
    97 		hasShownDocumentButton = NO;
    98 		
    99 		//Load our window
   100 		myWindow = [self window];
   101 
   102 		[[NSNotificationCenter defaultCenter] addObserver:self
   103 												 selector:@selector(tabDraggingNotificationReceived:)
   104 													 name:PSMTabDragDidBeginNotification
   105 												   object:nil];
   106 		
   107 		[[NSNotificationCenter defaultCenter] addObserver:self
   108 												 selector:@selector(tabDraggingNotificationReceived:)
   109 													 name:PSMTabDragDidEndNotification
   110 												   object:nil];
   111 		
   112 		[[NSNotificationCenter defaultCenter] addObserver:self
   113 												 selector:@selector(tabBarFrameChanged:)
   114 													 name:NSViewFrameDidChangeNotification
   115 												   object:tabView_tabBar];
   116 		
   117 		[[NSNotificationCenter defaultCenter] addObserver:self 
   118 												 selector:@selector(windowWillMiniaturize:)
   119 													 name:NSWindowWillMiniaturizeNotification
   120 												   object:myWindow];
   121 		//Prefs
   122 		[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_DUAL_WINDOW_INTERFACE];
   123 		
   124 		//Register as a tab drag observer so we know when tabs are dragged over our window and can show our tab bar
   125 		[myWindow registerForDraggedTypes:[NSArray arrayWithObject:@"PSMTabBarControlItemPBType"]];
   126 	}
   127 
   128     return self;
   129 }
   130 
   131 //dealloc
   132 - (void)dealloc
   133 {
   134 	[[NSNotificationCenter defaultCenter] removeObserver:self];
   135 
   136 	/* Ensure our window is quite clear we have no desire to ever hear from it again.  sendEvent: with a flags changed
   137 	 * event is being sent to this AIMessageWindowController instance by the window after dallocing, for some reason.
   138 	 * It seems likely a double-release is involved.  I can't reproduce this locally, either... but calling
   139 	 * [self setWindow:nil] appears to fix the problem where it was being experienced..
   140 	 *
   141 	 * Something is wrong elsewhere that this could be necessary, but this doesn't hurt I don't believe.
   142 	 */
   143 	[[self window] setDelegate:nil];
   144 	[self setWindow:nil];
   145 
   146     [tabView_tabBar setDelegate:nil];
   147 
   148 	[self.containedChats release];
   149 	[toolbarItems release];
   150 	[containerName release];
   151 	[containerID release];
   152 
   153 	[adium.preferenceController unregisterPreferenceObserver:self];
   154 
   155     [super dealloc];
   156 }
   157 
   158 //Human readable container name
   159 - (NSString *)name
   160 {
   161 	return containerName;
   162 }
   163 
   164 //Internal container ID
   165 - (NSString *)containerID
   166 {
   167 	return containerID;
   168 }
   169 
   170 //PSMTabBarControl accessor
   171 - (PSMTabBarControl *)tabBar
   172 {
   173 	return tabView_tabBar;
   174 }
   175 
   176 - (NSString *)adiumFrameAutosaveName
   177 {
   178 	return [self _frameSaveKey];
   179 }
   180 
   181 //Setup our window before it is displayed
   182 - (void)windowDidLoad
   183 {
   184 	[super windowDidLoad];
   185 	
   186 	NSWindow	*theWindow = [self window];
   187 
   188     //Exclude this window from the window menu (since we add it manually)
   189     [theWindow setExcludedFromWindowsMenu:YES];
   190 	[theWindow useOptimizedDrawing:YES];
   191 
   192 	[self _configureToolbar];
   193 
   194     //Remove any tabs from our tab view, it needs to start out empty
   195     while ([tabView_messages numberOfTabViewItems] > 0) {
   196         [tabView_messages removeTabViewItem:[tabView_messages tabViewItemAtIndex:0]];
   197     }
   198 	
   199 	//Setup the tab bar
   200 	tabView_tabStyle = [[[PSMAdiumTabStyle alloc] init] autorelease];
   201 	[tabView_tabBar setStyle:tabView_tabStyle];
   202 	[tabView_tabBar setCanCloseOnlyTab:YES];
   203 	[tabView_tabBar setUseOverflowMenu:NO];
   204 	[tabView_tabBar setAllowsResizing:NO];
   205 	[tabView_tabBar setSizeCellsToFit:YES];
   206 	[tabView_tabBar setHideForSingleTab:!alwaysShowTabs];
   207 	[tabView_tabBar setSelectsTabsOnMouseDown:YES];
   208 	[tabView_tabBar setAutomaticallyAnimates:NO];
   209 	[tabView_tabBar setAllowsScrubbing:YES];
   210 	[tabView_tabBar setAllowsBackgroundTabClosing:[[NSUserDefaults standardUserDefaults] boolForKey:@"AIAllowBackgroundTabClosing"]];
   211 	[tabView_tabBar setTearOffStyle:PSMTabBarTearOffAlphaWindow];
   212 }
   213 
   214 //Frames
   215 - (NSString *)_frameSaveKey
   216 {
   217 	if ([[adium.preferenceController preferenceForKey:KEY_TABBED_CHATTING
   218 												  group:PREF_GROUP_INTERFACE] boolValue] &&
   219 		![[adium.preferenceController preferenceForKey:KEY_GROUP_CHATS_BY_GROUP
   220 												   group:PREF_GROUP_INTERFACE] boolValue]) {
   221 		return KEY_MESSAGE_WINDOW_POSITION;
   222 
   223 	} else {
   224 		//Not using tabbed chatting, or we're tabbing by groups: Save the window position on a per-container basis
   225 		return [NSString stringWithFormat:@"%@ %@",KEY_MESSAGE_WINDOW_POSITION, containerID];	
   226 	}
   227 }
   228 - (BOOL)shouldCascadeWindows
   229 {
   230 	if ([[adium.preferenceController preferenceForKey:KEY_TABBED_CHATTING  group:PREF_GROUP_INTERFACE] boolValue])
   231 		return NO;
   232 	else //Not using tabbed chatting: Cascade if we have no frame
   233 		return ([self savedFrameString] == nil);
   234 }
   235 
   236 //Close the message window
   237 - (IBAction)closeWindow:(id)sender
   238 {
   239 	windowIsClosing = YES;
   240 	
   241 	[[self window] performClose:nil];
   242 }
   243 
   244 /*!
   245  * @brief Confirm if we should close the window.
   246  */
   247 - (BOOL)windowShouldClose:(id)window
   248 {
   249 	if (!windowIsClosing
   250 		&& self.containedChats.count > 1
   251 		&& [[adium.preferenceController preferenceForKey:KEY_CONFIRM_MSG_CLOSE group:PREF_GROUP_CONFIRMATIONS] boolValue]) {
   252 		NSString *suppressionText = nil;
   253 		
   254 		NSInteger unreadCount = 0;
   255 		
   256 		for (AIChat *chat in self.containedChats) {
   257 			if (chat.unviewedContentCount) {
   258 				unreadCount++;
   259 			}
   260 		}
   261 		
   262 		switch ([[adium.preferenceController preferenceForKey:KEY_CONFIRM_MSG_CLOSE_TYPE group:PREF_GROUP_CONFIRMATIONS] integerValue]) {
   263 			case AIMessageCloseAlways:
   264 				suppressionText = AILocalizedString(@"Do not warn when closing multiple chats", nil);
   265 				break;
   266 				
   267 			case AIMessageCloseUnread:
   268 				if (unreadCount) {
   269 					suppressionText = AILocalizedString(@"Do not warn when closing unread chats", nil);
   270 				}
   271 				break;
   272 		}
   273 		
   274 		NSString *question = nil;
   275 		if (unreadCount) {
   276 			if (unreadCount == 1) {
   277 				question = [NSString stringWithFormat:AILocalizedString(@"%u chats are open in this window, 1 of which has unviewed messages. Do you want to close this window anyway?",nil),
   278 							self.containedChats.count];
   279 			} else {
   280 				question = [NSString stringWithFormat:AILocalizedString(@"%u chats are open in this window, %u of which have unviewed messages. Do you want to close this window anyway?",nil),
   281 							self.containedChats.count,
   282 							unreadCount];	
   283 			}
   284 		} else {
   285 			question = [NSString stringWithFormat:AILocalizedString(@"%u chats are open in this window. Do you want to close this window anyway?",nil),
   286 						self.containedChats.count];
   287 		}
   288 		
   289 		if (suppressionText) {
   290 			NSAlert *alert = [NSAlert alertWithMessageText:AILocalizedString(@"Are you sure you want to close this window?", nil)
   291 											 defaultButton:AILocalizedString(@"Close", nil)
   292 										   alternateButton:AILocalizedStringFromTable(@"Cancel", @"Buttons", nil)
   293 											   otherButton:nil
   294 								 informativeTextWithFormat:question];
   295 			
   296 			[alert setShowsSuppressionButton:YES];
   297 			[[alert suppressionButton] setTitle:suppressionText];
   298 			
   299 			[alert beginSheetModalForWindow:self.window 
   300 							  modalDelegate:self 
   301 							 didEndSelector:@selector(closeAlertDidEnd:returnCode:contextInfo:) 
   302 								contextInfo:nil];
   303 			
   304 			return NO;
   305 		}
   306 	}
   307 	
   308 	return YES;
   309 }
   310 
   311 - (void)closeAlertDidEnd:(NSAlert *)alert returnCode:(int)result contextInfo:(void *)contextInfo;
   312 {
   313 	
   314 	if ([alert suppressionButton].state == NSOnState) {
   315 		[adium.preferenceController setPreference:nil
   316 										   forKey:KEY_CONFIRM_MSG_CLOSE
   317 											group:PREF_GROUP_CONFIRMATIONS];
   318 	}
   319 
   320 	if (result == NSAlertDefaultReturn) {
   321 		// Dismiss the alert sheet.
   322 		[self.window orderOut:nil];
   323 		// Don't prompt again.
   324 		windowIsClosing = YES;
   325 		// Close the window.
   326 		[self closeWindow:nil];
   327 	}
   328 }
   329 
   330 /*!
   331  * @brief Called as the window closes
   332  */
   333 - (void)windowWillClose:(id)sender
   334 {
   335     NSEnumerator			*enumerator;
   336     AIMessageTabViewItem	*tabViewItem;
   337 	
   338 	if ([tabView_tabBar orientation] == PSMTabBarVerticalOrientation) {
   339 		CGFloat widthToStore;
   340 		if ([tabView_tabBar isTabBarHidden]) {
   341 			widthToStore = lastTabBarWidth;
   342 		} else {
   343 			widthToStore = NSWidth([tabView_tabBar frame]);
   344 		}
   345 
   346 		[adium.preferenceController setPreference:[NSNumber numberWithDouble:widthToStore]
   347 											 forKey:KEY_VERTICAL_TABS_WIDTH
   348 											  group:PREF_GROUP_DUAL_WINDOW_INTERFACE];
   349 	}
   350 	
   351 	windowIsClosing = YES;
   352 	[super windowWillClose:sender];
   353 
   354 	[adium.preferenceController unregisterPreferenceObserver:self];
   355 
   356     //Close all our tabs (The array will change as we remove tabs, so we must work with a copy)
   357 	enumerator = [[tabView_messages tabViewItems] reverseObjectEnumerator];
   358     while ((tabViewItem = [enumerator nextObject])) {
   359 		[adium.interfaceController closeChat:tabViewItem.chat];
   360 	}
   361 
   362 	//Chats have all closed, set active to nil, let the interface know we closed.  We should skip this step if our
   363 	//window is no longer visible, since in that case another window will have already became active.
   364 	if ([[self window] isVisible] && [[self window] isKeyWindow]) {
   365 		[adium.interfaceController chatDidBecomeActive:nil];
   366 	}
   367 	[interface containerDidClose:self];
   368 
   369     return;
   370 }
   371 
   372 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
   373 							object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
   374 {
   375     if ([group isEqualToString:PREF_GROUP_DUAL_WINDOW_INTERFACE]) {
   376 		NSWindow	*window = [self window];
   377 		alwaysShowTabs = ![[prefDict objectForKey:KEY_AUTOHIDE_TABBAR] boolValue];
   378 		[tabView_tabBar setHideForSingleTab:!alwaysShowTabs];
   379 		NSNumber *useOverflow = [prefDict objectForKey:KEY_TABBAR_OVERFLOW];
   380 		[tabView_tabBar setUseOverflowMenu:(useOverflow ? [useOverflow boolValue] : YES)];
   381 		
   382 		[[tabView_tabBar overflowPopUpButton] setAlternateImage:[AIStatusIcons statusIconForStatusName:@"content" statusType:AIAvailableStatusType iconType:AIStatusIconTab direction:AIIconNormal]];
   383 		//NSImage *overflowImage = [[[NSImage alloc] initByReferencingFile:[[NSBundle mainBundle] pathForImageResource:@"overflow_overlay"]] autorelease];
   384 		//[[tabView_tabBar overflowPopUpButton] setAlternateImage:overflowImage];
   385 		
   386 		//change the frame of the tab bar according to the orientation
   387 		if (firstTime || [key isEqualToString:KEY_TABBAR_POSITION]) {
   388 			PSMTabBarOrientation orientation;
   389 			
   390 			tabPosition = [[prefDict objectForKey:KEY_TABBAR_POSITION] integerValue];
   391 			orientation = ((tabPosition == AdiumTabPositionBottom || tabPosition == AdiumTabPositionTop) ?
   392 						   PSMTabBarHorizontalOrientation :
   393 						   PSMTabBarVerticalOrientation);
   394 
   395 			NSRect tabBarFrame = [tabView_tabBar frame], tabViewMessagesFrame = [tabView_messages frame];
   396 			NSRect contentRect = [[[self window] contentView] frame];
   397 			
   398 			//remove the split view if the last orientation was vertical
   399 			if ([tabView_tabBar orientation] == PSMTabBarVerticalOrientation) {
   400 				[tabView_messages retain];
   401 				[tabView_messages removeFromSuperview];
   402 				[tabView_tabBar retain];
   403 				[tabView_tabBar removeFromSuperview];
   404 				[tabView_splitView removeFromSuperview];
   405 				
   406 				[[[self window] contentView] addSubview:tabView_messages];
   407 				[[[self window] contentView] addSubview:tabView_tabBar];
   408 				[tabView_messages release];
   409 				[tabView_tabBar release];
   410 			}
   411 			
   412 			[tabView_tabBar setOrientation:orientation];
   413 			
   414 			switch (orientation) {
   415 				case PSMTabBarHorizontalOrientation:
   416 				{
   417 					tabBarFrame.size.height = [tabView_tabBar isTabBarHidden] ? 1 : kPSMTabBarControlHeight;
   418 					tabBarFrame.size.width = contentRect.size.width + 1;
   419 					
   420 					//set the position of the tab bar (top/bottom)
   421 					if (tabPosition == AdiumTabPositionBottom) {
   422 						tabBarFrame.origin.y = NSMinY(contentRect);
   423 						tabViewMessagesFrame.origin.y = NSHeight(tabBarFrame) + ([tabView_tabBar isTabBarHidden] ? 0 : (HORIZONTAL_TAB_BAR_TO_VIEW_SPACING - 1));
   424 						tabViewMessagesFrame.size.height = (NSHeight(contentRect) - NSHeight(tabBarFrame) -
   425 															([tabView_tabBar isTabBarHidden] ? 0 : (HORIZONTAL_TAB_BAR_TO_VIEW_SPACING - 2)));
   426 						[tabView_tabBar setAutoresizingMask:(NSViewMaxYMargin | NSViewWidthSizable)];
   427 						
   428 					} else {
   429 						// This arbitrary sizedown is so that top tabs look visually connected to their content below.
   430 						tabBarFrame.size.height -= 3;
   431 						
   432 						tabBarFrame.origin.y = NSMaxY(contentRect) - NSHeight(tabBarFrame);
   433 						tabViewMessagesFrame.origin.y = NSMinY(contentRect);
   434 						tabViewMessagesFrame.size.height = NSHeight(contentRect) - NSHeight(tabBarFrame);
   435 						[tabView_tabBar setAutoresizingMask:(NSViewMinYMargin | NSViewWidthSizable)];
   436 					}
   437 					/* If the cell is less than 60, icon + title + unread message count may overlap */
   438 					[tabView_tabBar setCellMinWidth:60];
   439 					[tabView_tabBar setCellMaxWidth:250];
   440 					
   441 					tabBarFrame.origin.x = 0;
   442 					
   443 					//Items within the tabview draw frames, so be sure to clip the left and right edges.
   444 					tabViewMessagesFrame.origin.x = -1;
   445 					tabViewMessagesFrame.size.width = NSWidth(contentRect) + 2;
   446 					break;
   447 				}
   448 				case PSMTabBarVerticalOrientation:
   449 				{
   450 					CGFloat width = ([prefDict objectForKey:KEY_VERTICAL_TABS_WIDTH] ?
   451 								   [[prefDict objectForKey:KEY_VERTICAL_TABS_WIDTH] doubleValue] :
   452 								   100);
   453 					lastTabBarWidth = width;
   454 					
   455 					tabBarFrame.size.height = [[[self window] contentView] frame].size.height;
   456 					tabBarFrame.size.width = [tabView_tabBar isTabBarHidden] ? 1 : width;
   457 					tabBarFrame.origin.y = NSMinY(contentRect);
   458 					tabViewMessagesFrame.origin.y = NSMinY(contentRect);
   459 					tabViewMessagesFrame.size.height = NSHeight(contentRect) + 1;
   460 					tabViewMessagesFrame.size.width = NSWidth(contentRect) - NSWidth(tabBarFrame) - 5;
   461 					
   462 					//set the position of the tab bar (left/right)
   463 					if (tabPosition == AdiumTabPositionLeft) {
   464 						tabBarFrame.origin.x = NSMinX(contentRect);
   465 						tabViewMessagesFrame.origin.x = NSMaxX(tabBarFrame);
   466 						[tabView_tabBar setAutoresizingMask:NSViewHeightSizable];
   467 					} else {
   468 						tabViewMessagesFrame.origin.x = NSMinX(contentRect);
   469 						tabBarFrame.origin.x = NSWidth(contentRect) - NSWidth(tabBarFrame) + 1;
   470 						[tabView_tabBar setAutoresizingMask:NSViewHeightSizable | NSViewMinXMargin];
   471 					}
   472 					[tabView_tabBar setCellMinWidth:50];
   473 					[tabView_tabBar setCellMaxWidth:200];
   474 					
   475 					//put the subviews into a split view
   476 					NSRect splitViewRect = [[[self window] contentView] frame];
   477 					splitViewRect.size.width++;
   478 					splitViewRect.size.height++;
   479 					tabView_splitView = [[[AIMessageTabSplitView alloc] initWithFrame:splitViewRect] autorelease];
   480 					[tabView_splitView setDividerThickness:([tabView_tabBar isTabBarHidden] ? 0 : VERTICAL_DIVIDER_THICKNESS)];
   481 					[tabView_splitView setVertical:YES];
   482 					[tabView_splitView setDelegate:self];
   483 					if (tabPosition == AdiumTabPositionLeft) {
   484 						[tabView_splitView addSubview:tabView_tabBar];
   485 						[tabView_splitView addSubview:tabView_messages];
   486 						[tabView_splitView setLeftColor:[NSColor colorWithCalibratedWhite:0.92 alpha:1.0]
   487 											 rightColor:[NSColor colorWithCalibratedWhite:0.91 alpha:1.0]];
   488 					} else {
   489 						[tabView_splitView addSubview:tabView_messages];
   490 						[tabView_splitView addSubview:tabView_tabBar];
   491 						[tabView_splitView setLeftColor:[NSColor colorWithCalibratedWhite:0.91 alpha:1.0]
   492 											 rightColor:[NSColor colorWithCalibratedWhite:0.92 alpha:1.0]];
   493 					}
   494 					[tabView_splitView adjustSubviews];
   495 					[tabView_splitView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
   496 					[[[self window] contentView] addSubview:tabView_splitView];
   497 					break;
   498 				}
   499 			}
   500 			
   501 			[tabView_messages setFrame:tabViewMessagesFrame];
   502 			[tabView_tabBar setFrame:tabBarFrame];
   503 			
   504 			//update the tab bar and tab view frame
   505 			[[self window] display];
   506 		}
   507 		
   508 		//set tab style drawing attributes
   509 		[tabView_tabStyle setDrawsRight:(tabPosition == AdiumTabPositionRight)];
   510 		[tabView_tabStyle setDrawsUnified:(tabPosition == AdiumTabPositionTop)];
   511 		//[[[self window] toolbar] setShowsBaselineSeparator:(tabPosition != AdiumTabPositionTop)];
   512 		
   513 		[self _updateWindowTitleAndIcon];
   514 
   515 		AIWindowLevel	windowLevel = [[prefDict objectForKey:KEY_WINDOW_LEVEL] integerValue];
   516 		NSInteger				level = NSNormalWindowLevel;
   517 		
   518 		switch (windowLevel) {
   519 			case AINormalWindowLevel: level = NSNormalWindowLevel; break;
   520 			case AIFloatingWindowLevel: level = NSFloatingWindowLevel; break;
   521 			case AIDesktopWindowLevel: level = kCGDesktopWindowLevel; break;
   522 		}
   523 		[window setLevel:level];
   524 		[window setHidesOnDeactivate:[[prefDict objectForKey:KEY_WINDOW_HIDE] boolValue]];
   525     }
   526 }
   527 
   528 - (void)updateOverflowMenuUnviewedContentIcon
   529 {
   530 	BOOL someUnviewedContent = NO;
   531 	
   532 	NSInteger count = [[tabView_tabBar representedTabViewItems] count];
   533 	for (NSInteger i = [tabView_tabBar numberOfVisibleTabs]; i < count; i++) {
   534 		if ([[[[tabView_tabBar representedTabViewItems] objectAtIndex:i] chat] unviewedContentCount] > 0) {
   535 			someUnviewedContent = YES;
   536 			break;
   537 		}
   538 	}
   539 	
   540 	[[tabView_tabBar overflowPopUpButton] setAnimatingAlternateImage:someUnviewedContent];	
   541 }
   542 
   543 
   544 - (void)updateIconForTabViewItem:(AIMessageTabViewItem *)tabViewItem
   545 {
   546 	if (tabViewItem == [tabView_messages selectedTabViewItem]) {
   547 		[self _updateWindowTitleAndIcon];
   548 	}
   549 	
   550 	if ([[tabView_tabBar representedTabViewItems] indexOfObject:tabViewItem] >= [tabView_tabBar numberOfVisibleTabs]) {
   551 		//The chat is in the overflow menu. If any chat has unviewed content, it should be animating to demonstrate that.
   552 		[self updateOverflowMenuUnviewedContentIcon];
   553 	}
   554 }
   555 
   556 - (AdiumTabPosition)tabPosition
   557 {
   558 	return tabPosition;
   559 }
   560 
   561 //Contained Chats ------------------------------------------------------------------------------------------------------
   562 #pragma mark Contained Chats
   563 //Add a tab view item container at the end of the tabs (without changing the current selection)
   564 - (void)addTabViewItem:(AIMessageTabViewItem *)inTabViewItem
   565 {    
   566     [self addTabViewItem:inTabViewItem atIndex:-1 silent:NO];
   567 }
   568 
   569 //Add a tab view item container (without changing the current selection)
   570 //If silent is NO, the interface controller will be informed of the add
   571 - (void)addTabViewItem:(AIMessageTabViewItem *)inTabViewItem atIndex:(NSInteger)index silent:(BOOL)silent
   572 {
   573 	/* XXX This mirrors the hack in -[AIMessageTabViewItem initWithMessageView]. It may have been undone
   574 	 * in removeTabViewItem:silent: below if the tab was moving between windows.
   575 	 */
   576 	[inTabViewItem setIdentifier:inTabViewItem];
   577 
   578 	if (index == -1) {
   579 		[tabView_messages addTabViewItem:inTabViewItem];
   580 	} else {
   581 		[tabView_messages insertTabViewItem:inTabViewItem atIndex:index];
   582 	}
   583 
   584 	//Refresh our list and order of chats
   585 	[self _reloadContainedChats];
   586 	
   587 	if (![tabView_messages selectedTabViewItem]) [tabView_messages selectNextTabViewItem:nil];
   588 	
   589 	if (!silent) [adium.interfaceController chatDidOpen:inTabViewItem.chat];
   590 }
   591 
   592 //Remove a tab view item container
   593 //If silent is NO, the interface controller will be informed of the remove
   594 - (void)removeTabViewItem:(AIMessageTabViewItem *)inTabViewItem silent:(BOOL)silent
   595 {
   596 	/* When a tab isn't selected, its views are not within any window. We want the tab to be able to remove tracking rects
   597 	 * from the window before closing, so if it isn't selected we need to select it briefly to let this happen. Since this is
   598 	 * all within the same run loop, as long as code in the tab view's delegate is well-behaved and uses setNeedsDisplay: rather
   599 	 * than display if it does drawing, the UI shouldn't change at all.
   600 	 */
   601 	if ([tabView_messages selectedTabViewItem] != inTabViewItem) {
   602 		NSTabViewItem	*oldTabViewItem = [tabView_messages selectedTabViewItem];
   603 		[tabView_messages selectTabViewItem:inTabViewItem];
   604 		
   605 		//The tab view item needs to know that this window controller no longer contains it
   606 		[inTabViewItem setWindowController:nil];	
   607 
   608 		[tabView_messages selectTabViewItem:oldTabViewItem];
   609 
   610 	} else {
   611 		//The tab view item needs to know that this window controller no longer contains it
   612 		[inTabViewItem setWindowController:nil];
   613 	}
   614 	
   615 
   616     //If the tab is selected, select the next tab before closing it (To mirror the behavior of safari)
   617     if (!windowIsClosing && inTabViewItem == [tabView_messages selectedTabViewItem]) {
   618 		[tabView_messages selectNextTabViewItem:nil];
   619     }
   620 	
   621     //Remove the tab and let the interface know a container closed
   622 	[m_containedChats removeObject:inTabViewItem.chat];
   623 	if (!silent) [adium.interfaceController chatDidClose:inTabViewItem.chat];
   624 
   625 	//Now remove the tab view item from our NSTabView
   626     [tabView_messages removeTabViewItem:inTabViewItem];
   627 
   628 	/* AIMessageTabViewItem sets itself as its own identifer.  We have to break the recursive retain from the outside.
   629 	 * This must be done last so that the NSTabView and its delegate (PSMTabBarControl) can still make use of the idenfitier.
   630 	 */
   631 	[inTabViewItem setIdentifier:nil];
   632 
   633 	//close if we're empty
   634 	if (!windowIsClosing && [self.containedChats count] == 0) {
   635 		[self closeWindow:nil];
   636 	}
   637 }
   638 
   639 - (void)moveTabViewItem:(AIMessageTabViewItem *)inTabViewItem toIndex:(NSInteger)index
   640 {
   641 	AIChat	*chat = inTabViewItem.chat;
   642 
   643 	if ([self.containedChats indexOfObject:chat] != index) {
   644 		NSMutableArray *cells = [tabView_tabBar cells];
   645 		
   646 		[cells moveObject:[cells objectAtIndex:[[tabView_tabBar representedTabViewItems] indexOfObject:inTabViewItem]] toIndex:index];
   647 		[tabView_tabBar setNeedsDisplay:YES];
   648 		[m_containedChats moveObject:chat toIndex:index];
   649 		
   650 		[adium.interfaceController chatOrderDidChange];
   651 	}
   652 }
   653 
   654 //Returns YES if we are empty (currently contain no chats)
   655 - (BOOL)containerIsEmpty
   656 {
   657 	return [self.containedChats count] == 0;
   658 }
   659 
   660 //Returns an array of the chats we contain
   661 @synthesize containedChats = m_containedChats;
   662 
   663 - (void)_reloadContainedChats
   664 {
   665 	NSEnumerator			*enumerator;
   666 	AIMessageTabViewItem	*tabViewItem;
   667 
   668 	//Update our contained chats array to mirror the order of the tabs
   669 	[m_containedChats release]; m_containedChats = [[NSMutableArray alloc] init];
   670 	enumerator = [[tabView_messages tabViewItems] objectEnumerator];
   671 	
   672 	while ((tabViewItem = [enumerator nextObject])) {
   673 		[tabViewItem setWindowController:self];
   674 		[m_containedChats addObject:[tabViewItem chat]];
   675 	}
   676 }
   677 
   678 //Active Chat Tracking -------------------------------------------------------------------------------------------------
   679 #pragma mark Active Chat Tracking
   680 //Our selected tab is now the active chat
   681 - (void)windowDidBecomeKey:(NSNotification *)notification
   682 {
   683 	[adium.interfaceController chatDidBecomeActive:[(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] chat]];
   684 }
   685 
   686 //Our selected tab is no longer the active chat
   687 - (void)windowDidResignKey:(NSNotification *)notification
   688 {
   689 	[((AIMessageTabViewItem *)tabView_messages.selectedTabViewItem).messageViewController.messageDisplayController markForFocusChange];
   690 	[adium.interfaceController chatDidBecomeActive:nil];
   691 }
   692 
   693 //Update our window title
   694 - (void)_updateWindowTitleAndIcon
   695 {
   696 	NSString	*label = [(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] label];
   697 	NSString	*title;
   698 	NSButton	*button;
   699 	NSWindow	*window = [self window];
   700 	
   701 	//Window Title
   702     if (([tabView_messages numberOfTabViewItems] == 1) || !containerName) {
   703         title = (label ? [NSString stringWithFormat:@"%@", label] : nil);
   704     } else {
   705 		if (containerName && label) {
   706 			title = [NSString stringWithFormat:@"%@ - %@", containerName, label];
   707 		} else {
   708 			if (containerName)
   709 				title = containerName;
   710 			else if (label)
   711 				title = label;
   712 			else
   713 				title = nil;
   714 		}
   715     }
   716 	
   717 	if (title) [window setTitle:title];
   718 	
   719 	//Window Icon (We display state in the window title if tabs are not visible)
   720 	if (!hasShownDocumentButton) {
   721 		if ([window respondsToSelector:@selector(addDocumentIconButton)]) {
   722 			[window addDocumentIconButton];
   723 		}
   724 		hasShownDocumentButton = YES;
   725 	}
   726 
   727 	button = [window standardWindowButton:NSWindowDocumentIconButton];
   728 		  
   729 	if ([tabView_tabBar isTabBarHidden]) {
   730 		NSImage *image = [(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] stateIcon];
   731 		if (image != [button image]) {
   732 			[button setImage:image];
   733 		}
   734 
   735 	} else {
   736 		if ([button image]) {
   737 			[button setImage:nil];
   738 		}
   739 	}
   740 }
   741 
   742 - (AIChat *)activeChat
   743 {
   744 	AIMessageTabViewItem *selectedTabViewItem = (AIMessageTabViewItem *)[tabView_messages selectedTabViewItem];
   745 	
   746 	if (![selectedTabViewItem isKindOfClass:[AIMessageTabViewItem class]]) {
   747 		return nil;
   748 	}
   749 	
   750 	return [selectedTabViewItem chat];
   751 }
   752 
   753 //AISplitView Delegate -------------------------------------------------------------------------------------------------
   754 #pragma mark AISplitView Delegate
   755 
   756 #define MINIMUM_WIDTH_FOR_VERTICAL_TABS 50
   757 #define MAXIMUM_WIDTH_FOR_VERTICAL_TABS 250
   758 
   759 //handles the minimum size of vertical tabs
   760 - (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
   761 {
   762 	CGFloat min = proposedMin;
   763 
   764 	if (sender == tabView_splitView) {
   765 		switch (tabPosition) {
   766 			case AdiumTabPositionBottom:
   767 			case AdiumTabPositionTop:
   768 				/* should never be passed these */
   769 				break;
   770 			case AdiumTabPositionLeft:
   771 				min = ([tabView_tabBar isTabBarHidden] ? 0 : MINIMUM_WIDTH_FOR_VERTICAL_TABS);
   772 				break;
   773 			case AdiumTabPositionRight:
   774 				min = ([tabView_tabBar isTabBarHidden] ?
   775 					   NSWidth([tabView_splitView frame]) :
   776 					   (NSWidth([tabView_splitView frame]) - MAXIMUM_WIDTH_FOR_VERTICAL_TABS - [sender dividerThickness]));
   777 				break;				
   778 		}
   779 	} else {
   780 		NSLog(@"Unknown split view");
   781 	}
   782 	
   783 	return min;
   784 }
   785 
   786 //handles the maximum size of vertical tabs
   787 - (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
   788 {
   789 	CGFloat max = proposedMax;
   790 	
   791 	if (sender == tabView_splitView) {
   792 		switch (tabPosition) {
   793 			case AdiumTabPositionBottom:
   794 			case AdiumTabPositionTop:
   795 				/* should never be passed these */
   796 				break;
   797 			case AdiumTabPositionLeft:
   798 				max = MAXIMUM_WIDTH_FOR_VERTICAL_TABS;
   799 				break;
   800 			case AdiumTabPositionRight:
   801 				max = proposedMax - MINIMUM_WIDTH_FOR_VERTICAL_TABS;
   802 				break;
   803 		}
   804 	} else {
   805 		NSLog(@"Unknown split view");
   806 	}
   807 	
   808 	return max;
   809 }
   810 
   811 - (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
   812 {
   813 	NSRect messageFrame = [tabView_messages frame], tabBarFrame = [tabView_tabBar frame];
   814 	messageFrame.size = NSMakeSize([sender frame].size.width - tabBarFrame.size.width - [sender dividerThickness], [sender frame].size.height);
   815 	tabBarFrame.size = NSMakeSize(tabBarFrame.size.width, [sender frame].size.height);
   816 	
   817 	if (sender == tabView_splitView) {
   818 		switch (tabPosition) {
   819 			case AdiumTabPositionBottom:
   820 			case AdiumTabPositionTop:
   821 				/* should never be passed these */
   822 				break;
   823 			case AdiumTabPositionLeft:
   824 				messageFrame.origin.x = NSWidth(tabBarFrame) + [sender dividerThickness];
   825 				break;
   826 			case AdiumTabPositionRight:
   827 				messageFrame.origin.x = 0;
   828 				tabBarFrame.origin.x = NSWidth(messageFrame) + [sender dividerThickness];
   829 				break;
   830 		}
   831 	}
   832 	
   833 	[tabView_messages setFrame:messageFrame];
   834 	[tabView_tabBar setFrame:tabBarFrame];
   835 }
   836 
   837 
   838 //PSMTabBarControl Delegate -------------------------------------------------------------------------------------------------
   839 #pragma mark PSMTabBarControl Delegate
   840 
   841 //Handle closing a tab
   842 - (BOOL)tabView:(NSTabView *)tabView shouldCloseTabViewItem:(NSTabViewItem *)tabViewItem
   843 {
   844 	//The window controller handles removing the tab as we need to dispose of tracking rects properly
   845 	if ([tabViewItem respondsToSelector:@selector(chat)]) {
   846 		AIChat	*chat = [(AIMessageTabViewItem *)tabViewItem chat];
   847 		
   848 		[adium.interfaceController closeChat:chat];
   849 	}
   850 	
   851 	return NO;
   852 }
   853 
   854 - (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
   855 {
   856 	AIMessageTabViewItem *selectedTabViewItem = (AIMessageTabViewItem *)[tabView_messages selectedTabViewItem];
   857 	
   858 	if ([selectedTabViewItem isKindOfClass:[AIMessageTabViewItem class]]) {
   859         [selectedTabViewItem tabViewItemWillDeselect];
   860 	}
   861 }
   862 
   863 //Our selected tab has changed, update the active chat
   864 - (void)tabView:(NSTabView *)aTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
   865 {
   866 	if (tabViewItem != nil) {
   867 		AIChat	*chat = [(AIMessageTabViewItem *)tabViewItem chat];
   868         [(AIMessageTabViewItem *)tabViewItem tabViewItemWasSelected]; //Let the tab know it was selected
   869 		
   870         if ([[self window] isMainWindow]) { //If our window is main, set the newly selected container as active
   871 			[adium.interfaceController chatDidBecomeActive:chat];
   872         }
   873 		
   874         [self _updateWindowTitleAndIcon]; //Reflect change in window title
   875 		[adium.interfaceController chatDidBecomeVisible:chat inWindow:[self window]];
   876     }
   877 }
   878 
   879 - (BOOL)tabView:(NSTabView*)tabView shouldDragTabViewItem:(NSTabViewItem *)tabViewItem fromTabBar:(PSMTabBarControl *)tabBarControl
   880 {
   881 	return YES;
   882 }
   883 
   884 - (BOOL)tabView:(NSTabView*)tabView shouldDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl
   885 {
   886 	return YES;
   887 }
   888 
   889 - (void)tabView:(NSTabView *)tabView closeWindowForLastTabViewItem:(NSTabViewItem *)tabViewItem
   890 {
   891 	[self closeWindow:self];
   892 }
   893 
   894 //Contextual menu for tabs
   895 - (NSMenu *)tabView:(NSTabView *)tabView menuForTabViewItem:(NSTabViewItem *)tabViewItem
   896 {
   897 	AIChat			*chat = [(AIMessageTabViewItem *)tabViewItem chat];
   898     AIListContact	*selectedObject = chat.listObject.parentContact;
   899     NSMenu			*tmp = nil;
   900 
   901     if (selectedObject) {
   902 		NSMutableArray *locations;
   903 		if ([selectedObject isIntentionallyNotAStranger]) {
   904 			locations = [NSMutableArray arrayWithObjects:
   905 				[NSNumber numberWithInteger:Context_Contact_Manage],
   906 				[NSNumber numberWithInteger:Context_Contact_Action],
   907 				[NSNumber numberWithInteger:Context_Contact_NegativeAction],
   908 				[NSNumber numberWithInteger:Context_Contact_ChatAction],
   909 				[NSNumber numberWithInteger:Context_Contact_Additions], nil];
   910 		} else {
   911 			locations = [NSMutableArray arrayWithObjects:
   912 				[NSNumber numberWithInteger:Context_Contact_Manage],
   913 				[NSNumber numberWithInteger:Context_Contact_Action],
   914 				[NSNumber numberWithInteger:Context_Contact_NegativeAction],
   915 				[NSNumber numberWithInteger:Context_Contact_ChatAction],
   916 				[NSNumber numberWithInteger:Context_Contact_Stranger_ChatAction],
   917 				[NSNumber numberWithInteger:Context_Contact_Additions], nil];
   918 		}
   919 		
   920 		[locations addObject:[NSNumber numberWithInteger:Context_Tab_Action]];
   921 
   922 		tmp = [adium.menuController contextualMenuWithLocations:locations
   923 													 forListObject:selectedObject
   924 														   inChat:chat];
   925         
   926     } else if (chat.isGroupChat) {
   927 		NSArray *locations = [NSArray arrayWithObjects:
   928 							  [NSNumber numberWithInteger:Context_GroupChat_Manage],
   929 							  [NSNumber numberWithInteger:Context_GroupChat_Action],
   930 							  [NSNumber numberWithInteger:Context_Tab_Action], nil];
   931 		
   932 		tmp = [adium.menuController contextualMenuWithLocations:locations
   933 														forChat:chat];
   934 	}
   935 	
   936 	return tmp;
   937 }
   938 
   939 //Tab count changed
   940 - (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)tabView
   941 {
   942     [self _updateWindowTitleAndIcon];
   943 	[self _reloadContainedChats];
   944 	[adium.interfaceController chatOrderDidChange];
   945 	
   946 	//Remaining disabled until the last crasher I know of is removed
   947 	/*if ([tabView numberOfTabViewItems] > 0) {
   948 		[tabView_tabBar setAutomaticallyAnimates:YES];
   949 	}*/
   950 }
   951 
   952 //Tabs reordered
   953 - (void)tabView:(NSTabView*)aTabView didDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl;
   954 {
   955 	[self _reloadContainedChats];
   956 	[adium.interfaceController chatOrderDidChange];
   957 }
   958 
   959 //Allow dragging of text
   960 - (NSArray *)allowedDraggedTypesForTabView:(NSTabView *)aTabView
   961 {
   962 	return [NSArray arrayWithObjects:NSRTFPboardType, NSStringPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPDFPboardType, NSPICTPboardType, nil];
   963 }
   964 
   965 //Accept dragged text
   966 - (void)tabView:(NSTabView *)aTabView acceptedDraggingInfo:(id <NSDraggingInfo>)draggingInfo onTabViewItem:(NSTabViewItem *)tabViewItem
   967 {
   968 	[[(AIMessageTabViewItem *)tabViewItem messageViewController] addDraggedDataToTextEntryView:draggingInfo];
   969 }
   970 
   971 //Get an image representation of the chat
   972 - (NSImage *)tabView:(NSTabView *)tabView imageForTabViewItem:(NSTabViewItem *)tabViewItem offset:(NSSize *)offset styleMask:(unsigned *)styleMask
   973 {
   974 	// grabs whole window image
   975 	NSImage *viewImage = [[[NSImage alloc] init] autorelease];
   976 	NSRect contentFrame = [[[self window] contentView] frame];
   977 	[[[self window] contentView] lockFocus];
   978 	NSBitmapImageRep *viewRep = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:contentFrame] autorelease];
   979 	[viewImage addRepresentation:viewRep];
   980 	[[[self window] contentView] unlockFocus];
   981 	
   982     // grabs snapshot of dragged tabViewItem's view (represents content being dragged)
   983 	NSView *viewForImage = [tabViewItem view];
   984 	NSRect viewRect = [viewForImage frame];
   985 	NSImage *tabViewImage = [[[NSImage alloc] initWithSize:viewRect.size] autorelease];
   986 	[tabViewImage lockFocus];
   987 	[viewForImage drawRect:[viewForImage bounds]];
   988 	[tabViewImage unlockFocus];
   989 	
   990 	[viewImage lockFocus];
   991 	NSPoint tabOrigin = [tabView frame].origin;
   992 	tabOrigin.x += 10;
   993 	tabOrigin.y += 13;
   994 	[tabViewImage compositeToPoint:tabOrigin operation:NSCompositeSourceOver];
   995 	[viewImage unlockFocus];
   996 	
   997 	//draw over where the tab bar would usually be
   998 	NSRect tabFrame = [tabView_tabBar frame];
   999 	[viewImage lockFocus];
  1000 	[[NSColor windowBackgroundColor] set];
  1001 	NSRectFill(tabFrame);
  1002 	//draw the background flipped, which is actually the right way up
  1003 	NSAffineTransform *transform = [NSAffineTransform transform];
  1004 	[transform scaleXBy:1.0 yBy:-1.0];
  1005 	[transform concat];
  1006 	tabFrame.origin.y = -tabFrame.origin.y - tabFrame.size.height;
  1007 	[(id <PSMTabStyle>)[[tabView delegate] style] drawBackgroundInRect:tabFrame];
  1008 	[transform invert];
  1009 	[transform concat];
  1010 	
  1011 	[viewImage unlockFocus];
  1012 	
  1013 	id <PSMTabStyle> style = (id <PSMTabStyle>)[[tabView delegate] style];
  1014 	
  1015 	switch (tabPosition) {
  1016 		case AdiumTabPositionBottom:
  1017 			offset->width = [style leftMarginForTabBarControl];
  1018 			offset->height = contentFrame.size.height;
  1019 			break;
  1020 		case AdiumTabPositionTop:
  1021 			offset->width = [style leftMarginForTabBarControl];
  1022 			offset->height = 21;
  1023 			break;
  1024 		case AdiumTabPositionLeft:
  1025 			offset->width = 0;
  1026 			offset->height = 21 + [style topMarginForTabBarControl];
  1027 			break;
  1028 		case AdiumTabPositionRight:
  1029 			offset->width = [tabView_tabBar frame].origin.x;
  1030 			offset->height = 21 + [style topMarginForTabBarControl];
  1031 			break;
  1032 	}
  1033 	
  1034 	*styleMask = NSTitledWindowMask;
  1035 	
  1036 	return viewImage;
  1037 }
  1038 
  1039 //Create a new tab window
  1040 - (PSMTabBarControl *)tabView:(NSTabView *)tabView newTabBarForDraggedTabViewItem:(NSTabViewItem *)tabViewItem atPoint:(NSPoint)point
  1041 {
  1042 	id newController = [interface openNewContainer];
  1043 	NSRect frame;
  1044 	id <PSMTabStyle> style = (id <PSMTabStyle>)[[tabView delegate] style];
  1045 	
  1046 	//set the size of the new window
  1047 	//set the size and origin separately so that toolbar visibility and size doesn't mess things up
  1048 	frame.size = [[self window] frame].size;
  1049 	frame.origin = NSZeroPoint;
  1050 	[[newController window] setFrame:frame display:NO];
  1051 	
  1052 	switch (tabPosition) {
  1053 		case AdiumTabPositionBottom:
  1054 			point.x -= [style leftMarginForTabBarControl];
  1055 			point.y -= 22;
  1056 			break;
  1057 		case AdiumTabPositionTop:
  1058 			point.x -= [style leftMarginForTabBarControl];
  1059 			point.y -= NSHeight([[[newController window] contentView] frame]) + 1;
  1060 			break;
  1061 		case AdiumTabPositionLeft:
  1062 			point.y -= NSHeight([[[newController window] contentView] frame]) - [style topMarginForTabBarControl] + 1;
  1063 			break;
  1064 		case AdiumTabPositionRight:
  1065 			point.x -= NSMinX([tabView_tabBar frame]);
  1066 			point.y -= NSHeight([[[newController window] contentView] frame]) - [style topMarginForTabBarControl] + 1;
  1067 	}
  1068 	
  1069 	//set the origin point of the new window
  1070 	frame.origin = point;
  1071 	[[newController window] setFrame:frame display:NO];
  1072 	
  1073 	return [newController tabBar];
  1074 }
  1075 
  1076 - (void)tabView:(NSTabView *)tabView tabBarDidHide:(PSMTabBarControl *)tabBarControl
  1077 {
  1078     //hide the space between the tab bar and the tab view
  1079     NSRect frame = [tabView frame];
  1080 	switch ([tabBarControl orientation]) {
  1081 		case PSMTabBarHorizontalOrientation:
  1082 			/* We put a space between a bottom horizontal tab bar and the message tab view.
  1083 			 * This space is not needed when the tab bar is at the top.
  1084 			 */			
  1085 			if (tabPosition == AdiumTabPositionBottom) {
  1086 				frame.origin.y -= HORIZONTAL_TAB_BAR_TO_VIEW_SPACING;
  1087 				frame.size.height += HORIZONTAL_TAB_BAR_TO_VIEW_SPACING;				
  1088 			}
  1089 			break;
  1090 
  1091 		case PSMTabBarVerticalOrientation:
  1092 			frame.origin.x -= VERTICAL_TAB_BAR_TO_VIEW_SPACING;
  1093 			frame.size.width += VERTICAL_TAB_BAR_TO_VIEW_SPACING;
  1094 			
  1095 			[tabView_splitView setDividerThickness:0];
  1096 			break;
  1097 	}
  1098 
  1099 	[tabView setFrame:frame];
  1100 	[tabBarControl setHidden:YES];
  1101 	[tabView setNeedsDisplay:YES];
  1102 
  1103 	[[tabView_messages tabViewItems] makeObjectsPerformSelector:@selector(tabViewDidChangeVisibility)];
  1104 }
  1105 
  1106 - (void)tabView:(NSTabView *)tabView tabBarDidUnhide:(PSMTabBarControl *)tabBarControl
  1107 {
  1108 	//show the space between the tab bar and the tab view
  1109     NSRect frame = [tabView frame];
  1110 	
  1111 	switch ([tabBarControl orientation]) {
  1112 		case PSMTabBarHorizontalOrientation:
  1113 			/* We put a space between a bottom horizontal tab bar and the message tab view.
  1114 			 * This space is not needed when the tab bar is at the top.
  1115 			 */
  1116 			if (tabPosition == AdiumTabPositionBottom) {
  1117 				frame.origin.y += HORIZONTAL_TAB_BAR_TO_VIEW_SPACING;
  1118 				frame.size.height -= HORIZONTAL_TAB_BAR_TO_VIEW_SPACING;				
  1119 			}
  1120 			break;
  1121 
  1122 		case PSMTabBarVerticalOrientation:
  1123 			frame.origin.x += VERTICAL_TAB_BAR_TO_VIEW_SPACING;
  1124 			frame.size.width -= VERTICAL_TAB_BAR_TO_VIEW_SPACING;
  1125 			
  1126 			[tabView_splitView setDividerThickness:VERTICAL_DIVIDER_THICKNESS];
  1127 			break;
  1128     }
  1129     
  1130     [tabView setFrame:frame];
  1131 	[tabBarControl setHidden:NO];
  1132     [tabView setNeedsDisplay:YES];
  1133 	
  1134 	[[tabView_messages tabViewItems] makeObjectsPerformSelector:@selector(tabViewDidChangeVisibility)];
  1135 }
  1136 
  1137 - (float)desiredWidthForVerticalTabBar:(PSMTabBarControl *)tabBarControl
  1138 {
  1139 	return (lastTabBarWidth ? lastTabBarWidth : 120);
  1140 }
  1141 
  1142 - (NSString *)tabView:(NSTabView *)tabView toolTipForTabViewItem:(NSTabViewItem *)tabViewItem
  1143 {
  1144 	AIChat		*chat = [(AIMessageTabViewItem *)tabViewItem chat];
  1145 	NSString	*tooltip = nil;
  1146 
  1147 	if (chat.isGroupChat) {
  1148 		tooltip = [NSString stringWithFormat:AILocalizedString(@"%@ in %@","AccountName on ChatRoomName"), chat.account.formattedUID, chat.name];
  1149 	} else {
  1150 		AIListObject	*destination = chat.listObject;
  1151 		NSString		*destinationDisplayName = destination.displayName;
  1152 		NSString		*destinationFormattedUID = destination.formattedUID;
  1153 		BOOL			includeDestination = NO;
  1154 		BOOL			includeSource = NO;
  1155 		
  1156 		if (destinationFormattedUID && destinationDisplayName &&
  1157 			![[destinationDisplayName compactedString] isEqualToString:[destinationFormattedUID compactedString]]) {
  1158 			includeDestination = YES;
  1159 		}
  1160 
  1161 		NSUInteger onlineAccounts = 0;
  1162 		for (AIAccount *account in adium.accountController.accounts) {
  1163 			if (onlineAccounts >= 2) break;
  1164 			if (account.online) onlineAccounts++;
  1165 		}
  1166 
  1167 		if (onlineAccounts >= 2)
  1168 			includeSource = YES;
  1169 
  1170 		AILog(@"Displaying tooltip for %@ --> %@ (%@) --> %@ (%@)", chat, chat.account, chat.account.formattedUID, destination, destinationFormattedUID);
  1171 		if (includeDestination && includeSource) {
  1172 			tooltip = [NSString stringWithFormat:AILocalizedString(@"%@ talking to %@","AccountName talking to Username"), chat.account.formattedUID, destinationFormattedUID];
  1173 
  1174 		} else if (includeDestination) {
  1175 			tooltip = destinationFormattedUID;
  1176 			
  1177 		} else if (includeSource) {
  1178 			tooltip = chat.account.formattedUID;
  1179 		}
  1180 	}
  1181 	
  1182 	return tooltip;
  1183 }
  1184 
  1185 - (void)tabView:(NSTabView *)aTabView tabViewItem:(NSTabViewItem *)tabViewItem isInOverflowMenu:(BOOL)inOverflowMenu
  1186 {
  1187 	//Wait until the next run loop, then update the icons so the overflow menu will be updated appropriately
  1188 	[self performSelector:@selector(updateOverflowMenuUnviewedContentIcon)
  1189 			   withObject:nil
  1190 			   afterDelay:0];
  1191 }
  1192 
  1193 //Tab Bar Visibility --------------------------------------------------------------------------------------------------
  1194 #pragma mark Tab Bar Visibility/Drag And Drop
  1195 
  1196 //Replaced by PSMTabBarControl
  1197 
  1198 //Make sure auto-hide suppression is off after a drag completes
  1199 - (void)tabDraggingNotificationReceived:(NSNotification *)notification
  1200 {
  1201 	if ([[notification name] isEqualToString:PSMTabDragDidBeginNotification]) {
  1202 		[tabView_tabBar setHideForSingleTab:NO];
  1203 	} else {
  1204 		[tabView_tabBar setHideForSingleTab:!alwaysShowTabs];
  1205 	}
  1206 }
  1207 
  1208 //Save width of the vertical tabs when changed
  1209 - (void)tabBarFrameChanged:(NSNotification *)notification {
  1210 	if ([tabView_tabBar orientation] == PSMTabBarVerticalOrientation) {
  1211 		if (![tabView_tabBar isTabBarHidden]) {
  1212 			CGFloat newWidth = NSWidth([tabView_tabBar frame]);
  1213 			if (newWidth >= MINIMUM_WIDTH_FOR_VERTICAL_TABS)
  1214 				lastTabBarWidth = newWidth;
  1215 			
  1216 		}
  1217 	}
  1218 }
  1219 
  1220 /*//Custom Tabs Delegate -------------------------------------------------------------------------------------------------
  1221 #pragma mark Custom Tabs Delegate
  1222 //Bring our window to the front
  1223 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
  1224 {
  1225 	NSDragOperation tmp = NSDragOperationNone;
  1226     NSString 		*type = [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:TAB_CELL_IDENTIFIER,nil]];
  1227 
  1228     if (sender == nil || type) {
  1229         if (![[self window] isKeyWindow]) [[self window] makeKeyAndOrderFront:nil];
  1230 		[self _suppressTabHiding:YES];
  1231         tmp = NSDragOperationPrivate;
  1232     }
  1233 	return tmp;
  1234 }
  1235 
  1236 - (void)draggingExited:(id <NSDraggingInfo>)sender
  1237 {
  1238 	NSString 		*type = [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:TAB_CELL_IDENTIFIER,nil]];
  1239 	
  1240     if (sender == nil || type) [self _suppressTabHiding:NO];
  1241 }
  1242 
  1243 - (void)_suppressTabHiding:(BOOL)suppress
  1244 {
  1245 	supressHiding = suppress;
  1246 	[self updateTabBarVisibilityAndAnimate:YES];
  1247 }
  1248 
  1249 //Send the print message to our view
  1250 - (void)adiumPrint:(id)sender
  1251 {
  1252 	id	controller = [(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] messageViewController];
  1253 	
  1254 	if ([controller respondsToSelector:@selector(adiumPrint:)]) {
  1255 		[controller adiumPrint:sender];
  1256 	}
  1257 }*/
  1258 
  1259 //Toolbar --------------------------------------------------------------------------------------------------------------
  1260 #pragma mark Toolbar
  1261 //Install our toolbar
  1262 - (void)_configureToolbar
  1263 {
  1264 //	NSToolbar *toolbar; change this if need be
  1265     toolbar = [[[NSToolbar alloc] initWithIdentifier:TOOLBAR_MESSAGE_WINDOW] autorelease];
  1266 	
  1267     [toolbar setDelegate:self];
  1268     [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
  1269     [toolbar setSizeMode:NSToolbarSizeModeSmall];
  1270     [toolbar setVisible:YES];
  1271     [toolbar setAllowsUserCustomization:YES];
  1272     [toolbar setAutosavesConfiguration:YES];
  1273 	
  1274     //
  1275 	toolbarItems = [[adium.toolbarController toolbarItemsForToolbarTypes:[NSArray arrayWithObjects:@"General", @"ListObject", @"TextEntry", @"MessageWindow", nil]] retain];
  1276 
  1277 	/* Seemingly randomly, setToolbar: may throw:
  1278 	 * Exception:	NSInternalInconsistencyException
  1279 	 * Reason:		Uninitialized rectangle passed to [View initWithFrame:].
  1280 	 *
  1281 	 * With the same window positioning information as a user for whom this happens consistently, I can't reproduce. Let's
  1282 	 * fail to set the toolbar gracefully.
  1283 	 */
  1284 	@try
  1285 	{
  1286 		[[self window] setToolbar:toolbar];
  1287 	}
  1288 	@catch(id exc)
  1289 	{
  1290 		NSLog(@"Warning: While setting the message window's toolbar, exception %@ was thrown.", exc);
  1291 	}
  1292 }
  1293 
  1294 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
  1295 {
  1296 	return [AIToolbarUtilities toolbarItemFromDictionary:toolbarItems withIdentifier:itemIdentifier];
  1297 }
  1298 
  1299 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
  1300 {
  1301     return [NSArray arrayWithObjects:@"UserIcon",@"Encryption",  NSToolbarSeparatorItemIdentifier, 
  1302 		@"SourceDestination", @"InsertEmoticon", @"BlockParticipants", @"LinkEditor", @"SafariLink", @"AddBookmark", NSToolbarShowColorsItemIdentifier,
  1303 		NSToolbarShowFontsItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, @"SendFile",
  1304 		@"ShowInfo", @"LogViewer", nil];
  1305 }
  1306 
  1307 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
  1308 {
  1309     return [[toolbarItems allKeys] arrayByAddingObjectsFromArray:
  1310 		[NSArray arrayWithObjects:NSToolbarSeparatorItemIdentifier,
  1311 			NSToolbarSpaceItemIdentifier,
  1312 			NSToolbarFlexibleSpaceItemIdentifier,
  1313 			NSToolbarShowColorsItemIdentifier,
  1314 			NSToolbarShowFontsItemIdentifier,
  1315 			NSToolbarCustomizeToolbarItemIdentifier, nil]];
  1316 }
  1317 
  1318 - (void)toolbarWillAddItem:(NSNotification *)notification
  1319 {
  1320 
  1321 	NSToolbarItem *item = [[notification userInfo] objectForKey:@"item"];
  1322 
  1323 	if ([[item itemIdentifier] isEqualToString:NSToolbarShowFontsItemIdentifier]) {
  1324 		[item setTarget:adium.interfaceController];
  1325 		[item setAction:@selector(toggleFontPanel:)];
  1326 	}
  1327 }
  1328 
  1329 - (void)removeToolbarItemWithIdentifier:(NSString*)identifier
  1330 {
  1331 	NSArray			*itemArray = [toolbar items];
  1332 	NSEnumerator	*enumerator = [itemArray objectEnumerator];
  1333 	NSToolbarItem	*item;
  1334 	NSInteger		index = NSNotFound;
  1335 
  1336 	while ((item = [enumerator nextObject])) {
  1337 		if ([[item itemIdentifier] isEqualToString:identifier]) {
  1338 			index = [itemArray indexOfObject:item];
  1339 			break;
  1340 		}
  1341 	}
  1342 
  1343 	if (index != NSNotFound) {
  1344 		[toolbar removeItemAtIndex:index];
  1345 	}
  1346 }
  1347 
  1348 #pragma mark Miniaturization
  1349 /*!
  1350  * @brief Our window is about to minimize
  1351  *
  1352  * Set our miniwindow image, which will display in the dock, appropriately.
  1353  */
  1354 - (void)windowWillMiniaturize:(NSNotification *)notification
  1355 {
  1356 	NSImage *miniwindowImage;
  1357 	NSImage	*chatImage = [[(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] chat] chatImage];
  1358 	NSImage	*appImage = [adium.dockController baseApplicationIconImage];
  1359 	NSSize	chatImageSize = [chatImage size];
  1360 	NSSize	appImageSize = [appImage size];
  1361 	NSSize	newChatImageSize;
  1362 	NSSize	badgeSize;
  1363 	
  1364 	miniwindowImage = [[NSImage alloc] initWithSize:NSMakeSize(128,128)];
  1365 	
  1366 	//Determine the properly scaled chat image size
  1367 	newChatImageSize = NSMakeSize(96,96);
  1368 	if (chatImageSize.width != chatImageSize.height) {
  1369 		if (chatImageSize.width > chatImageSize.height) {
  1370 			//Give width priority: Make the height change by the same proportion as the width will change
  1371 			newChatImageSize.height = chatImageSize.height * (newChatImageSize.width / chatImageSize.width);
  1372 		} else {
  1373 			//Give height priority: Make the width change by the same proportion as the height will change
  1374 			newChatImageSize.width = chatImageSize.width * (newChatImageSize.height / chatImageSize.height);
  1375 		}		
  1376 	}
  1377 	
  1378 	//OS X 10.4 always returns a square application icon of 128x128, but better safe than sorry
  1379 	badgeSize = NSMakeSize(48, 48);
  1380 	if (appImageSize.width != appImageSize.height) {
  1381 		if (appImageSize.width > appImageSize.height) {
  1382 			//Give width priority: Make the height change by the same proportion as the width will change
  1383 			badgeSize.height = appImageSize.height * (badgeSize.width / appImageSize.width);
  1384 		} else {
  1385 			//Give height priority: Make the width change by the same proportion as the height will change
  1386 			badgeSize.width = appImageSize.width * (badgeSize.height / appImageSize.height);
  1387 		}		
  1388 	}
  1389 	
  1390 	[miniwindowImage lockFocus];
  1391 	{
  1392 		//Draw the chat image with space around it (the dock will do ugly scaling if we don't make a transparent border)
  1393 		[chatImage drawInRect:NSMakeRect((128 - newChatImageSize.width)/2, (128 - newChatImageSize.height)/2,
  1394 										 newChatImageSize.width, newChatImageSize.height)
  1395 					 fromRect:NSMakeRect(0, 0, chatImageSize.width, chatImageSize.height)
  1396 					operation:NSCompositeSourceOver
  1397 					 fraction:1.0];
  1398 		
  1399 		//Draw the Adium icon as a badge in the bottom right
  1400 		[appImage drawInRect:NSMakeRect(128 - badgeSize.width,
  1401 										0,
  1402 										badgeSize.width,
  1403 										badgeSize.height)
  1404 					fromRect:NSMakeRect(0, 0, appImageSize.width, appImageSize.height)
  1405 				   operation:NSCompositeSourceOver
  1406 					fraction:1.0];
  1407 	}
  1408 	[miniwindowImage unlockFocus];
  1409 	
  1410 	//Set the image
  1411 	[[self window] setMiniwindowImage:miniwindowImage];
  1412 	
  1413 	//Cleanup
  1414 	[miniwindowImage release];
  1415 }
  1416 
  1417 - (BOOL)window:(NSWindow *)sender shouldDragDocumentWithEvent:(NSEvent *)mouseEvent from:(NSPoint)startPoint withPasteboard:(NSPasteboard *)pasteboard
  1418 {
  1419 	return NO;
  1420 }
  1421 
  1422 #pragma mark Printing
  1423 /*!
  1424  * @brief Support for printing.  Forward the print command to the active tab view's message view controller
  1425  */
  1426 - (void)adiumPrint:(id)sender
  1427 {
  1428 	[[(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] messageViewController] adiumPrint:sender];
  1429 }
  1430 
  1431 #pragma mark Gestures
  1432 
  1433 /*!
  1434  * @brief Responds to a swipe gesture
  1435  *
  1436  * This is a private method added in AppKit 949.18.0.
  1437  */
  1438 - (void)swipeWithEvent:(NSEvent *)inEvent
  1439 {
  1440 	// We don't do anything for vertical swipes.
  1441 	if ([inEvent deltaY] != 0)
  1442 		return;
  1443 	
  1444 	// Horizontal swipe; +1f is left, -1f is right.
  1445 	if ([inEvent deltaX] == -1) {
  1446 		[adium.interfaceController nextChat:nil];
  1447 	} else {
  1448 		[adium.interfaceController previousChat:nil];
  1449 	}
  1450 }
  1451 
  1452 //inherit this
  1453 @dynamic window;
  1454 
  1455 @end