//
// $Id$
//
// SPPreferenceController.m
// sequel-pro
//
// Created by Stuart Connolly (stuconnolly.com) on Dec 10, 2008
// Modified by Ben Perry (benperry.com.au) on Mar 28, 2009
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// More info at
#import "SPPreferenceController.h"
#import "SPWindowAdditions.h"
#import "SPFavoriteTextFieldCell.h"
#import "KeyChain.h"
#import "TableDocument.h"
#define FAVORITES_PB_DRAG_TYPE @"SequelProPreferencesPasteboard"
#define PREFERENCE_TOOLBAR_GENERAL @"Preference Toolbar General"
#define PREFERENCE_TOOLBAR_TABLES @"Preference Toolbar Tables"
#define PREFERENCE_TOOLBAR_FAVORITES @"Preference Toolbar Favorites"
#define PREFERENCE_TOOLBAR_NOTIFICATIONS @"Preference Toolbar Notifications"
#define PREFERENCE_TOOLBAR_AUTOUPDATE @"Preference Toolbar Auto Update"
#define PREFERENCE_TOOLBAR_NETWORK @"Preference Toolbar Network"
#define PREFERENCE_TOOLBAR_EDITOR @"Preference Toolbar Editor"
#define PREFERENCE_TOOLBAR_SHORTCUTS @"Preference Toolbar Shortcuts"
#pragma mark -
@interface SPPreferenceController (PrivateAPI)
- (void)_setupToolbar;
- (void)_resizeWindowForContentView:(NSView *)view;
@end
#pragma mark -
@implementation SPPreferenceController
// -------------------------------------------------------------------------------
// init
// -------------------------------------------------------------------------------
- (id)init
{
if (self = [super initWithWindowNibName:@"Preferences"]) {
prefs = [NSUserDefaults standardUserDefaults];
[self applyRevisionChanges];
currentFavorite = nil;
keychain = nil;
}
return self;
}
// -------------------------------------------------------------------------------
// windowDidLoad
// -------------------------------------------------------------------------------
- (void)windowDidLoad
{
[self _setupToolbar];
keychain = [[KeyChain alloc] init];
SPFavoriteTextFieldCell *tableCell = [[[SPFavoriteTextFieldCell alloc] init] autorelease];
[tableCell setImage:[NSImage imageNamed:@"database"]];
// Replace column's NSTextFieldCell with custom SWProfileTextFieldCell
[[[favoritesTableView tableColumns] objectAtIndex:0] setDataCell:tableCell];
[favoritesTableView registerForDraggedTypes:[NSArray arrayWithObject:FAVORITES_PB_DRAG_TYPE]];
[favoritesTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
[favoritesTableView reloadData];
// Hide the tabs on the favorites tab view - left visible in IB for easy use
[favoritesTabView setTabViewType:NSNoTabsNoBorder];
[self updateDefaultFavoritePopup];
[prefs synchronize];
}
#pragma mark -
#pragma mark Preferences upgrade routine
// -------------------------------------------------------------------------------
// applyRevisionChanges
// Checks the revision number, applies any preference upgrades, and updates to
// latest revision.
// Currently uses both lastUsedVersion and LastUsedVersion for <0.9.5 compatibility.
// -------------------------------------------------------------------------------
- (void)applyRevisionChanges
{
int currentVersionNumber, recordedVersionNumber = 0;
// Get the current bundle version number (the SVN build number) for per-version upgrades
currentVersionNumber = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] intValue];
// Get the current revision
if ([prefs objectForKey:@"lastUsedVersion"]) recordedVersionNumber = [[prefs objectForKey:@"lastUsedVersion"] intValue];
if ([prefs objectForKey:@"LastUsedVersion"]) recordedVersionNumber = [[prefs objectForKey:@"LastUsedVersion"] intValue];
// Skip processing if the current version matches or is less than recorded version
if (currentVersionNumber <= recordedVersionNumber) return;
// If no recorded version, update to current revision and skip processing
if (!recordedVersionNumber) {
[prefs setObject:[NSNumber numberWithInt:currentVersionNumber] forKey:@"LastUsedVersion"];
return;
}
// For versions prior to r336 (0.9.4), where column widths have been saved, walk through them and remove
// any table widths set to 15 or less (fix for mangled columns caused by Issue #140)
if (recordedVersionNumber < 336 && [prefs objectForKey:@"tableColumnWidths"] != nil) {
NSEnumerator *databaseEnumerator, *tableEnumerator, *columnEnumerator;
NSString *databaseKey, *tableKey, *columnKey;
NSMutableDictionary *newDatabase, *newTable;
float columnWidth;
NSMutableDictionary *newTableColumnWidths = [[NSMutableDictionary alloc] init];
databaseEnumerator = [[prefs objectForKey:@"tableColumnWidths"] keyEnumerator];
while (databaseKey = [databaseEnumerator nextObject]) {
newDatabase = [[NSMutableDictionary alloc] init];
tableEnumerator = [[[prefs objectForKey:@"tableColumnWidths"] objectForKey:databaseKey] keyEnumerator];
while (tableKey = [tableEnumerator nextObject]) {
newTable = [[NSMutableDictionary alloc] init];
columnEnumerator = [[[[prefs objectForKey:@"tableColumnWidths"] objectForKey:databaseKey] objectForKey:tableKey] keyEnumerator];
while (columnKey = [columnEnumerator nextObject]) {
columnWidth = [[[[[prefs objectForKey:@"tableColumnWidths"] objectForKey:databaseKey] objectForKey:tableKey] objectForKey:columnKey] floatValue];
if (columnWidth >= 15) {
[newTable setObject:[NSNumber numberWithFloat:columnWidth] forKey:[NSString stringWithString:columnKey]];
}
}
if ([newTable count]) {
[newDatabase setObject:[NSDictionary dictionaryWithDictionary:newTable] forKey:[NSString stringWithString:tableKey]];
}
[newTable release];
}
if ([newDatabase count]) {
[newTableColumnWidths setObject:[NSDictionary dictionaryWithDictionary:newDatabase] forKey:[NSString stringWithString:databaseKey]];
}
[newDatabase release];
}
[prefs setObject:[NSDictionary dictionaryWithDictionary:newTableColumnWidths] forKey:@"tableColumnWidths"];
[newTableColumnWidths release];
}
// For versions prior to r561 (0.9.5), migrate old pref keys where they exist to the new pref keys
if (recordedVersionNumber < 561) {
NSEnumerator *keyEnumerator;
NSString *oldKey, *newKey;
NSDictionary *keysToUpgrade = [NSDictionary dictionaryWithObjectsAndKeys:
@"encoding", @"DefaultEncoding",
@"useMonospacedFonts", @"UseMonospacedFonts",
@"reloadAfterAdding", @"ReloadAfterAddingRow",
@"reloadAfterEditing", @"ReloadAfterEditingRow",
@"reloadAfterRemoving", @"ReloadAfterRemovingRow",
@"dontShowBlob", @"LoadBlobsAsNeeded",
@"fetchRowCount", @"FetchCorrectRowCount",
@"limitRows", @"LimitResults",
@"limitRowsValue", @"LimitResultsValue",
@"nullValue", @"NullValue",
@"showError", @"ShowNoAffectedRowsError",
@"connectionTimeout", @"ConnectionTimeoutValue",
@"keepAliveInterval", @"KeepAliveInterval",
@"lastFavoriteIndex", @"LastFavoriteIndex",
nil];
keyEnumerator = [keysToUpgrade keyEnumerator];
while (newKey = [keyEnumerator nextObject]) {
oldKey = [keysToUpgrade objectForKey:newKey];
if ([prefs objectForKey:oldKey]) {
[prefs setObject:[prefs objectForKey:oldKey] forKey:newKey];
[prefs removeObjectForKey:oldKey];
}
}
// Remove outdated keys
[prefs removeObjectForKey:@"lastUsedVersion"];
[prefs removeObjectForKey:@"version"];
}
// For versions prior to r567 (0.9.5), add a timestamp-based identifier to favorites and keychain entries
if (recordedVersionNumber < 567 && [prefs objectForKey:@"favorites"]) {
int i;
NSMutableArray *favoritesArray = [NSMutableArray arrayWithArray:[prefs objectForKey:@"favorites"]];
NSMutableDictionary *favorite;
NSString *password, *keychainName, *keychainAccount;
KeyChain *upgradeKeychain = [[KeyChain alloc] init];
// Cycle through the favorites, generating a timestamp-derived ID for each and renaming associated keychain items.
for (i = 0; i < [favoritesArray count]; i++) {
favorite = [NSMutableDictionary dictionaryWithDictionary:[favoritesArray objectAtIndex:i]];
if ([favorite objectForKey:@"id"]) continue;
[favorite setObject:[NSNumber numberWithInt:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]] forKey:@"id"];
keychainName = [NSString stringWithFormat:@"Sequel Pro : %@", [favorite objectForKey:@"name"]];
keychainAccount = [NSString stringWithFormat:@"%@@%@/%@",
[favorite objectForKey:@"user"], [favorite objectForKey:@"host"], [favorite objectForKey:@"database"]];
password = [upgradeKeychain getPasswordForName:keychainName account:keychainAccount];
[upgradeKeychain deletePasswordForName:keychainName account:keychainAccount];
if (password && [password length]) {
keychainName = [NSString stringWithFormat:@"Sequel Pro : %@ (%i)", [favorite objectForKey:@"name"], [[favorite objectForKey:@"id"] intValue]];
[upgradeKeychain addPassword:password forName:keychainName account:keychainAccount];
}
[favoritesArray replaceObjectAtIndex:i withObject:[NSDictionary dictionaryWithDictionary:favorite]];
}
[prefs setObject:[NSArray arrayWithArray:favoritesArray] forKey:@"favorites"];
[upgradeKeychain release];
password = nil;
}
// For versions prior to r981 (~0.9.6), upgrade the favourites to include a connection type for each
if (recordedVersionNumber < 981 && [prefs objectForKey:@"favorites"]) {
int i;
NSMutableArray *favoritesArray = [NSMutableArray arrayWithArray:[prefs objectForKey:@"favorites"]];
NSMutableDictionary *favorite;
// Cycle through the favorites
for (i = 0; i < [favoritesArray count]; i++) {
favorite = [NSMutableDictionary dictionaryWithDictionary:[favoritesArray objectAtIndex:i]];
if ([favorite objectForKey:@"type"]) continue;
// If the favorite has a socket, or has the host set to "localhost", set to socket-type connection
if ([[favorite objectForKey:@"host"] isEqualToString:@"localhost"]
|| ([favorite objectForKey:@"socket"] && [[favorite objectForKey:@"socket"] length]))
{
[favorite setObject:[NSNumber numberWithInt:1] forKey:@"type"];
// If SSH details are set, set to tunnel connection
} else if ([favorite objectForKey:@"useSSH"] && [[favorite objectForKey:@"useSSH"] intValue]) {
[favorite setObject:[NSNumber numberWithInt:2] forKey:@"type"];
// Default to TCP/IP
} else {
[favorite setObject:[NSNumber numberWithInt:0] forKey:@"type"];
}
// Remove SSH tunnel flag - no longer required
[favorite removeObjectForKey:@"useSSH"];
[favoritesArray replaceObjectAtIndex:i withObject:[NSDictionary dictionaryWithDictionary:favorite]];
}
[prefs setObject:[NSArray arrayWithArray:favoritesArray] forKey:@"favorites"];
}
// Update the prefs revision
[prefs setObject:[NSNumber numberWithInt:currentVersionNumber] forKey:@"LastUsedVersion"];
}
#pragma mark -
#pragma mark IBAction methods
// -------------------------------------------------------------------------------
// addFavorite:
// -------------------------------------------------------------------------------
- (IBAction)addFavorite:(id)sender
{
NSNumber *favoriteid = [NSNumber numberWithInt:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]];
// Create default favorite
NSMutableDictionary *favorite = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"New Favorite", [NSNumber numberWithInt:0], @"", @"", @"", @"", @"", @"", @"", @"", favoriteid, nil]
forKeys:[NSArray arrayWithObjects:@"name", @"type", @"host", @"socket", @"user", @"port", @"database", @"sshHost", @"sshUser", @"sshPort", @"id", nil]];
[favoritesController addObject:favorite];
[favoritesController setSelectionIndex:[[favoritesController arrangedObjects] count]-1];
[favoritesTableView reloadData];
[favoritesTableView scrollRowToVisible:[favoritesTableView selectedRow]];
[self updateDefaultFavoritePopup];
}
// -------------------------------------------------------------------------------
// removeFavorite:
// -------------------------------------------------------------------------------
- (IBAction)removeFavorite:(id)sender
{
if ([favoritesTableView numberOfSelectedRows] == 1) {
// Get selected favorite's details
NSString *name = [favoritesController valueForKeyPath:@"selection.name"];
NSString *user = [favoritesController valueForKeyPath:@"selection.user"];
NSString *host = [favoritesController valueForKeyPath:@"selection.host"];
NSString *database = [favoritesController valueForKeyPath:@"selection.database"];
NSString *sshUser = [favoritesController valueForKeyPath:@"selection.sshUser"];
NSString *sshHost = [favoritesController valueForKeyPath:@"selection.sshHost"];
NSString *favoriteid = [favoritesController valueForKeyPath:@"selection.id"];
// Remove passwords from the Keychain
[keychain deletePasswordForName:[keychain nameForFavoriteName:name id:favoriteid]
account:[keychain accountForUser:user host:host database:database]];
[keychain deletePasswordForName:[keychain nameForSSHForFavoriteName:name id:favoriteid]
account:[keychain accountForSSHUser:sshUser sshHost:sshHost]];
// Reset last used favorite
if ([favoritesTableView selectedRow] == [prefs integerForKey:@"LastFavoriteIndex"]) {
[prefs setInteger:0 forKey:@"LastFavoriteIndex"];
}
// Reset default favorite
if ([favoritesTableView selectedRow] == [prefs integerForKey:@"DefaultFavorite"]) {
[prefs setInteger:[prefs integerForKey:@"LastFavoriteIndex"] forKey:@"DefaultFavorite"];
}
[favoritesController removeObjectAtArrangedObjectIndex:[favoritesTableView selectedRow]];
[favoritesTableView reloadData];
[self updateDefaultFavoritePopup];
}
}
// -------------------------------------------------------------------------------
// duplicateFavorite:
// -------------------------------------------------------------------------------
- (IBAction)duplicateFavorite:(id)sender
{
if ([favoritesTableView numberOfSelectedRows] == 1) {
NSString *keychainName, *keychainAccount, *password, *keychainSSHName, *keychainSSHAccount, *sshPassword;
NSMutableDictionary *favorite = [NSMutableDictionary dictionaryWithDictionary:[[favoritesController arrangedObjects] objectAtIndex:[favoritesTableView selectedRow]]];
NSNumber *favoriteid = [NSNumber numberWithInt:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]];
// Select the keychain passwords for duplication
keychainName = [keychain nameForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]];
keychainAccount = [keychain accountForUser:[favorite objectForKey:@"user"] host:[favorite objectForKey:@"host"] database:[favorite objectForKey:@"database"]];
password = [keychain getPasswordForName:keychainName account:keychainAccount];
keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]];
keychainSSHAccount = [keychain accountForSSHUser:[favorite objectForKey:@"sshUser"] sshHost:[favorite objectForKey:@"sshHost"]];
sshPassword = [keychain getPasswordForName:keychainSSHName account:keychainSSHAccount];
// Update the unique ID
[favorite setObject:favoriteid forKey:@"id"];
// Alter the name for clarity
[favorite setObject:[NSString stringWithFormat:@"%@ Copy", [favorite objectForKey:@"name"]] forKey:@"name"];
// Create new keychain items if appropriate
if (password && [password length]) {
keychainName = [keychain nameForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]];
[keychain addPassword:password forName:keychainName account:keychainAccount];
}
if (sshPassword && [sshPassword length]) {
keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:@"name"] id:[favorite objectForKey:@"id"]];
[keychain addPassword:sshPassword forName:keychainSSHName account:keychainSSHAccount];
}
password = nil, sshPassword = nil;
[favoritesController addObject:favorite];
[favoritesController setSelectionIndex:[[favoritesController arrangedObjects] count]-1];
[favoritesTableView reloadData];
[favoritesTableView scrollRowToVisible:[favoritesTableView selectedRow]];
[self updateDefaultFavoritePopup];
}
}
// -------------------------------------------------------------------------------
// saveFavorite:
// -------------------------------------------------------------------------------
- (IBAction)saveFavorite:(id)sender
{
}
// -------------------------------------------------------------------------------
// updateDefaultFavorite:
// -------------------------------------------------------------------------------
- (IBAction)updateDefaultFavorite:(id)sender
{
if ([defaultFavoritePopup indexOfSelectedItem] == 0) {
[prefs setBool:YES forKey:@"SelectLastFavoriteUsed"];
} else {
[prefs setBool:NO forKey:@"SelectLastFavoriteUsed"];
// Minus 2 from index to account for the "Last Used" and separator items
[prefs setInteger:[defaultFavoritePopup indexOfSelectedItem]-2 forKey:@"DefaultFavorite"];
}
}
#pragma mark -
#pragma mark Toolbar item IBAction methods
// -------------------------------------------------------------------------------
// displayGeneralPreferences:
// -------------------------------------------------------------------------------
- (IBAction)displayGeneralPreferences:(id)sender
{
[toolbar setSelectedItemIdentifier:PREFERENCE_TOOLBAR_GENERAL];
[self _resizeWindowForContentView:generalView];
}
// -------------------------------------------------------------------------------
// displayTablePreferences:
// -------------------------------------------------------------------------------
- (IBAction)displayTablePreferences:(id)sender
{
[toolbar setSelectedItemIdentifier:PREFERENCE_TOOLBAR_TABLES];
[self _resizeWindowForContentView:tablesView];
}
// -------------------------------------------------------------------------------
// displayEditorPreferences:
// -------------------------------------------------------------------------------
- (IBAction)displayEditorPreferences:(id)sender
{
[toolbar setSelectedItemIdentifier:PREFERENCE_TOOLBAR_EDITOR];
NSFont *nf = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorFont"]];
[editorFontName setStringValue:[NSString stringWithFormat:@"%@, %.1f pt", [nf displayName], [nf pointSize]]];
[self _resizeWindowForContentView:editorView];
}
// -------------------------------------------------------------------------------
// displayFavoritePreferences:
// -------------------------------------------------------------------------------
- (IBAction)displayFavoritePreferences:(id)sender
{
[toolbar setSelectedItemIdentifier:PREFERENCE_TOOLBAR_FAVORITES];
[self _resizeWindowForContentView:favoritesView];
// Set the default favorite popup back to preference
if (sender == [defaultFavoritePopup lastItem]) {
if (![prefs boolForKey:@"SelectLastFavoriteUsed"]) {
[defaultFavoritePopup selectItemAtIndex:[prefs integerForKey:@"DefaultFavorite"]+2];
} else {
[defaultFavoritePopup selectItemAtIndex:0];
}
}
}
// -------------------------------------------------------------------------------
// displayNotificationPreferences:
// -------------------------------------------------------------------------------
- (IBAction)displayNotificationPreferences:(id)sender
{
[toolbar setSelectedItemIdentifier:PREFERENCE_TOOLBAR_NOTIFICATIONS];
[self _resizeWindowForContentView:notificationsView];
}
// -------------------------------------------------------------------------------
// displayAutoUpdatePreferences:
// -------------------------------------------------------------------------------
- (IBAction)displayAutoUpdatePreferences:(id)sender
{
[toolbar setSelectedItemIdentifier:PREFERENCE_TOOLBAR_AUTOUPDATE];
[self _resizeWindowForContentView:autoUpdateView];
}
// -------------------------------------------------------------------------------
// displayNetworkPreferences:
// -------------------------------------------------------------------------------
- (IBAction)displayNetworkPreferences:(id)sender
{
[toolbar setSelectedItemIdentifier:PREFERENCE_TOOLBAR_NETWORK];
[self _resizeWindowForContentView:networkView];
}
#pragma mark -
#pragma mark TableView datasource methods
// -------------------------------------------------------------------------------
// numberOfRowsInTableView:
// -------------------------------------------------------------------------------
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [[favoritesController arrangedObjects] count];
}
// -------------------------------------------------------------------------------
// tableView:objectValueForTableColumn:row:
// -------------------------------------------------------------------------------
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
return [[[favoritesController arrangedObjects] objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]];
}
#pragma mark -
#pragma mark TableView drag & drop datasource methods
// -------------------------------------------------------------------------------
// tableView:writeRows:toPasteboard:
// -------------------------------------------------------------------------------
- (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray *)rows toPasteboard:(NSPasteboard *)pboard
{
int originalRow;
NSArray *pboardTypes;
if ([rows count] == 1) {
pboardTypes = [NSArray arrayWithObject:FAVORITES_PB_DRAG_TYPE];
originalRow = [[rows objectAtIndex:0] intValue];
[pboard declareTypes:pboardTypes owner:nil];
[pboard setString:[[NSNumber numberWithInt:originalRow] stringValue] forType:FAVORITES_PB_DRAG_TYPE];
return YES;
}
else {
return NO;
}
}
// -------------------------------------------------------------------------------
// tableView:validateDrop:proposedRow:proposedDropOperation:
// -------------------------------------------------------------------------------
- (NSDragOperation)tableView:(NSTableView *)tv validateDrop:(id )info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
{
int originalRow;
NSArray *pboardTypes = [[info draggingPasteboard] types];
if (([pboardTypes count] > 1) && (row != -1)) {
if (([pboardTypes containsObject:FAVORITES_PB_DRAG_TYPE]) && (operation == NSTableViewDropAbove)) {
originalRow = [[[info draggingPasteboard] stringForType:FAVORITES_PB_DRAG_TYPE] intValue];
if ((row != originalRow) && (row != (originalRow + 1))) {
return NSDragOperationMove;
}
}
}
return NSDragOperationNone;
}
// -------------------------------------------------------------------------------
// tableView:acceptDrop:row:dropOperation:
// -------------------------------------------------------------------------------
- (BOOL)tableView:(NSTableView *)tv acceptDrop:(id )info row:(int)row dropOperation:(NSTableViewDropOperation)operation
{
int originalRow;
int destinationRow;
int lastFavoriteIndexCached;
NSMutableDictionary *draggedRow;
originalRow = [[[info draggingPasteboard] stringForType:FAVORITES_PB_DRAG_TYPE] intValue];
destinationRow = row;
if (destinationRow > originalRow) {
destinationRow--;
}
draggedRow = [NSMutableDictionary dictionaryWithDictionary:[[favoritesController arrangedObjects] objectAtIndex:originalRow]];
//Before deleting this favorite, we need to save the current index.
//because removeObjectAtArrangedObjectIndex will set prefs LastFavoriteIndex to 0
lastFavoriteIndexCached = [prefs integerForKey:@"LastFavoriteIndex"];
[favoritesController removeObjectAtArrangedObjectIndex:originalRow];
[favoritesController insertObject:draggedRow atArrangedObjectIndex:destinationRow];
[favoritesTableView reloadData];
[favoritesTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRow] byExtendingSelection:NO];
// Update default favorite to take on new value
if (lastFavoriteIndexCached == originalRow) {
[prefs setInteger:destinationRow forKey:@"LastFavoriteIndex"];
}
// Update default favorite to take on new value
if ([prefs integerForKey:@"DefaultFavorite"] == originalRow) {
[prefs setInteger:destinationRow forKey:@"DefaultFavorite"];
}
[self updateDefaultFavoritePopup];
return YES;
}
#pragma mark -
#pragma mark TableView delegate methods
// -------------------------------------------------------------------------------
// tableView:willDisplayCell:forTableColumn:row:
// -------------------------------------------------------------------------------
- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)index
{
if ([cell isKindOfClass:[SPFavoriteTextFieldCell class]]) {
[cell setFavoriteName:[[[favoritesController arrangedObjects] objectAtIndex:index] objectForKey:@"name"]];
[cell setFavoriteHost:[[[favoritesController arrangedObjects] objectAtIndex:index] objectForKey:@"host"]];
}
}
// -------------------------------------------------------------------------------
// tableViewSelectionDidChange:
// -------------------------------------------------------------------------------
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
if ([[favoritesTableView selectedRowIndexes] count] > 0) {
[favoritesController setSelectionIndexes:[favoritesTableView selectedRowIndexes]];
}
// If no selection is present, blank the password fields (which can't use bindings)
if ([[favoritesTableView selectedRowIndexes] count] == 0) {
[standardPasswordField setStringValue:@""];
[socketPasswordField setStringValue:@""];
[sshSQLPasswordField setStringValue:@""];
[sshPasswordField setStringValue:@""];
return;
}
// Keep a copy of the favorite as it currently stands
if (currentFavorite) [currentFavorite release];
currentFavorite = [[[favoritesController selectedObjects] objectAtIndex:0] copy];
// Retrieve and set the password.
NSString *keychainName = [keychain nameForFavoriteName:[currentFavorite objectForKey:@"name"] id:[currentFavorite objectForKey:@"id"]];
NSString *keychainAccount = [keychain accountForUser:[currentFavorite objectForKey:@"user"] host:[currentFavorite objectForKey:@"host"] database:[currentFavorite objectForKey:@"database"]];
NSString *passwordValue = [keychain getPasswordForName:keychainName account:keychainAccount];
[standardPasswordField setStringValue:passwordValue];
[socketPasswordField setStringValue:passwordValue];
[sshSQLPasswordField setStringValue:passwordValue];
// Retrieve the SSH keychain password if appropriate.
NSString *keychainSSHName = [keychain nameForSSHForFavoriteName:[currentFavorite objectForKey:@"name"] id:[currentFavorite objectForKey:@"id"]];
NSString *keychainSSHAccount = [keychain accountForSSHUser:[currentFavorite objectForKey:@"sshUser"] sshHost:[currentFavorite objectForKey:@"sshHost"]];
[sshPasswordField setStringValue:[keychain getPasswordForName:keychainSSHName account:keychainSSHAccount]];
}
#pragma mark -
#pragma mark Toolbar delegate methods
// -------------------------------------------------------------------------------
// toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
// -------------------------------------------------------------------------------
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
{
if ([itemIdentifier isEqualToString:PREFERENCE_TOOLBAR_GENERAL]) {
return generalItem;
}
else if ([itemIdentifier isEqualToString:PREFERENCE_TOOLBAR_TABLES]) {
return tablesItem;
}
else if ([itemIdentifier isEqualToString:PREFERENCE_TOOLBAR_FAVORITES]) {
return favoritesItem;
}
else if ([itemIdentifier isEqualToString:PREFERENCE_TOOLBAR_NOTIFICATIONS]) {
return notificationsItem;
}
else if ([itemIdentifier isEqualToString:PREFERENCE_TOOLBAR_AUTOUPDATE]) {
return autoUpdateItem;
}
else if ([itemIdentifier isEqualToString:PREFERENCE_TOOLBAR_NETWORK]) {
return networkItem;
}
else if ([itemIdentifier isEqualToString:PREFERENCE_TOOLBAR_EDITOR]) {
return editorItem;
}
else if ([itemIdentifier isEqualToString:PREFERENCE_TOOLBAR_SHORTCUTS]) {
return shortcutItem;
}
return [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
}
// -------------------------------------------------------------------------------
// toolbarAllowedItemIdentifiers:
// -------------------------------------------------------------------------------
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:PREFERENCE_TOOLBAR_GENERAL, PREFERENCE_TOOLBAR_TABLES, PREFERENCE_TOOLBAR_FAVORITES, PREFERENCE_TOOLBAR_NOTIFICATIONS, PREFERENCE_TOOLBAR_EDITOR, PREFERENCE_TOOLBAR_SHORTCUTS, PREFERENCE_TOOLBAR_AUTOUPDATE, PREFERENCE_TOOLBAR_NETWORK, nil];
}
// -------------------------------------------------------------------------------
// toolbarDefaultItemIdentifiers:
// -------------------------------------------------------------------------------
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:PREFERENCE_TOOLBAR_GENERAL, PREFERENCE_TOOLBAR_TABLES, PREFERENCE_TOOLBAR_FAVORITES, PREFERENCE_TOOLBAR_NOTIFICATIONS, PREFERENCE_TOOLBAR_EDITOR, PREFERENCE_TOOLBAR_SHORTCUTS, PREFERENCE_TOOLBAR_AUTOUPDATE, PREFERENCE_TOOLBAR_NETWORK, nil];
}
// -------------------------------------------------------------------------------
// toolbarSelectableItemIdentifiers:
// -------------------------------------------------------------------------------
- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:PREFERENCE_TOOLBAR_GENERAL, PREFERENCE_TOOLBAR_TABLES, PREFERENCE_TOOLBAR_FAVORITES, PREFERENCE_TOOLBAR_NOTIFICATIONS, PREFERENCE_TOOLBAR_EDITOR, PREFERENCE_TOOLBAR_SHORTCUTS, PREFERENCE_TOOLBAR_AUTOUPDATE, PREFERENCE_TOOLBAR_NETWORK, nil];
}
#pragma mark -
#pragma mark SplitView delegate methods
// -------------------------------------------------------------------------------
// splitView:constrainMaxCoordinate:ofSubviewAt:
// -------------------------------------------------------------------------------
- (float)splitView:(NSSplitView *)sender constrainMaxCoordinate:(float)proposedMax ofSubviewAt:(int)offset
{
return (proposedMax - 220);
}
// -------------------------------------------------------------------------------
// splitView:constrainMinCoordinate:ofSubviewAt:
// -------------------------------------------------------------------------------
- (float)splitView:(NSSplitView *)sender constrainMinCoordinate:(float)proposedMin ofSubviewAt:(int)offset
{
return (proposedMin + 100);
}
#pragma mark -
#pragma mark TextField delegate methods and type change action
// -------------------------------------------------------------------------------
// control:textShouldEndEditing:
// Trap editing end notifications and use them to update the keychain password
// appropriately when name, host, user, password or database changes.
// -------------------------------------------------------------------------------
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor
{
// Request a password refresh to keep keychain references in synch with favorites
[self updateFavoritePasswordsFromField:control];
// Proceed with editing
return YES;
}
// -------------------------------------------------------------------------------
// favoriteTypeDidChange:
// Update the favorite host when the type changes.
// -------------------------------------------------------------------------------
- (IBAction)favoriteTypeDidChange:(id)sender
{
if ([sender indexOfSelectedItem] == 1) { // Socket
[favoritesController setValue:@"localhost" forKeyPath:@"selection.host"];
} else if ([[favoritesController valueForKeyPath:@"selection.host"] isEqualToString:@"localhost"]) {
[favoritesController setValue:@"" forKeyPath:@"selection.host"];
}
// Request a password refresh to keep keychain references in synch with the favorites
[self updateFavoritePasswordsFromField:nil];
}
// -------------------------------------------------------------------------------
// updateFavoritePasswordsFromField:
// Check all fields used in the keychain names against the old values for that
// favorite, and update the keychain names to match if necessary.
// If an (optional) recognised password field is supplied, that field is assumed
// to have changed and is used to supply the new value.
// -------------------------------------------------------------------------------
- (void)updateFavoritePasswordsFromField:(NSControl *)passwordControl
{
if (!currentFavorite) return;
NSString *passwordValue;
NSString *oldKeychainName, *newKeychainName;
NSString *oldKeychainAccount, *newKeychainAccount;
// SQL passwords are indexed by name, host, user and database. If any of these
// have changed, or a standard password field has, alter the keychain item to match.
if (![[currentFavorite objectForKey:@"name"] isEqualToString:[favoritesController valueForKeyPath:@"selection.name"]]
|| ![[currentFavorite objectForKey:@"host"] isEqualToString:[favoritesController valueForKeyPath:@"selection.host"]]
|| ![[currentFavorite objectForKey:@"user"] isEqualToString:[favoritesController valueForKeyPath:@"selection.user"]]
|| ![[currentFavorite objectForKey:@"database"] isEqualToString:[favoritesController valueForKeyPath:@"selection.database"]]
|| passwordControl == standardPasswordField || passwordControl == socketPasswordField || passwordControl == sshSQLPasswordField)
{
// Determine the correct password field to read the password from, defaulting to standard
if (passwordControl == socketPasswordField) {
passwordValue = [socketPasswordField stringValue];
} else if (passwordControl == sshSQLPasswordField) {
passwordValue = [sshSQLPasswordField stringValue];
} else {
passwordValue = [standardPasswordField stringValue];
}
// Get the old keychain name and account strings
oldKeychainName = [keychain nameForFavoriteName:[currentFavorite objectForKey:@"name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
oldKeychainAccount = [keychain accountForUser:[currentFavorite objectForKey:@"user"] host:[currentFavorite objectForKey:@"host"] database:[currentFavorite objectForKey:@"database"]];
// Delete the old keychain item
[keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount];
// Set up the new keychain name and account strings
newKeychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
newKeychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:[favoritesController valueForKeyPath:@"selection.host"] database:[favoritesController valueForKeyPath:@"selection.database"]];
// Add the new keychain item if the password field has a value
if ([passwordValue length])
[keychain addPassword:passwordValue forName:newKeychainName account:newKeychainAccount];
// Synch password changes
[standardPasswordField setStringValue:passwordValue];
[socketPasswordField setStringValue:passwordValue];
[sshSQLPasswordField setStringValue:passwordValue];
passwordValue = @"";
}
// If SSH account/password details have changed, update the keychain to match
if (![[currentFavorite objectForKey:@"name"] isEqualToString:[favoritesController valueForKeyPath:@"selection.name"]]
|| ![[currentFavorite objectForKey:@"sshHost"] isEqualToString:[favoritesController valueForKeyPath:@"selection.sshHost"]]
|| ![[currentFavorite objectForKey:@"sshUser"] isEqualToString:[favoritesController valueForKeyPath:@"selection.sshUser"]]
|| passwordControl == sshPasswordField) {
// Get the old keychain name and account strings
oldKeychainName = [keychain nameForSSHForFavoriteName:[currentFavorite objectForKey:@"name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
oldKeychainAccount = [keychain accountForSSHUser:[currentFavorite objectForKey:@"sshUser"] sshHost:[currentFavorite objectForKey:@"sshHost"]];
// Delete the old keychain item
[keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount];
// Set up the new keychain name and account strings
newKeychainName = [keychain nameForSSHForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]];
newKeychainAccount = [keychain accountForSSHUser:[favoritesController valueForKeyPath:@"selection.sshUser"] sshHost:[favoritesController valueForKeyPath:@"selection.sshHost"]];
// Add the new keychain item if the password field has a value
if ([[sshPasswordField stringValue] length])
[keychain addPassword:[sshPasswordField stringValue] forName:newKeychainName account:newKeychainAccount];
}
// Update the current favorite
if (currentFavorite) [currentFavorite release], currentFavorite = nil;
if ([[favoritesTableView selectedRowIndexes] count] > 0)
currentFavorite = [[[favoritesController selectedObjects] objectAtIndex:0] copy];
}
#pragma mark -
#pragma mark Window delegate methods
// -------------------------------------------------------------------------------
// windowWillClose:
// Trap window close notifications and use them to ensure changes are saved.
// -------------------------------------------------------------------------------
- (void)windowWillClose:(NSNotification *)notification
{
// Mark the currently selected field in the window as having finished editing, to trigger saves.
if ([preferencesWindow firstResponder])
[preferencesWindow endEditingFor:[preferencesWindow firstResponder]];
}
#pragma mark -
#pragma mark Other
- (void)setGrowlEnabled:(BOOL)value
{
if (value) {
NSRunInformationalAlertPanel(
NSLocalizedString(@"growl_prefs_title", "Title for Growl Notifications Alert Dialog"),
NSLocalizedString(@"growl_prefs_msg", @"Message for Growl Notifications Alert Dialog"),
nil,
nil,
nil
);
}
[prefs setBool:value forKey:@"GrowlEnabled"];
}
- (BOOL)growlEnabled
{
return [prefs boolForKey:@"GrowlEnabled"];
}
// -------------------------------------------------------------------------------
// updateDefaultFavoritePopup:
//
// Build the default favorite popup button
// -------------------------------------------------------------------------------
- (void)updateDefaultFavoritePopup;
{
[defaultFavoritePopup removeAllItems];
// Use the last used favorite
[defaultFavoritePopup addItemWithTitle:@"Last Used"];
[[defaultFavoritePopup menu] addItem:[NSMenuItem separatorItem]];
// Load in current favorites
[defaultFavoritePopup addItemsWithTitles:[[favoritesController arrangedObjects] valueForKeyPath:@"name"]];
// Add item to switch to edit favorites pane
[[defaultFavoritePopup menu] addItem:[NSMenuItem separatorItem]];
[defaultFavoritePopup addItemWithTitle:@"Edit Favorites…"];
[[[defaultFavoritePopup menu] itemWithTitle:@"Edit Favorites…"] setAction:@selector(displayFavoritePreferences:)];
[[[defaultFavoritePopup menu] itemWithTitle:@"Edit Favorites…"] setTarget:self];
// Select the default favorite from prefs
if (![prefs boolForKey:@"SelectLastFavoriteUsed"]) {
[defaultFavoritePopup selectItemAtIndex:[prefs integerForKey:@"DefaultFavorite"] + 2];
} else {
[defaultFavoritePopup selectItemAtIndex:0];
}
}
// -------------------------------------------------------------------------------
// selectFavorite:
//
// Selects the specified favorite(s) in the favorites list
// -------------------------------------------------------------------------------
- (void)selectFavorites:(NSArray *)favorites
{
[favoritesController setSelectedObjects:favorites];
[favoritesTableView scrollRowToVisible:[favoritesController selectionIndex]];
}
// -------------------------------------------------------------------------------
// selectFavoriteAtIndex:
//
// Selects the favorite at the specified index in the favorites list
// -------------------------------------------------------------------------------
- (void)selectFavoriteAtIndex:(unsigned int)theIndex
{
[favoritesController setSelectionIndex:theIndex];
[favoritesTableView scrollRowToVisible:theIndex];
}
// -------------------------------------------------------------------------------
// query editor font selection
//
// -------------------------------------------------------------------------------
// show the font panel
- (IBAction)showCustomQueryFontPanel:(id)sender
{
[[NSFontPanel sharedFontPanel] setPanelFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorFont"]] isMultiple:NO];
[[NSFontPanel sharedFontPanel] makeKeyAndOrderFront:self];
}
// reset syntax highlighting colors
- (IBAction)setDefaultColors:(id)sender
{
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.000 green:0.455 blue:0.000 alpha:1.000]] forKey:@"CustomQueryEditorCommentColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.769 green:0.102 blue:0.086 alpha:1.000]] forKey:@"CustomQueryEditorQuoteColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.200 green:0.250 blue:1.000 alpha:1.000]] forKey:@"CustomQueryEditorSQLKeywordColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.000 green:0.000 blue:0.658 alpha:1.000]] forKey:@"CustomQueryEditorBacktickColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.506 green:0.263 blue:0.000 alpha:1.000]] forKey:@"CustomQueryEditorNumericColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.500 green:0.500 blue:0.500 alpha:1.000]] forKey:@"CustomQueryEditorVariableColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.950 green:0.950 blue:0.950 alpha:1.000]] forKey:@"CustomQueryEditorHighlightQueryColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor blackColor]] forKey:@"CustomQueryEditorTextColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor blackColor]] forKey:@"CustomQueryEditorCaretColor"];
[prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor whiteColor]] forKey:@"CustomQueryEditorBackgroundColor"];
}
// set font panel's valid modes
- (unsigned int)validModesForFontPanel:(NSFontPanel *)fontPanel
{
return (NSFontPanelAllModesMask ^ NSFontPanelAllEffectsModeMask);
}
// Action receiver for a font change in the font panel
- (void)changeFont:(id)sender
{
NSFont *nf = [[NSFontPanel sharedFontPanel] panelConvertFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorFont"]]];
[prefs setObject:[NSArchiver archivedDataWithRootObject:nf] forKey:@"CustomQueryEditorFont"];
[editorFontName setStringValue:[NSString stringWithFormat:@"%@, %.1f pt", [nf displayName], [nf pointSize]]];
}
// -------------------------------------------------------------------------------
// dealloc
// -------------------------------------------------------------------------------
- (void)dealloc
{
if (keychain) [keychain release], keychain = nil;
if (currentFavorite) [currentFavorite release];
[super dealloc];
}
@end
#pragma mark -
@implementation SPPreferenceController (PrivateAPI)
// -------------------------------------------------------------------------------
// _setupToolbar
//
// Constructs the preferences' window toolbar.
// -------------------------------------------------------------------------------
- (void)_setupToolbar
{
toolbar = [[[NSToolbar alloc] initWithIdentifier:@"Preference Toolbar"] autorelease];
// General preferences
generalItem = [[NSToolbarItem alloc] initWithItemIdentifier:PREFERENCE_TOOLBAR_GENERAL];
[generalItem setLabel:NSLocalizedString(@"General", @"")];
[generalItem setImage:[NSImage imageNamed:@"toolbar-preferences-general"]];
[generalItem setTarget:self];
[generalItem setAction:@selector(displayGeneralPreferences:)];
// Table preferences
tablesItem = [[NSToolbarItem alloc] initWithItemIdentifier:PREFERENCE_TOOLBAR_TABLES];
[tablesItem setLabel:NSLocalizedString(@"Tables", @"")];
[tablesItem setImage:[NSImage imageNamed:@"toolbar-preferences-tables"]];
[tablesItem setTarget:self];
[tablesItem setAction:@selector(displayTablePreferences:)];
// Favorite preferences
favoritesItem = [[NSToolbarItem alloc] initWithItemIdentifier:PREFERENCE_TOOLBAR_FAVORITES];
[favoritesItem setLabel:NSLocalizedString(@"Favorites", @"")];
[favoritesItem setImage:[NSImage imageNamed:@"toolbar-preferences-favorites"]];
[favoritesItem setTarget:self];
[favoritesItem setAction:@selector(displayFavoritePreferences:)];
// Notification preferences
notificationsItem = [[NSToolbarItem alloc] initWithItemIdentifier:PREFERENCE_TOOLBAR_NOTIFICATIONS];
[notificationsItem setLabel:NSLocalizedString(@"Alerts & Logs", @"")];
[notificationsItem setImage:[NSImage imageNamed:@"toolbar-preferences-notifications"]];
[notificationsItem setTarget:self];
[notificationsItem setAction:@selector(displayNotificationPreferences:)];
// Editor preferences
editorItem = [[NSToolbarItem alloc] initWithItemIdentifier:PREFERENCE_TOOLBAR_EDITOR];
[editorItem setLabel:NSLocalizedString(@"Query Editor", @"")];
[editorItem setImage:[NSImage imageNamed:@"toolbar-preferences-queryeditor"]];
[editorItem setTarget:self];
[editorItem setAction:@selector(displayEditorPreferences:)];
// Shortcut preferences
/*shortcutItem = [[NSToolbarItem alloc] initWithItemIdentifier:PREFERENCE_TOOLBAR_SHORTCUTS];
[shortcutItem setLabel:NSLocalizedString(@"Shortcuts", @"")];
[shortcutItem setImage:[NSImage imageNamed:@"toolbar-preferences-shortcuts"]];
[shortcutItem setTarget:self];
[shortcutItem setAction:@selector(NSBeep)];*/
// AutoUpdate preferences
autoUpdateItem = [[NSToolbarItem alloc] initWithItemIdentifier:PREFERENCE_TOOLBAR_AUTOUPDATE];
[autoUpdateItem setLabel:NSLocalizedString(@"Auto Update", @"")];
[autoUpdateItem setImage:[NSImage imageNamed:@"toolbar-preferences-autoupdate"]];
[autoUpdateItem setTarget:self];
[autoUpdateItem setAction:@selector(displayAutoUpdatePreferences:)];
// Network preferences
networkItem = [[NSToolbarItem alloc] initWithItemIdentifier:PREFERENCE_TOOLBAR_NETWORK];
[networkItem setLabel:NSLocalizedString(@"Network", @"")];
[networkItem setImage:[NSImage imageNamed:@"toolbar-preferences-network"]];
[networkItem setTarget:self];
[networkItem setAction:@selector(displayNetworkPreferences:)];
[toolbar setDelegate:self];
[toolbar setSelectedItemIdentifier:PREFERENCE_TOOLBAR_GENERAL];
[toolbar setAllowsUserCustomization:NO];
[preferencesWindow setToolbar:toolbar];
[preferencesWindow setShowsToolbarButton:NO];
[self displayGeneralPreferences:nil];
}
// -------------------------------------------------------------------------------
// _resizeWindowForContentView:
//
// Resizes the window to the size of the supplied view.
// -------------------------------------------------------------------------------
- (void)_resizeWindowForContentView:(NSView *)view
{
// remove all current views
NSEnumerator *en = [[[preferencesWindow contentView] subviews] objectEnumerator];
NSView *subview;
while (subview = [en nextObject])
{
[subview removeFromSuperview];
}
// resize window
[preferencesWindow resizeForContentView:view titleBarVisible:YES];
// add view
[[preferencesWindow contentView] addSubview:view];
[view setFrameOrigin:NSMakePoint(0, 0)];
}
@end