Patch from Jesse Rusak which prevents the message window and contact list from steali...
[adiumx.git] / Source / AIXtrasManager.m
blob46e134d612b4beaf5af219b903dd55d0c4c61336
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 "AIXtrasManager.h"
18 #import "AIXtraInfo.h"
19 #import "AIPathUtilities.h"
20 #import <AIUtilities/AIImageTextCell.h>
21 #import <AIUtilities/AIGradientCell.h>
22 #import <AIUtilities/AIImageAdditions.h>
23 #import <AIUtilities/AIArrayAdditions.h>
24 #import <AIUtilities/AIFileManagerAdditions.h>
25 #import <Adium/AIDockControllerProtocol.h>
27 #import "AIXtraPreviewController.h"
29 #define ADIUM_XTRAS_PAGE                                        AILocalizedString(@"http://www.adiumxtras.com/","Adium xtras page. Localized only if a translated version exists.")
31 @implementation AIXtrasManager
33 static AIXtrasManager *manager;
35 + (AIXtrasManager *) sharedManager
37         return manager;
40 - (void)installPlugin
42         manager = self;
45 - (void)showXtras
47         [self loadXtras];
49         if(![window isVisible]) {
50                 showInfo = NO;
51                 
52                 [[adium notificationCenter] addObserver:self
53                                                                            selector:@selector(xtrasChanged:)
54                                                                                    name:Adium_Xtras_Changed
55                                                                                  object:nil];
56                 [NSBundle loadNibNamed:@"XtrasManager" owner:self];
57                 
58                 AIImageTextCell                 *cell;
59                 //Configure our tableViews
60                 cell = [[AIImageTextCell alloc] init];
61                 [cell setFont:[NSFont systemFontOfSize:12]];
62                 [cell setDrawsGradientHighlight:YES];
63                 [[sidebar tableColumnWithIdentifier:@"name"] setDataCell:cell];
64                 [cell release];
65                 
66                 cell = [[AIImageTextCell alloc] init];
67                 [cell setFont:[NSFont systemFontOfSize:12]];
68                 [cell setDrawsGradientHighlight:YES];
69                 [[xtraList tableColumnWithIdentifier:@"xtras"] setDataCell:cell];
70                 [cell release];
71                 
72                 [previewContainerView setHasVerticalScroller:YES];
73                 [previewContainerView setAutohidesScrollers:YES];
74                 [previewContainerView setBorderType:NSBezelBorder];
76                 [deleteButton setLocalizedString:AILocalizedStringFromTable(@"Delete", @"Buttons", nil)];
77                 [button_getMoreXtras setLocalizedString:AILocalizedStringFromTable(@"Get More Xtras", @"Buttons", "Button in the Xtras Manager to go to adiumxtras.com to get more adiumxtras")];
79                 [self setCategory:nil];
80         }
81                 
82         [window makeKeyAndOrderFront:nil];
85 - (void)windowWillClose:(NSNotification *)aNotification
87         [[adium notificationCenter] removeObserver:self
88                                                                                   name:Adium_Xtras_Changed
89                                                                                 object:nil];
90         
91         [categories release]; categories = nil;
95 - (void)xtrasChanged:(NSNotification *)not
97         //Clear our cache of loaded Xtras
98         [self loadXtras];
99         
100         //Now redisplay our current category, in case it changed
101         [self setCategory:nil];
104 int categorySort(id categoryA, id categoryB, void * context)
106         return [[categoryA objectForKey:@"Name"] caseInsensitiveCompare:[categoryB objectForKey:@"Name"]];
109 - (void)loadXtras
111         [categories release];
112         categories = [[NSMutableArray alloc] init];
113         
114         [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
115                 [NSNumber numberWithInt:AIMessageStylesDirectory], @"Directory",
116                 AILocalizedString(@"Message Styles", "AdiumXtras category name"), @"Name",
117                 [NSImage imageNamed:@"AdiumMessageStyle"], @"Image", nil]];
119         [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
120                 [NSNumber numberWithInt:AIContactListDirectory], @"Directory",
121                 AILocalizedString(@"Contact List Themes", "AdiumXtras category name"), @"Name",
122                 [NSImage imageNamed:@"AdiumListTheme"], @"Image", nil]];
123         
125         [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
126                 [NSNumber numberWithInt:AIStatusIconsDirectory], @"Directory",
127                 AILocalizedString(@"Status Icons", "AdiumXtras category name"), @"Name",
128                 [NSImage imageNamed:@"AdiumStatusIcons"], @"Image", nil]];
129         
130         [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
131                 [NSNumber numberWithInt:AISoundsDirectory], @"Directory",
132                 AILocalizedString(@"Sound Sets", "AdiumXtras category name"), @"Name",
133                 [NSImage imageNamed:@"AdiumSoundset"], @"Image", nil]];
134         
135         [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
136                 [NSNumber numberWithInt:AIDockIconsDirectory], @"Directory",
137                 AILocalizedString(@"Dock Icons", "AdiumXtras category name"), @"Name",
138                 [NSImage imageNamed:@"AdiumIcon"], @"Image", nil]];
139         
140         [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
141                 [NSNumber numberWithInt:AIEmoticonsDirectory], @"Directory",
142                 AILocalizedString(@"Emoticons", "AdiumXtras category name"), @"Name",
143                 [NSImage imageNamed:@"AdiumEmoticonset"], @"Image", nil]];
144         
145         [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
146                 [NSNumber numberWithInt:AIScriptsDirectory], @"Directory",
147                 AILocalizedString(@"Scripts", "AdiumXtras category name"), @"Name",
148                 [NSImage imageNamed:@"AdiumScripts"], @"Image", nil]];
150         [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
151                 [NSNumber numberWithInt:AIServiceIconsDirectory], @"Directory",
152                 AILocalizedString(@"Service Icons", "AdiumXtras category name"), @"Name",
153                 [NSImage imageNamed:@"AdiumServiceIcons"], @"Image", nil]];
154         
155         [categories sortUsingFunction:categorySort context:NULL];
158 - (NSArray *)arrayOfXtrasAtPaths:(NSArray *)paths
160         NSMutableArray  *contents = [NSMutableArray array];
161         NSEnumerator    *dirEnu;
162         NSString                *path, *xtraName;
163         NSFileManager   *manager = [NSFileManager defaultManager];
165         dirEnu = [paths objectEnumerator];
166         while ((path = [dirEnu nextObject])) {
167                 NSEnumerator    *xEnu;
169                 xEnu = [[manager directoryContentsAtPath:path] objectEnumerator];
170                 while ((xtraName = [xEnu nextObject])) {
171                         if (![xtraName hasPrefix:@"."]) {
172                                 [contents addObject:[AIXtraInfo infoWithURL:[NSURL fileURLWithPath:[path stringByAppendingPathComponent:xtraName]]]];
173                         }
174                 }
175         }
177         return contents;
180 - (void)dealloc
182         [categories release];
184         [super dealloc];
187 - (NSArray *)xtrasForCategoryAtIndex:(int)inIndex
189         if (inIndex == -1) return nil;
191         NSDictionary    *xtrasDict = [categories objectAtIndex:inIndex];
192         NSArray                 *xtras;
193         
194         if (!(xtras = [xtrasDict objectForKey:@"Xtras"])) {
195                 xtras = [self arrayOfXtrasAtPaths:AISearchPathForDirectoriesInDomains([[xtrasDict objectForKey:@"Directory"] intValue],
196                                                                                                                                                           AIAllDomainsMask & ~AIInternalDomainMask,
197                                                                                                                                                           YES)];
198                 NSMutableDictionary *newDictionary = [xtrasDict mutableCopy];
199                 [newDictionary setObject:xtras forKey:@"Xtras"];
200                 [categories replaceObjectAtIndex:inIndex
201                                                           withObject:newDictionary];
202                 [newDictionary release];
203         }
204         
205         return xtras;
208 - (IBAction)setCategory:(id)sender
210         [selectedCategory autorelease];
211         selectedCategory = [[self xtrasForCategoryAtIndex:[sidebar selectedRow]] retain];
213         [xtraList selectRow:0 byExtendingSelection:NO];
214         [xtraList reloadData];
216         [self updatePreview];
219 - (void)updatePreview
221         AIXtraInfo *xtra = nil;
223         if ([selectedCategory count] > 0 && [xtraList selectedRow] != -1) {
224                 xtra = [selectedCategory objectAtIndex:[xtraList selectedRow]];
225         }
227         if (xtra) {
228                 //[showInfoControl setHidden:NO];
229                 if(showInfo)
230                         [NSBundle loadNibNamed:@"XtraInfoView" owner:self];
231                 else {
232                         [NSBundle loadNibNamed:@"XtraPreviewImageView" owner:self];
233                         /*      NSString * xtraType = [xtra type];
234                         
235                         if ([xtraType isEqualToString:AIXtraTypeEmoticons])
236                         [NSBundle loadNibNamed:@"EmoticonPreviewView" owner:self];
237                         else if ([xtraType isEqualToString:AIXtraTypeDockIcon])
238                         [NSBundle loadNibNamed:@"DockIconPreviewView" owner:self];
239                         else if ([xtraType isEqualToString:AIXtraTypeMessageStyle])
240                         [NSBundle loadNibNamed:@"WebkitMessageStylePreviewView" owner:self];
241                         else if ([xtraType isEqualToString:AIXtraTypeStatusIcons]) {
242                                 [NSBundle loadNibNamed:@"StatusIconPreviewView" owner:self];
243                         }
244                         else if ([xtraType isEqualToString:AIXtraTypeServiceIcons]) {
245                                 [NSBundle loadNibNamed:@"ServiceIconPreviewView" owner:self];
246                         }
247                         else { //catchall behavior is to just show the readme
248                                 [NSBundle loadNibNamed:@"XtraInfoView" owner:self];
249                                 [showInfoControl setHidden:YES];
250                         }*/
251                 }
252                 if (previewController/* && previewContainerView*/) {
253                         NSView *pv = [previewController previewView];
254                         NSSize docSize = [previewContainerView documentVisibleRect].size;
255                         NSRect viewFrame = [pv frame];
256                         viewFrame.size.width = docSize.width;
257                         if([pv respondsToSelector:@selector(image)]) viewFrame.size.height = [[(NSImageView *)pv image]size].height;
258                         if(viewFrame.size.height < docSize.height) viewFrame.size.height = docSize.height;
259                         [pv setFrameSize:viewFrame.size];
260                         [previewContainerView setDocumentView:pv];
261                         [previewController setXtra:xtra];
262                         [previewContainerView setNeedsDisplay:YES];
263                 }               
264         }
267 - (IBAction) setShowsInfo:(id)sender
269         showInfo = ([sender selectedSegment] != 0);
271         [self updatePreview];
274 - (void)deleteXtrasAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo
276         if (returnCode == NSAlertDefaultReturn) {
277                 NSFileManager * manager = [NSFileManager defaultManager];
278                 NSIndexSet * indices = [xtraList selectedRowIndexes];
279                 NSMutableSet * pathExtensions = [NSMutableSet set];
280                 NSString * path;
281                 for (int i = [indices lastIndex]; i >= 0; i--) {
282                         if ([indices containsIndex:i]) {
283                                 path = [[selectedCategory objectAtIndex:i] path];
284                                 [pathExtensions addObject:[path pathExtension]];
285                                 [manager trashFileAtPath:path];
286                         }
287                 }
288                 [xtraList selectRow:0 byExtendingSelection:NO];
289                 [selectedCategory removeObjectsAtIndexes:indices];
290                 [xtraList reloadData];
291                 /*
292                  XXX this is ugly. We should use the AIXtraInfo's type instead of the path extension
293                 */
294                 NSEnumerator * extEnu = [pathExtensions objectEnumerator];
295                 while ((path = [extEnu nextObject])) { //usually this will only run once
296                         [[adium notificationCenter] postNotificationName:Adium_Xtras_Changed
297                                                                                                           object:path];
298                 }
299         }
302 - (IBAction) deleteXtra:(id)sender
304         int selectionCount = [[xtraList selectedRowIndexes] count];
306         NSAlert * warning = [NSAlert alertWithMessageText:((selectionCount > 1) ?
307                                                                                                            [NSString stringWithFormat:AILocalizedString(@"Delete %i Xtras?", nil), selectionCount] :
308                                                                                                            AILocalizedString(@"Delete Xtra?", nil))
309                                                                                 defaultButton:AILocalizedString(@"Delete", nil)
310                                                                           alternateButton:AILocalizedString(@"Cancel", nil)
311                                                                                   otherButton:nil
312                                                         informativeTextWithFormat:((selectionCount > 1) ?
313                                                                                                            AILocalizedString(@"The selected Xtras will be moved to the Trash.", nil) :
314                                                                                                            AILocalizedString(@"The selected Xtra will be moved to the Trash.", nil))];
315         [warning beginSheetModalForWindow:window
316                                                 modalDelegate:self
317                                            didEndSelector:@selector(deleteXtrasAlertDidEnd:returnCode:contextInfo:)
318                                                   contextInfo:nil];
321 - (IBAction) browseXtras:(id)sender
323         [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_XTRAS_PAGE]];
326 - (IBAction) checkForUpdates:(id)sender
328         
331 + (BOOL) createXtraBundleAtPath:(NSString *)path 
333         NSString *contentsPath  = [path stringByAppendingPathComponent:@"Contents"];
334         NSString *resourcesPath = [contentsPath stringByAppendingPathComponent:@"Resources"];
335         NSString *infoPlistPath = [contentsPath stringByAppendingPathComponent:@"Info.plist"];
337         NSFileManager * manager = [NSFileManager defaultManager];
338         NSString * name = [[path lastPathComponent] stringByDeletingPathExtension];
339         if (![manager fileExistsAtPath:path]) {
340                 [manager createDirectoryAtPath:path attributes:nil];
341                 [manager createDirectoryAtPath:contentsPath attributes:nil];
343                 //Info.plist
344                 [[NSDictionary dictionaryWithObjectsAndKeys:
345                         @"English", kCFBundleDevelopmentRegionKey,
346                         name, kCFBundleNameKey,
347                         @"AdIM", @"CFBundlePackageType",
348                         [@"com.adiumx." stringByAppendingString:name], kCFBundleIdentifierKey,
349                         [NSNumber numberWithInt:1], @"XtraBundleVersion",
350                         @"1.0", kCFBundleInfoDictionaryVersionKey,
351                         nil] writeToFile:infoPlistPath atomically:YES];
353                 //Resources
354                 [manager createDirectoryAtPath:resourcesPath attributes:nil];
355         }
357         BOOL isDir = NO, success;
358         success = [manager fileExistsAtPath:resourcesPath isDirectory:&isDir] && isDir;
359         if (success)
360                 success = [manager fileExistsAtPath:infoPlistPath isDirectory:&isDir] && !isDir;
361         return success;
364 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
366         if (tableView == sidebar) {
367                 [cell setImage:[[categories objectAtIndex:row] objectForKey:@"Image"]];
368                 [cell setSubString:nil];
369         }
370         else {
371                 [cell setImage:[[selectedCategory objectAtIndex:row] icon]];
372                 [cell setSubString:nil];
373         }
376 - (int)numberOfRowsInTableView:(NSTableView *)tableView
378         if (tableView == sidebar) {
379                 return [categories count];
380         }
381         else {
382                 return [selectedCategory count];
383         }
386 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
388         if (tableView == sidebar) {
389                 return [[categories objectAtIndex:row] objectForKey:@"Name"];
390         } else {
391                 NSString * name = [[selectedCategory objectAtIndex:row] name];
392                 return (name != nil) ? name : @"";
393         }
396 - (void)tableViewSelectionDidChange:(NSNotification *)aNotification
398         if ([aNotification object] == xtraList) {
399                 //int   selectedRow = [xtraList selectedRow];
400                 //if ((selectedRow >= 0) && (selectedRow < [selectedCategory count])) {
401                         //AIXtraInfo *xtraInfo  = [AIXtraInfo infoWithURL:[NSURL fileURLWithPath:[[selectedCategory objectAtIndex:selectedRow] path]]];
402                         if ([[xtraList selectedRowIndexes] count] > 0)
403                                 [deleteButton setEnabled:YES];
404                 //      if ([[xtraList selectedRowIndexes] count] == 1)
405                 //              [previewController setXtra:xtraInfo];
406                 //      else
407                 //              [deleteButton setEnabled:NO];
408                 //}
409                 
410         } else if ([aNotification object] == sidebar) {
411                 [self setCategory:nil];
412         }
415 - (void)tableViewDeleteSelectedRows:(NSTableView *)tableView
417         
420 @end