Core/Source/GrowlPreferencePane.m
author Peter Hosey <hg@boredzo.org>
Tue Sep 01 19:30:48 2009 -0700 (2009-09-01)
changeset 4365 82f50f3678bb
parent 4364 b000509c2696
child 4498 bed4a154b38f
child 4819 2e39ce17d1ea
permissions -rw-r--r--
Use CFRetain here instead of a non-obvious application of CFStringCreateWithFormat.
boredzo@2487
     1
//
boredzo@2487
     2
//  GrowlPreferencePane.m
boredzo@2487
     3
//  Growl
boredzo@2487
     4
//
boredzo@2487
     5
//  Created by Karl Adam on Wed Apr 21 2004.
ingmarstein@3040
     6
//  Copyright 2004-2006 The Growl Project. All rights reserved.
boredzo@2487
     7
//
boredzo@2487
     8
// This file is under the BSD License, refer to License.txt for details
boredzo@2487
     9
boredzo@2487
    10
#import "GrowlPreferencePane.h"
boredzo@2487
    11
#import "GrowlPreferencesController.h"
boredzo@2487
    12
#import "GrowlDefinesInternal.h"
boredzo@2487
    13
#import "GrowlDefines.h"
boredzo@2487
    14
#import "GrowlTicketController.h"
boredzo@2487
    15
#import "GrowlApplicationTicket.h"
ingmarstein@2695
    16
#import "GrowlPlugin.h"
boredzo@2487
    17
#import "GrowlPluginController.h"
boredzo@2487
    18
#import "GrowlVersionUtilities.h"
ingmarstein@2517
    19
#import "GrowlBrowserEntry.h"
boredzo@2487
    20
#import "NSStringAdditions.h"
boredzo@2487
    21
#import "TicketsArrayController.h"
bgannin@2877
    22
#import "ACImageAndTextCell.h"
boredzo@2487
    23
#import <ApplicationServices/ApplicationServices.h>
ingmarstein@2695
    24
#include <SystemConfiguration/SystemConfiguration.h>
ingmarstein@2652
    25
#include "CFGrowlAdditions.h"
bgannin@3317
    26
#include "GrowlPositionPicker.h"
boredzo@2487
    27
bgannin@3917
    28
#include <Carbon/Carbon.h>
bgannin@3917
    29
boredzo@2487
    30
#define PING_TIMEOUT		3
boredzo@2487
    31
boredzo@2487
    32
//This is the frame of the preference view that we should get back.
Rudy@4246
    33
#define DISPLAY_PREF_FRAME NSMakeRect(16.0, 58.0, 354.0, 289.0)
boredzo@2487
    34
boredzo@2487
    35
@interface NSNetService(TigerCompatibility)
boredzo@2487
    36
boredzo@2487
    37
- (void) resolveWithTimeout:(NSTimeInterval)timeout;
boredzo@2487
    38
boredzo@2487
    39
@end
boredzo@2487
    40
Peter@4189
    41
@interface GrowlPreferencePane (PRIVATE)
Peter@4189
    42
Peter@4189
    43
- (void) populateDisplaysPopUpButton:(NSPopUpButton *)popUp nameOfSelectedDisplay:(NSString *)nameOfSelectedDisplay includeDefaultMenuItem:(BOOL)includeDefault;
Peter@4189
    44
Peter@4189
    45
@end
Peter@4189
    46
boredzo@2487
    47
@implementation GrowlPreferencePane
boredzo@2487
    48
boredzo@2487
    49
- (id) initWithBundle:(NSBundle *)bundle {
boredzo@2487
    50
	//	Check that we're running Panther
boredzo@2487
    51
	//	if a user with a previous OS version tries to launch us - switch out the pane.
boredzo@2487
    52
boredzo@2487
    53
	NSApp = [NSApplication sharedApplication];
boredzo@2487
    54
	if (![NSApp respondsToSelector:@selector(replyToOpenOrPrint:)]) {
evands@3782
    55
		if (NSRunInformationalAlertPanel(NSLocalizedStringFromTableInBundle(@"System requirements not met", nil, bundle, "Title for the dialogue shown if attempting to run Growl on 10.2 or earlier"),
evands@3782
    56
										 NSLocalizedStringFromTableInBundle(@"Mac OS X 10.3 \"Panther\" or greater is required.", nil, bundle, nil), 
evands@3782
    57
										 NSLocalizedStringFromTableInBundle(@"Quit", nil, bundle, "Quit button title"), 
evands@3782
    58
										 NSLocalizedStringFromTableInBundle(@"Upgrade Mac OS X...", nil, bundle, "Button title"), 
evands@3782
    59
										 nil) == NSAlertAlternateReturn) {
evands@3782
    60
			[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.apple.com/macosx/"]];
boredzo@2487
    61
		}
boredzo@2487
    62
		[NSApp terminate:nil];
boredzo@2487
    63
	}
boredzo@2487
    64
boredzo@2487
    65
	if ((self = [super initWithBundle:bundle])) {
ingmarstein@2672
    66
		pid = getpid();
boredzo@2487
    67
		loadedPrefPanes = [[NSMutableArray alloc] init];
ingmarstein@2540
    68
		preferencesController = [GrowlPreferencesController sharedController];
boredzo@2487
    69
boredzo@2487
    70
		NSNotificationCenter *nc = [NSDistributedNotificationCenter defaultCenter];
boredzo@2487
    71
		[nc addObserver:self selector:@selector(growlLaunched:)   name:GROWL_IS_READY object:nil];
boredzo@2487
    72
		[nc addObserver:self selector:@selector(growlTerminated:) name:GROWL_SHUTDOWN object:nil];
boredzo@2487
    73
		[nc addObserver:self selector:@selector(reloadPrefs:)     name:GrowlPreferencesChanged object:nil];
boredzo@2487
    74
ingmarstein@2652
    75
		CFStringRef file = (CFStringRef)[bundle pathForResource:@"GrowlDefaults" ofType:@"plist"];
ingmarstein@2652
    76
		CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, file, kCFURLPOSIXPathStyle, /*isDirectory*/ false);
ingmarstein@2673
    77
		NSDictionary *defaultDefaults = (NSDictionary *)createPropertyListFromURL((NSURL *)fileURL, kCFPropertyListImmutable, NULL, NULL);
ingmarstein@2652
    78
		CFRelease(fileURL);
ingmarstein@2652
    79
		if (defaultDefaults) {
ingmarstein@2652
    80
			[preferencesController registerDefaults:defaultDefaults];
ingmarstein@2652
    81
			[defaultDefaults release];
ingmarstein@2652
    82
		}
boredzo@2487
    83
	}
boredzo@2487
    84
boredzo@2487
    85
	return self;
boredzo@2487
    86
}
boredzo@2487
    87
boredzo@2487
    88
- (void) dealloc {
boredzo@2487
    89
	[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
boredzo@2487
    90
	[browser         release];
boredzo@2487
    91
	[services        release];
boredzo@2487
    92
	[pluginPrefPane  release];
boredzo@2487
    93
	[loadedPrefPanes release];
boredzo@2487
    94
	[tickets         release];
boredzo@2487
    95
	[plugins         release];
boredzo@2487
    96
	[currentPlugin   release];
ingmarstein@2647
    97
	CFRelease(customHistArray);
ofri@2936
    98
	[versionCheckURL release];
ofri@2936
    99
	[growlWebSiteURL release];
ofri@2936
   100
	[growlForumURL release];
Peter@4188
   101
	[growlBugSubmissionURL release];
ofri@2936
   102
	[growlDonateURL release];
ingmarstein@2647
   103
	CFRelease(images);
boredzo@2487
   104
	[super dealloc];
boredzo@2487
   105
}
boredzo@2487
   106
boredzo@2496
   107
- (void) awakeFromNib {
bgannin@2877
   108
	ACImageAndTextCell *imageTextCell = [[[ACImageAndTextCell alloc] init] autorelease];
boredzo@2496
   109
boredzo@2496
   110
	[ticketsArrayController addObserver:self forKeyPath:@"selection" options:0 context:nil];
boredzo@2496
   111
	[displayPluginsArrayController addObserver:self forKeyPath:@"selection" options:0 context:nil];
boredzo@2496
   112
boredzo@2496
   113
	[self setCanRemoveTicket:NO];
boredzo@2496
   114
boredzo@2496
   115
	browser = [[NSNetServiceBrowser alloc] init];
boredzo@2496
   116
boredzo@2496
   117
	// create a deep mutable copy of the forward destinations
ingmarstein@2540
   118
	NSArray *destinations = [preferencesController objectForKey:GrowlForwardDestinationsKey];
boredzo@2496
   119
	NSEnumerator *destEnum = [destinations objectEnumerator];
boredzo@2496
   120
	NSMutableArray *theServices = [[NSMutableArray alloc] initWithCapacity:[destinations count]];
boredzo@2496
   121
	NSDictionary *destination;
boredzo@2496
   122
	while ((destination = [destEnum nextObject])) {
boredzo@2496
   123
		GrowlBrowserEntry *entry = [[GrowlBrowserEntry alloc] initWithDictionary:destination];
boredzo@2496
   124
		[entry setOwner:self];
boredzo@2496
   125
		[theServices addObject:entry];
boredzo@2496
   126
		[entry release];
boredzo@2496
   127
	}
boredzo@2496
   128
	[self setServices:theServices];
boredzo@2496
   129
	[theServices release];
boredzo@2496
   130
boredzo@2496
   131
	[browser setDelegate:self];
boredzo@2496
   132
	[browser searchForServicesOfType:@"_growl._tcp." inDomain:@""];
boredzo@2496
   133
boredzo@2496
   134
	[self setupAboutTab];
boredzo@2496
   135
ingmarstein@2540
   136
	if ([preferencesController isGrowlMenuEnabled] && ![GrowlPreferencePane isGrowlMenuRunning])
ingmarstein@2540
   137
		[preferencesController enableGrowlMenu];
boredzo@2496
   138
ofri@2936
   139
	growlWebSiteURL = [[NSURL alloc] initWithString:@"http://growl.info"];
ofri@2936
   140
	growlForumURL = [[NSURL alloc] initWithString:@"http://forums.cocoaforge.com/viewforum.php?f=6"];
Peter@4188
   141
	growlBugSubmissionURL = [[NSURL alloc] initWithString:@"http://growl.info/reportabug.php"];
tick@2940
   142
	growlDonateURL = [[NSURL alloc] initWithString:@"http://growl.info/donate.php"];
boredzo@2496
   143
ingmarstein@2647
   144
	customHistArray = CFArrayCreateMutable(kCFAllocatorDefault, 3, &kCFTypeArrayCallBacks);
ingmarstein@2647
   145
	id value = [preferencesController objectForKey:GrowlCustomHistKey1];
ingmarstein@2647
   146
	if (value) {
ingmarstein@2647
   147
		CFArrayAppendValue(customHistArray, value);
ingmarstein@2647
   148
		value = [preferencesController objectForKey:GrowlCustomHistKey2];
ingmarstein@2647
   149
		if (value) {
ingmarstein@2647
   150
			CFArrayAppendValue(customHistArray, value);
ingmarstein@2647
   151
			value = [preferencesController objectForKey:GrowlCustomHistKey3];
ingmarstein@2647
   152
			if (value)
ingmarstein@2647
   153
				CFArrayAppendValue(customHistArray, value);
ingmarstein@2647
   154
		}
ingmarstein@2647
   155
	}
boredzo@2496
   156
	[self updateLogPopupMenu];
Rudy@4246
   157
	NSInteger typePref = [preferencesController integerForKey:GrowlLogTypeKey];
boredzo@2496
   158
	[logFileType selectCellAtRow:typePref column:0];
ingmarstein@2587
   159
ingmarstein@2587
   160
	[growlApplications setDoubleAction:@selector(tableViewDoubleClick:)];
ingmarstein@2587
   161
	[growlApplications setTarget:self];
bgannin@3317
   162
	
bgannin@3317
   163
	// bind the global position picker programmatically since its a custom view, register for notification so we can handle updating manually
bgannin@3317
   164
	[globalPositionPicker bind:@"selectedPosition" toObject:preferencesController withKeyPath:@"selectedPosition" options:nil];
bgannin@3317
   165
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatePosition:) name:GrowlPositionPickerChangedSelectionNotification object:globalPositionPicker];
bgannin@3317
   166
bgannin@3317
   167
	// bind the app level position picker programmatically since its a custom view, register for notification so we can handle updating manually
bgannin@3317
   168
	[appPositionPicker bind:@"selectedPosition" toObject:ticketsArrayController withKeyPath:@"selection.selectedPosition" options:nil];
bgannin@3317
   169
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatePosition:) name:GrowlPositionPickerChangedSelectionNotification object:appPositionPicker];
bgannin@3317
   170
	
bgannin@2877
   171
	[applicationNameAndIconColumn setDataCell:imageTextCell];
rudy@3133
   172
	[networkTableView reloadData];
evands@3617
   173
	
evands@3617
   174
	// Select the default style if possible. 
evands@3617
   175
	{
evands@3617
   176
		id arrangedObjects = [displayPluginsArrayController arrangedObjects];
Rudy@4246
   177
		NSUInteger count = [arrangedObjects count];
evands@3617
   178
		NSString *defaultDisplayPluginName = [[self preferencesController] defaultDisplayPluginName];
Rudy@4246
   179
		NSUInteger defaultStyleRow = NSNotFound;
Rudy@4246
   180
		for (NSUInteger i = 0; i < count; i++) {
evands@3617
   181
			if ([[[arrangedObjects objectAtIndex:i] valueForKey:@"CFBundleName"] isEqualToString:defaultDisplayPluginName]) {
evands@3617
   182
				defaultStyleRow = i;
evands@3617
   183
				break;
evands@3617
   184
			}
evands@3617
   185
		}
evands@3617
   186
evands@3617
   187
		if (defaultStyleRow != NSNotFound) {
evands@3617
   188
			/* Wait until the next run loop; otherwise everything isn't finished loading and we throw an exception.
evands@3617
   189
			* This is setting the view for the Displays tab, which isn't initially visible, so the user won't see
evands@3617
   190
			* the flicker. I'm don't know why this is necessary. -evands
evands@3617
   191
			*/
evands@3617
   192
			[self performSelector:@selector(selectRow:)
evands@3617
   193
					   withObject:[NSIndexSet indexSetWithIndex:defaultStyleRow]
evands@3617
   194
					   afterDelay:0];
evands@3617
   195
		}
bgannin@3956
   196
		
bgannin@3956
   197
		[[NSNotificationCenter defaultCenter] addObserver:self
bgannin@3957
   198
												 selector:@selector(translateSeparatorsInMenu:)
bgannin@3956
   199
													 name:NSPopUpButtonWillPopUpNotification
bgannin@3956
   200
												    object:soundMenuButton];
evands@3617
   201
	}
evands@3617
   202
}
evands@3617
   203
evands@3617
   204
- (void)selectRow:(NSIndexSet *)indexSet
evands@3617
   205
{
evands@3617
   206
	[displayPluginsTable selectRowIndexes:indexSet byExtendingSelection:NO];
boredzo@2496
   207
}
boredzo@2496
   208
boredzo@2496
   209
- (void) mainViewDidLoad {
boredzo@2496
   210
	[[NSDistributedNotificationCenter defaultCenter] addObserver:self
boredzo@2496
   211
														selector:@selector(appRegistered:)
boredzo@2496
   212
															name:GROWL_APP_REGISTRATION_CONF
boredzo@2496
   213
														  object:nil];
boredzo@2496
   214
}
boredzo@2496
   215
boredzo@2487
   216
#pragma mark -
boredzo@2487
   217
ingmarstein@2572
   218
/*!
ingmarstein@2572
   219
 * @brief Returns the bundle version of the Growl.prefPane bundle.
ingmarstein@2572
   220
 */
boredzo@2487
   221
- (NSString *) bundleVersion {
boredzo@4084
   222
	return [[self bundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
boredzo@2487
   223
}
boredzo@2487
   224
ingmarstein@2572
   225
/*!
ingmarstein@2572
   226
 * @brief Checks if a newer version of Growl is available at the Growl download site.
ingmarstein@2572
   227
 *
ingmarstein@2572
   228
 * The version.xml file is a property list which contains version numbers and
ingmarstein@2572
   229
 * download URLs for several components.
ingmarstein@2572
   230
 */
boredzo@2487
   231
- (IBAction) checkVersion:(id)sender {
boredzo@2487
   232
#pragma unused(sender)
hg@4306
   233
	/*
hg@4306
   234
	 [growlVersionProgress startAnimation:self];
hg@4306
   235
ingmarstein@2572
   236
	[growlVersionProgress stopAnimation:self];
hg@4306
   237
	*/
boredzo@2487
   238
}
boredzo@2487
   239
boredzo@2487
   240
- (void) downloadSelector:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
boredzo@2487
   241
#pragma unused(sheet)
ingmarstein@2649
   242
	CFURLRef downloadURL = (CFURLRef)contextInfo;
boredzo@2487
   243
	if (returnCode == NSAlertDefaultReturn)
ingmarstein@2649
   244
		[[NSWorkspace sharedWorkspace] openURL:(NSURL *)downloadURL];
ingmarstein@2649
   245
	CFRelease(downloadURL);
boredzo@2487
   246
}
boredzo@2487
   247
ingmarstein@2572
   248
/*!
ingmarstein@2572
   249
 * @brief Returns if GrowlMenu is currently running.
ingmarstein@2572
   250
 */
boredzo@2487
   251
+ (BOOL) isGrowlMenuRunning {
boredzo@2496
   252
	return [[GrowlPreferencesController sharedController] isRunning:@"com.Growl.MenuExtra"];
boredzo@2487
   253
}
boredzo@2487
   254
boredzo@2487
   255
//subclassed from NSPreferencePane; called before the pane is displayed.
boredzo@2487
   256
- (void) willSelect {
ingmarstein@2540
   257
	NSString *lastVersion = [preferencesController objectForKey:LastKnownVersionKey];
boredzo@2487
   258
	NSString *currentVersion = [self bundleVersion];
boredzo@2487
   259
	if (!(lastVersion && [lastVersion isEqualToString:currentVersion])) {
ingmarstein@2540
   260
		if ([preferencesController isGrowlRunning]) {
ingmarstein@2540
   261
			[preferencesController setGrowlRunning:NO noMatterWhat:NO];
ingmarstein@2540
   262
			[preferencesController setGrowlRunning:YES noMatterWhat:YES];
boredzo@2487
   263
		}
ingmarstein@2540
   264
		[preferencesController setObject:currentVersion forKey:LastKnownVersionKey];
boredzo@2487
   265
	}
evands@3619
   266
boredzo@2487
   267
	[self checkGrowlRunning];
boredzo@2487
   268
}
boredzo@2487
   269
boredzo@2487
   270
- (void) didSelect {
ingmarstein@2647
   271
	[self reloadPreferences:nil];
boredzo@2487
   272
}
boredzo@2487
   273
ingmarstein@2572
   274
/*!
ingmarstein@2572
   275
 * @brief copy images to avoid resizing the original images stored in the tickets.
ingmarstein@2572
   276
 */
boredzo@2487
   277
- (void) cacheImages {
boredzo@2487
   278
	if (images)
ingmarstein@2647
   279
		CFArrayRemoveAllValues(images);
ingmarstein@2572
   280
	else
ingmarstein@2647
   281
		images = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
ingmarstein@2647
   282
ingmarstein@3010
   283
	NSEnumerator *enumerator = [[ticketsArrayController content] objectEnumerator];
boredzo@2487
   284
	GrowlApplicationTicket *ticket;
boredzo@2487
   285
	while ((ticket = [enumerator nextObject])) {
boredzo@2487
   286
		NSImage *icon = [[ticket icon] copy];
boredzo@2487
   287
		[icon setScalesWhenResized:YES];
Rudy@4246
   288
		[icon setSize:NSMakeSize(32.0, 32.0)];
ingmarstein@2647
   289
		CFArrayAppendValue(images, icon);
boredzo@2487
   290
		[icon release];
boredzo@2487
   291
	}
boredzo@2487
   292
}
boredzo@2487
   293
boredzo@2487
   294
- (NSMutableArray *) tickets {
boredzo@2487
   295
	return tickets;
boredzo@2487
   296
}
boredzo@2487
   297
boredzo@3059
   298
//using setTickets: will tip off the controller (KVO).
boredzo@3059
   299
//use this to set the tickets secretly.
boredzo@3059
   300
- (void) setTicketsWithoutTellingAnybody:(NSArray *)theTickets {
boredzo@2487
   301
	if (theTickets != tickets) {
ingmarstein@2572
   302
		if (tickets)
ingmarstein@2572
   303
			[tickets setArray:theTickets];
ingmarstein@2572
   304
		else
ingmarstein@2572
   305
			tickets = [theTickets mutableCopy];
boredzo@2487
   306
	}
boredzo@2487
   307
}
boredzo@2487
   308
boredzo@3059
   309
//we don't need to do any special extra magic here - just being setTickets: is enough to tip off the controller.
boredzo@3059
   310
- (void) setTickets:(NSArray *)theTickets {
boredzo@3059
   311
	[self setTicketsWithoutTellingAnybody:theTickets];
boredzo@3059
   312
}
boredzo@3059
   313
boredzo@2487
   314
- (void) removeFromTicketsAtIndex:(int)indexToRemove {
boredzo@3059
   315
	NSIndexSet *indices = [NSIndexSet indexSetWithIndex:indexToRemove];
boredzo@3059
   316
	[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"tickets"];
boredzo@3059
   317
boredzo@3059
   318
	[tickets removeObjectAtIndex:indexToRemove];
boredzo@3059
   319
boredzo@3059
   320
	[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"tickets"];
boredzo@2487
   321
}
boredzo@2487
   322
boredzo@2487
   323
- (void) insertInTickets:(GrowlApplicationTicket *)newTicket {
boredzo@3059
   324
	NSIndexSet *indices = [NSIndexSet indexSetWithIndex:[tickets count]];
boredzo@3059
   325
	[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"tickets"];
boredzo@3059
   326
boredzo@3059
   327
	[tickets addObject:newTicket];
boredzo@3059
   328
boredzo@3059
   329
	[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"tickets"];
boredzo@2487
   330
}
boredzo@2487
   331
boredzo@2487
   332
- (void) reloadDisplayPluginView {
boredzo@2487
   333
	NSArray *selectedPlugins = [displayPluginsArrayController selectedObjects];
Rudy@4246
   334
	NSUInteger numPlugins = [plugins count];
boredzo@2487
   335
	[currentPlugin release];
boredzo@2487
   336
	if (numPlugins > 0U && selectedPlugins && [selectedPlugins count] > 0U)
boredzo@2487
   337
		currentPlugin = [[selectedPlugins objectAtIndex:0U] retain];
boredzo@2487
   338
	else
boredzo@2487
   339
		currentPlugin = nil;
ingmarstein@2943
   340
eridius@2773
   341
	NSString *currentPluginName = [currentPlugin objectForKey:(NSString *)kCFBundleNameKey];
eridius@2773
   342
	currentPluginController = (GrowlPlugin *)[pluginController pluginInstanceWithName:currentPluginName];
eridius@2773
   343
	[self loadViewForDisplay:currentPluginName];
eridius@2773
   344
	[displayAuthor setStringValue:[currentPlugin objectForKey:@"GrowlPluginAuthor"]];
eridius@2773
   345
	[displayVersion setStringValue:[currentPlugin objectForKey:(NSString *)kCFBundleNameKey]];
boredzo@2487
   346
}
boredzo@2487
   347
ingmarstein@2572
   348
/*!
ingmarstein@2572
   349
 * @brief Called when a distributed GrowlPreferencesChanged notification is received.
ingmarstein@2572
   350
 */
boredzo@2487
   351
- (void) reloadPrefs:(NSNotification *)notification {
boredzo@2487
   352
	// ignore notifications which are sent by ourselves
evands@4016
   353
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4016
   354
boredzo@2487
   355
	NSNumber *pidValue = [[notification userInfo] objectForKey:@"pid"];
boredzo@2487
   356
	if (!pidValue || [pidValue intValue] != pid)
ingmarstein@2647
   357
		[self reloadPreferences:[notification object]];
evands@4016
   358
	
evands@4016
   359
	[pool release];
boredzo@2487
   360
}
boredzo@2487
   361
bgannin@3317
   362
- (void) updatePosition:(NSNotification *)notification {
bgannin@3317
   363
	if([notification object] == globalPositionPicker) {
bgannin@3317
   364
		[preferencesController setInteger:[globalPositionPicker selectedPosition] forKey:GROWL_POSITION_PREFERENCE_KEY];
bgannin@3317
   365
	}
bgannin@3317
   366
	else if([notification object] == appPositionPicker) {
bgannin@3317
   367
		// a cheap hack around selection not providing a workable object
boredzo@3681
   368
		NSArray *selection = [ticketsArrayController selectedObjects];
boredzo@3682
   369
		if ([selection count] > 0)
boredzo@3681
   370
			[[selection objectAtIndex:0] setSelectedPosition:[appPositionPicker selectedPosition]];
bgannin@3317
   371
	}
bgannin@3317
   372
}
bgannin@3317
   373
ingmarstein@2572
   374
/*!
ingmarstein@2572
   375
 * @brief Reloads the preferences and updates the GUI accordingly.
ingmarstein@2572
   376
 */
ingmarstein@2647
   377
- (void) reloadPreferences:(NSString *)object {
ingmarstein@2672
   378
	if (!object || [object isEqualToString:@"GrowlTicketChanged"]) {
ingmarstein@2672
   379
		GrowlTicketController *ticketController = [GrowlTicketController sharedController];
ingmarstein@2672
   380
		[ticketController loadAllSavedTickets];
ingmarstein@2672
   381
		[self setTickets:[[ticketController allSavedTickets] allValues]];
ingmarstein@2672
   382
		[self cacheImages];
ingmarstein@2672
   383
	}
ingmarstein@2943
   384
hg@4219
   385
	[self setDisplayPlugins:[[[GrowlPluginController sharedController] displayPlugins] valueForKey:GrowlPluginInfoKeyName]];
eridius@2773
   386
eridius@2773
   387
#ifdef THIS_CODE_WAS_REMOVED_AND_I_DONT_KNOW_WHY
eridius@2773
   388
	if (!object || [object isEqualToString:@"GrowlTicketChanged"])
eridius@2773
   389
		[self setTickets:[[ticketController allSavedTickets] allValues]];
eridius@2773
   390
eridius@2773
   391
	[preferencesController setSquelchMode:[preferencesController squelchMode]];
eridius@2773
   392
	[preferencesController setGrowlMenuEnabled:[preferencesController isGrowlMenuEnabled]];
eridius@2773
   393
eridius@2773
   394
	[self cacheImages];
eridius@2773
   395
#endif
boredzo@2487
   396
boredzo@2487
   397
	// If Growl is enabled, ensure the helper app is launched
ingmarstein@2540
   398
	if ([preferencesController boolForKey:GrowlEnabledKey])
ingmarstein@2540
   399
		[preferencesController launchGrowl:NO];
boredzo@2487
   400
ingmarstein@2672
   401
	if ([plugins count] > 0U)
boredzo@2487
   402
		[self reloadDisplayPluginView];
ingmarstein@2672
   403
	else
boredzo@2487
   404
		[self loadViewForDisplay:nil];
boredzo@2487
   405
}
boredzo@2487
   406
boredzo@2487
   407
- (BOOL) growlIsRunning {
boredzo@2487
   408
	return growlIsRunning;
boredzo@2487
   409
}
boredzo@2487
   410
boredzo@2487
   411
- (void) setGrowlIsRunning:(BOOL)flag {
boredzo@2487
   412
	growlIsRunning = flag;
boredzo@2487
   413
}
boredzo@2487
   414
boredzo@2487
   415
- (void) updateRunningStatus {
boredzo@2487
   416
	[startStopGrowl setEnabled:YES];
boredzo@2487
   417
	NSBundle *bundle = [self bundle];
boredzo@2487
   418
	[startStopGrowl setTitle:
boredzo@2487
   419
		growlIsRunning ? NSLocalizedStringFromTableInBundle(@"Stop Growl",nil,bundle,@"")
boredzo@2487
   420
					   : NSLocalizedStringFromTableInBundle(@"Start Growl",nil,bundle,@"")];
boredzo@2487
   421
	[growlRunningStatus setStringValue:
boredzo@2487
   422
		growlIsRunning ? NSLocalizedStringFromTableInBundle(@"Growl is running.",nil,bundle,@"")
boredzo@2487
   423
					   : NSLocalizedStringFromTableInBundle(@"Growl is stopped.",nil,bundle,@"")];
boredzo@2487
   424
	[growlRunningProgress stopAnimation:self];
boredzo@2487
   425
}
boredzo@2487
   426
boredzo@2487
   427
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
boredzo@2487
   428
						change:(NSDictionary *)change context:(void *)context {
boredzo@2487
   429
#pragma unused(change, context)
boredzo@2487
   430
	if ([keyPath isEqualToString:@"selection"]) {
boredzo@2487
   431
		if ((object == ticketsArrayController))
boredzo@2487
   432
			[self setCanRemoveTicket:(activeTableView == growlApplications) && [ticketsArrayController canRemove]];
boredzo@2487
   433
		else if (object == displayPluginsArrayController)
boredzo@2487
   434
			[self reloadDisplayPluginView];
boredzo@2487
   435
	}
boredzo@2487
   436
}
boredzo@2487
   437
boredzo@2487
   438
- (void) writeForwardDestinations {
boredzo@2487
   439
	NSMutableArray *destinations = [[NSMutableArray alloc] initWithCapacity:[services count]];
boredzo@2487
   440
	NSEnumerator *enumerator = [services objectEnumerator];
boredzo@2487
   441
	GrowlBrowserEntry *entry;
boredzo@2487
   442
	while ((entry = [enumerator nextObject]))
evands@4153
   443
		[destinations addObject:[entry properties]];
ingmarstein@2540
   444
	[preferencesController setObject:destinations forKey:GrowlForwardDestinationsKey];
boredzo@2487
   445
	[destinations release];
boredzo@2487
   446
}
boredzo@2487
   447
boredzo@2487
   448
#pragma mark -
boredzo@2496
   449
#pragma mark Bindings accessors (not for programmatic use)
boredzo@2496
   450
boredzo@2496
   451
- (GrowlPluginController *) pluginController {
boredzo@2496
   452
	if (!pluginController)
boredzo@2496
   453
		pluginController = [GrowlPluginController sharedController];
boredzo@2496
   454
boredzo@2496
   455
	return pluginController;
boredzo@2496
   456
}
boredzo@2496
   457
- (GrowlPreferencesController *) preferencesController {
boredzo@2496
   458
	if (!preferencesController)
boredzo@2496
   459
		preferencesController = [GrowlPreferencesController sharedController];
boredzo@2496
   460
boredzo@2496
   461
	return preferencesController;
boredzo@2496
   462
}
boredzo@2496
   463
ingmarstein@3168
   464
- (NSArray *) sounds {
bgannin@3913
   465
	NSMutableArray *soundNames = [[NSMutableArray alloc] init];
bgannin@3913
   466
	
bgannin@3913
   467
	NSArray *paths = [NSArray arrayWithObjects:@"/System/Library/Sounds",
bgannin@3913
   468
												@"/Library/Sounds",
bgannin@3913
   469
											   [NSString stringWithFormat:@"%@/Library/Sounds", NSHomeDirectory()],
bgannin@3913
   470
											   nil];
bgannin@3913
   471
bgannin@3913
   472
	NSString *directory;
bgannin@3913
   473
	NSEnumerator *dirEnumerator = [paths objectEnumerator];
bgannin@3913
   474
	while ((directory = [dirEnumerator nextObject])) {
bgannin@3913
   475
		BOOL isDirectory = NO;
bgannin@3913
   476
		
bgannin@3913
   477
		if ([[NSFileManager defaultManager] fileExistsAtPath:directory isDirectory:&isDirectory]) {
bgannin@3913
   478
			if (isDirectory) {
bgannin@3956
   479
				[soundNames addObject:@"-"];
bgannin@3956
   480
				
bgannin@3913
   481
				NSArray *files = [[NSFileManager defaultManager] directoryContentsAtPath:directory];
bgannin@3913
   482
bgannin@3913
   483
				NSString *filename = nil;
bgannin@3913
   484
				NSEnumerator *fileEnumerator = [files objectEnumerator];
bgannin@3913
   485
				while ((filename = [fileEnumerator nextObject])) {
bgannin@3913
   486
					NSString *file = [filename stringByDeletingPathExtension];
bgannin@3913
   487
			
bgannin@3913
   488
					if (![file isEqualToString:@".DS_Store"])
bgannin@3913
   489
						[soundNames addObject:file];
bgannin@3913
   490
				}
bgannin@3913
   491
			}
bgannin@3913
   492
		}
bgannin@3913
   493
	}
bgannin@3913
   494
	
ingmarstein@3168
   495
	return [soundNames autorelease];
ingmarstein@3168
   496
}
ingmarstein@3168
   497
bgannin@3957
   498
- (void)translateSeparatorsInMenu:(NSNotification *)notification
bgannin@3956
   499
{
bgannin@3957
   500
	NSPopUpButton * button = [notification object];
bgannin@3956
   501
	
bgannin@3957
   502
	NSMenu *menu = [button menu];
bgannin@3956
   503
	
Rudy@4246
   504
	NSInteger itemIndex = 0;
bgannin@3957
   505
	
evands@3963
   506
	while ((itemIndex = [menu indexOfItemWithTitle:@"-"]) != -1) {
bgannin@3957
   507
		[menu removeItemAtIndex:itemIndex];
bgannin@3957
   508
		[menu insertItem:[NSMenuItem separatorItem] atIndex:itemIndex];
bgannin@3956
   509
	}
bgannin@3956
   510
}
bgannin@3956
   511
boredzo@2487
   512
#pragma mark Growl running state
boredzo@2487
   513
ingmarstein@2572
   514
/*!
ingmarstein@2572
   515
 * @brief Launches GrowlHelperApp.
ingmarstein@2572
   516
 */
boredzo@2487
   517
- (void) launchGrowl {
boredzo@2487
   518
	// Don't allow the button to be clicked while we update
boredzo@2487
   519
	[startStopGrowl setEnabled:NO];
boredzo@2487
   520
	[growlRunningProgress startAnimation:self];
boredzo@2487
   521
boredzo@2487
   522
	// Update our status visible to the user
boredzo@2487
   523
	[growlRunningStatus setStringValue:NSLocalizedStringFromTableInBundle(@"Launching Growl...",nil,[self bundle],@"")];
boredzo@2487
   524
ingmarstein@2540
   525
	[preferencesController setGrowlRunning:YES noMatterWhat:NO];
boredzo@2487
   526
boredzo@2487
   527
	// After 4 seconds force a status update, in case Growl didn't start/stop
boredzo@2487
   528
	[self performSelector:@selector(checkGrowlRunning)
boredzo@2487
   529
			   withObject:nil
boredzo@2487
   530
			   afterDelay:4.0];
boredzo@2487
   531
}
boredzo@2487
   532
ingmarstein@2572
   533
/*!
ingmarstein@2572
   534
 * @brief Terminates running GrowlHelperApp instances.
ingmarstein@2572
   535
 */
boredzo@2487
   536
- (void) terminateGrowl {
boredzo@2487
   537
	// Don't allow the button to be clicked while we update
boredzo@2487
   538
	[startStopGrowl setEnabled:NO];
boredzo@2487
   539
	[growlRunningProgress startAnimation:self];
boredzo@2487
   540
boredzo@2487
   541
	// Update our status visible to the user
boredzo@2487
   542
	[growlRunningStatus setStringValue:NSLocalizedStringFromTableInBundle(@"Terminating Growl...",nil,[self bundle],@"")];
boredzo@2487
   543
boredzo@2487
   544
	// Ask the Growl Helper App to shutdown
ingmarstein@2540
   545
	[preferencesController setGrowlRunning:NO noMatterWhat:NO];
boredzo@2487
   546
boredzo@2487
   547
	// After 4 seconds force a status update, in case growl didn't start/stop
boredzo@2487
   548
	[self performSelector:@selector(checkGrowlRunning)
boredzo@2487
   549
			   withObject:nil
boredzo@2487
   550
			   afterDelay:4.0];
boredzo@2487
   551
}
boredzo@2487
   552
boredzo@2487
   553
#pragma mark "General" tab pane
boredzo@2487
   554
boredzo@2487
   555
- (IBAction) startStopGrowl:(id) sender {
boredzo@2487
   556
#pragma unused(sender)
boredzo@2487
   557
	// Make sure growlIsRunning is correct
ingmarstein@2540
   558
	if (growlIsRunning != [preferencesController isGrowlRunning]) {
boredzo@2487
   559
		// Nope - lets just flip it and update status
boredzo@2487
   560
		[self setGrowlIsRunning:!growlIsRunning];
boredzo@2487
   561
		[self updateRunningStatus];
boredzo@2487
   562
		return;
boredzo@2487
   563
	}
boredzo@2487
   564
boredzo@2487
   565
	// Our desired state is a toggle of the current state;
boredzo@2487
   566
	if (growlIsRunning)
boredzo@2487
   567
		[self terminateGrowl];
boredzo@2487
   568
	else
boredzo@2487
   569
		[self launchGrowl];
boredzo@2487
   570
}
boredzo@2487
   571
boredzo@2487
   572
tick@2979
   573
/*
boredzo@2487
   574
- (IBAction) customFileChosen:(id)sender {
boredzo@2487
   575
	int selected = [sender indexOfSelectedItem];
boredzo@2487
   576
	if ((selected == [sender numberOfItems] - 1) || (selected == -1)) {
boredzo@2487
   577
		NSSavePanel *sp = [NSSavePanel savePanel];
boredzo@2487
   578
		[sp setRequiredFileType:@"log"];
boredzo@2487
   579
		[sp setCanSelectHiddenExtension:YES];
boredzo@2487
   580
boredzo@2487
   581
		int runResult = [sp runModalForDirectory:nil file:@""];
boredzo@2487
   582
		NSString *saveFilename = [sp filename];
boredzo@2487
   583
		if (runResult == NSFileHandlingPanelOKButton) {
boredzo@2487
   584
			unsigned saveFilenameIndex = NSNotFound;
ingmarstein@2647
   585
			unsigned                 i = CFArrayGetCount(customHistArray);
boredzo@2487
   586
			if (i) {
boredzo@2487
   587
				while (--i) {
ingmarstein@2647
   588
					if ([(id)CFArrayGetValueAtIndex(customHistArray, i) isEqual:saveFilename]) {
boredzo@2487
   589
						saveFilenameIndex = i;
boredzo@2487
   590
						break;
boredzo@2487
   591
					}
boredzo@2487
   592
				}
boredzo@2487
   593
			}
boredzo@2487
   594
			if (saveFilenameIndex == NSNotFound) {
ingmarstein@2647
   595
				if (CFArrayGetCount(customHistArray) == 3U)
ingmarstein@2647
   596
					CFArrayRemoveValueAtIndex(customHistArray, 2);
ingmarstein@2647
   597
			} else
ingmarstein@2647
   598
				CFArrayRemoveValueAtIndex(customHistArray, saveFilenameIndex);
ingmarstein@2647
   599
			CFArrayInsertValueAtIndex(customHistArray, 0, saveFilename);
boredzo@2487
   600
		}
boredzo@2487
   601
	} else {
ingmarstein@2647
   602
		CFStringRef temp = CFRetain(CFArrayGetValueAtIndex(customHistArray, selected));
ingmarstein@2647
   603
		CFArrayRemoveValueAtIndex(customHistArray, selected);
ingmarstein@2647
   604
		CFArrayInsertValueAtIndex(customHistArray, 0, temp);
ingmarstein@2647
   605
		CFRelease(temp);
ingmarstein@2647
   606
	}
ingmarstein@2647
   607
ingmarstein@2647
   608
	unsigned numHistItems = CFArrayGetCount(customHistArray);
boredzo@2487
   609
	if (numHistItems) {
ingmarstein@2647
   610
		id s = (id)CFArrayGetValueAtIndex(customHistArray, 0);
ingmarstein@2540
   611
		[preferencesController setObject:s forKey:GrowlCustomHistKey1];
boredzo@2487
   612
ingmarstein@2647
   613
		if ((numHistItems > 1U) && (s = (id)CFArrayGetValueAtIndex(customHistArray, 1)))
ingmarstein@2540
   614
			[preferencesController setObject:s forKey:GrowlCustomHistKey2];
boredzo@2487
   615
ingmarstein@2647
   616
		if ((numHistItems > 2U) && (s = (id)CFArrayGetValueAtIndex(customHistArray, 2)))
ingmarstein@2540
   617
			[preferencesController setObject:s forKey:GrowlCustomHistKey3];
boredzo@2487
   618
boredzo@2487
   619
		//[[logFileType cellAtRow:1 column:0] setEnabled:YES];
boredzo@2487
   620
		[logFileType selectCellAtRow:1 column:0];
boredzo@2487
   621
	}
boredzo@2487
   622
boredzo@2487
   623
	[self updateLogPopupMenu];
zaudragon@2980
   624
}*/
boredzo@2487
   625
boredzo@2487
   626
- (void) updateLogPopupMenu {
boredzo@2487
   627
	[customMenuButton removeAllItems];
boredzo@2487
   628
Rudy@4246
   629
	CFIndex numHistItems = CFArrayGetCount(customHistArray);
ingmarstein@2647
   630
	for (int i = 0U; i < numHistItems; i++) {
ingmarstein@2647
   631
		NSArray *pathComponentry = [[(NSString *)CFArrayGetValueAtIndex(customHistArray, i) stringByAbbreviatingWithTildeInPath] pathComponents];
Rudy@4246
   632
		NSUInteger numPathComponents = [pathComponentry count];
boredzo@2487
   633
		if (numPathComponents > 2U) {
boredzo@2487
   634
			unichar ellipsis = 0x2026;
boredzo@2487
   635
			NSMutableString *arg = [[NSMutableString alloc] initWithCharacters:&ellipsis length:1U];
boredzo@2487
   636
			[arg appendString:@"/"];
boredzo@2487
   637
			[arg appendString:[pathComponentry objectAtIndex:(numPathComponents - 2U)]];
boredzo@2487
   638
			[arg appendString:@"/"];
boredzo@2487
   639
			[arg appendString:[pathComponentry objectAtIndex:(numPathComponents - 1U)]];
boredzo@2487
   640
			[customMenuButton insertItemWithTitle:arg atIndex:i];
boredzo@2487
   641
			[arg release];
boredzo@2487
   642
		} else
ingmarstein@2647
   643
			[customMenuButton insertItemWithTitle:[(NSString *)CFArrayGetValueAtIndex(customHistArray, i) stringByAbbreviatingWithTildeInPath] atIndex:i];
boredzo@2487
   644
	}
boredzo@2487
   645
	// No separator if there's no file list yet
ingmarstein@2647
   646
	if (numHistItems > 0)
boredzo@2487
   647
		[[customMenuButton menu] addItem:[NSMenuItem separatorItem]];
zaudragon@2980
   648
	[customMenuButton addItemWithTitle:NSLocalizedStringFromTableInBundle(@"Browse menu item title", nil, [self bundle], nil)];
boredzo@2487
   649
	//select first item, if any
boredzo@2487
   650
	[customMenuButton selectItemAtIndex:numHistItems ? 0 : -1];
boredzo@2487
   651
}
zaudragon@2980
   652
boredzo@2487
   653
#pragma mark "Applications" tab pane
boredzo@2487
   654
boredzo@2487
   655
- (BOOL) canRemoveTicket {
boredzo@2487
   656
	return canRemoveTicket;
boredzo@2487
   657
}
boredzo@2487
   658
boredzo@2487
   659
- (void) setCanRemoveTicket:(BOOL)flag {
boredzo@2487
   660
	canRemoveTicket = flag;
boredzo@2487
   661
}
boredzo@2487
   662
boredzo@2487
   663
- (void) deleteTicket:(id)sender {
boredzo@2487
   664
#pragma unused(sender)
bgannin@2937
   665
	NSString *appName = [[[ticketsArrayController selectedObjects] objectAtIndex:0U] applicationName];
evands@3782
   666
	NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"Are you sure you want to remove %@?", nil, [self bundle], nil), appName]
evands@3782
   667
									 defaultButton:NSLocalizedStringFromTableInBundle(@"Remove", nil, [self bundle], "Button title for removing something")
evands@3782
   668
								   alternateButton:NSLocalizedStringFromTableInBundle(@"Cancel", nil, [self bundle], "Button title for canceling")
ingmarstein@2943
   669
									   otherButton:nil
evands@3782
   670
						 informativeTextWithFormat:[NSString stringWithFormat:
evands@3782
   671
													NSLocalizedStringFromTableInBundle(@"This will remove all Growl settings for %@.", nil, [self bundle], ""), appName]];
bgannin@2881
   672
	[alert setIcon:[[[NSImage alloc] initWithContentsOfFile:[[self bundle] pathForImageResource:@"growl-icon"]] autorelease]];
bgannin@2881
   673
	[alert beginSheetModalForWindow:[[NSApplication sharedApplication] keyWindow] modalDelegate:self didEndSelector:@selector(deleteCallbackDidEnd:returnCode:contextInfo:) contextInfo:nil];
bgannin@2881
   674
}
bgannin@2881
   675
bgannin@2881
   676
// this method is used as our callback to determine whether or not to delete the ticket
ingmarstein@2943
   677
-(void) deleteCallbackDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)eventID {
ofri@2936
   678
#pragma unused(alert)
ofri@2936
   679
#pragma unused(eventID)
ingmarstein@2941
   680
	if (returnCode == NSAlertDefaultReturn) {
bgannin@2881
   681
		GrowlApplicationTicket *ticket = [[ticketsArrayController selectedObjects] objectAtIndex:0U];
bgannin@2881
   682
		NSString *path = [ticket path];
ingmarstein@2943
   683
bgannin@2881
   684
		if ([[NSFileManager defaultManager] removeFileAtPath:path handler:nil]) {
bgannin@2881
   685
			CFNumberRef pidValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid);
bgannin@2881
   686
			CFStringRef keys[2] = { CFSTR("TicketName"), CFSTR("pid") };
bgannin@2881
   687
			CFTypeRef   values[2] = { [ticket applicationName], pidValue };
bgannin@2881
   688
			CFDictionaryRef userInfo = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
bgannin@2881
   689
			CFRelease(pidValue);
bgannin@2881
   690
			CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
bgannin@2881
   691
												 (CFStringRef)GrowlPreferencesChanged,
bgannin@2881
   692
												 CFSTR("GrowlTicketDeleted"),
bgannin@2881
   693
												 userInfo, false);
bgannin@2881
   694
			CFRelease(userInfo);
Rudy@4246
   695
			NSUInteger idx = [tickets indexOfObject:ticket];
bgannin@2881
   696
			CFArrayRemoveValueAtIndex(images, idx);
ingmarstein@2943
   697
Rudy@4246
   698
			NSUInteger oldSelectionIndex = [ticketsArrayController selectionIndex];
ingmarstein@2943
   699
bgannin@2881
   700
			///	Hmm... This doesn't work for some reason....
bgannin@3448
   701
			//	Even though the same method definitely (?) probably works in the appRegistered: method...
ingmarstein@2943
   702
bgannin@2881
   703
			//	[self removeFromTicketsAtIndex:	[ticketsArrayController selectionIndex]];
ingmarstein@2943
   704
bgannin@2881
   705
			NSMutableArray *newTickets = [tickets mutableCopy];
bgannin@2881
   706
			[newTickets removeObject:ticket];
bgannin@2881
   707
			[self setTickets:newTickets];
bgannin@2881
   708
			[newTickets release];
ingmarstein@2943
   709
bgannin@2881
   710
			if (oldSelectionIndex >= [tickets count])
bgannin@2881
   711
				oldSelectionIndex = [tickets count] - 1;
rudy@2902
   712
			[self cacheImages];
bgannin@2881
   713
			[ticketsArrayController setSelectionIndex:oldSelectionIndex];
ingmarstein@2943
   714
		}
boredzo@2487
   715
	}
boredzo@2487
   716
}
boredzo@2487
   717
bgannin@3448
   718
-(IBAction)playSound:(id)sender
bgannin@3448
   719
{
bgannin@3448
   720
	if([sender indexOfSelectedItem] > 0) // The 0 item is "None"
bgannin@3448
   721
		[[NSSound soundNamed:[[sender selectedItem] title]] play];
bgannin@3448
   722
}
bgannin@3448
   723
Peter@4189
   724
- (IBAction) showApplicationConfigurationTab:(id)sender {
Peter@4189
   725
	if ([ticketsArrayController selectionIndex] != NSNotFound) {
Peter@4189
   726
		[self populateDisplaysPopUpButton:displayMenuButton nameOfSelectedDisplay:[[ticketsArrayController selection] valueForKey:@"displayPluginName"] includeDefaultMenuItem:YES];
Peter@4189
   727
		[self populateDisplaysPopUpButton:notificationDisplayMenuButton nameOfSelectedDisplay:[[notificationsArrayController selection] valueForKey:@"displayPluginName"] includeDefaultMenuItem:YES];
Peter@4189
   728
Peter@4189
   729
		[applicationsTab selectLastTabViewItem:sender];
Peter@4189
   730
		[configurationTab selectFirstTabViewItem:sender];
Peter@4189
   731
	}
Peter@4189
   732
}
Peter@4189
   733
Peter@4189
   734
- (IBAction) changeNameOfDisplayForApplication:(id)sender {
Peter@4189
   735
	NSString *newDisplayPluginName = [[sender selectedItem] representedObject];
Peter@4189
   736
	[[ticketsArrayController selectedObjects] setValue:newDisplayPluginName forKey:@"displayPluginName"];
Peter@4189
   737
	[self showPreview:sender];
Peter@4189
   738
}
Peter@4189
   739
- (IBAction) changeNameOfDisplayForNotification:(id)sender {
Peter@4189
   740
	NSString *newDisplayPluginName = [[sender selectedItem] representedObject];
Peter@4189
   741
	[[notificationsArrayController selectedObjects] setValue:newDisplayPluginName forKey:@"displayPluginName"];
Peter@4189
   742
	[self showPreview:sender];
Peter@4189
   743
}
Peter@4189
   744
hg@4238
   745
- (NSIndexSet *) selectedNotificationIndexes {
hg@4238
   746
	return selectedNotificationIndexes;
hg@4238
   747
}
hg@4238
   748
- (void) setSelectedNotificationIndexes:(NSIndexSet *)newSelectedNotificationIndexes {
hg@4238
   749
	if(selectedNotificationIndexes != newSelectedNotificationIndexes) {
hg@4238
   750
		[selectedNotificationIndexes release];
hg@4238
   751
		selectedNotificationIndexes = [newSelectedNotificationIndexes copy];
hg@4238
   752
hg@4306
   753
		int indexOfMenuItem = [[notificationDisplayMenuButton menu] indexOfItemWithRepresentedObject:[[notificationsArrayController selection] valueForKey:@"displayPluginName"]];
hg@4238
   754
		if (indexOfMenuItem < 0)
hg@4238
   755
			indexOfMenuItem = 0;
hg@4238
   756
		[notificationDisplayMenuButton selectItemAtIndex:indexOfMenuItem];
hg@4238
   757
	}
hg@4238
   758
}
hg@4238
   759
rudy@3133
   760
#pragma mark "Display" tab pane
boredzo@2487
   761
bgannin@3280
   762
- (IBAction) showDisabledDisplays:(id)sender {
bgannin@3280
   763
#pragma unused(sender)
bgannin@3280
   764
	[disabledDisplaysList setString:[[pluginController disabledPlugins] componentsJoinedByString:@"\n"]];
bgannin@3280
   765
	
bgannin@3280
   766
	[NSApp beginSheet:disabledDisplaysSheet 
bgannin@3280
   767
	   modalForWindow:[[self mainView] window]
bgannin@3280
   768
		modalDelegate:nil
bgannin@3280
   769
	   didEndSelector:nil
bgannin@3280
   770
		  contextInfo:nil];
bgannin@3280
   771
}
bgannin@3280
   772
bgannin@3280
   773
- (IBAction) endDisabledDisplays:(id)sender {
bgannin@3280
   774
#pragma unused(sender)
bgannin@3280
   775
	[NSApp endSheet:disabledDisplaysSheet];
bgannin@3280
   776
	[disabledDisplaysSheet orderOut:disabledDisplaysSheet];
bgannin@3280
   777
}
bgannin@3280
   778
bgannin@3280
   779
// Returns a boolean based on whether any disabled displays are present, used for the 'hidden' binding of the button on the tab
bgannin@3280
   780
- (BOOL)hasDisabledDisplays {
bgannin@3280
   781
	return [pluginController disabledPluginsPresent];
bgannin@3280
   782
}
bgannin@3280
   783
bgannin@3917
   784
// Popup buttons that post preview notifications support suppressing the preview with the Option key
ingmarstein@2672
   785
- (IBAction) showPreview:(id)sender {
bgannin@3917
   786
	if(([sender isKindOfClass:[NSPopUpButton class]]) && (GetCurrentKeyModifiers() & optionKey))
bgannin@3917
   787
		return;
bgannin@3917
   788
	
bgannin@3917
   789
	NSDictionary *pluginToUse = currentPlugin;
Peter@4189
   790
	NSString *pluginName = nil;
Peter@4189
   791
	
Peter@4189
   792
	if ([sender isKindOfClass:[NSPopUpButton class]]) {
Peter@4189
   793
		NSPopUpButton *popUp = (NSPopUpButton *)sender;
Peter@4189
   794
		if (sender == displayMenuButton || sender == notificationDisplayMenuButton)
Peter@4189
   795
			pluginName = [[popUp selectedItem] representedObject];
Peter@4189
   796
		else
evands@3963
   797
#warning This does not work if the popup button is not using the exact same order as displayPluginsArrayController - a default or separator item breaks it
Peter@4189
   798
			pluginToUse = [[displayPluginsArrayController content] objectAtIndex:[popUp indexOfSelectedItem]];
Peter@4189
   799
	}
Peter@4189
   800
Peter@4189
   801
	if (!pluginName)
Peter@4189
   802
		pluginName = [pluginToUse objectForKey:GrowlPluginInfoKeyName];
bgannin@3917
   803
			
evands@3513
   804
	[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GrowlPreview
Peter@4189
   805
																   object:pluginName];
boredzo@2487
   806
}
boredzo@2487
   807
boredzo@2487
   808
- (void) loadViewForDisplay:(NSString *)displayName {
boredzo@2487
   809
	NSView *newView = nil;
boredzo@2487
   810
	NSPreferencePane *prefPane = nil, *oldPrefPane = nil;
boredzo@2487
   811
boredzo@2487
   812
	if (pluginPrefPane)
boredzo@2487
   813
		oldPrefPane = pluginPrefPane;
boredzo@2487
   814
boredzo@2487
   815
	if (displayName) {
boredzo@2487
   816
		// Old plugins won't support the new protocol. Check first
boredzo@2487
   817
		if ([currentPluginController respondsToSelector:@selector(preferencePane)])
boredzo@2487
   818
			prefPane = [currentPluginController preferencePane];
boredzo@2487
   819
boredzo@2487
   820
		if (prefPane == pluginPrefPane) {
boredzo@2487
   821
			// Don't bother swapping anything
boredzo@2487
   822
			return;
boredzo@2487
   823
		} else {
boredzo@2487
   824
			[pluginPrefPane release];
boredzo@2487
   825
			pluginPrefPane = [prefPane retain];
boredzo@2487
   826
			[oldPrefPane willUnselect];
boredzo@2487
   827
		}
boredzo@2487
   828
		if (pluginPrefPane) {
boredzo@2487
   829
			if ([loadedPrefPanes containsObject:pluginPrefPane]) {
boredzo@2487
   830
				newView = [pluginPrefPane mainView];
boredzo@2487
   831
			} else {
boredzo@2487
   832
				newView = [pluginPrefPane loadMainView];
boredzo@2487
   833
				[loadedPrefPanes addObject:pluginPrefPane];
boredzo@2487
   834
			}
boredzo@2487
   835
			[pluginPrefPane willSelect];
boredzo@2487
   836
		}
boredzo@2487
   837
	} else {
boredzo@2487
   838
		[pluginPrefPane release];
boredzo@2487
   839
		pluginPrefPane = nil;
boredzo@2487
   840
	}
boredzo@2487
   841
	if (!newView)
boredzo@2487
   842
		newView = displayDefaultPrefView;
boredzo@2487
   843
	if (displayPrefView != newView) {
boredzo@2487
   844
		// Make sure the new view is framed correctly
boredzo@2487
   845
		[newView setFrame:DISPLAY_PREF_FRAME];
boredzo@2487
   846
		[[displayPrefView superview] replaceSubview:displayPrefView with:newView];
boredzo@2487
   847
		displayPrefView = newView;
boredzo@2487
   848
boredzo@2487
   849
		if (pluginPrefPane) {
boredzo@2487
   850
			[pluginPrefPane didSelect];
boredzo@2487
   851
			// Hook up key view chain
boredzo@2487
   852
			[displayPluginsTable setNextKeyView:[pluginPrefPane firstKeyView]];
boredzo@2487
   853
			[[pluginPrefPane lastKeyView] setNextKeyView:previewButton];
boredzo@2487
   854
			//[[displayPluginsTable window] makeFirstResponder:[pluginPrefPane initialKeyView]];
boredzo@2487
   855
		} else {
boredzo@2487
   856
			[displayPluginsTable setNextKeyView:previewButton];
boredzo@2487
   857
		}
boredzo@2487
   858
boredzo@2487
   859
		if (oldPrefPane)
boredzo@2487
   860
			[oldPrefPane didUnselect];
boredzo@2487
   861
	}
boredzo@2487
   862
}
boredzo@2487
   863
boredzo@2487
   864
#pragma mark About Tab
boredzo@2487
   865
boredzo@2487
   866
- (void) setupAboutTab {
bgannin@3982
   867
	[aboutVersionString setStringValue:[NSString stringWithFormat:@"%@ %@", 
bgannin@3982
   868
										[[self bundle] objectForInfoDictionaryKey:@"CFBundleName"], 
bgannin@3982
   869
										[[self bundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]]];
boredzo@2487
   870
	[aboutBoxTextView readRTFDFromFile:[[self bundle] pathForResource:@"About" ofType:@"rtf"]];
boredzo@2487
   871
}
boredzo@2487
   872
boredzo@2487
   873
- (IBAction) openGrowlWebSite:(id)sender {
boredzo@2487
   874
#pragma unused(sender)
ofri@2936
   875
	[[NSWorkspace sharedWorkspace] openURL:growlWebSiteURL];
boredzo@2487
   876
}
boredzo@2487
   877
boredzo@2487
   878
- (IBAction) openGrowlForum:(id)sender {
boredzo@2487
   879
#pragma unused(sender)
ofri@2936
   880
	[[NSWorkspace sharedWorkspace] openURL:growlForumURL];
boredzo@2487
   881
}
boredzo@2487
   882
Peter@4188
   883
- (IBAction) openGrowlBugSubmissionPage:(id)sender {
boredzo@2487
   884
#pragma unused(sender)
Peter@4188
   885
	[[NSWorkspace sharedWorkspace] openURL:growlBugSubmissionURL];
boredzo@2487
   886
}
boredzo@2487
   887
tick@2934
   888
- (IBAction) openGrowlDonate:(id)sender {
tick@2934
   889
 #pragma unused(sender)
ofri@2936
   890
	[[NSWorkspace sharedWorkspace] openURL:growlDonateURL];
tick@2934
   891
}
boredzo@3952
   892
#pragma mark TableView data source methods
ingmarstein@2647
   893
Rudy@4246
   894
- (NSInteger) numberOfRowsInTableView:(NSTableView*)tableView {
rudy@3133
   895
	if(tableView == networkTableView) {
rudy@3133
   896
		return [[self services] count];
rudy@3133
   897
	}
rudy@3133
   898
	return 0;
rudy@3133
   899
}
boredzo@2487
   900
- (void) tableViewDidClickInBody:(NSTableView *)tableView {
boredzo@2487
   901
	activeTableView = tableView;
boredzo@2487
   902
	[self setCanRemoveTicket:(activeTableView == growlApplications) && [ticketsArrayController canRemove]];
boredzo@2487
   903
}
boredzo@2487
   904
Rudy@4246
   905
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
rudy@3133
   906
#pragma unused(aTableView)
rudy@3133
   907
	if(aTableColumn == servicePasswordColumn) {
rudy@3133
   908
		[[services objectAtIndex:rowIndex] setPassword:anObject];
rudy@3133
   909
	}
rudy@3133
   910
rudy@3133
   911
}
rudy@3133
   912
Rudy@4246
   913
- (id) tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
ingmarstein@2943
   914
#pragma unused(aTableView)
ingmarstein@3010
   915
	// we check to make sure we have the image + text column and then set its image manually
ingmarstein@2941
   916
	if (aTableColumn == applicationNameAndIconColumn) {
ingmarstein@3010
   917
		NSArray *arrangedTickets = [ticketsArrayController arrangedObjects];
Rudy@4246
   918
		NSUInteger idx = [tickets indexOfObject:[arrangedTickets objectAtIndex:rowIndex]];
ingmarstein@3010
   919
		[[aTableColumn dataCellForRow:rowIndex] setImage:(NSImage *)CFArrayGetValueAtIndex(images,idx)];
rudy@3133
   920
	} else if (aTableColumn == servicePasswordColumn) {
rudy@3133
   921
		return [[services objectAtIndex:rowIndex] password];
bgannin@2877
   922
	}
ingmarstein@2943
   923
evands@2928
   924
	return nil;
bgannin@2877
   925
}
bgannin@2877
   926
ingmarstein@2587
   927
- (IBAction) tableViewDoubleClick:(id)sender {
Peter@4189
   928
	[self showApplicationConfigurationTab:sender];
ingmarstein@2587
   929
}
ingmarstein@2587
   930
boredzo@2487
   931
#pragma mark NSNetServiceBrowser Delegate Methods
boredzo@2487
   932
boredzo@2487
   933
- (void) netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
boredzo@2487
   934
#pragma unused(aNetServiceBrowser)
boredzo@2487
   935
	// check if a computer with this name has already been added
boredzo@2487
   936
	NSString *name = [aNetService name];
boredzo@2487
   937
	NSEnumerator *enumerator = [services objectEnumerator];
boredzo@2487
   938
	GrowlBrowserEntry *entry;
ingmarstein@2661
   939
	while ((entry = [enumerator nextObject])) {
ingmarstein@2661
   940
		if ([[entry computerName] isEqualToString:name]) {
ingmarstein@2661
   941
			[entry setActive:YES];
boredzo@2487
   942
			return;
ingmarstein@2661
   943
		}
ingmarstein@2661
   944
	}
boredzo@2487
   945
boredzo@2487
   946
	// don't add the local machine
Rudy@4363
   947
	CFStringRef localHostName = nil;
Rudy@4363
   948
	localHostName = SCDynamicStoreCopyComputerName(/*store*/ NULL,
ingmarstein@2634
   949
															   /*nameEncoding*/ NULL);
Rudy@4363
   950
	if(!localHostName)
hg@4365
   951
		localHostName = CFRetain(CFSTR("localhost"));
Rudy@4363
   952
	
ingmarstein@2634
   953
	CFComparisonResult isLocalHost = CFStringCompare(localHostName, (CFStringRef)name, 0);
ingmarstein@2634
   954
	CFRelease(localHostName);
ingmarstein@2634
   955
	if (isLocalHost == kCFCompareEqualTo)
boredzo@2487
   956
		return;
boredzo@2487
   957
boredzo@2487
   958
	// add a new entry at the end
evands@4153
   959
	entry = [[GrowlBrowserEntry alloc] initWithComputerName:name];
boredzo@2487
   960
	[self willChangeValueForKey:@"services"];
boredzo@2487
   961
	[services addObject:entry];
boredzo@2487
   962
	[self didChangeValueForKey:@"services"];
boredzo@2487
   963
	[entry release];
boredzo@2487
   964
boredzo@2487
   965
	if (!moreComing)
boredzo@2487
   966
		[self writeForwardDestinations];
boredzo@2487
   967
}
boredzo@2487
   968
boredzo@2487
   969
- (void) netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
boredzo@2487
   970
#pragma unused(aNetServiceBrowser)
ingmarstein@2661
   971
	NSEnumerator *serviceEnum = [services objectEnumerator];
boredzo@2487
   972
	GrowlBrowserEntry *currentEntry;
boredzo@2487
   973
	NSString *name = [aNetService name];
boredzo@2487
   974
ingmarstein@2661
   975
	while ((currentEntry = [serviceEnum nextObject])) {
boredzo@2487
   976
		if ([[currentEntry computerName] isEqualToString:name]) {
ingmarstein@2661
   977
			[currentEntry setActive:NO];
boredzo@2487
   978
			break;
boredzo@2487
   979
		}
boredzo@2487
   980
	}
boredzo@2487
   981
boredzo@2487
   982
	if (!moreComing)
boredzo@2487
   983
		[self writeForwardDestinations];
boredzo@2487
   984
}
boredzo@2487
   985
ingmarstein@2517
   986
#pragma mark Bonjour
ingmarstein@2517
   987
ingmarstein@2517
   988
- (void) resolveService:(id)sender {
evands@4153
   989
	NSLog(@"What calls resolveService:?");
ingmarstein@2517
   990
}
ingmarstein@2517
   991
ingmarstein@2517
   992
- (NSMutableArray *) services {
ingmarstein@2517
   993
	return services;
ingmarstein@2517
   994
}
ingmarstein@2517
   995
ingmarstein@2517
   996
- (void) setServices:(NSMutableArray *)theServices {
ingmarstein@2517
   997
	if (theServices != services) {
ingmarstein@2661
   998
		if (theServices) {
ingmarstein@2661
   999
			if (services)
ingmarstein@2661
  1000
				[services setArray:theServices];
ingmarstein@2661
  1001
			else
ingmarstein@2661
  1002
				services = [theServices retain];
ingmarstein@2661
  1003
		} else {
ingmarstein@2661
  1004
			[services release];
ingmarstein@2661
  1005
			services = nil;
ingmarstein@2661
  1006
		}
ingmarstein@2517
  1007
	}
ingmarstein@2517
  1008
}
ingmarstein@2517
  1009
Rudy@4246
  1010
- (NSUInteger) countOfServices {
ingmarstein@2517
  1011
	return [services count];
ingmarstein@2517
  1012
}
ingmarstein@2517
  1013
ingmarstein@2517
  1014
- (id) objectInServicesAtIndex:(unsigned)idx {
ingmarstein@2517
  1015
	return [services objectAtIndex:idx];
ingmarstein@2517
  1016
}
ingmarstein@2517
  1017
ingmarstein@2517
  1018
- (void) insertObject:(id)anObject inServicesAtIndex:(unsigned)idx {
ingmarstein@2517
  1019
	[services insertObject:anObject atIndex:idx];
ingmarstein@2517
  1020
}
ingmarstein@2517
  1021
ingmarstein@2517
  1022
- (void) replaceObjectInServicesAtIndex:(unsigned)idx withObject:(id)anObject {
ingmarstein@2517
  1023
	[services replaceObjectAtIndex:idx withObject:anObject];
ingmarstein@2517
  1024
}
ingmarstein@2517
  1025
boredzo@2487
  1026
#pragma mark Detecting Growl
boredzo@2487
  1027
boredzo@2487
  1028
- (void) checkGrowlRunning {
ingmarstein@2540
  1029
	[self setGrowlIsRunning:[preferencesController isGrowlRunning]];
boredzo@2487
  1030
	[self updateRunningStatus];
boredzo@2487
  1031
}
boredzo@2487
  1032
ingmarstein@2517
  1033
#pragma mark "Display Options" tab pane
ingmarstein@2517
  1034
ingmarstein@2517
  1035
- (NSArray *) displayPlugins {
ingmarstein@2517
  1036
	return plugins;
ingmarstein@2517
  1037
}
ingmarstein@2517
  1038
ingmarstein@2517
  1039
- (void) setDisplayPlugins:(NSArray *)thePlugins {
ingmarstein@2572
  1040
	if (thePlugins != plugins) {
ingmarstein@2572
  1041
		[plugins release];
ingmarstein@2572
  1042
		plugins = [thePlugins retain];
ingmarstein@2572
  1043
	}
ingmarstein@2517
  1044
}
ingmarstein@2517
  1045
Peter@4189
  1046
#pragma mark Display pop-up menus
Peter@4189
  1047
Peter@4189
  1048
//Empties the pop-up menu and fills it out with a menu item for each display, optionally including a special menu item for the default display, selecting the menu item whose name is nameOfSelectedDisplay.
Peter@4189
  1049
- (void) populateDisplaysPopUpButton:(NSPopUpButton *)popUp nameOfSelectedDisplay:(NSString *)nameOfSelectedDisplay includeDefaultMenuItem:(BOOL)includeDefault {
Peter@4189
  1050
	NSMenu *menu = [popUp menu];
Peter@4190
  1051
	NSString *nameOfDisplay = nil, *displayNameOfDisplay;
Peter@4189
  1052
Peter@4189
  1053
	NSMenuItem *selectedItem = nil;
Peter@4189
  1054
Peter@4189
  1055
	[popUp removeAllItems];
Peter@4189
  1056
Peter@4189
  1057
	if (includeDefault) {
Peter@4190
  1058
		displayNameOfDisplay = NSLocalizedStringFromTableInBundle(@"Default", nil, [NSBundle bundleForClass:[self class]], /*comment*/ @"Title of menu item for default display");
Peter@4190
  1059
		NSMenuItem *item = [menu addItemWithTitle:displayNameOfDisplay
Peter@4189
  1060
										   action:NULL
Peter@4189
  1061
									keyEquivalent:@""];
Peter@4189
  1062
		[item setRepresentedObject:nil];
Peter@4189
  1063
Peter@4189
  1064
		if (!nameOfSelectedDisplay)
Peter@4189
  1065
			selectedItem = item;
Peter@4189
  1066
Peter@4189
  1067
		[menu addItem:[NSMenuItem separatorItem]];
Peter@4189
  1068
	}
Peter@4189
  1069
Peter@4189
  1070
	NSEnumerator *displaysEnum = [[plugins sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectEnumerator];
Peter@4189
  1071
	while ((nameOfDisplay = [displaysEnum nextObject])) {
Peter@4190
  1072
		displayNameOfDisplay = [[pluginController pluginDictionaryWithName:nameOfDisplay] pluginHumanReadableName];
Peter@4190
  1073
		if (!displayNameOfDisplay)
Peter@4190
  1074
			displayNameOfDisplay = nameOfDisplay;
Peter@4190
  1075
Peter@4190
  1076
		NSMenuItem *item = [menu addItemWithTitle:displayNameOfDisplay
Peter@4189
  1077
										   action:NULL
Peter@4189
  1078
									keyEquivalent:@""];
Peter@4189
  1079
		[item setRepresentedObject:nameOfDisplay];
Peter@4189
  1080
Peter@4189
  1081
		if (nameOfSelectedDisplay && [nameOfSelectedDisplay respondsToSelector:@selector(isEqualToString:)] && [nameOfSelectedDisplay isEqualToString:nameOfDisplay])
Peter@4189
  1082
			selectedItem = item;
Peter@4189
  1083
	}
Peter@4189
  1084
Peter@4189
  1085
	[popUp selectItem:selectedItem];
Peter@4189
  1086
}
Peter@4189
  1087
boredzo@2487
  1088
#pragma mark -
boredzo@2487
  1089
ingmarstein@2572
  1090
/*!
ingmarstein@2572
  1091
 * @brief Refresh preferences when a new application registers with Growl
ingmarstein@2572
  1092
 */
boredzo@2487
  1093
- (void) appRegistered: (NSNotification *) note {
evands@4016
  1094
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4016
  1095
boredzo@2487
  1096
	NSString *app = [note object];
boredzo@2487
  1097
	GrowlApplicationTicket *newTicket = [[GrowlApplicationTicket alloc] initTicketForApplication:app];
boredzo@2487
  1098
boredzo@2487
  1099
	/*
boredzo@2487
  1100
	 *	Because the tickets array is under KVObservation by the TicketsArrayController
boredzo@2487
  1101
	 *	We need to remove the ticket using the correct KVC method:
boredzo@2487
  1102
	 */
boredzo@2487
  1103
boredzo@2487
  1104
	NSEnumerator *ticketEnumerator = [tickets objectEnumerator];
boredzo@2487
  1105
	GrowlApplicationTicket *ticket;
boredzo@2487
  1106
	int removalIndex = -1;
boredzo@2487
  1107
ingmarstein@2540
  1108
	int	i = 0;
boredzo@2487
  1109
	while ((ticket = [ticketEnumerator nextObject])) {
boredzo@2487
  1110
		if ([[ticket applicationName] isEqualToString:app]) {
boredzo@2487
  1111
			removalIndex = i;
boredzo@2487
  1112
			break;
boredzo@2487
  1113
		}
ingmarstein@2540
  1114
		++i;
boredzo@2487
  1115
	}
boredzo@2487
  1116
boredzo@2487
  1117
	if (removalIndex != -1)
boredzo@2487
  1118
		[self removeFromTicketsAtIndex:removalIndex];
boredzo@2487
  1119
	[self insertInTickets:newTicket];
boredzo@2487
  1120
	[newTicket release];
boredzo@2487
  1121
boredzo@2487
  1122
	[self cacheImages];
evands@4016
  1123
	
evands@4016
  1124
	[pool release];
boredzo@2487
  1125
}
boredzo@2487
  1126
boredzo@2487
  1127
- (void) growlLaunched:(NSNotification *)note {
boredzo@2487
  1128
#pragma unused(note)
evands@4016
  1129
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4016
  1130
boredzo@2487
  1131
	[self setGrowlIsRunning:YES];
boredzo@2487
  1132
	[self updateRunningStatus];
evands@4016
  1133
	
evands@4016
  1134
	[pool release];
boredzo@2487
  1135
}
boredzo@2487
  1136
boredzo@2487
  1137
- (void) growlTerminated:(NSNotification *)note {
boredzo@2487
  1138
#pragma unused(note)
evands@4016
  1139
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4016
  1140
boredzo@2487
  1141
	[self setGrowlIsRunning:NO];
boredzo@2487
  1142
	[self updateRunningStatus];
evands@4016
  1143
	
evands@4016
  1144
	[pool release];
boredzo@2487
  1145
}
boredzo@2487
  1146
boredzo@2487
  1147
@end