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 "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
49 if(![window isVisible]) {
52 [[adium notificationCenter] addObserver:self
53 selector:@selector(xtrasChanged:)
54 name:Adium_Xtras_Changed
56 [NSBundle loadNibNamed:@"XtrasManager" owner:self];
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];
66 cell = [[AIImageTextCell alloc] init];
67 [cell setFont:[NSFont systemFontOfSize:12]];
68 [cell setDrawsGradientHighlight:YES];
69 [[xtraList tableColumnWithIdentifier:@"xtras"] setDataCell:cell];
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];
82 [window makeKeyAndOrderFront:nil];
85 - (void)windowWillClose:(NSNotification *)aNotification
87 [[adium notificationCenter] removeObserver:self
88 name:Adium_Xtras_Changed
91 [categories release]; categories = nil;
95 - (void)xtrasChanged:(NSNotification *)not
97 //Clear our cache of loaded Xtras
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"]];
111 [categories release];
112 categories = [[NSMutableArray alloc] init];
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]];
125 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
126 [NSNumber numberWithInt:AIStatusIconsDirectory], @"Directory",
127 AILocalizedString(@"Status Icons", "AdiumXtras category name"), @"Name",
128 [NSImage imageNamed:@"AdiumStatusIcons"], @"Image", nil]];
130 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
131 [NSNumber numberWithInt:AISoundsDirectory], @"Directory",
132 AILocalizedString(@"Sound Sets", "AdiumXtras category name"), @"Name",
133 [NSImage imageNamed:@"AdiumSoundset"], @"Image", nil]];
135 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
136 [NSNumber numberWithInt:AIDockIconsDirectory], @"Directory",
137 AILocalizedString(@"Dock Icons", "AdiumXtras category name"), @"Name",
138 [NSImage imageNamed:@"AdiumIcon"], @"Image", nil]];
140 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
141 [NSNumber numberWithInt:AIEmoticonsDirectory], @"Directory",
142 AILocalizedString(@"Emoticons", "AdiumXtras category name"), @"Name",
143 [NSImage imageNamed:@"AdiumEmoticonset"], @"Image", nil]];
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]];
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])) {
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]]]];
182 [categories release];
187 - (NSArray *)xtrasForCategoryAtIndex:(int)inIndex
189 if (inIndex == -1) return nil;
191 NSDictionary *xtrasDict = [categories objectAtIndex:inIndex];
194 if (!(xtras = [xtrasDict objectForKey:@"Xtras"])) {
195 xtras = [self arrayOfXtrasAtPaths:AISearchPathForDirectoriesInDomains([[xtrasDict objectForKey:@"Directory"] intValue],
196 AIAllDomainsMask & ~AIInternalDomainMask,
198 NSMutableDictionary *newDictionary = [xtrasDict mutableCopy];
199 [newDictionary setObject:xtras forKey:@"Xtras"];
200 [categories replaceObjectAtIndex:inIndex
201 withObject:newDictionary];
202 [newDictionary release];
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]];
228 //[showInfoControl setHidden:NO];
230 [NSBundle loadNibNamed:@"XtraInfoView" owner:self];
232 [NSBundle loadNibNamed:@"XtraPreviewImageView" owner:self];
233 /* NSString * xtraType = [xtra type];
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];
244 else if ([xtraType isEqualToString:AIXtraTypeServiceIcons]) {
245 [NSBundle loadNibNamed:@"ServiceIconPreviewView" owner:self];
247 else { //catchall behavior is to just show the readme
248 [NSBundle loadNibNamed:@"XtraInfoView" owner:self];
249 [showInfoControl setHidden:YES];
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];
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];
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];
288 [xtraList selectRow:0 byExtendingSelection:NO];
289 [selectedCategory removeObjectsAtIndexes:indices];
290 [xtraList reloadData];
292 XXX this is ugly. We should use the AIXtraInfo's type instead of the path extension
294 NSEnumerator * extEnu = [pathExtensions objectEnumerator];
295 while ((path = [extEnu nextObject])) { //usually this will only run once
296 [[adium notificationCenter] postNotificationName:Adium_Xtras_Changed
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)
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
317 didEndSelector:@selector(deleteXtrasAlertDidEnd:returnCode:contextInfo:)
321 - (IBAction) browseXtras:(id)sender
323 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_XTRAS_PAGE]];
326 - (IBAction) checkForUpdates:(id)sender
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];
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];
354 [manager createDirectoryAtPath:resourcesPath attributes:nil];
357 BOOL isDir = NO, success;
358 success = [manager fileExistsAtPath:resourcesPath isDirectory:&isDir] && isDir;
360 success = [manager fileExistsAtPath:infoPlistPath isDirectory:&isDir] && !isDir;
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];
371 [cell setImage:[[selectedCategory objectAtIndex:row] icon]];
372 [cell setSubString:nil];
376 - (int)numberOfRowsInTableView:(NSTableView *)tableView
378 if (tableView == sidebar) {
379 return [categories count];
382 return [selectedCategory count];
386 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
388 if (tableView == sidebar) {
389 return [[categories objectAtIndex:row] objectForKey:@"Name"];
391 NSString * name = [[selectedCategory objectAtIndex:row] name];
392 return (name != nil) ? name : @"";
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];
407 // [deleteButton setEnabled:NO];
410 } else if ([aNotification object] == sidebar) {
411 [self setCategory:nil];
415 - (void)tableViewDeleteSelectedRows:(NSTableView *)tableView