Core/Source/GrowlPreferencePane.m
author Peter Hosey
Tue Apr 14 18:04:03 2009 -0700 (2009-04-14)
changeset 4189 30c4b669cad3
parent 4188 71e205ca6b20
child 4190 e0dba26e13a1
permissions -rw-r--r--
Fixed the off-by-two error in the displays pop-ups on the Applications tab (#251376).

Among the changes:
1. We no longer use Bindings to populate the pop-up menus. Instead, we populate them ourselves.
2. Likewise, we no longer use Bindings to set the pop-up menus' selections, and the pop-up menus do not talk directly to the relevant array controllers. Instead, the pop-ups send action messages to the preference pane, and each action method changes the application's or notification's display setting.
3. The Configure button and table view double-click action now both go through the same method.
4. The action method for activating the Applications tab (see #3) repopulates the displays pop-ups and updates their selections.
5. We now identify the selected display by its bundle name (in the representedObject property of the menu item), instead of by its index.
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.
boredzo@2487
    33
#define DISPLAY_PREF_FRAME NSMakeRect(16.0f, 58.0f, 354.0f, 289.0f)
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];
ingmarstein@2540
   157
	int 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];
evands@3617
   177
		int count = [arrangedObjects count];
evands@3617
   178
		NSString *defaultDisplayPluginName = [[self preferencesController] defaultDisplayPluginName];
evands@3617
   179
		int defaultStyleRow = NSNotFound;
evands@3617
   180
		for (int 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)
boredzo@2487
   233
	[growlVersionProgress startAnimation:self];
boredzo@2487
   234
boredzo@2487
   235
	if (!versionCheckURL)
ofri@2936
   236
		versionCheckURL = [[NSURL alloc] initWithString:@"http://growl.info/version.xml"];
boredzo@2487
   237
boredzo@2487
   238
	NSBundle *bundle = [self bundle];
boredzo@2487
   239
	NSDictionary *infoDict = [bundle infoDictionary];
boredzo@2487
   240
	NSString *currVersionNumber = [infoDict objectForKey:(NSString *)kCFBundleVersionKey];
ofri@2936
   241
	NSDictionary *productVersionDict = [[NSDictionary alloc] initWithContentsOfURL:versionCheckURL];
boredzo@2487
   242
	NSString *executableName = [infoDict objectForKey:(NSString *)kCFBundleExecutableKey];
boredzo@2487
   243
	NSString *latestVersionNumber = [productVersionDict objectForKey:executableName];
ingmarstein@2591
   244
ingmarstein@2649
   245
	CFURLRef downloadURL = CFURLCreateWithString(kCFAllocatorDefault,
ingmarstein@2649
   246
		(CFStringRef)[productVersionDict objectForKey:[executableName stringByAppendingString:@"DownloadURL"]], NULL);
boredzo@2487
   247
	/*
evands@3619
   248
	 NSLog([[[NSBundle bundleWithIdentifier:GROWL_PREFPANE_BUNDLE_IDENTIFIER] infoDictionary] objectForKey:(NSString *)kCFBundleExecutableKey] );
ingmarstein@2572
   249
	 NSLog(currVersionNumber);
ingmarstein@2572
   250
	 NSLog(latestVersionNumber);
ingmarstein@2572
   251
	 */
ingmarstein@2591
   252
boredzo@2487
   253
	// do nothing--be quiet if there is no active connection or if the
boredzo@2487
   254
	// version number could not be downloaded
boredzo@2487
   255
	if (latestVersionNumber && (compareVersionStringsTranslating1_0To0_5(latestVersionNumber, currVersionNumber) > 0))
boredzo@2487
   256
		NSBeginAlertSheet(/*title*/ NSLocalizedStringFromTableInBundle(@"Update Available", nil, bundle, @""),
boredzo@2487
   257
						  /*defaultButton*/ nil, // use default localized button title ("OK" in English)
boredzo@2487
   258
						  /*alternateButton*/ NSLocalizedStringFromTableInBundle(@"Cancel", nil, bundle, @""),
boredzo@2487
   259
						  /*otherButton*/ nil,
boredzo@2487
   260
						  /*docWindow*/ nil,
boredzo@2487
   261
						  /*modalDelegate*/ self,
boredzo@2487
   262
						  /*didEndSelector*/ NULL,
boredzo@2487
   263
						  /*didDismissSelector*/ @selector(downloadSelector:returnCode:contextInfo:),
ingmarstein@2649
   264
						  /*contextInfo*/ (void *)downloadURL,
ingmarstein@2572
   265
						  /*msg*/ NSLocalizedStringFromTableInBundle(@"A newer version of Growl is available online. Would you like to download it now?", nil, [self bundle], @""));
boredzo@2487
   266
	else
ingmarstein@2649
   267
		CFRelease(downloadURL);
ingmarstein@2591
   268
boredzo@2487
   269
	[productVersionDict release];
ingmarstein@2591
   270
ingmarstein@2572
   271
	[growlVersionProgress stopAnimation:self];
boredzo@2487
   272
}
boredzo@2487
   273
boredzo@2487
   274
- (void) downloadSelector:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
boredzo@2487
   275
#pragma unused(sheet)
ingmarstein@2649
   276
	CFURLRef downloadURL = (CFURLRef)contextInfo;
boredzo@2487
   277
	if (returnCode == NSAlertDefaultReturn)
ingmarstein@2649
   278
		[[NSWorkspace sharedWorkspace] openURL:(NSURL *)downloadURL];
ingmarstein@2649
   279
	CFRelease(downloadURL);
boredzo@2487
   280
}
boredzo@2487
   281
ingmarstein@2572
   282
/*!
ingmarstein@2572
   283
 * @brief Returns if GrowlMenu is currently running.
ingmarstein@2572
   284
 */
boredzo@2487
   285
+ (BOOL) isGrowlMenuRunning {
boredzo@2496
   286
	return [[GrowlPreferencesController sharedController] isRunning:@"com.Growl.MenuExtra"];
boredzo@2487
   287
}
boredzo@2487
   288
boredzo@2487
   289
//subclassed from NSPreferencePane; called before the pane is displayed.
boredzo@2487
   290
- (void) willSelect {
ingmarstein@2540
   291
	NSString *lastVersion = [preferencesController objectForKey:LastKnownVersionKey];
boredzo@2487
   292
	NSString *currentVersion = [self bundleVersion];
boredzo@2487
   293
	if (!(lastVersion && [lastVersion isEqualToString:currentVersion])) {
ingmarstein@2540
   294
		if ([preferencesController isGrowlRunning]) {
ingmarstein@2540
   295
			[preferencesController setGrowlRunning:NO noMatterWhat:NO];
ingmarstein@2540
   296
			[preferencesController setGrowlRunning:YES noMatterWhat:YES];
boredzo@2487
   297
		}
ingmarstein@2540
   298
		[preferencesController setObject:currentVersion forKey:LastKnownVersionKey];
boredzo@2487
   299
	}
evands@3619
   300
boredzo@2487
   301
	[self checkGrowlRunning];
boredzo@2487
   302
}
boredzo@2487
   303
boredzo@2487
   304
- (void) didSelect {
ingmarstein@2647
   305
	[self reloadPreferences:nil];
boredzo@2487
   306
}
boredzo@2487
   307
ingmarstein@2572
   308
/*!
ingmarstein@2572
   309
 * @brief copy images to avoid resizing the original images stored in the tickets.
ingmarstein@2572
   310
 */
boredzo@2487
   311
- (void) cacheImages {
boredzo@2487
   312
	if (images)
ingmarstein@2647
   313
		CFArrayRemoveAllValues(images);
ingmarstein@2572
   314
	else
ingmarstein@2647
   315
		images = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
ingmarstein@2647
   316
ingmarstein@3010
   317
	NSEnumerator *enumerator = [[ticketsArrayController content] objectEnumerator];
boredzo@2487
   318
	GrowlApplicationTicket *ticket;
boredzo@2487
   319
	while ((ticket = [enumerator nextObject])) {
boredzo@2487
   320
		NSImage *icon = [[ticket icon] copy];
boredzo@2487
   321
		[icon setScalesWhenResized:YES];
ingmarstein@2572
   322
		[icon setSize:NSMakeSize(32.0f, 32.0f)];
ingmarstein@2647
   323
		CFArrayAppendValue(images, icon);
boredzo@2487
   324
		[icon release];
boredzo@2487
   325
	}
boredzo@2487
   326
}
boredzo@2487
   327
boredzo@2487
   328
- (NSMutableArray *) tickets {
boredzo@2487
   329
	return tickets;
boredzo@2487
   330
}
boredzo@2487
   331
boredzo@3059
   332
//using setTickets: will tip off the controller (KVO).
boredzo@3059
   333
//use this to set the tickets secretly.
boredzo@3059
   334
- (void) setTicketsWithoutTellingAnybody:(NSArray *)theTickets {
boredzo@2487
   335
	if (theTickets != tickets) {
ingmarstein@2572
   336
		if (tickets)
ingmarstein@2572
   337
			[tickets setArray:theTickets];
ingmarstein@2572
   338
		else
ingmarstein@2572
   339
			tickets = [theTickets mutableCopy];
boredzo@2487
   340
	}
boredzo@2487
   341
}
boredzo@2487
   342
boredzo@3059
   343
//we don't need to do any special extra magic here - just being setTickets: is enough to tip off the controller.
boredzo@3059
   344
- (void) setTickets:(NSArray *)theTickets {
boredzo@3059
   345
	[self setTicketsWithoutTellingAnybody:theTickets];
boredzo@3059
   346
}
boredzo@3059
   347
boredzo@2487
   348
- (void) removeFromTicketsAtIndex:(int)indexToRemove {
boredzo@3059
   349
	NSIndexSet *indices = [NSIndexSet indexSetWithIndex:indexToRemove];
boredzo@3059
   350
	[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"tickets"];
boredzo@3059
   351
boredzo@3059
   352
	[tickets removeObjectAtIndex:indexToRemove];
boredzo@3059
   353
boredzo@3059
   354
	[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"tickets"];
boredzo@2487
   355
}
boredzo@2487
   356
boredzo@2487
   357
- (void) insertInTickets:(GrowlApplicationTicket *)newTicket {
boredzo@3059
   358
	NSIndexSet *indices = [NSIndexSet indexSetWithIndex:[tickets count]];
boredzo@3059
   359
	[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"tickets"];
boredzo@3059
   360
boredzo@3059
   361
	[tickets addObject:newTicket];
boredzo@3059
   362
boredzo@3059
   363
	[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"tickets"];
boredzo@2487
   364
}
boredzo@2487
   365
boredzo@2487
   366
- (void) reloadDisplayPluginView {
boredzo@2487
   367
	NSArray *selectedPlugins = [displayPluginsArrayController selectedObjects];
boredzo@2487
   368
	unsigned numPlugins = [plugins count];
boredzo@2487
   369
	[currentPlugin release];
boredzo@2487
   370
	if (numPlugins > 0U && selectedPlugins && [selectedPlugins count] > 0U)
boredzo@2487
   371
		currentPlugin = [[selectedPlugins objectAtIndex:0U] retain];
boredzo@2487
   372
	else
boredzo@2487
   373
		currentPlugin = nil;
ingmarstein@2943
   374
eridius@2773
   375
	NSString *currentPluginName = [currentPlugin objectForKey:(NSString *)kCFBundleNameKey];
eridius@2773
   376
	currentPluginController = (GrowlPlugin *)[pluginController pluginInstanceWithName:currentPluginName];
eridius@2773
   377
	[self loadViewForDisplay:currentPluginName];
eridius@2773
   378
	[displayAuthor setStringValue:[currentPlugin objectForKey:@"GrowlPluginAuthor"]];
eridius@2773
   379
	[displayVersion setStringValue:[currentPlugin objectForKey:(NSString *)kCFBundleNameKey]];
boredzo@2487
   380
}
boredzo@2487
   381
ingmarstein@2572
   382
/*!
ingmarstein@2572
   383
 * @brief Called when a distributed GrowlPreferencesChanged notification is received.
ingmarstein@2572
   384
 */
boredzo@2487
   385
- (void) reloadPrefs:(NSNotification *)notification {
boredzo@2487
   386
	// ignore notifications which are sent by ourselves
evands@4016
   387
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4016
   388
boredzo@2487
   389
	NSNumber *pidValue = [[notification userInfo] objectForKey:@"pid"];
boredzo@2487
   390
	if (!pidValue || [pidValue intValue] != pid)
ingmarstein@2647
   391
		[self reloadPreferences:[notification object]];
evands@4016
   392
	
evands@4016
   393
	[pool release];
boredzo@2487
   394
}
boredzo@2487
   395
bgannin@3317
   396
- (void) updatePosition:(NSNotification *)notification {
bgannin@3317
   397
	if([notification object] == globalPositionPicker) {
bgannin@3317
   398
		[preferencesController setInteger:[globalPositionPicker selectedPosition] forKey:GROWL_POSITION_PREFERENCE_KEY];
bgannin@3317
   399
	}
bgannin@3317
   400
	else if([notification object] == appPositionPicker) {
bgannin@3317
   401
		// a cheap hack around selection not providing a workable object
boredzo@3681
   402
		NSArray *selection = [ticketsArrayController selectedObjects];
boredzo@3682
   403
		if ([selection count] > 0)
boredzo@3681
   404
			[[selection objectAtIndex:0] setSelectedPosition:[appPositionPicker selectedPosition]];
bgannin@3317
   405
	}
bgannin@3317
   406
}
bgannin@3317
   407
ingmarstein@2572
   408
/*!
ingmarstein@2572
   409
 * @brief Reloads the preferences and updates the GUI accordingly.
ingmarstein@2572
   410
 */
ingmarstein@2647
   411
- (void) reloadPreferences:(NSString *)object {
ingmarstein@2672
   412
	if (!object || [object isEqualToString:@"GrowlTicketChanged"]) {
ingmarstein@2672
   413
		GrowlTicketController *ticketController = [GrowlTicketController sharedController];
ingmarstein@2672
   414
		[ticketController loadAllSavedTickets];
ingmarstein@2672
   415
		[self setTickets:[[ticketController allSavedTickets] allValues]];
ingmarstein@2672
   416
		[self cacheImages];
ingmarstein@2672
   417
	}
ingmarstein@2943
   418
eridius@2773
   419
	[self setDisplayPlugins:[[GrowlPluginController sharedController] registeredPluginNamesArrayForType:GROWL_VIEW_EXTENSION]];
eridius@2773
   420
eridius@2773
   421
#ifdef THIS_CODE_WAS_REMOVED_AND_I_DONT_KNOW_WHY
eridius@2773
   422
	if (!object || [object isEqualToString:@"GrowlTicketChanged"])
eridius@2773
   423
		[self setTickets:[[ticketController allSavedTickets] allValues]];
eridius@2773
   424
eridius@2773
   425
	[preferencesController setSquelchMode:[preferencesController squelchMode]];
eridius@2773
   426
	[preferencesController setGrowlMenuEnabled:[preferencesController isGrowlMenuEnabled]];
eridius@2773
   427
eridius@2773
   428
	[self cacheImages];
eridius@2773
   429
#endif
boredzo@2487
   430
boredzo@2487
   431
	// If Growl is enabled, ensure the helper app is launched
ingmarstein@2540
   432
	if ([preferencesController boolForKey:GrowlEnabledKey])
ingmarstein@2540
   433
		[preferencesController launchGrowl:NO];
boredzo@2487
   434
ingmarstein@2672
   435
	if ([plugins count] > 0U)
boredzo@2487
   436
		[self reloadDisplayPluginView];
ingmarstein@2672
   437
	else
boredzo@2487
   438
		[self loadViewForDisplay:nil];
boredzo@2487
   439
}
boredzo@2487
   440
boredzo@2487
   441
- (BOOL) growlIsRunning {
boredzo@2487
   442
	return growlIsRunning;
boredzo@2487
   443
}
boredzo@2487
   444
boredzo@2487
   445
- (void) setGrowlIsRunning:(BOOL)flag {
boredzo@2487
   446
	growlIsRunning = flag;
boredzo@2487
   447
}
boredzo@2487
   448
boredzo@2487
   449
- (void) updateRunningStatus {
boredzo@2487
   450
	[startStopGrowl setEnabled:YES];
boredzo@2487
   451
	NSBundle *bundle = [self bundle];
boredzo@2487
   452
	[startStopGrowl setTitle:
boredzo@2487
   453
		growlIsRunning ? NSLocalizedStringFromTableInBundle(@"Stop Growl",nil,bundle,@"")
boredzo@2487
   454
					   : NSLocalizedStringFromTableInBundle(@"Start Growl",nil,bundle,@"")];
boredzo@2487
   455
	[growlRunningStatus setStringValue:
boredzo@2487
   456
		growlIsRunning ? NSLocalizedStringFromTableInBundle(@"Growl is running.",nil,bundle,@"")
boredzo@2487
   457
					   : NSLocalizedStringFromTableInBundle(@"Growl is stopped.",nil,bundle,@"")];
boredzo@2487
   458
	[growlRunningProgress stopAnimation:self];
boredzo@2487
   459
}
boredzo@2487
   460
boredzo@2487
   461
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
boredzo@2487
   462
						change:(NSDictionary *)change context:(void *)context {
boredzo@2487
   463
#pragma unused(change, context)
boredzo@2487
   464
	if ([keyPath isEqualToString:@"selection"]) {
boredzo@2487
   465
		if ((object == ticketsArrayController))
boredzo@2487
   466
			[self setCanRemoveTicket:(activeTableView == growlApplications) && [ticketsArrayController canRemove]];
boredzo@2487
   467
		else if (object == displayPluginsArrayController)
boredzo@2487
   468
			[self reloadDisplayPluginView];
boredzo@2487
   469
	}
boredzo@2487
   470
}
boredzo@2487
   471
boredzo@2487
   472
- (void) writeForwardDestinations {
boredzo@2487
   473
	NSMutableArray *destinations = [[NSMutableArray alloc] initWithCapacity:[services count]];
boredzo@2487
   474
	NSEnumerator *enumerator = [services objectEnumerator];
boredzo@2487
   475
	GrowlBrowserEntry *entry;
boredzo@2487
   476
	while ((entry = [enumerator nextObject]))
evands@4153
   477
		[destinations addObject:[entry properties]];
ingmarstein@2540
   478
	[preferencesController setObject:destinations forKey:GrowlForwardDestinationsKey];
boredzo@2487
   479
	[destinations release];
boredzo@2487
   480
}
boredzo@2487
   481
boredzo@2487
   482
#pragma mark -
boredzo@2496
   483
#pragma mark Bindings accessors (not for programmatic use)
boredzo@2496
   484
boredzo@2496
   485
- (GrowlPluginController *) pluginController {
boredzo@2496
   486
	if (!pluginController)
boredzo@2496
   487
		pluginController = [GrowlPluginController sharedController];
boredzo@2496
   488
boredzo@2496
   489
	return pluginController;
boredzo@2496
   490
}
boredzo@2496
   491
- (GrowlPreferencesController *) preferencesController {
boredzo@2496
   492
	if (!preferencesController)
boredzo@2496
   493
		preferencesController = [GrowlPreferencesController sharedController];
boredzo@2496
   494
boredzo@2496
   495
	return preferencesController;
boredzo@2496
   496
}
boredzo@2496
   497
ingmarstein@3168
   498
- (NSArray *) sounds {
bgannin@3913
   499
	NSMutableArray *soundNames = [[NSMutableArray alloc] init];
bgannin@3913
   500
	
bgannin@3913
   501
	NSArray *paths = [NSArray arrayWithObjects:@"/System/Library/Sounds",
bgannin@3913
   502
												@"/Library/Sounds",
bgannin@3913
   503
											   [NSString stringWithFormat:@"%@/Library/Sounds", NSHomeDirectory()],
bgannin@3913
   504
											   nil];
bgannin@3913
   505
bgannin@3913
   506
	NSString *directory;
bgannin@3913
   507
	NSEnumerator *dirEnumerator = [paths objectEnumerator];
bgannin@3913
   508
	while ((directory = [dirEnumerator nextObject])) {
bgannin@3913
   509
		BOOL isDirectory = NO;
bgannin@3913
   510
		
bgannin@3913
   511
		if ([[NSFileManager defaultManager] fileExistsAtPath:directory isDirectory:&isDirectory]) {
bgannin@3913
   512
			if (isDirectory) {
bgannin@3956
   513
				[soundNames addObject:@"-"];
bgannin@3956
   514
				
bgannin@3913
   515
				NSArray *files = [[NSFileManager defaultManager] directoryContentsAtPath:directory];
bgannin@3913
   516
bgannin@3913
   517
				NSString *filename = nil;
bgannin@3913
   518
				NSEnumerator *fileEnumerator = [files objectEnumerator];
bgannin@3913
   519
				while ((filename = [fileEnumerator nextObject])) {
bgannin@3913
   520
					NSString *file = [filename stringByDeletingPathExtension];
bgannin@3913
   521
			
bgannin@3913
   522
					if (![file isEqualToString:@".DS_Store"])
bgannin@3913
   523
						[soundNames addObject:file];
bgannin@3913
   524
				}
bgannin@3913
   525
			}
bgannin@3913
   526
		}
bgannin@3913
   527
	}
bgannin@3913
   528
	
ingmarstein@3168
   529
	return [soundNames autorelease];
ingmarstein@3168
   530
}
ingmarstein@3168
   531
bgannin@3957
   532
- (void)translateSeparatorsInMenu:(NSNotification *)notification
bgannin@3956
   533
{
bgannin@3957
   534
	NSPopUpButton * button = [notification object];
bgannin@3956
   535
	
bgannin@3957
   536
	NSMenu *menu = [button menu];
bgannin@3956
   537
	
bgannin@3957
   538
	int itemIndex = 0;
bgannin@3957
   539
	
evands@3963
   540
	while ((itemIndex = [menu indexOfItemWithTitle:@"-"]) != -1) {
bgannin@3957
   541
		[menu removeItemAtIndex:itemIndex];
bgannin@3957
   542
		[menu insertItem:[NSMenuItem separatorItem] atIndex:itemIndex];
bgannin@3956
   543
	}
bgannin@3956
   544
}
bgannin@3956
   545
boredzo@2487
   546
#pragma mark Growl running state
boredzo@2487
   547
ingmarstein@2572
   548
/*!
ingmarstein@2572
   549
 * @brief Launches GrowlHelperApp.
ingmarstein@2572
   550
 */
boredzo@2487
   551
- (void) launchGrowl {
boredzo@2487
   552
	// Don't allow the button to be clicked while we update
boredzo@2487
   553
	[startStopGrowl setEnabled:NO];
boredzo@2487
   554
	[growlRunningProgress startAnimation:self];
boredzo@2487
   555
boredzo@2487
   556
	// Update our status visible to the user
boredzo@2487
   557
	[growlRunningStatus setStringValue:NSLocalizedStringFromTableInBundle(@"Launching Growl...",nil,[self bundle],@"")];
boredzo@2487
   558
ingmarstein@2540
   559
	[preferencesController setGrowlRunning:YES noMatterWhat:NO];
boredzo@2487
   560
boredzo@2487
   561
	// After 4 seconds force a status update, in case Growl didn't start/stop
boredzo@2487
   562
	[self performSelector:@selector(checkGrowlRunning)
boredzo@2487
   563
			   withObject:nil
boredzo@2487
   564
			   afterDelay:4.0];
boredzo@2487
   565
}
boredzo@2487
   566
ingmarstein@2572
   567
/*!
ingmarstein@2572
   568
 * @brief Terminates running GrowlHelperApp instances.
ingmarstein@2572
   569
 */
boredzo@2487
   570
- (void) terminateGrowl {
boredzo@2487
   571
	// Don't allow the button to be clicked while we update
boredzo@2487
   572
	[startStopGrowl setEnabled:NO];
boredzo@2487
   573
	[growlRunningProgress startAnimation:self];
boredzo@2487
   574
boredzo@2487
   575
	// Update our status visible to the user
boredzo@2487
   576
	[growlRunningStatus setStringValue:NSLocalizedStringFromTableInBundle(@"Terminating Growl...",nil,[self bundle],@"")];
boredzo@2487
   577
boredzo@2487
   578
	// Ask the Growl Helper App to shutdown
ingmarstein@2540
   579
	[preferencesController setGrowlRunning:NO noMatterWhat:NO];
boredzo@2487
   580
boredzo@2487
   581
	// After 4 seconds force a status update, in case growl didn't start/stop
boredzo@2487
   582
	[self performSelector:@selector(checkGrowlRunning)
boredzo@2487
   583
			   withObject:nil
boredzo@2487
   584
			   afterDelay:4.0];
boredzo@2487
   585
}
boredzo@2487
   586
boredzo@2487
   587
#pragma mark "General" tab pane
boredzo@2487
   588
boredzo@2487
   589
- (IBAction) startStopGrowl:(id) sender {
boredzo@2487
   590
#pragma unused(sender)
boredzo@2487
   591
	// Make sure growlIsRunning is correct
ingmarstein@2540
   592
	if (growlIsRunning != [preferencesController isGrowlRunning]) {
boredzo@2487
   593
		// Nope - lets just flip it and update status
boredzo@2487
   594
		[self setGrowlIsRunning:!growlIsRunning];
boredzo@2487
   595
		[self updateRunningStatus];
boredzo@2487
   596
		return;
boredzo@2487
   597
	}
boredzo@2487
   598
boredzo@2487
   599
	// Our desired state is a toggle of the current state;
boredzo@2487
   600
	if (growlIsRunning)
boredzo@2487
   601
		[self terminateGrowl];
boredzo@2487
   602
	else
boredzo@2487
   603
		[self launchGrowl];
boredzo@2487
   604
}
boredzo@2487
   605
boredzo@2487
   606
tick@2979
   607
/*
boredzo@2487
   608
- (IBAction) customFileChosen:(id)sender {
boredzo@2487
   609
	int selected = [sender indexOfSelectedItem];
boredzo@2487
   610
	if ((selected == [sender numberOfItems] - 1) || (selected == -1)) {
boredzo@2487
   611
		NSSavePanel *sp = [NSSavePanel savePanel];
boredzo@2487
   612
		[sp setRequiredFileType:@"log"];
boredzo@2487
   613
		[sp setCanSelectHiddenExtension:YES];
boredzo@2487
   614
boredzo@2487
   615
		int runResult = [sp runModalForDirectory:nil file:@""];
boredzo@2487
   616
		NSString *saveFilename = [sp filename];
boredzo@2487
   617
		if (runResult == NSFileHandlingPanelOKButton) {
boredzo@2487
   618
			unsigned saveFilenameIndex = NSNotFound;
ingmarstein@2647
   619
			unsigned                 i = CFArrayGetCount(customHistArray);
boredzo@2487
   620
			if (i) {
boredzo@2487
   621
				while (--i) {
ingmarstein@2647
   622
					if ([(id)CFArrayGetValueAtIndex(customHistArray, i) isEqual:saveFilename]) {
boredzo@2487
   623
						saveFilenameIndex = i;
boredzo@2487
   624
						break;
boredzo@2487
   625
					}
boredzo@2487
   626
				}
boredzo@2487
   627
			}
boredzo@2487
   628
			if (saveFilenameIndex == NSNotFound) {
ingmarstein@2647
   629
				if (CFArrayGetCount(customHistArray) == 3U)
ingmarstein@2647
   630
					CFArrayRemoveValueAtIndex(customHistArray, 2);
ingmarstein@2647
   631
			} else
ingmarstein@2647
   632
				CFArrayRemoveValueAtIndex(customHistArray, saveFilenameIndex);
ingmarstein@2647
   633
			CFArrayInsertValueAtIndex(customHistArray, 0, saveFilename);
boredzo@2487
   634
		}
boredzo@2487
   635
	} else {
ingmarstein@2647
   636
		CFStringRef temp = CFRetain(CFArrayGetValueAtIndex(customHistArray, selected));
ingmarstein@2647
   637
		CFArrayRemoveValueAtIndex(customHistArray, selected);
ingmarstein@2647
   638
		CFArrayInsertValueAtIndex(customHistArray, 0, temp);
ingmarstein@2647
   639
		CFRelease(temp);
ingmarstein@2647
   640
	}
ingmarstein@2647
   641
ingmarstein@2647
   642
	unsigned numHistItems = CFArrayGetCount(customHistArray);
boredzo@2487
   643
	if (numHistItems) {
ingmarstein@2647
   644
		id s = (id)CFArrayGetValueAtIndex(customHistArray, 0);
ingmarstein@2540
   645
		[preferencesController setObject:s forKey:GrowlCustomHistKey1];
boredzo@2487
   646
ingmarstein@2647
   647
		if ((numHistItems > 1U) && (s = (id)CFArrayGetValueAtIndex(customHistArray, 1)))
ingmarstein@2540
   648
			[preferencesController setObject:s forKey:GrowlCustomHistKey2];
boredzo@2487
   649
ingmarstein@2647
   650
		if ((numHistItems > 2U) && (s = (id)CFArrayGetValueAtIndex(customHistArray, 2)))
ingmarstein@2540
   651
			[preferencesController setObject:s forKey:GrowlCustomHistKey3];
boredzo@2487
   652
boredzo@2487
   653
		//[[logFileType cellAtRow:1 column:0] setEnabled:YES];
boredzo@2487
   654
		[logFileType selectCellAtRow:1 column:0];
boredzo@2487
   655
	}
boredzo@2487
   656
boredzo@2487
   657
	[self updateLogPopupMenu];
zaudragon@2980
   658
}*/
boredzo@2487
   659
boredzo@2487
   660
- (void) updateLogPopupMenu {
boredzo@2487
   661
	[customMenuButton removeAllItems];
boredzo@2487
   662
ingmarstein@2647
   663
	int numHistItems = CFArrayGetCount(customHistArray);
ingmarstein@2647
   664
	for (int i = 0U; i < numHistItems; i++) {
ingmarstein@2647
   665
		NSArray *pathComponentry = [[(NSString *)CFArrayGetValueAtIndex(customHistArray, i) stringByAbbreviatingWithTildeInPath] pathComponents];
boredzo@2487
   666
		unsigned numPathComponents = [pathComponentry count];
boredzo@2487
   667
		if (numPathComponents > 2U) {
boredzo@2487
   668
			unichar ellipsis = 0x2026;
boredzo@2487
   669
			NSMutableString *arg = [[NSMutableString alloc] initWithCharacters:&ellipsis length:1U];
boredzo@2487
   670
			[arg appendString:@"/"];
boredzo@2487
   671
			[arg appendString:[pathComponentry objectAtIndex:(numPathComponents - 2U)]];
boredzo@2487
   672
			[arg appendString:@"/"];
boredzo@2487
   673
			[arg appendString:[pathComponentry objectAtIndex:(numPathComponents - 1U)]];
boredzo@2487
   674
			[customMenuButton insertItemWithTitle:arg atIndex:i];
boredzo@2487
   675
			[arg release];
boredzo@2487
   676
		} else
ingmarstein@2647
   677
			[customMenuButton insertItemWithTitle:[(NSString *)CFArrayGetValueAtIndex(customHistArray, i) stringByAbbreviatingWithTildeInPath] atIndex:i];
boredzo@2487
   678
	}
boredzo@2487
   679
	// No separator if there's no file list yet
ingmarstein@2647
   680
	if (numHistItems > 0)
boredzo@2487
   681
		[[customMenuButton menu] addItem:[NSMenuItem separatorItem]];
zaudragon@2980
   682
	[customMenuButton addItemWithTitle:NSLocalizedStringFromTableInBundle(@"Browse menu item title", nil, [self bundle], nil)];
boredzo@2487
   683
	//select first item, if any
boredzo@2487
   684
	[customMenuButton selectItemAtIndex:numHistItems ? 0 : -1];
boredzo@2487
   685
}
zaudragon@2980
   686
boredzo@2487
   687
#pragma mark "Applications" tab pane
boredzo@2487
   688
boredzo@2487
   689
- (BOOL) canRemoveTicket {
boredzo@2487
   690
	return canRemoveTicket;
boredzo@2487
   691
}
boredzo@2487
   692
boredzo@2487
   693
- (void) setCanRemoveTicket:(BOOL)flag {
boredzo@2487
   694
	canRemoveTicket = flag;
boredzo@2487
   695
}
boredzo@2487
   696
boredzo@2487
   697
- (void) deleteTicket:(id)sender {
boredzo@2487
   698
#pragma unused(sender)
bgannin@2937
   699
	NSString *appName = [[[ticketsArrayController selectedObjects] objectAtIndex:0U] applicationName];
evands@3782
   700
	NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"Are you sure you want to remove %@?", nil, [self bundle], nil), appName]
evands@3782
   701
									 defaultButton:NSLocalizedStringFromTableInBundle(@"Remove", nil, [self bundle], "Button title for removing something")
evands@3782
   702
								   alternateButton:NSLocalizedStringFromTableInBundle(@"Cancel", nil, [self bundle], "Button title for canceling")
ingmarstein@2943
   703
									   otherButton:nil
evands@3782
   704
						 informativeTextWithFormat:[NSString stringWithFormat:
evands@3782
   705
													NSLocalizedStringFromTableInBundle(@"This will remove all Growl settings for %@.", nil, [self bundle], ""), appName]];
bgannin@2881
   706
	[alert setIcon:[[[NSImage alloc] initWithContentsOfFile:[[self bundle] pathForImageResource:@"growl-icon"]] autorelease]];
bgannin@2881
   707
	[alert beginSheetModalForWindow:[[NSApplication sharedApplication] keyWindow] modalDelegate:self didEndSelector:@selector(deleteCallbackDidEnd:returnCode:contextInfo:) contextInfo:nil];
bgannin@2881
   708
}
bgannin@2881
   709
bgannin@2881
   710
// this method is used as our callback to determine whether or not to delete the ticket
ingmarstein@2943
   711
-(void) deleteCallbackDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)eventID {
ofri@2936
   712
#pragma unused(alert)
ofri@2936
   713
#pragma unused(eventID)
ingmarstein@2941
   714
	if (returnCode == NSAlertDefaultReturn) {
bgannin@2881
   715
		GrowlApplicationTicket *ticket = [[ticketsArrayController selectedObjects] objectAtIndex:0U];
bgannin@2881
   716
		NSString *path = [ticket path];
ingmarstein@2943
   717
bgannin@2881
   718
		if ([[NSFileManager defaultManager] removeFileAtPath:path handler:nil]) {
bgannin@2881
   719
			CFNumberRef pidValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid);
bgannin@2881
   720
			CFStringRef keys[2] = { CFSTR("TicketName"), CFSTR("pid") };
bgannin@2881
   721
			CFTypeRef   values[2] = { [ticket applicationName], pidValue };
bgannin@2881
   722
			CFDictionaryRef userInfo = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
bgannin@2881
   723
			CFRelease(pidValue);
bgannin@2881
   724
			CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
bgannin@2881
   725
												 (CFStringRef)GrowlPreferencesChanged,
bgannin@2881
   726
												 CFSTR("GrowlTicketDeleted"),
bgannin@2881
   727
												 userInfo, false);
bgannin@2881
   728
			CFRelease(userInfo);
bgannin@2881
   729
			unsigned idx = [tickets indexOfObject:ticket];
bgannin@2881
   730
			CFArrayRemoveValueAtIndex(images, idx);
ingmarstein@2943
   731
bgannin@2881
   732
			unsigned oldSelectionIndex = [ticketsArrayController selectionIndex];
ingmarstein@2943
   733
bgannin@2881
   734
			///	Hmm... This doesn't work for some reason....
bgannin@3448
   735
			//	Even though the same method definitely (?) probably works in the appRegistered: method...
ingmarstein@2943
   736
bgannin@2881
   737
			//	[self removeFromTicketsAtIndex:	[ticketsArrayController selectionIndex]];
ingmarstein@2943
   738
bgannin@2881
   739
			NSMutableArray *newTickets = [tickets mutableCopy];
bgannin@2881
   740
			[newTickets removeObject:ticket];
bgannin@2881
   741
			[self setTickets:newTickets];
bgannin@2881
   742
			[newTickets release];
ingmarstein@2943
   743
bgannin@2881
   744
			if (oldSelectionIndex >= [tickets count])
bgannin@2881
   745
				oldSelectionIndex = [tickets count] - 1;
rudy@2902
   746
			[self cacheImages];
bgannin@2881
   747
			[ticketsArrayController setSelectionIndex:oldSelectionIndex];
ingmarstein@2943
   748
		}
boredzo@2487
   749
	}
boredzo@2487
   750
}
boredzo@2487
   751
bgannin@3448
   752
-(IBAction)playSound:(id)sender
bgannin@3448
   753
{
bgannin@3448
   754
	if([sender indexOfSelectedItem] > 0) // The 0 item is "None"
bgannin@3448
   755
		[[NSSound soundNamed:[[sender selectedItem] title]] play];
bgannin@3448
   756
}
bgannin@3448
   757
Peter@4189
   758
- (IBAction) showApplicationConfigurationTab:(id)sender {
Peter@4189
   759
	if ([ticketsArrayController selectionIndex] != NSNotFound) {
Peter@4189
   760
		[self populateDisplaysPopUpButton:displayMenuButton nameOfSelectedDisplay:[[ticketsArrayController selection] valueForKey:@"displayPluginName"] includeDefaultMenuItem:YES];
Peter@4189
   761
		[self populateDisplaysPopUpButton:notificationDisplayMenuButton nameOfSelectedDisplay:[[notificationsArrayController selection] valueForKey:@"displayPluginName"] includeDefaultMenuItem:YES];
Peter@4189
   762
Peter@4189
   763
		[applicationsTab selectLastTabViewItem:sender];
Peter@4189
   764
		[configurationTab selectFirstTabViewItem:sender];
Peter@4189
   765
	}
Peter@4189
   766
}
Peter@4189
   767
Peter@4189
   768
- (IBAction) changeNameOfDisplayForApplication:(id)sender {
Peter@4189
   769
	NSString *newDisplayPluginName = [[sender selectedItem] representedObject];
Peter@4189
   770
	[[ticketsArrayController selectedObjects] setValue:newDisplayPluginName forKey:@"displayPluginName"];
Peter@4189
   771
	[self showPreview:sender];
Peter@4189
   772
}
Peter@4189
   773
- (IBAction) changeNameOfDisplayForNotification:(id)sender {
Peter@4189
   774
	NSString *newDisplayPluginName = [[sender selectedItem] representedObject];
Peter@4189
   775
	[[notificationsArrayController selectedObjects] setValue:newDisplayPluginName forKey:@"displayPluginName"];
Peter@4189
   776
	[self showPreview:sender];
Peter@4189
   777
}
Peter@4189
   778
rudy@3133
   779
#pragma mark "Display" tab pane
boredzo@2487
   780
bgannin@3280
   781
- (IBAction) showDisabledDisplays:(id)sender {
bgannin@3280
   782
#pragma unused(sender)
bgannin@3280
   783
	[disabledDisplaysList setString:[[pluginController disabledPlugins] componentsJoinedByString:@"\n"]];
bgannin@3280
   784
	
bgannin@3280
   785
	[NSApp beginSheet:disabledDisplaysSheet 
bgannin@3280
   786
	   modalForWindow:[[self mainView] window]
bgannin@3280
   787
		modalDelegate:nil
bgannin@3280
   788
	   didEndSelector:nil
bgannin@3280
   789
		  contextInfo:nil];
bgannin@3280
   790
}
bgannin@3280
   791
bgannin@3280
   792
- (IBAction) endDisabledDisplays:(id)sender {
bgannin@3280
   793
#pragma unused(sender)
bgannin@3280
   794
	[NSApp endSheet:disabledDisplaysSheet];
bgannin@3280
   795
	[disabledDisplaysSheet orderOut:disabledDisplaysSheet];
bgannin@3280
   796
}
bgannin@3280
   797
bgannin@3280
   798
// Returns a boolean based on whether any disabled displays are present, used for the 'hidden' binding of the button on the tab
bgannin@3280
   799
- (BOOL)hasDisabledDisplays {
bgannin@3280
   800
	return [pluginController disabledPluginsPresent];
bgannin@3280
   801
}
bgannin@3280
   802
bgannin@3917
   803
// Popup buttons that post preview notifications support suppressing the preview with the Option key
ingmarstein@2672
   804
- (IBAction) showPreview:(id)sender {
bgannin@3917
   805
	if(([sender isKindOfClass:[NSPopUpButton class]]) && (GetCurrentKeyModifiers() & optionKey))
bgannin@3917
   806
		return;
bgannin@3917
   807
	
bgannin@3917
   808
	NSDictionary *pluginToUse = currentPlugin;
Peter@4189
   809
	NSString *pluginName = nil;
Peter@4189
   810
	
Peter@4189
   811
	if ([sender isKindOfClass:[NSPopUpButton class]]) {
Peter@4189
   812
		NSPopUpButton *popUp = (NSPopUpButton *)sender;
Peter@4189
   813
		if (sender == displayMenuButton || sender == notificationDisplayMenuButton)
Peter@4189
   814
			pluginName = [[popUp selectedItem] representedObject];
Peter@4189
   815
		else
evands@3963
   816
#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
   817
			pluginToUse = [[displayPluginsArrayController content] objectAtIndex:[popUp indexOfSelectedItem]];
Peter@4189
   818
	}
Peter@4189
   819
Peter@4189
   820
	if (!pluginName)
Peter@4189
   821
		pluginName = [pluginToUse objectForKey:GrowlPluginInfoKeyName];
bgannin@3917
   822
			
evands@3513
   823
	[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GrowlPreview
Peter@4189
   824
																   object:pluginName];
boredzo@2487
   825
}
boredzo@2487
   826
boredzo@2487
   827
- (void) loadViewForDisplay:(NSString *)displayName {
boredzo@2487
   828
	NSView *newView = nil;
boredzo@2487
   829
	NSPreferencePane *prefPane = nil, *oldPrefPane = nil;
boredzo@2487
   830
boredzo@2487
   831
	if (pluginPrefPane)
boredzo@2487
   832
		oldPrefPane = pluginPrefPane;
boredzo@2487
   833
boredzo@2487
   834
	if (displayName) {
boredzo@2487
   835
		// Old plugins won't support the new protocol. Check first
boredzo@2487
   836
		if ([currentPluginController respondsToSelector:@selector(preferencePane)])
boredzo@2487
   837
			prefPane = [currentPluginController preferencePane];
boredzo@2487
   838
boredzo@2487
   839
		if (prefPane == pluginPrefPane) {
boredzo@2487
   840
			// Don't bother swapping anything
boredzo@2487
   841
			return;
boredzo@2487
   842
		} else {
boredzo@2487
   843
			[pluginPrefPane release];
boredzo@2487
   844
			pluginPrefPane = [prefPane retain];
boredzo@2487
   845
			[oldPrefPane willUnselect];
boredzo@2487
   846
		}
boredzo@2487
   847
		if (pluginPrefPane) {
boredzo@2487
   848
			if ([loadedPrefPanes containsObject:pluginPrefPane]) {
boredzo@2487
   849
				newView = [pluginPrefPane mainView];
boredzo@2487
   850
			} else {
boredzo@2487
   851
				newView = [pluginPrefPane loadMainView];
boredzo@2487
   852
				[loadedPrefPanes addObject:pluginPrefPane];
boredzo@2487
   853
			}
boredzo@2487
   854
			[pluginPrefPane willSelect];
boredzo@2487
   855
		}
boredzo@2487
   856
	} else {
boredzo@2487
   857
		[pluginPrefPane release];
boredzo@2487
   858
		pluginPrefPane = nil;
boredzo@2487
   859
	}
boredzo@2487
   860
	if (!newView)
boredzo@2487
   861
		newView = displayDefaultPrefView;
boredzo@2487
   862
	if (displayPrefView != newView) {
boredzo@2487
   863
		// Make sure the new view is framed correctly
boredzo@2487
   864
		[newView setFrame:DISPLAY_PREF_FRAME];
boredzo@2487
   865
		[[displayPrefView superview] replaceSubview:displayPrefView with:newView];
boredzo@2487
   866
		displayPrefView = newView;
boredzo@2487
   867
boredzo@2487
   868
		if (pluginPrefPane) {
boredzo@2487
   869
			[pluginPrefPane didSelect];
boredzo@2487
   870
			// Hook up key view chain
boredzo@2487
   871
			[displayPluginsTable setNextKeyView:[pluginPrefPane firstKeyView]];
boredzo@2487
   872
			[[pluginPrefPane lastKeyView] setNextKeyView:previewButton];
boredzo@2487
   873
			//[[displayPluginsTable window] makeFirstResponder:[pluginPrefPane initialKeyView]];
boredzo@2487
   874
		} else {
boredzo@2487
   875
			[displayPluginsTable setNextKeyView:previewButton];
boredzo@2487
   876
		}
boredzo@2487
   877
boredzo@2487
   878
		if (oldPrefPane)
boredzo@2487
   879
			[oldPrefPane didUnselect];
boredzo@2487
   880
	}
boredzo@2487
   881
}
boredzo@2487
   882
boredzo@2487
   883
#pragma mark About Tab
boredzo@2487
   884
boredzo@2487
   885
- (void) setupAboutTab {
bgannin@3982
   886
	[aboutVersionString setStringValue:[NSString stringWithFormat:@"%@ %@", 
bgannin@3982
   887
										[[self bundle] objectForInfoDictionaryKey:@"CFBundleName"], 
bgannin@3982
   888
										[[self bundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]]];
boredzo@2487
   889
	[aboutBoxTextView readRTFDFromFile:[[self bundle] pathForResource:@"About" ofType:@"rtf"]];
boredzo@2487
   890
}
boredzo@2487
   891
boredzo@2487
   892
- (IBAction) openGrowlWebSite:(id)sender {
boredzo@2487
   893
#pragma unused(sender)
ofri@2936
   894
	[[NSWorkspace sharedWorkspace] openURL:growlWebSiteURL];
boredzo@2487
   895
}
boredzo@2487
   896
boredzo@2487
   897
- (IBAction) openGrowlForum:(id)sender {
boredzo@2487
   898
#pragma unused(sender)
ofri@2936
   899
	[[NSWorkspace sharedWorkspace] openURL:growlForumURL];
boredzo@2487
   900
}
boredzo@2487
   901
Peter@4188
   902
- (IBAction) openGrowlBugSubmissionPage:(id)sender {
boredzo@2487
   903
#pragma unused(sender)
Peter@4188
   904
	[[NSWorkspace sharedWorkspace] openURL:growlBugSubmissionURL];
boredzo@2487
   905
}
boredzo@2487
   906
tick@2934
   907
- (IBAction) openGrowlDonate:(id)sender {
tick@2934
   908
 #pragma unused(sender)
ofri@2936
   909
	[[NSWorkspace sharedWorkspace] openURL:growlDonateURL];
tick@2934
   910
}
boredzo@3952
   911
#pragma mark TableView data source methods
ingmarstein@2647
   912
rudy@3133
   913
- (int) numberOfRowsInTableView:(NSTableView*)tableView {
rudy@3133
   914
	if(tableView == networkTableView) {
rudy@3133
   915
		return [[self services] count];
rudy@3133
   916
	}
rudy@3133
   917
	return 0;
rudy@3133
   918
}
boredzo@2487
   919
- (void) tableViewDidClickInBody:(NSTableView *)tableView {
boredzo@2487
   920
	activeTableView = tableView;
boredzo@2487
   921
	[self setCanRemoveTicket:(activeTableView == growlApplications) && [ticketsArrayController canRemove]];
boredzo@2487
   922
}
boredzo@2487
   923
rudy@3133
   924
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex {
rudy@3133
   925
#pragma unused(aTableView)
rudy@3133
   926
	if(aTableColumn == servicePasswordColumn) {
rudy@3133
   927
		[[services objectAtIndex:rowIndex] setPassword:anObject];
rudy@3133
   928
	}
rudy@3133
   929
rudy@3133
   930
}
rudy@3133
   931
ingmarstein@3010
   932
- (id) tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex {
ingmarstein@2943
   933
#pragma unused(aTableView)
ingmarstein@3010
   934
	// we check to make sure we have the image + text column and then set its image manually
ingmarstein@2941
   935
	if (aTableColumn == applicationNameAndIconColumn) {
ingmarstein@3010
   936
		NSArray *arrangedTickets = [ticketsArrayController arrangedObjects];
ingmarstein@3010
   937
		unsigned idx = [tickets indexOfObject:[arrangedTickets objectAtIndex:rowIndex]];
ingmarstein@3010
   938
		[[aTableColumn dataCellForRow:rowIndex] setImage:(NSImage *)CFArrayGetValueAtIndex(images,idx)];
rudy@3133
   939
	} else if (aTableColumn == servicePasswordColumn) {
rudy@3133
   940
		return [[services objectAtIndex:rowIndex] password];
bgannin@2877
   941
	}
ingmarstein@2943
   942
evands@2928
   943
	return nil;
bgannin@2877
   944
}
bgannin@2877
   945
ingmarstein@2587
   946
- (IBAction) tableViewDoubleClick:(id)sender {
Peter@4189
   947
	[self showApplicationConfigurationTab:sender];
ingmarstein@2587
   948
}
ingmarstein@2587
   949
boredzo@2487
   950
#pragma mark NSNetServiceBrowser Delegate Methods
boredzo@2487
   951
boredzo@2487
   952
- (void) netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
boredzo@2487
   953
#pragma unused(aNetServiceBrowser)
boredzo@2487
   954
	// check if a computer with this name has already been added
boredzo@2487
   955
	NSString *name = [aNetService name];
boredzo@2487
   956
	NSEnumerator *enumerator = [services objectEnumerator];
boredzo@2487
   957
	GrowlBrowserEntry *entry;
ingmarstein@2661
   958
	while ((entry = [enumerator nextObject])) {
ingmarstein@2661
   959
		if ([[entry computerName] isEqualToString:name]) {
ingmarstein@2661
   960
			[entry setActive:YES];
boredzo@2487
   961
			return;
ingmarstein@2661
   962
		}
ingmarstein@2661
   963
	}
boredzo@2487
   964
boredzo@2487
   965
	// don't add the local machine
ingmarstein@2634
   966
	CFStringRef localHostName = SCDynamicStoreCopyComputerName(/*store*/ NULL,
ingmarstein@2634
   967
															   /*nameEncoding*/ NULL);
ingmarstein@2634
   968
	CFComparisonResult isLocalHost = CFStringCompare(localHostName, (CFStringRef)name, 0);
ingmarstein@2634
   969
	CFRelease(localHostName);
ingmarstein@2634
   970
	if (isLocalHost == kCFCompareEqualTo)
boredzo@2487
   971
		return;
boredzo@2487
   972
boredzo@2487
   973
	// add a new entry at the end
evands@4153
   974
	entry = [[GrowlBrowserEntry alloc] initWithComputerName:name];
boredzo@2487
   975
	[self willChangeValueForKey:@"services"];
boredzo@2487
   976
	[services addObject:entry];
boredzo@2487
   977
	[self didChangeValueForKey:@"services"];
boredzo@2487
   978
	[entry release];
boredzo@2487
   979
boredzo@2487
   980
	if (!moreComing)
boredzo@2487
   981
		[self writeForwardDestinations];
boredzo@2487
   982
}
boredzo@2487
   983
boredzo@2487
   984
- (void) netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
boredzo@2487
   985
#pragma unused(aNetServiceBrowser)
ingmarstein@2661
   986
	NSEnumerator *serviceEnum = [services objectEnumerator];
boredzo@2487
   987
	GrowlBrowserEntry *currentEntry;
boredzo@2487
   988
	NSString *name = [aNetService name];
boredzo@2487
   989
ingmarstein@2661
   990
	while ((currentEntry = [serviceEnum nextObject])) {
boredzo@2487
   991
		if ([[currentEntry computerName] isEqualToString:name]) {
ingmarstein@2661
   992
			[currentEntry setActive:NO];
boredzo@2487
   993
			break;
boredzo@2487
   994
		}
boredzo@2487
   995
	}
boredzo@2487
   996
boredzo@2487
   997
	if (!moreComing)
boredzo@2487
   998
		[self writeForwardDestinations];
boredzo@2487
   999
}
boredzo@2487
  1000
ingmarstein@2517
  1001
#pragma mark Bonjour
ingmarstein@2517
  1002
ingmarstein@2517
  1003
- (void) resolveService:(id)sender {
evands@4153
  1004
	NSLog(@"What calls resolveService:?");
ingmarstein@2517
  1005
}
ingmarstein@2517
  1006
ingmarstein@2517
  1007
- (NSMutableArray *) services {
ingmarstein@2517
  1008
	return services;
ingmarstein@2517
  1009
}
ingmarstein@2517
  1010
ingmarstein@2517
  1011
- (void) setServices:(NSMutableArray *)theServices {
ingmarstein@2517
  1012
	if (theServices != services) {
ingmarstein@2661
  1013
		if (theServices) {
ingmarstein@2661
  1014
			if (services)
ingmarstein@2661
  1015
				[services setArray:theServices];
ingmarstein@2661
  1016
			else
ingmarstein@2661
  1017
				services = [theServices retain];
ingmarstein@2661
  1018
		} else {
ingmarstein@2661
  1019
			[services release];
ingmarstein@2661
  1020
			services = nil;
ingmarstein@2661
  1021
		}
ingmarstein@2517
  1022
	}
ingmarstein@2517
  1023
}
ingmarstein@2517
  1024
ingmarstein@2517
  1025
- (unsigned) countOfServices {
ingmarstein@2517
  1026
	return [services count];
ingmarstein@2517
  1027
}
ingmarstein@2517
  1028
ingmarstein@2517
  1029
- (id) objectInServicesAtIndex:(unsigned)idx {
ingmarstein@2517
  1030
	return [services objectAtIndex:idx];
ingmarstein@2517
  1031
}
ingmarstein@2517
  1032
ingmarstein@2517
  1033
- (void) insertObject:(id)anObject inServicesAtIndex:(unsigned)idx {
ingmarstein@2517
  1034
	[services insertObject:anObject atIndex:idx];
ingmarstein@2517
  1035
}
ingmarstein@2517
  1036
ingmarstein@2517
  1037
- (void) replaceObjectInServicesAtIndex:(unsigned)idx withObject:(id)anObject {
ingmarstein@2517
  1038
	[services replaceObjectAtIndex:idx withObject:anObject];
ingmarstein@2517
  1039
}
ingmarstein@2517
  1040
boredzo@2487
  1041
#pragma mark Detecting Growl
boredzo@2487
  1042
boredzo@2487
  1043
- (void) checkGrowlRunning {
ingmarstein@2540
  1044
	[self setGrowlIsRunning:[preferencesController isGrowlRunning]];
boredzo@2487
  1045
	[self updateRunningStatus];
boredzo@2487
  1046
}
boredzo@2487
  1047
ingmarstein@2517
  1048
#pragma mark "Display Options" tab pane
ingmarstein@2517
  1049
ingmarstein@2517
  1050
- (NSArray *) displayPlugins {
ingmarstein@2517
  1051
	return plugins;
ingmarstein@2517
  1052
}
ingmarstein@2517
  1053
ingmarstein@2517
  1054
- (void) setDisplayPlugins:(NSArray *)thePlugins {
ingmarstein@2572
  1055
	if (thePlugins != plugins) {
ingmarstein@2572
  1056
		[plugins release];
ingmarstein@2572
  1057
		plugins = [thePlugins retain];
ingmarstein@2572
  1058
	}
ingmarstein@2517
  1059
}
ingmarstein@2517
  1060
Peter@4189
  1061
#pragma mark Display pop-up menus
Peter@4189
  1062
Peter@4189
  1063
//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
  1064
- (void) populateDisplaysPopUpButton:(NSPopUpButton *)popUp nameOfSelectedDisplay:(NSString *)nameOfSelectedDisplay includeDefaultMenuItem:(BOOL)includeDefault {
Peter@4189
  1065
	NSMenu *menu = [popUp menu];
Peter@4189
  1066
	NSString *nameOfDisplay = nil;
Peter@4189
  1067
Peter@4189
  1068
	NSMenuItem *selectedItem = nil;
Peter@4189
  1069
Peter@4189
  1070
	[popUp removeAllItems];
Peter@4189
  1071
Peter@4189
  1072
	if (includeDefault) {
Peter@4189
  1073
		NSMenuItem *item = [menu addItemWithTitle:NSLocalizedStringFromTableInBundle(@"Default", nil, [NSBundle bundleForClass:[self class]], /*comment*/ @"Title of menu item for default display")
Peter@4189
  1074
										   action:NULL
Peter@4189
  1075
									keyEquivalent:@""];
Peter@4189
  1076
		[item setRepresentedObject:nil];
Peter@4189
  1077
Peter@4189
  1078
		if (!nameOfSelectedDisplay)
Peter@4189
  1079
			selectedItem = item;
Peter@4189
  1080
Peter@4189
  1081
		[menu addItem:[NSMenuItem separatorItem]];
Peter@4189
  1082
	}
Peter@4189
  1083
Peter@4189
  1084
	NSEnumerator *displaysEnum = [[plugins sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectEnumerator];
Peter@4189
  1085
	while ((nameOfDisplay = [displaysEnum nextObject])) {
Peter@4189
  1086
		NSMenuItem *item = [menu addItemWithTitle:nameOfDisplay
Peter@4189
  1087
										   action:NULL
Peter@4189
  1088
									keyEquivalent:@""];
Peter@4189
  1089
		[item setRepresentedObject:nameOfDisplay];
Peter@4189
  1090
Peter@4189
  1091
		if (nameOfSelectedDisplay && [nameOfSelectedDisplay respondsToSelector:@selector(isEqualToString:)] && [nameOfSelectedDisplay isEqualToString:nameOfDisplay])
Peter@4189
  1092
			selectedItem = item;
Peter@4189
  1093
	}
Peter@4189
  1094
Peter@4189
  1095
	[popUp selectItem:selectedItem];
Peter@4189
  1096
}
Peter@4189
  1097
boredzo@2487
  1098
#pragma mark -
boredzo@2487
  1099
ingmarstein@2572
  1100
/*!
ingmarstein@2572
  1101
 * @brief Refresh preferences when a new application registers with Growl
ingmarstein@2572
  1102
 */
boredzo@2487
  1103
- (void) appRegistered: (NSNotification *) note {
evands@4016
  1104
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4016
  1105
boredzo@2487
  1106
	NSString *app = [note object];
boredzo@2487
  1107
	GrowlApplicationTicket *newTicket = [[GrowlApplicationTicket alloc] initTicketForApplication:app];
boredzo@2487
  1108
boredzo@2487
  1109
	/*
boredzo@2487
  1110
	 *	Because the tickets array is under KVObservation by the TicketsArrayController
boredzo@2487
  1111
	 *	We need to remove the ticket using the correct KVC method:
boredzo@2487
  1112
	 */
boredzo@2487
  1113
boredzo@2487
  1114
	NSEnumerator *ticketEnumerator = [tickets objectEnumerator];
boredzo@2487
  1115
	GrowlApplicationTicket *ticket;
boredzo@2487
  1116
	int removalIndex = -1;
boredzo@2487
  1117
ingmarstein@2540
  1118
	int	i = 0;
boredzo@2487
  1119
	while ((ticket = [ticketEnumerator nextObject])) {
boredzo@2487
  1120
		if ([[ticket applicationName] isEqualToString:app]) {
boredzo@2487
  1121
			removalIndex = i;
boredzo@2487
  1122
			break;
boredzo@2487
  1123
		}
ingmarstein@2540
  1124
		++i;
boredzo@2487
  1125
	}
boredzo@2487
  1126
boredzo@2487
  1127
	if (removalIndex != -1)
boredzo@2487
  1128
		[self removeFromTicketsAtIndex:removalIndex];
boredzo@2487
  1129
	[self insertInTickets:newTicket];
boredzo@2487
  1130
	[newTicket release];
boredzo@2487
  1131
boredzo@2487
  1132
	[self cacheImages];
evands@4016
  1133
	
evands@4016
  1134
	[pool release];
boredzo@2487
  1135
}
boredzo@2487
  1136
boredzo@2487
  1137
- (void) growlLaunched:(NSNotification *)note {
boredzo@2487
  1138
#pragma unused(note)
evands@4016
  1139
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4016
  1140
boredzo@2487
  1141
	[self setGrowlIsRunning:YES];
boredzo@2487
  1142
	[self updateRunningStatus];
evands@4016
  1143
	
evands@4016
  1144
	[pool release];
boredzo@2487
  1145
}
boredzo@2487
  1146
boredzo@2487
  1147
- (void) growlTerminated:(NSNotification *)note {
boredzo@2487
  1148
#pragma unused(note)
evands@4016
  1149
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4016
  1150
boredzo@2487
  1151
	[self setGrowlIsRunning:NO];
boredzo@2487
  1152
	[self updateRunningStatus];
evands@4016
  1153
	
evands@4016
  1154
	[pool release];
boredzo@2487
  1155
}
boredzo@2487
  1156
boredzo@2487
  1157
@end