Prevent files created by dragging from {{{AIImageViewWithImagePicker}}} wells from...
[adiumx.git] / Source / AIContactInfoWindowController.m
blob2f7c786e1aa8cfb3c519f8c8fb2575a6770dca0e
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  */
17 #import "AIContactInfoWindowController.h"
18 #import "AIContactAccountsPane.h"
19 #import "AIContactProfilePane.h"
20 #import "AIContactSettingsPane.h"
21 #import "ESContactAlertsPane.h"
22 #import "ESContactInfoListController.h"
23 #import "AIContactInfoImageViewWithImagePicker.h"
24 #import <Adium/AIInterfaceControllerProtocol.h>
25 #import <Adium/AIPreferenceControllerProtocol.h>
26 #import <Adium/AIAccountControllerProtocol.h>
27 #import <Adium/AIContactAlertsControllerProtocol.h>
28 #import <Adium/AIListGroup.h>
29 #import <Adium/AIMetaContact.h>
30 #import <Adium/AIModularPaneCategoryView.h>
31 #import <Adium/AIListObject.h>
32 #import <Adium/AIListOutlineView.h>
33 #import <Adium/AIService.h>
34 #import <AIUtilities/AIDictionaryAdditions.h>
35 #import <AIUtilities/AIImageAdditions.h>
36 #import <AIUtilities/AIImageViewWithImagePicker.h>
37 #import <AIUtilities/AIOutlineViewAdditions.h>
38 #import <AIUtilities/AIStringAdditions.h>
39 #import <AIUtilities/AITabViewAdditions.h>
41 #define CONTACT_INFO_NIB                                @"ContactInfoWindow"                    //Filename of the contact info nib
42 #define KEY_INFO_WINDOW_FRAME                   @"Contact Info Window Frame"    //
43 #define KEY_INFO_SELECTED_CATEGORY              @"Selected Info Category"               //
45 #define CONTACT_INFO_THEME                              @"Contact Info List Theme"
46 #define CONTACT_INFO_LAYOUT                             @"Contact Info List Layout"
48 @interface AIContactInfoWindowController (PRIVATE)
49 - (id)initWithWindowNibName:(NSString *)windowNibName;
50 - (void)selectionChanged:(NSNotification *)notification;
52 - (void)localizeTabViewItemTitles;
53 - (void)configureDrawer;
54 - (void)configureVisiblityOfTabViewItemsForListObject:(AIListObject *)inObject;
55 - (void)configurePane:(AIContactInfoPane *)inPane;
56 - (void)setupMetaContactDrawer;
58 @end
60 @implementation AIContactInfoWindowController
62 static AIContactInfoWindowController *sharedContactInfoInstance = nil;
64 //Return the shared contact info window
65 + (id)showInfoWindowForListObject:(AIListObject *)listObject
67         //Create the window
68         if (!sharedContactInfoInstance) {
69                 sharedContactInfoInstance = [[self alloc] initWithWindowNibName:CONTACT_INFO_NIB];
70         }
72         //Configure and show window
74         if ([listObject isKindOfClass:[AIListContact class]]) {
75                 AIListContact *parentContact = [(AIListContact *)listObject parentContact];
76                 
77                 /* Use the parent contact if it is a valid meta contact which contains contacts
78                  * If this contact is within a metacontact but not currently listed on any buddy list, we don't want to 
79                  * display the effectively-invisible metacontact's info but rather the info of this contact itself.
80                  */
81                 if (![parentContact isKindOfClass:[AIMetaContact class]] ||
82                         [[(AIMetaContact *)parentContact listContacts] count]) {
83                         listObject = parentContact;
84                 }
85         }
87         [sharedContactInfoInstance configureForListObject:listObject];
88         [[sharedContactInfoInstance window] makeKeyAndOrderFront:nil];
90         return (sharedContactInfoInstance);
93 //Close the info window
94 + (void)closeInfoWindow
96         if (sharedContactInfoInstance) {
97                 [sharedContactInfoInstance closeWindow:nil];
98         }
101 - (void)dealloc
103         //If we removed the account and info tab view items, we're currently also retaining them
104         if ([tabView_category indexOfTabViewItem:tabViewItem_info] == NSNotFound) {
105                 [tabViewItem_accounts release]; tabViewItem_accounts = nil;
106                 [tabViewItem_info release]; tabViewItem_info = nil;
107         }
109         [displayedObject release]; displayedObject = nil;
110         [loadedPanes release]; loadedPanes = nil;
111         
112         [super dealloc];
116 - (NSString *)adiumFrameAutosaveName
118         return KEY_INFO_WINDOW_FRAME;
121 //Setup the window before it is displayed
122 - (void)windowDidLoad
124         [super windowDidLoad];
126         int                              selectedTab;
127         NSTabViewItem   *tabViewItem;
129         //
130         loadedPanes = [[NSMutableDictionary alloc] init];
132         //Localization
133         [self localizeTabViewItemTitles];
134         [button_removeContact setToolTip:AILocalizedString(@"Disassociate the selected contact from this meta contact. This does not remove the contact from your contact list.",nil)];
135         [button_removeContact setEnabled:NO];
137         //Select the previously selected category
138         selectedTab = [[[adium preferenceController] preferenceForKey:KEY_INFO_SELECTED_CATEGORY
139                                                                                                                         group:PREF_GROUP_WINDOW_POSITIONS] intValue];
140         if (selectedTab < 0 || selectedTab >= [tabView_category numberOfTabViewItems]) selectedTab = 0;
142         tabViewItem = [tabView_category tabViewItemAtIndex:selectedTab];
144         //NSTabView won't send the willSelectTabViewItem: properly when we call selectTabViewItem:
145         [self tabView:tabView_category willSelectTabViewItem:tabViewItem];
146         [tabView_category selectTabViewItem:tabViewItem];
148         [imageView_userIcon setAnimates:YES];
149         [imageView_userIcon setMaxSize:NSMakeSize(256, 256)];
151         //Monitor the selected contact
152         [[adium notificationCenter] addObserver:self
153                                                                    selector:@selector(selectionChanged:)
154                                                                            name:Interface_ContactSelectionChanged
155                                                                          object:nil];
157         contactListController = [[ESContactInfoListController alloc] initWithContactListView:contactListView
158                                                                                                                                                         inScrollView:scrollView_contactList
159                                                                                                                                                                 delegate:self];
160         
161         [self setupMetaContactDrawer];
164 - (void)localizeTabViewItemTitles
166         NSEnumerator    *enumerator = [[tabView_category tabViewItems] objectEnumerator];
167         NSTabViewItem   *tabViewItem;
168         while ((tabViewItem = [enumerator nextObject])) {
169                 NSString        *label = nil;
170                 int                     identifier = [[tabViewItem identifier] intValue];
172                 switch (identifier) {
173                         case AIInfo_Profile:
174                                 label = AILocalizedString(@"Info","short form of tab view item title for Contact Info window's first tab");
175                                 break;
176                         case AIInfo_Accounts:
177                                 label = AILocalizedString(@"Accounts",nil);
178                                 break;
179                         case AIInfo_Alerts:
180                                 label = AILocalizedString(@"Events", "Name of preferences and tab for specifying what Adium should do when events occur - for example, display a Growl alert when John signs on.");
181                                 break;
182                         case AIInfo_Settings:
183                                 label = AILocalizedString(@"Settings","tab view item title for Contact Settings (Settings)");
184                                 break;
185                 }
187                 [tabViewItem setLabel:label];
188         }
191 //called as the window closes
192 - (void)windowWillClose:(id)sender
194         NSEnumerator            *enumerator;
195         AIContactInfoPane       *pane;
197         [super windowWillClose:sender];
199         //Take focus away from any controls to ensure that they register changes and save
200         [[self window] makeFirstResponder:tabView_category];
202         //Close all open panes
203         enumerator = [loadedPanes objectEnumerator];
204         while ((pane = [enumerator nextObject])) {
205                 [pane closeView];
206         }
208         //Save the selected category
209         [[adium preferenceController] setPreference:[NSNumber numberWithInt:[tabView_category indexOfSelectedTabViewItem]]
210                                                                                  forKey:KEY_INFO_SELECTED_CATEGORY
211                                                                                   group:PREF_GROUP_WINDOW_POSITIONS];
213         //Close down
214         [[adium notificationCenter] removeObserver:self];
215         [self autorelease]; sharedContactInfoInstance = nil;
218 - (NSImage *)tabView:(NSTabView *)tabView imageForTabViewItem:(NSTabViewItem *)tabViewItem
220         if (tabView == tabView_category) {
221                 return [NSImage imageNamed:[NSString stringWithFormat:@"info%@",[tabViewItem identifier]] forClass:[self class]];
222         }
224         return nil;
228 - (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
230         if (tabView == tabView_category) {
231                 AIContactInfoPane *pane = nil;
232                 
233                 //Take focus away from any textual controls to ensure that they register changes and save
234                 if ([[[self window] firstResponder] isKindOfClass:[NSText class]]) {
235                         [[self window] makeFirstResponder:nil];
236                 }
237                 
238                 int identifier = [[tabViewItem identifier] intValue];
239                 if (!(pane = [loadedPanes objectForKey:[NSNumber numberWithInt:identifier]])) {
240                         switch (identifier) {
241                                 case AIInfo_Profile:
242                                         pane = [AIContactProfilePane contactInfoPane];
243                                         [view_Profile setPanes:[NSArray arrayWithObject:pane]];
245                                         break;
246                                 case AIInfo_Accounts:
247                                         pane = [AIContactAccountsPane contactInfoPane];
248                                         [view_Accounts setPanes:[NSArray arrayWithObject:pane]];
249                                         break;
250                                 case AIInfo_Alerts:
251                                         pane = [ESContactAlertsPane contactInfoPane];
252                                         [view_Alerts setPanes:[NSArray arrayWithObject:pane]];
253                                         break;
254                                 case AIInfo_Settings:
255                                         pane = [AIContactSettingsPane contactInfoPane];
256                                         [view_Settings setPanes:[NSArray arrayWithObject:pane]];
257                                         break;
258                         }
259                         
260                         if (pane) {
261                                 [loadedPanes setObject:pane
262                                                                 forKey:[NSNumber numberWithInt:identifier]];
263                         } else {
264                                 NSLog(@"%@: Could not load pane for identifier %i",self,identifier);
265                         }
266                 }
268                 //Configure the loaded panes
269                 [self configurePane:pane];
270         }
273 //When the contact list selection changes, then configure the window for the new contact
274 - (void)selectionChanged:(NSNotification *)notification
276         AIListObject    *object = [[adium interfaceController] selectedListObject];
277         if (object) [self configureForListObject:[[adium interfaceController] selectedListObject]];
280 - (void)updateUserIcon
282         NSImage         *userIcon;
283         NSSize          userIconSize, imageView_userIconSize;
285         //User Icon
286         if (!(userIcon = [displayedObject userIcon])) {
287                 userIcon = [NSImage imageNamed:@"DefaultIcon" forClass:[self class]];
288         }
289         
290         /* NSScaleProportionally will lock an animated GIF into a single frame.  We therefore use NSScaleNone if
291          * we are already at the right size or smaller than the right size; otherwise we scale proportionally to
292          * fit the frame.
293          */
294         userIconSize = [userIcon size];
295         imageView_userIconSize = [imageView_userIcon frame].size;
296         
297         [imageView_userIcon setImageScaling:(((userIconSize.width <= imageView_userIconSize.width) && (userIconSize.height <= imageView_userIconSize.height)) ?
298                                                                                  NSScaleNone :
299                                                                                  NSScaleProportionally)];
300         [imageView_userIcon setImage:userIcon];
301         [imageView_userIcon setTitle:(displayedObject ?
302                                                                   [NSString stringWithFormat:AILocalizedString(@"%@'s Image",nil),[displayedObject displayName]] :
303                                                                   AILocalizedString(@"Image Picker",nil))];
305         //Show the reset image button if a preference is set on this object, overriding its serverside icon
306         [imageView_userIcon setShowResetImageButton:([displayedObject preferenceForKey:KEY_USER_ICON
307                                                                                                                                                          group:PREF_GROUP_USERICONS
308                                                                                                                          ignoreInheritedValues:YES] != nil)];
311 //Change the list object
312 - (void)configureForListObject:(AIListObject *)inObject
314         if (inObject == nil || displayedObject != inObject) {
315                 BOOL            useDisplayName = NO;
317                 //Update our displayed object
318                 [displayedObject release];
319                 displayedObject = [inObject retain];
321                 //Update our window title
322                 if (inObject) {
323                         [[self window] setTitle:[NSString stringWithFormat:AILocalizedString(@"%@'s Info",nil),[inObject displayName]]];
324                 } else {
325                         [[self window] setTitle:AILocalizedString(@"Contact Info",nil)];
326                 }
328                 //Service
329                 if ([inObject isKindOfClass:[AIListContact class]]) {
330                         NSString        *displayServiceID;
331                         if ([inObject isKindOfClass:[AIMetaContact class]]) {
332                                 if ([[(AIMetaContact *)inObject listContacts] count] > 1) {
333                                         displayServiceID = AILocalizedString(@"Meta", "Short string used to identify the 'service' of a multiple-service meta contact");
334                                         useDisplayName = YES;
335                                 } else {
336                                         displayServiceID = [[[(AIMetaContact *)inObject preferredContact] service] shortDescription];
337                                 }
339                         } else {
340                                 displayServiceID = [[inObject service] shortDescription];
341                         }
343                         [textField_service setStringValue:(displayServiceID ? displayServiceID : @"")];
345                 } else if ([inObject isKindOfClass:[AIListGroup class]]) {
346                         [textField_service setLocalizedString:AILocalizedString(@"Group",nil)];
347                 } else {
348                         [textField_service setStringValue:@""];
349                 }
351                 //Account name
352                 if (inObject) {
353                         NSString        *formattedUID;
355                         if (!useDisplayName && (formattedUID = [inObject formattedUID])) {
356                                 [textField_accountName setStringValue:formattedUID];
357                         } else {
358                                 NSString        *displayName;
360                                 if ((displayName = [inObject displayName])) {
361                                         [textField_accountName setStringValue:displayName];
362                                 } else {
363                                         [textField_accountName setStringValue:[inObject UID]];
364                                 }
365                         }
367                 } else {
368                         [textField_accountName setStringValue:@""];
369                 }
371                 [self updateUserIcon];
373                 //Configure our subpanes
374                 [self configureVisiblityOfTabViewItemsForListObject:inObject];
376                 //Confiugre the drawer
377                 [self configureDrawer];
378                 
379                 //Reconfigure the currently selected tab view item
380                 [self tabView:tabView_category willSelectTabViewItem:[tabView_category selectedTabViewItem]];
382         }
385 //Configure our views
386 - (void)configurePane:(AIContactInfoPane *)pane
388         if (displayedObject) {
389                 [pane configureForListObject:displayedObject];
390         }
393 - (void)configureVisiblityOfTabViewItemsForListObject:(AIListObject *)inObject
395         if ([inObject isKindOfClass:[AIListGroup class]]) {
396                 //Remove the info and account items for groups
397                 if ([tabView_category indexOfTabViewItem:tabViewItem_info] != NSNotFound) {
398                         [tabViewItem_accounts retain];
399                         [tabViewItem_info retain];
400                         
401                         //Store the tab view item selected out of accounts or info, if one is selected
402                         NSTabViewItem *currentlySelected = [tabView_category selectedTabViewItem];
403                         tabViewItem_lastSelectedForListContacts = ((currentlySelected == tabViewItem_accounts || currentlySelected == tabViewItem_info) ?
404                                                                                                            currentlySelected :
405                                                                                                            nil);
407                         [tabView_category removeTabViewItem:tabViewItem_accounts];
408                         [tabView_category removeTabViewItem:tabViewItem_info];
409                 }
410                 
411         } else {
412                 //Add the info and account items back in if they are missing
413                 if ([tabView_category indexOfTabViewItem:tabViewItem_info] == NSNotFound) {
414                         [tabView_category insertTabViewItem:tabViewItem_accounts atIndex:0];
415                         [tabView_category insertTabViewItem:tabViewItem_info atIndex:0];
416                         
417                         //Restore the tab view item last selected for a contact if we have one stored
418                         if (tabViewItem_lastSelectedForListContacts) {
419                                 [tabView_category selectTabViewItem:tabViewItem_lastSelectedForListContacts];
420                                 tabViewItem_lastSelectedForListContacts = nil;
421                         }
422                         
423                         [tabViewItem_accounts release];
424                         [tabViewItem_info release];
425                 }                       
426         }
427         
428 #warning need to hide panes for bookmarks
431 #pragma mark AIImageViewWithImagePicker Delegate
432 // AIImageViewWithImagePicker Delegate ---------------------------------------------------------------------
433 - (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)sender didChangeToImageData:(NSData *)imageData
435         if (displayedObject) {
436                 [displayedObject setUserIconData:imageData];
437         }
438         
439         [self updateUserIcon];
442 - (void)deleteInImageViewWithImagePicker:(AIImageViewWithImagePicker *)sender
444         if (displayedObject) {
445                 //Remove the preference
446                 [displayedObject setUserIconData:nil];
448                 [self updateUserIcon];
449         }
453  If the userIcon was bigger than our image view's frame, it will have been clipped before being passed
454  to the AIImageViewWithImagePicker.  This delegate method lets us pass the original, unmodified userIcon.
455  */
456 - (NSImage *)imageForImageViewWithImagePicker:(AIImageViewWithImagePicker *)picker
458         return ([displayedObject userIcon]);
461 - (NSImage *)emptyPictureImageForImageViewWithImagePicker:(AIImageViewWithImagePicker *)picker
463         return [AIServiceIcons serviceIconForObject:displayedObject type:AIServiceIconLarge direction:AIIconNormal];
466 - (NSString *)fileNameForImageInImagePicker:(AIImageViewWithImagePicker *)picker
468         NSString *fileName = [[displayedObject displayName] safeFilenameString];
469         if ([fileName hasPrefix:@"."]) {
470                 fileName = [fileName substringFromIndex:1];
471         }
472         return fileName;
475 #pragma mark Contact List (metaContact)
476 - (void)setupMetaContactDrawer
478         NSDictionary    *themeDict = [NSDictionary dictionaryNamed:CONTACT_INFO_THEME forClass:[self class]];
479         NSDictionary    *layoutDict = [NSDictionary dictionaryNamed:CONTACT_INFO_LAYOUT forClass:[self class]];
481         [contactListController updateLayoutFromPrefDict:layoutDict andThemeFromPrefDict:themeDict];
483         [contactListController setHideRoot:YES];
486 - (void)configureDrawer
488         AIListObject    *listObject = ([displayedObject isKindOfClass:[AIListContact class]] ?
489                                                                    [(AIListContact *)displayedObject parentContact] :
490                                                                    displayedObject);
491         
492         if ([listObject isKindOfClass:[AIMetaContact class]] &&
493                 ([[(AIMetaContact *)listObject listContacts] count] > 1)) {
494                 [contactListController setContactListRoot:(AIMetaContact *)listObject];
495                 [drawer_metaContact open];
497                 NSRect  outlineFrame = [contactListView frame];
498                 int             totalHeight = [contactListView totalHeight];
500                 if (outlineFrame.size.height != totalHeight) {
501                         outlineFrame.size.height = totalHeight;
502                         [contactListView setFrame:outlineFrame];
503                         [contactListView setNeedsDisplay:YES];
504                 }
506         } else {
507                 [drawer_metaContact close];
508                 [contactListController setContactListRoot:nil];
509         }
511 - (IBAction)addContact:(id)sender
516 - (IBAction)removeContact:(id)sender
518         NSEnumerator    *enumerator;
519         AIListObject    *aListObject;
520         AIMetaContact   *contactListRoot = (AIMetaContact *)[contactListController contactListRoot];
522         enumerator = [[contactListView arrayOfSelectedItems] objectEnumerator];
523         while ((aListObject = [enumerator nextObject])) {
524                 [[adium contactController] removeAllListObjectsMatching:aListObject
525                                                                                                 fromMetaContact:contactListRoot];
526         }
528         //The contents of the metaContact have now changed; reload
529         [contactListView reloadData];
531         [contactListController outlineViewSelectionDidChange:nil];
534 - (float)drawerTrailingOffset
536         return [drawer_metaContact trailingOffset];
539 - (void)performDefaultActionOnSelectedObject:(AIListObject *)listObject sender:(NSOutlineView *)sender
544 - (void)contactInfoListControllerSelectionDidChangeToListObject:(AIListObject *)listObject
546         AILog(@"Configuring Info List for %@",listObject);
547         [self configureForListObject:listObject];
549         //Only enable the remove contact button if a contact within the metacontact is selected
550         [button_removeContact setEnabled:(listObject && (listObject != (AIListObject *)[contactListController contactListRoot]))];
553 @end