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.
2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
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.
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.
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.
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>
43 #define KEY_MESSAGE_WINDOW_POSITION @"Message Window"
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
52 #define HORIZONTAL_TAB_BAR_TO_VIEW_SPACING 7
54 #define KEY_VERTICAL_TABS_WIDTH @"Vertical Tabs Width"
55 #define VERTICAL_DIVIDER_THICKNESS 4
56 #define VERTICAL_TAB_BAR_TO_VIEW_SPACING 3
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;
66 //Used to squelch compiler warnings on this private call
67 @interface NSWindow (AISecretWindowDocumentIconAdditions)
68 - (void)addDocumentIconButton;
71 @implementation AIMessageWindowController
73 //Create a new message window controller
74 + (AIMessageWindowController *)messageWindowControllerForInterface:(AIDualWindowInterfacePlugin *)inInterface
75 withID:(NSString *)inContainerID
76 name:(NSString *)inName
78 return [[[self alloc] initWithWindowNibName:MESSAGE_WINDOW_NIB
80 containerID:inContainerID
81 containerName:inName] autorelease];
85 - (id)initWithWindowNibName:(NSString *)windowNibName
86 interface:(AIDualWindowInterfacePlugin *)inInterface
87 containerID:(NSString *)inContainerID
88 containerName:(NSString *)inName
90 if ((self = [super initWithWindowNibName:windowNibName])) {
93 interface = [inInterface retain];
94 containerName = [inName retain];
95 containerID = [inContainerID retain];
96 m_containedChats = [[NSMutableArray alloc] init];
97 hasShownDocumentButton = NO;
100 myWindow = [self window];
102 [[NSNotificationCenter defaultCenter] addObserver:self
103 selector:@selector(tabDraggingNotificationReceived:)
104 name:PSMTabDragDidBeginNotification
107 [[NSNotificationCenter defaultCenter] addObserver:self
108 selector:@selector(tabDraggingNotificationReceived:)
109 name:PSMTabDragDidEndNotification
112 [[NSNotificationCenter defaultCenter] addObserver:self
113 selector:@selector(tabBarFrameChanged:)
114 name:NSViewFrameDidChangeNotification
115 object:tabView_tabBar];
117 [[NSNotificationCenter defaultCenter] addObserver:self
118 selector:@selector(windowWillMiniaturize:)
119 name:NSWindowWillMiniaturizeNotification
122 [adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_DUAL_WINDOW_INTERFACE];
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"]];
134 [[NSNotificationCenter defaultCenter] removeObserver:self];
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..
141 * Something is wrong elsewhere that this could be necessary, but this doesn't hurt I don't believe.
143 [[self window] setDelegate:nil];
144 [self setWindow:nil];
146 [tabView_tabBar setDelegate:nil];
148 [self.containedChats release];
149 [toolbarItems release];
150 [containerName release];
151 [containerID release];
153 [adium.preferenceController unregisterPreferenceObserver:self];
158 //Human readable container name
161 return containerName;
164 //Internal container ID
165 - (NSString *)containerID
170 //PSMTabBarControl accessor
171 - (PSMTabBarControl *)tabBar
173 return tabView_tabBar;
176 - (NSString *)adiumFrameAutosaveName
178 return [self _frameSaveKey];
181 //Setup our window before it is displayed
182 - (void)windowDidLoad
184 [super windowDidLoad];
186 NSWindow *theWindow = [self window];
188 //Exclude this window from the window menu (since we add it manually)
189 [theWindow setExcludedFromWindowsMenu:YES];
190 [theWindow useOptimizedDrawing:YES];
192 [self _configureToolbar];
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]];
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];
215 - (NSString *)_frameSaveKey
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;
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];
228 - (BOOL)shouldCascadeWindows
230 if ([[adium.preferenceController preferenceForKey:KEY_TABBED_CHATTING group:PREF_GROUP_INTERFACE] boolValue])
232 else //Not using tabbed chatting: Cascade if we have no frame
233 return ([self savedFrameString] == nil);
236 //Close the message window
237 - (IBAction)closeWindow:(id)sender
239 windowIsClosing = YES;
241 [[self window] performClose:nil];
245 * @brief Confirm if we should close the window.
247 - (BOOL)windowShouldClose:(id)window
250 && self.containedChats.count > 1
251 && [[adium.preferenceController preferenceForKey:KEY_CONFIRM_MSG_CLOSE group:PREF_GROUP_CONFIRMATIONS] boolValue]) {
252 NSString *suppressionText = nil;
254 NSInteger unreadCount = 0;
256 for (AIChat *chat in self.containedChats) {
257 if (chat.unviewedContentCount) {
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);
267 case AIMessageCloseUnread:
269 suppressionText = AILocalizedString(@"Do not warn when closing unread chats", nil);
274 NSString *question = nil;
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];
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,
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];
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)
294 informativeTextWithFormat:question];
296 [alert setShowsSuppressionButton:YES];
297 [[alert suppressionButton] setTitle:suppressionText];
299 [alert beginSheetModalForWindow:self.window
301 didEndSelector:@selector(closeAlertDidEnd:returnCode:contextInfo:)
311 - (void)closeAlertDidEnd:(NSAlert *)alert returnCode:(int)result contextInfo:(void *)contextInfo;
314 if ([alert suppressionButton].state == NSOnState) {
315 [adium.preferenceController setPreference:nil
316 forKey:KEY_CONFIRM_MSG_CLOSE
317 group:PREF_GROUP_CONFIRMATIONS];
320 if (result == NSAlertDefaultReturn) {
321 // Dismiss the alert sheet.
322 [self.window orderOut:nil];
323 // Don't prompt again.
324 windowIsClosing = YES;
326 [self closeWindow:nil];
331 * @brief Called as the window closes
333 - (void)windowWillClose:(id)sender
335 NSEnumerator *enumerator;
336 AIMessageTabViewItem *tabViewItem;
338 if ([tabView_tabBar orientation] == PSMTabBarVerticalOrientation) {
339 CGFloat widthToStore;
340 if ([tabView_tabBar isTabBarHidden]) {
341 widthToStore = lastTabBarWidth;
343 widthToStore = NSWidth([tabView_tabBar frame]);
346 [adium.preferenceController setPreference:[NSNumber numberWithDouble:widthToStore]
347 forKey:KEY_VERTICAL_TABS_WIDTH
348 group:PREF_GROUP_DUAL_WINDOW_INTERFACE];
351 windowIsClosing = YES;
352 [super windowWillClose:sender];
354 [adium.preferenceController unregisterPreferenceObserver:self];
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];
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];
367 [interface containerDidClose:self];
372 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
373 object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
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)];
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];
386 //change the frame of the tab bar according to the orientation
387 if (firstTime || [key isEqualToString:KEY_TABBAR_POSITION]) {
388 PSMTabBarOrientation orientation;
390 tabPosition = [[prefDict objectForKey:KEY_TABBAR_POSITION] integerValue];
391 orientation = ((tabPosition == AdiumTabPositionBottom || tabPosition == AdiumTabPositionTop) ?
392 PSMTabBarHorizontalOrientation :
393 PSMTabBarVerticalOrientation);
395 NSRect tabBarFrame = [tabView_tabBar frame], tabViewMessagesFrame = [tabView_messages frame];
396 NSRect contentRect = [[[self window] contentView] frame];
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];
406 [[[self window] contentView] addSubview:tabView_messages];
407 [[[self window] contentView] addSubview:tabView_tabBar];
408 [tabView_messages release];
409 [tabView_tabBar release];
412 [tabView_tabBar setOrientation:orientation];
414 switch (orientation) {
415 case PSMTabBarHorizontalOrientation:
417 tabBarFrame.size.height = [tabView_tabBar isTabBarHidden] ? 1 : kPSMTabBarControlHeight;
418 tabBarFrame.size.width = contentRect.size.width + 1;
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)];
429 // This arbitrary sizedown is so that top tabs look visually connected to their content below.
430 tabBarFrame.size.height -= 3;
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)];
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];
441 tabBarFrame.origin.x = 0;
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;
448 case PSMTabBarVerticalOrientation:
450 CGFloat width = ([prefDict objectForKey:KEY_VERTICAL_TABS_WIDTH] ?
451 [[prefDict objectForKey:KEY_VERTICAL_TABS_WIDTH] doubleValue] :
453 lastTabBarWidth = width;
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;
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];
468 tabViewMessagesFrame.origin.x = NSMinX(contentRect);
469 tabBarFrame.origin.x = NSWidth(contentRect) - NSWidth(tabBarFrame) + 1;
470 [tabView_tabBar setAutoresizingMask:NSViewHeightSizable | NSViewMinXMargin];
472 [tabView_tabBar setCellMinWidth:50];
473 [tabView_tabBar setCellMaxWidth:200];
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]];
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]];
494 [tabView_splitView adjustSubviews];
495 [tabView_splitView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
496 [[[self window] contentView] addSubview:tabView_splitView];
501 [tabView_messages setFrame:tabViewMessagesFrame];
502 [tabView_tabBar setFrame:tabBarFrame];
504 //update the tab bar and tab view frame
505 [[self window] display];
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)];
513 [self _updateWindowTitleAndIcon];
515 AIWindowLevel windowLevel = [[prefDict objectForKey:KEY_WINDOW_LEVEL] integerValue];
516 NSInteger level = NSNormalWindowLevel;
518 switch (windowLevel) {
519 case AINormalWindowLevel: level = NSNormalWindowLevel; break;
520 case AIFloatingWindowLevel: level = NSFloatingWindowLevel; break;
521 case AIDesktopWindowLevel: level = kCGDesktopWindowLevel; break;
523 [window setLevel:level];
524 [window setHidesOnDeactivate:[[prefDict objectForKey:KEY_WINDOW_HIDE] boolValue]];
528 - (void)updateOverflowMenuUnviewedContentIcon
530 BOOL someUnviewedContent = NO;
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;
540 [[tabView_tabBar overflowPopUpButton] setAnimatingAlternateImage:someUnviewedContent];
544 - (void)updateIconForTabViewItem:(AIMessageTabViewItem *)tabViewItem
546 if (tabViewItem == [tabView_messages selectedTabViewItem]) {
547 [self _updateWindowTitleAndIcon];
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];
556 - (AdiumTabPosition)tabPosition
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
566 [self addTabViewItem:inTabViewItem atIndex:-1 silent:NO];
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
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.
576 [inTabViewItem setIdentifier:inTabViewItem];
579 [tabView_messages addTabViewItem:inTabViewItem];
581 [tabView_messages insertTabViewItem:inTabViewItem atIndex:index];
584 //Refresh our list and order of chats
585 [self _reloadContainedChats];
587 if (![tabView_messages selectedTabViewItem]) [tabView_messages selectNextTabViewItem:nil];
589 if (!silent) [adium.interfaceController chatDidOpen:inTabViewItem.chat];
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
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.
601 if ([tabView_messages selectedTabViewItem] != inTabViewItem) {
602 NSTabViewItem *oldTabViewItem = [tabView_messages selectedTabViewItem];
603 [tabView_messages selectTabViewItem:inTabViewItem];
605 //The tab view item needs to know that this window controller no longer contains it
606 [inTabViewItem setWindowController:nil];
608 [tabView_messages selectTabViewItem:oldTabViewItem];
611 //The tab view item needs to know that this window controller no longer contains it
612 [inTabViewItem setWindowController:nil];
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];
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];
625 //Now remove the tab view item from our NSTabView
626 [tabView_messages removeTabViewItem:inTabViewItem];
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.
631 [inTabViewItem setIdentifier:nil];
633 //close if we're empty
634 if (!windowIsClosing && [self.containedChats count] == 0) {
635 [self closeWindow:nil];
639 - (void)moveTabViewItem:(AIMessageTabViewItem *)inTabViewItem toIndex:(NSInteger)index
641 AIChat *chat = inTabViewItem.chat;
643 if ([self.containedChats indexOfObject:chat] != index) {
644 NSMutableArray *cells = [tabView_tabBar cells];
646 [cells moveObject:[cells objectAtIndex:[[tabView_tabBar representedTabViewItems] indexOfObject:inTabViewItem]] toIndex:index];
647 [tabView_tabBar setNeedsDisplay:YES];
648 [m_containedChats moveObject:chat toIndex:index];
650 [adium.interfaceController chatOrderDidChange];
654 //Returns YES if we are empty (currently contain no chats)
655 - (BOOL)containerIsEmpty
657 return [self.containedChats count] == 0;
660 //Returns an array of the chats we contain
661 @synthesize containedChats = m_containedChats;
663 - (void)_reloadContainedChats
665 NSEnumerator *enumerator;
666 AIMessageTabViewItem *tabViewItem;
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];
672 while ((tabViewItem = [enumerator nextObject])) {
673 [tabViewItem setWindowController:self];
674 [m_containedChats addObject:[tabViewItem chat]];
678 //Active Chat Tracking -------------------------------------------------------------------------------------------------
679 #pragma mark Active Chat Tracking
680 //Our selected tab is now the active chat
681 - (void)windowDidBecomeKey:(NSNotification *)notification
683 [adium.interfaceController chatDidBecomeActive:[(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] chat]];
686 //Our selected tab is no longer the active chat
687 - (void)windowDidResignKey:(NSNotification *)notification
689 [((AIMessageTabViewItem *)tabView_messages.selectedTabViewItem).messageViewController.messageDisplayController markForFocusChange];
690 [adium.interfaceController chatDidBecomeActive:nil];
693 //Update our window title
694 - (void)_updateWindowTitleAndIcon
696 NSString *label = [(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] label];
699 NSWindow *window = [self window];
702 if (([tabView_messages numberOfTabViewItems] == 1) || !containerName) {
703 title = (label ? [NSString stringWithFormat:@"%@", label] : nil);
705 if (containerName && label) {
706 title = [NSString stringWithFormat:@"%@ - %@", containerName, label];
709 title = containerName;
717 if (title) [window setTitle:title];
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];
724 hasShownDocumentButton = YES;
727 button = [window standardWindowButton:NSWindowDocumentIconButton];
729 if ([tabView_tabBar isTabBarHidden]) {
730 NSImage *image = [(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] stateIcon];
731 if (image != [button image]) {
732 [button setImage:image];
736 if ([button image]) {
737 [button setImage:nil];
742 - (AIChat *)activeChat
744 AIMessageTabViewItem *selectedTabViewItem = (AIMessageTabViewItem *)[tabView_messages selectedTabViewItem];
746 if (![selectedTabViewItem isKindOfClass:[AIMessageTabViewItem class]]) {
750 return [selectedTabViewItem chat];
753 //AISplitView Delegate -------------------------------------------------------------------------------------------------
754 #pragma mark AISplitView Delegate
756 #define MINIMUM_WIDTH_FOR_VERTICAL_TABS 50
757 #define MAXIMUM_WIDTH_FOR_VERTICAL_TABS 250
759 //handles the minimum size of vertical tabs
760 - (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
762 CGFloat min = proposedMin;
764 if (sender == tabView_splitView) {
765 switch (tabPosition) {
766 case AdiumTabPositionBottom:
767 case AdiumTabPositionTop:
768 /* should never be passed these */
770 case AdiumTabPositionLeft:
771 min = ([tabView_tabBar isTabBarHidden] ? 0 : MINIMUM_WIDTH_FOR_VERTICAL_TABS);
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]));
780 NSLog(@"Unknown split view");
786 //handles the maximum size of vertical tabs
787 - (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
789 CGFloat max = proposedMax;
791 if (sender == tabView_splitView) {
792 switch (tabPosition) {
793 case AdiumTabPositionBottom:
794 case AdiumTabPositionTop:
795 /* should never be passed these */
797 case AdiumTabPositionLeft:
798 max = MAXIMUM_WIDTH_FOR_VERTICAL_TABS;
800 case AdiumTabPositionRight:
801 max = proposedMax - MINIMUM_WIDTH_FOR_VERTICAL_TABS;
805 NSLog(@"Unknown split view");
811 - (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
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);
817 if (sender == tabView_splitView) {
818 switch (tabPosition) {
819 case AdiumTabPositionBottom:
820 case AdiumTabPositionTop:
821 /* should never be passed these */
823 case AdiumTabPositionLeft:
824 messageFrame.origin.x = NSWidth(tabBarFrame) + [sender dividerThickness];
826 case AdiumTabPositionRight:
827 messageFrame.origin.x = 0;
828 tabBarFrame.origin.x = NSWidth(messageFrame) + [sender dividerThickness];
833 [tabView_messages setFrame:messageFrame];
834 [tabView_tabBar setFrame:tabBarFrame];
838 //PSMTabBarControl Delegate -------------------------------------------------------------------------------------------------
839 #pragma mark PSMTabBarControl Delegate
841 //Handle closing a tab
842 - (BOOL)tabView:(NSTabView *)tabView shouldCloseTabViewItem:(NSTabViewItem *)tabViewItem
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];
848 [adium.interfaceController closeChat:chat];
854 - (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
856 AIMessageTabViewItem *selectedTabViewItem = (AIMessageTabViewItem *)[tabView_messages selectedTabViewItem];
858 if ([selectedTabViewItem isKindOfClass:[AIMessageTabViewItem class]]) {
859 [selectedTabViewItem tabViewItemWillDeselect];
863 //Our selected tab has changed, update the active chat
864 - (void)tabView:(NSTabView *)aTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
866 if (tabViewItem != nil) {
867 AIChat *chat = [(AIMessageTabViewItem *)tabViewItem chat];
868 [(AIMessageTabViewItem *)tabViewItem tabViewItemWasSelected]; //Let the tab know it was selected
870 if ([[self window] isMainWindow]) { //If our window is main, set the newly selected container as active
871 [adium.interfaceController chatDidBecomeActive:chat];
874 [self _updateWindowTitleAndIcon]; //Reflect change in window title
875 [adium.interfaceController chatDidBecomeVisible:chat inWindow:[self window]];
879 - (BOOL)tabView:(NSTabView*)tabView shouldDragTabViewItem:(NSTabViewItem *)tabViewItem fromTabBar:(PSMTabBarControl *)tabBarControl
884 - (BOOL)tabView:(NSTabView*)tabView shouldDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl
889 - (void)tabView:(NSTabView *)tabView closeWindowForLastTabViewItem:(NSTabViewItem *)tabViewItem
891 [self closeWindow:self];
894 //Contextual menu for tabs
895 - (NSMenu *)tabView:(NSTabView *)tabView menuForTabViewItem:(NSTabViewItem *)tabViewItem
897 AIChat *chat = [(AIMessageTabViewItem *)tabViewItem chat];
898 AIListContact *selectedObject = chat.listObject.parentContact;
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];
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];
920 [locations addObject:[NSNumber numberWithInteger:Context_Tab_Action]];
922 tmp = [adium.menuController contextualMenuWithLocations:locations
923 forListObject:selectedObject
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];
932 tmp = [adium.menuController contextualMenuWithLocations:locations
940 - (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)tabView
942 [self _updateWindowTitleAndIcon];
943 [self _reloadContainedChats];
944 [adium.interfaceController chatOrderDidChange];
946 //Remaining disabled until the last crasher I know of is removed
947 /*if ([tabView numberOfTabViewItems] > 0) {
948 [tabView_tabBar setAutomaticallyAnimates:YES];
953 - (void)tabView:(NSTabView*)aTabView didDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl;
955 [self _reloadContainedChats];
956 [adium.interfaceController chatOrderDidChange];
959 //Allow dragging of text
960 - (NSArray *)allowedDraggedTypesForTabView:(NSTabView *)aTabView
962 return [NSArray arrayWithObjects:NSRTFPboardType, NSStringPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPDFPboardType, NSPICTPboardType, nil];
965 //Accept dragged text
966 - (void)tabView:(NSTabView *)aTabView acceptedDraggingInfo:(id <NSDraggingInfo>)draggingInfo onTabViewItem:(NSTabViewItem *)tabViewItem
968 [[(AIMessageTabViewItem *)tabViewItem messageViewController] addDraggedDataToTextEntryView:draggingInfo];
971 //Get an image representation of the chat
972 - (NSImage *)tabView:(NSTabView *)tabView imageForTabViewItem:(NSTabViewItem *)tabViewItem offset:(NSSize *)offset styleMask:(unsigned *)styleMask
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];
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];
990 [viewImage lockFocus];
991 NSPoint tabOrigin = [tabView frame].origin;
994 [tabViewImage compositeToPoint:tabOrigin operation:NSCompositeSourceOver];
995 [viewImage unlockFocus];
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];
1006 tabFrame.origin.y = -tabFrame.origin.y - tabFrame.size.height;
1007 [(id <PSMTabStyle>)[[tabView delegate] style] drawBackgroundInRect:tabFrame];
1011 [viewImage unlockFocus];
1013 id <PSMTabStyle> style = (id <PSMTabStyle>)[[tabView delegate] style];
1015 switch (tabPosition) {
1016 case AdiumTabPositionBottom:
1017 offset->width = [style leftMarginForTabBarControl];
1018 offset->height = contentFrame.size.height;
1020 case AdiumTabPositionTop:
1021 offset->width = [style leftMarginForTabBarControl];
1022 offset->height = 21;
1024 case AdiumTabPositionLeft:
1026 offset->height = 21 + [style topMarginForTabBarControl];
1028 case AdiumTabPositionRight:
1029 offset->width = [tabView_tabBar frame].origin.x;
1030 offset->height = 21 + [style topMarginForTabBarControl];
1034 *styleMask = NSTitledWindowMask;
1039 //Create a new tab window
1040 - (PSMTabBarControl *)tabView:(NSTabView *)tabView newTabBarForDraggedTabViewItem:(NSTabViewItem *)tabViewItem atPoint:(NSPoint)point
1042 id newController = [interface openNewContainer];
1044 id <PSMTabStyle> style = (id <PSMTabStyle>)[[tabView delegate] style];
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];
1052 switch (tabPosition) {
1053 case AdiumTabPositionBottom:
1054 point.x -= [style leftMarginForTabBarControl];
1057 case AdiumTabPositionTop:
1058 point.x -= [style leftMarginForTabBarControl];
1059 point.y -= NSHeight([[[newController window] contentView] frame]) + 1;
1061 case AdiumTabPositionLeft:
1062 point.y -= NSHeight([[[newController window] contentView] frame]) - [style topMarginForTabBarControl] + 1;
1064 case AdiumTabPositionRight:
1065 point.x -= NSMinX([tabView_tabBar frame]);
1066 point.y -= NSHeight([[[newController window] contentView] frame]) - [style topMarginForTabBarControl] + 1;
1069 //set the origin point of the new window
1070 frame.origin = point;
1071 [[newController window] setFrame:frame display:NO];
1073 return [newController tabBar];
1076 - (void)tabView:(NSTabView *)tabView tabBarDidHide:(PSMTabBarControl *)tabBarControl
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.
1085 if (tabPosition == AdiumTabPositionBottom) {
1086 frame.origin.y -= HORIZONTAL_TAB_BAR_TO_VIEW_SPACING;
1087 frame.size.height += HORIZONTAL_TAB_BAR_TO_VIEW_SPACING;
1091 case PSMTabBarVerticalOrientation:
1092 frame.origin.x -= VERTICAL_TAB_BAR_TO_VIEW_SPACING;
1093 frame.size.width += VERTICAL_TAB_BAR_TO_VIEW_SPACING;
1095 [tabView_splitView setDividerThickness:0];
1099 [tabView setFrame:frame];
1100 [tabBarControl setHidden:YES];
1101 [tabView setNeedsDisplay:YES];
1103 [[tabView_messages tabViewItems] makeObjectsPerformSelector:@selector(tabViewDidChangeVisibility)];
1106 - (void)tabView:(NSTabView *)tabView tabBarDidUnhide:(PSMTabBarControl *)tabBarControl
1108 //show the space between the tab bar and the tab view
1109 NSRect frame = [tabView frame];
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.
1116 if (tabPosition == AdiumTabPositionBottom) {
1117 frame.origin.y += HORIZONTAL_TAB_BAR_TO_VIEW_SPACING;
1118 frame.size.height -= HORIZONTAL_TAB_BAR_TO_VIEW_SPACING;
1122 case PSMTabBarVerticalOrientation:
1123 frame.origin.x += VERTICAL_TAB_BAR_TO_VIEW_SPACING;
1124 frame.size.width -= VERTICAL_TAB_BAR_TO_VIEW_SPACING;
1126 [tabView_splitView setDividerThickness:VERTICAL_DIVIDER_THICKNESS];
1130 [tabView setFrame:frame];
1131 [tabBarControl setHidden:NO];
1132 [tabView setNeedsDisplay:YES];
1134 [[tabView_messages tabViewItems] makeObjectsPerformSelector:@selector(tabViewDidChangeVisibility)];
1137 - (float)desiredWidthForVerticalTabBar:(PSMTabBarControl *)tabBarControl
1139 return (lastTabBarWidth ? lastTabBarWidth : 120);
1142 - (NSString *)tabView:(NSTabView *)tabView toolTipForTabViewItem:(NSTabViewItem *)tabViewItem
1144 AIChat *chat = [(AIMessageTabViewItem *)tabViewItem chat];
1145 NSString *tooltip = nil;
1147 if (chat.isGroupChat) {
1148 tooltip = [NSString stringWithFormat:AILocalizedString(@"%@ in %@","AccountName on ChatRoomName"), chat.account.formattedUID, chat.name];
1150 AIListObject *destination = chat.listObject;
1151 NSString *destinationDisplayName = destination.displayName;
1152 NSString *destinationFormattedUID = destination.formattedUID;
1153 BOOL includeDestination = NO;
1154 BOOL includeSource = NO;
1156 if (destinationFormattedUID && destinationDisplayName &&
1157 ![[destinationDisplayName compactedString] isEqualToString:[destinationFormattedUID compactedString]]) {
1158 includeDestination = YES;
1161 NSUInteger onlineAccounts = 0;
1162 for (AIAccount *account in adium.accountController.accounts) {
1163 if (onlineAccounts >= 2) break;
1164 if (account.online) onlineAccounts++;
1167 if (onlineAccounts >= 2)
1168 includeSource = YES;
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];
1174 } else if (includeDestination) {
1175 tooltip = destinationFormattedUID;
1177 } else if (includeSource) {
1178 tooltip = chat.account.formattedUID;
1185 - (void)tabView:(NSTabView *)aTabView tabViewItem:(NSTabViewItem *)tabViewItem isInOverflowMenu:(BOOL)inOverflowMenu
1187 //Wait until the next run loop, then update the icons so the overflow menu will be updated appropriately
1188 [self performSelector:@selector(updateOverflowMenuUnviewedContentIcon)
1193 //Tab Bar Visibility --------------------------------------------------------------------------------------------------
1194 #pragma mark Tab Bar Visibility/Drag And Drop
1196 //Replaced by PSMTabBarControl
1198 //Make sure auto-hide suppression is off after a drag completes
1199 - (void)tabDraggingNotificationReceived:(NSNotification *)notification
1201 if ([[notification name] isEqualToString:PSMTabDragDidBeginNotification]) {
1202 [tabView_tabBar setHideForSingleTab:NO];
1204 [tabView_tabBar setHideForSingleTab:!alwaysShowTabs];
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;
1220 /*//Custom Tabs Delegate -------------------------------------------------------------------------------------------------
1221 #pragma mark Custom Tabs Delegate
1222 //Bring our window to the front
1223 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
1225 NSDragOperation tmp = NSDragOperationNone;
1226 NSString *type = [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:TAB_CELL_IDENTIFIER,nil]];
1228 if (sender == nil || type) {
1229 if (![[self window] isKeyWindow]) [[self window] makeKeyAndOrderFront:nil];
1230 [self _suppressTabHiding:YES];
1231 tmp = NSDragOperationPrivate;
1236 - (void)draggingExited:(id <NSDraggingInfo>)sender
1238 NSString *type = [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:TAB_CELL_IDENTIFIER,nil]];
1240 if (sender == nil || type) [self _suppressTabHiding:NO];
1243 - (void)_suppressTabHiding:(BOOL)suppress
1245 supressHiding = suppress;
1246 [self updateTabBarVisibilityAndAnimate:YES];
1249 //Send the print message to our view
1250 - (void)adiumPrint:(id)sender
1252 id controller = [(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] messageViewController];
1254 if ([controller respondsToSelector:@selector(adiumPrint:)]) {
1255 [controller adiumPrint:sender];
1259 //Toolbar --------------------------------------------------------------------------------------------------------------
1260 #pragma mark Toolbar
1261 //Install our toolbar
1262 - (void)_configureToolbar
1264 // NSToolbar *toolbar; change this if need be
1265 toolbar = [[[NSToolbar alloc] initWithIdentifier:TOOLBAR_MESSAGE_WINDOW] autorelease];
1267 [toolbar setDelegate:self];
1268 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
1269 [toolbar setSizeMode:NSToolbarSizeModeSmall];
1270 [toolbar setVisible:YES];
1271 [toolbar setAllowsUserCustomization:YES];
1272 [toolbar setAutosavesConfiguration:YES];
1275 toolbarItems = [[adium.toolbarController toolbarItemsForToolbarTypes:[NSArray arrayWithObjects:@"General", @"ListObject", @"TextEntry", @"MessageWindow", nil]] retain];
1277 /* Seemingly randomly, setToolbar: may throw:
1278 * Exception: NSInternalInconsistencyException
1279 * Reason: Uninitialized rectangle passed to [View initWithFrame:].
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.
1286 [[self window] setToolbar:toolbar];
1290 NSLog(@"Warning: While setting the message window's toolbar, exception %@ was thrown.", exc);
1294 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
1296 return [AIToolbarUtilities toolbarItemFromDictionary:toolbarItems withIdentifier:itemIdentifier];
1299 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
1301 return [NSArray arrayWithObjects:@"UserIcon",@"Encryption", NSToolbarSeparatorItemIdentifier,
1302 @"SourceDestination", @"InsertEmoticon", @"BlockParticipants", @"LinkEditor", @"SafariLink", @"AddBookmark", NSToolbarShowColorsItemIdentifier,
1303 NSToolbarShowFontsItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, @"SendFile",
1304 @"ShowInfo", @"LogViewer", nil];
1307 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
1309 return [[toolbarItems allKeys] arrayByAddingObjectsFromArray:
1310 [NSArray arrayWithObjects:NSToolbarSeparatorItemIdentifier,
1311 NSToolbarSpaceItemIdentifier,
1312 NSToolbarFlexibleSpaceItemIdentifier,
1313 NSToolbarShowColorsItemIdentifier,
1314 NSToolbarShowFontsItemIdentifier,
1315 NSToolbarCustomizeToolbarItemIdentifier, nil]];
1318 - (void)toolbarWillAddItem:(NSNotification *)notification
1321 NSToolbarItem *item = [[notification userInfo] objectForKey:@"item"];
1323 if ([[item itemIdentifier] isEqualToString:NSToolbarShowFontsItemIdentifier]) {
1324 [item setTarget:adium.interfaceController];
1325 [item setAction:@selector(toggleFontPanel:)];
1329 - (void)removeToolbarItemWithIdentifier:(NSString*)identifier
1331 NSArray *itemArray = [toolbar items];
1332 NSEnumerator *enumerator = [itemArray objectEnumerator];
1333 NSToolbarItem *item;
1334 NSInteger index = NSNotFound;
1336 while ((item = [enumerator nextObject])) {
1337 if ([[item itemIdentifier] isEqualToString:identifier]) {
1338 index = [itemArray indexOfObject:item];
1343 if (index != NSNotFound) {
1344 [toolbar removeItemAtIndex:index];
1348 #pragma mark Miniaturization
1350 * @brief Our window is about to minimize
1352 * Set our miniwindow image, which will display in the dock, appropriately.
1354 - (void)windowWillMiniaturize:(NSNotification *)notification
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;
1364 miniwindowImage = [[NSImage alloc] initWithSize:NSMakeSize(128,128)];
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);
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);
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);
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);
1390 [miniwindowImage lockFocus];
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
1399 //Draw the Adium icon as a badge in the bottom right
1400 [appImage drawInRect:NSMakeRect(128 - badgeSize.width,
1404 fromRect:NSMakeRect(0, 0, appImageSize.width, appImageSize.height)
1405 operation:NSCompositeSourceOver
1408 [miniwindowImage unlockFocus];
1411 [[self window] setMiniwindowImage:miniwindowImage];
1414 [miniwindowImage release];
1417 - (BOOL)window:(NSWindow *)sender shouldDragDocumentWithEvent:(NSEvent *)mouseEvent from:(NSPoint)startPoint withPasteboard:(NSPasteboard *)pasteboard
1422 #pragma mark Printing
1424 * @brief Support for printing. Forward the print command to the active tab view's message view controller
1426 - (void)adiumPrint:(id)sender
1428 [[(AIMessageTabViewItem *)[tabView_messages selectedTabViewItem] messageViewController] adiumPrint:sender];
1431 #pragma mark Gestures
1434 * @brief Responds to a swipe gesture
1436 * This is a private method added in AppKit 949.18.0.
1438 - (void)swipeWithEvent:(NSEvent *)inEvent
1440 // We don't do anything for vertical swipes.
1441 if ([inEvent deltaY] != 0)
1444 // Horizontal swipe; +1f is left, -1f is right.
1445 if ([inEvent deltaX] == -1) {
1446 [adium.interfaceController nextChat:nil];
1448 [adium.interfaceController previousChat:nil];