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 "AIXtraPreviewController.h"
20 #import <Adium/AIDockControllerProtocol.h>
21 #import <Adium/AIPathUtilities.h>
22 #import <Adium/KNShelfSplitView.h>
23 #import <AIUtilities/AIArrayAdditions.h>
24 #import <AIUtilities/AIAlternatingRowTableView.h>
25 #import <AIUtilities/AIFileManagerAdditions.h>
26 #import <AIUtilities/AIImageTextCell.h>
27 #import <AIUtilities/AIImageAdditions.h>
28 #import <AIUtilities/AIToolbarUtilities.h>
30 #define ADIUM_XTRAS_PAGE AILocalizedString(@"http://www.adiumxtras.com/","Adium xtras page. Localized only if a translated version exists.")
31 #define DELETE AILocalizedStringFromTable(@"Delete", @"Buttons", nil)
32 #define GET_MORE_XTRAS AILocalizedStringFromTable(@"Get More Xtras", @"Buttons", "Button in the Xtras Manager to go to adiumxtras.com to get more adiumxtras")
34 #define MINIMUM_SOURCE_LIST_WIDTH 40
36 @interface AIXtrasManager (PRIVATE)
37 - (void)installToolbar;
38 - (void)updateForSelectedCategory;
41 @implementation AIXtrasManager
43 static AIXtrasManager *manager;
45 + (AIXtrasManager *) sharedManager
57 [window setTitle:AILocalizedString(@"Xtras Manager", "Xtras Manager window title")];
59 [self installToolbar];
61 //This is the Mail.app source list background color... which differs from the iTunes one.
62 [tableView_categories setBackgroundColor:[NSColor colorWithCalibratedRed:.9059
66 [tableView_categories setDrawsGradientSelection:YES];
68 AIImageTextCell *cell;
69 //Configure our tableViews
70 cell = [[AIImageTextCell alloc] init];
71 [cell setFont:[NSFont systemFontOfSize:12]];
72 [[tableView_categories tableColumnWithIdentifier:@"name"] setDataCell:cell];
75 cell = [[AIImageTextCell alloc] init];
76 [cell setFont:[NSFont systemFontOfSize:12]];
77 [[xtraList tableColumnWithIdentifier:@"xtras"] setDataCell:cell];
81 [previewContainerView setHasVerticalScroller:YES];
82 [previewContainerView setAutohidesScrollers:YES];
83 [previewContainerView setBorderType:NSBezelBorder];
85 [tableView_categories selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
95 [[adium notificationCenter] addObserver:self
96 selector:@selector(xtrasChanged:)
97 name:AIXtrasDidChangeNotification
99 [NSBundle loadNibNamed:@"XtrasManager" owner:self];
100 [self windowDidLoad];
103 [window makeKeyAndOrderFront:nil];
106 - (void)windowWillClose:(NSNotification *)aNotification
108 [[adium notificationCenter] removeObserver:self
109 name:AIXtrasDidChangeNotification
112 [categories release]; categories = nil;
113 [toolbarItems release]; toolbarItems = nil;
115 //Release top-level nib objects besides the window
116 [view_content release]; view_content = nil;
117 [view_shelf release]; view_shelf = nil;
119 //XXX don't need to do this when this is a window controller
124 - (void)xtrasChanged:(NSNotification *)not
126 //Clear our cache of loaded Xtras
129 //Now redisplay our current category, in case it changed
130 [self updateForSelectedCategory];
133 int categorySort(id categoryA, id categoryB, void * context)
135 return [[categoryA objectForKey:@"Name"] caseInsensitiveCompare:[categoryB objectForKey:@"Name"]];
140 [categories release];
141 categories = [[NSMutableArray alloc] init];
143 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
144 [NSNumber numberWithInt:AIMessageStylesDirectory], @"Directory",
145 AILocalizedString(@"Message Styles", "AdiumXtras category name"), @"Name",
146 [NSImage imageNamed:@"AdiumMessageStyle"], @"Image", nil]];
148 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
149 [NSNumber numberWithInt:AIContactListDirectory], @"Directory",
150 AILocalizedString(@"Contact List Themes", "AdiumXtras category name"), @"Name",
151 [NSImage imageNamed:@"AdiumListTheme"], @"Image", nil]];
154 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
155 [NSNumber numberWithInt:AIStatusIconsDirectory], @"Directory",
156 AILocalizedString(@"Status Icons", "AdiumXtras category name"), @"Name",
157 [NSImage imageNamed:@"AdiumStatusIcons"], @"Image", nil]];
159 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
160 [NSNumber numberWithInt:AISoundsDirectory], @"Directory",
161 AILocalizedString(@"Sound Sets", "AdiumXtras category name"), @"Name",
162 [NSImage imageNamed:@"AdiumSoundset"], @"Image", nil]];
164 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
165 [NSNumber numberWithInt:AIDockIconsDirectory], @"Directory",
166 AILocalizedString(@"Dock Icons", "AdiumXtras category name"), @"Name",
167 [NSImage imageNamed:@"AdiumIcon"], @"Image", nil]];
169 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
170 [NSNumber numberWithInt:AIEmoticonsDirectory], @"Directory",
171 AILocalizedString(@"Emoticons", "AdiumXtras category name"), @"Name",
172 [NSImage imageNamed:@"AdiumEmoticonset"], @"Image", nil]];
174 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
175 [NSNumber numberWithInt:AIScriptsDirectory], @"Directory",
176 AILocalizedString(@"Scripts", "AdiumXtras category name"), @"Name",
177 [NSImage imageNamed:@"AdiumScripts"], @"Image", nil]];
179 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
180 [NSNumber numberWithInt:AIServiceIconsDirectory], @"Directory",
181 AILocalizedString(@"Service Icons", "AdiumXtras category name"), @"Name",
182 [NSImage imageNamed:@"AdiumServiceIcons"], @"Image", nil]];
184 [categories addObject:[NSDictionary dictionaryWithObjectsAndKeys:
185 [NSNumber numberWithInt:AIMenuBarIconsDirectory], @"Directory",
186 AILocalizedString(@"Menu Bar Icons", "AdiumXtras category name"), @"Name",
187 [NSImage imageNamed:@"AdiumMenuBarIcons"], @"Image", nil]];
189 [categories sortUsingFunction:categorySort context:NULL];
192 - (NSArray *)arrayOfXtrasAtPaths:(NSArray *)paths
194 NSMutableArray *contents = [NSMutableArray array];
195 NSEnumerator *dirEnu;
196 NSString *path, *xtraName;
197 NSFileManager *manager = [NSFileManager defaultManager];
199 dirEnu = [paths objectEnumerator];
200 while ((path = [dirEnu nextObject])) {
203 xEnu = [[manager directoryContentsAtPath:path] objectEnumerator];
204 while ((xtraName = [xEnu nextObject])) {
205 if (![xtraName hasPrefix:@"."]) {
206 [contents addObject:[AIXtraInfo infoWithURL:[NSURL fileURLWithPath:[path stringByAppendingPathComponent:xtraName]]]];
216 [categories release];
221 - (NSArray *)xtrasForCategoryAtIndex:(int)inIndex
223 if (inIndex == -1) return nil;
225 NSDictionary *xtrasDict = [categories objectAtIndex:inIndex];
228 if (!(xtras = [xtrasDict objectForKey:@"Xtras"])) {
229 xtras = [self arrayOfXtrasAtPaths:AISearchPathForDirectoriesInDomains([[xtrasDict objectForKey:@"Directory"] intValue],
230 AIAllDomainsMask & ~AIInternalDomainMask,
232 NSMutableDictionary *newDictionary = [xtrasDict mutableCopy];
233 [newDictionary setObject:xtras forKey:@"Xtras"];
234 [categories replaceObjectAtIndex:inIndex
235 withObject:newDictionary];
236 [newDictionary release];
242 - (void)updateForSelectedCategory
244 [selectedCategory autorelease];
245 selectedCategory = [[self xtrasForCategoryAtIndex:[tableView_categories selectedRow]] retain];
247 [xtraList reloadData];
248 if ([xtraList numberOfRows]) {
249 [xtraList selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
252 [self updatePreview];
255 - (void)updatePreview
257 AIXtraInfo *xtra = nil;
259 if ([selectedCategory count] > 0 && [xtraList selectedRow] != -1) {
260 xtra = [selectedCategory objectAtIndex:[xtraList selectedRow]];
264 //[showInfoControl setHidden:NO];
266 [NSBundle loadNibNamed:@"XtraInfoView" owner:self];
268 [NSBundle loadNibNamed:@"XtraPreviewImageView" owner:self];
269 /* NSString * xtraType = [xtra type];
271 if ([xtraType isEqualToString:AIXtraTypeEmoticons])
272 [NSBundle loadNibNamed:@"EmoticonPreviewView" owner:self];
273 else if ([xtraType isEqualToString:AIXtraTypeDockIcon])
274 [NSBundle loadNibNamed:@"DockIconPreviewView" owner:self];
275 else if ([xtraType isEqualToString:AIXtraTypeMessageStyle])
276 [NSBundle loadNibNamed:@"WebkitMessageStylePreviewView" owner:self];
277 else if ([xtraType isEqualToString:AIXtraTypeStatusIcons]) {
278 [NSBundle loadNibNamed:@"StatusIconPreviewView" owner:self];
280 else if ([xtraType isEqualToString:AIXtraTypeServiceIcons]) {
281 [NSBundle loadNibNamed:@"ServiceIconPreviewView" owner:self];
283 else { //catchall behavior is to just show the readme
284 [NSBundle loadNibNamed:@"XtraInfoView" owner:self];
285 [showInfoControl setHidden:YES];
288 if (previewController/* && previewContainerView*/) {
289 NSView *pv = [previewController previewView];
290 NSSize docSize = [previewContainerView documentVisibleRect].size;
291 NSRect viewFrame = [pv frame];
292 viewFrame.size.width = docSize.width;
293 if([pv respondsToSelector:@selector(image)]) viewFrame.size.height = [[(NSImageView *)pv image]size].height;
294 if(viewFrame.size.height < docSize.height) viewFrame.size.height = docSize.height;
295 [pv setFrameSize:viewFrame.size];
296 [previewContainerView setDocumentView:pv];
297 [previewController setXtra:xtra];
298 [previewContainerView setNeedsDisplay:YES];
303 - (IBAction) setShowsInfo:(id)sender
305 showInfo = ([sender selectedSegment] != 0);
307 [self updatePreview];
310 - (void)deleteXtrasAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo
312 if (returnCode == NSAlertDefaultReturn) {
313 NSFileManager * manager = [NSFileManager defaultManager];
314 NSIndexSet * indices = [xtraList selectedRowIndexes];
315 NSMutableSet * pathExtensions = [NSMutableSet set];
317 for (int i = [indices lastIndex]; i >= 0; i--) {
318 if ([indices containsIndex:i]) {
319 path = [[selectedCategory objectAtIndex:i] path];
320 [pathExtensions addObject:[path pathExtension]];
321 [manager trashFileAtPath:path];
324 [xtraList selectRow:0 byExtendingSelection:NO];
325 [selectedCategory removeObjectsAtIndexes:indices];
326 [xtraList reloadData];
328 XXX this is ugly. We should use the AIXtraInfo's type instead of the path extension
330 NSEnumerator * extEnu = [pathExtensions objectEnumerator];
331 while ((path = [extEnu nextObject])) { //usually this will only run once
332 [[adium notificationCenter] postNotificationName:AIXtrasDidChangeNotification
338 - (IBAction) deleteXtra:(id)sender
340 int selectionCount = [[xtraList selectedRowIndexes] count];
342 NSAlert * warning = [NSAlert alertWithMessageText:((selectionCount > 1) ?
343 [NSString stringWithFormat:AILocalizedString(@"Delete %i Xtras?", nil), selectionCount] :
344 AILocalizedString(@"Delete Xtra?", nil))
345 defaultButton:AILocalizedString(@"Delete", nil)
346 alternateButton:AILocalizedString(@"Cancel", nil)
348 informativeTextWithFormat:((selectionCount > 1) ?
349 AILocalizedString(@"The selected Xtras will be moved to the Trash.", nil) :
350 AILocalizedString(@"The selected Xtra will be moved to the Trash.", nil))];
351 [warning beginSheetModalForWindow:window
353 didEndSelector:@selector(deleteXtrasAlertDidEnd:returnCode:contextInfo:)
357 - (IBAction)browseXtras:(id)sender
359 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_XTRAS_PAGE]];
362 - (IBAction)checkForUpdates:(id)sender
367 + (BOOL)createXtraBundleAtPath:(NSString *)path
369 NSString *contentsPath = [path stringByAppendingPathComponent:@"Contents"];
370 NSString *resourcesPath = [contentsPath stringByAppendingPathComponent:@"Resources"];
371 NSString *infoPlistPath = [contentsPath stringByAppendingPathComponent:@"Info.plist"];
373 NSFileManager * manager = [NSFileManager defaultManager];
374 NSString * name = [[path lastPathComponent] stringByDeletingPathExtension];
375 if (![manager fileExistsAtPath:path]) {
376 [manager createDirectoryAtPath:path attributes:nil];
377 [manager createDirectoryAtPath:contentsPath attributes:nil];
380 [[NSDictionary dictionaryWithObjectsAndKeys:
381 @"English", kCFBundleDevelopmentRegionKey,
382 name, kCFBundleNameKey,
383 @"AdIM", @"CFBundlePackageType",
384 [@"com.adiumx." stringByAppendingString:name], kCFBundleIdentifierKey,
385 [NSNumber numberWithInt:1], @"XtraBundleVersion",
386 @"1.0", kCFBundleInfoDictionaryVersionKey,
387 nil] writeToFile:infoPlistPath atomically:YES];
390 [manager createDirectoryAtPath:resourcesPath attributes:nil];
393 BOOL isDir = NO, success;
394 success = [manager fileExistsAtPath:resourcesPath isDirectory:&isDir] && isDir;
396 success = [manager fileExistsAtPath:infoPlistPath isDirectory:&isDir] && !isDir;
400 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
402 if (tableView == tableView_categories) {
403 [cell setImage:[[categories objectAtIndex:row] objectForKey:@"Image"]];
404 [cell setSubString:nil];
407 [cell setImage:[[selectedCategory objectAtIndex:row] icon]];
408 [cell setSubString:nil];
412 - (int)numberOfRowsInTableView:(NSTableView *)tableView
414 if (tableView == tableView_categories) {
415 return [categories count];
418 return [selectedCategory count];
422 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
424 if (tableView == tableView_categories) {
425 return [[categories objectAtIndex:row] objectForKey:@"Name"];
427 NSString * name = [[selectedCategory objectAtIndex:row] name];
428 return (name != nil) ? name : @"";
432 - (void)tableViewSelectionDidChange:(NSNotification *)aNotification
434 if ([aNotification object] == xtraList) {
435 //int selectedRow = [xtraList selectedRow];
436 //if ((selectedRow >= 0) && (selectedRow < [selectedCategory count])) {
437 //AIXtraInfo *xtraInfo = [AIXtraInfo infoWithURL:[NSURL fileURLWithPath:[[selectedCategory objectAtIndex:selectedRow] path]]];
438 // if ([[xtraList selectedRowIndexes] count] == 1)
439 // [previewController setXtra:xtraInfo];
442 } else if ([aNotification object] == tableView_categories) {
443 [self updateForSelectedCategory];
447 - (void)tableViewDeleteSelectedRows:(NSTableView *)tableView
449 [self deleteXtra:tableView];
452 #pragma mark Placeholder until this is a window controller
460 - (void)installToolbar
462 NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier:@"XtrasManager:Toolbar"] autorelease];
464 [toolbar setDelegate:self];
465 [toolbar setDisplayMode:NSToolbarDisplayModeIconAndLabel];
466 [toolbar setSizeMode:NSToolbarSizeModeRegular];
467 [toolbar setVisible:YES];
468 [toolbar setAllowsUserCustomization:YES];
469 [toolbar setAutosavesConfiguration:YES];
470 toolbarItems = [[NSMutableDictionary alloc] init];
473 [AIToolbarUtilities addToolbarItemToDictionary:toolbarItems
474 withIdentifier:@"delete"
477 toolTip:AILocalizedString(@"Delete the selection",nil)
479 settingSelector:@selector(setImage:)
480 itemContent:[NSImage imageNamed:@"remove" forClass:[self class]]
481 action:@selector(deleteXtra:)
484 [AIToolbarUtilities addToolbarItemToDictionary:toolbarItems
485 withIdentifier:@"getmoreXtras"
487 paletteLabel:GET_MORE_XTRAS
488 toolTip:GET_MORE_XTRAS
490 settingSelector:@selector(setImage:)
491 itemContent:[NSImage imageNamed:@"xtras_duck" forClass:[self class]]
492 action:@selector(browseXtras:)
495 [[self window] setToolbar:toolbar];
498 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
500 return [AIToolbarUtilities toolbarItemFromDictionary:toolbarItems withIdentifier:itemIdentifier];
503 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
505 return [NSArray arrayWithObjects:@"getmoreXtras", NSToolbarFlexibleSpaceItemIdentifier, @"delete", nil];
508 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
510 return [[toolbarItems allKeys] arrayByAddingObjectsFromArray:
511 [NSArray arrayWithObjects:NSToolbarSeparatorItemIdentifier,
512 NSToolbarSpaceItemIdentifier,
513 NSToolbarFlexibleSpaceItemIdentifier,
514 NSToolbarCustomizeToolbarItemIdentifier, nil]];
517 - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
519 if ([[theItem itemIdentifier] isEqualToString:@"delete"]) {
520 return ([[xtraList selectedRowIndexes] count] > 0);
527 - (float)shelfSplitView:(KNShelfSplitView *)shelfSplitView validateWidth:(float)proposedWidth
529 return ((proposedWidth > MINIMUM_SOURCE_LIST_WIDTH) ? proposedWidth : MINIMUM_SOURCE_LIST_WIDTH);