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