// // $Id$ // // SPEditorPreferencePane.m // sequel-pro // // Created by Stuart Connolly (stuconnolly.com) on October 31, 2010 // Copyright (c) 2010 Stuart Connolly. All rights reserved. // // 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 <http://code.google.com/p/sequel-pro/> #import "SPEditorPreferencePane.h" #import "SPPreferenceController.h" #import "SPColorWellCell.h" #import "SPAlertSheets.h" #import "SPCategoryAdditions.h" // Constants static NSString *SPImportColorScheme = @"ImportColorScheme"; static NSString *SPExportColorScheme = @"ExportColorScheme"; static NSString *SPSaveColorScheme = @"SaveColorScheme"; static NSString *SPDefaultColorSchemeName = @"Default"; static NSString *SPDefaultColorSchemeNameLC = @"default"; static NSString *SPCustomColorSchemeName = @"User-defined"; static NSString *SPCustomColorSchemeNameLC = @"user-defined"; #define SP_EXPORT_COLOR_SCHEME_NAME_STRING NSLocalizedString(@"MyTheme",@"Preferences : Themes : Initial filename for 'Export'") @interface SPEditorPreferencePane (PrivateAPI) - (BOOL)_checkForUnsavedTheme; - (NSArray *)_getAvailableThemes; - (void)_saveColorThemeAtPath:(NSString *)path; - (BOOL)_loadColorSchemeFromFile:(NSString *)filename; @end @implementation SPEditorPreferencePane #pragma mark - #pragma mark Initialisation /** * Init. */ - (id)init { if ((self = [super init])) { themePath = [[[NSFileManager defaultManager] applicationSupportDirectoryForSubDirectory:SPThemesSupportFolder error:nil] retain]; editThemeListItems = [[NSArray arrayWithArray:[self _getAvailableThemes]] retain]; editorColors = [[NSArray arrayWithObjects: SPCustomQueryEditorTextColor, SPCustomQueryEditorBackgroundColor, SPCustomQueryEditorCaretColor, SPCustomQueryEditorCommentColor, SPCustomQueryEditorSQLKeywordColor, SPCustomQueryEditorNumericColor, SPCustomQueryEditorQuoteColor, SPCustomQueryEditorBacktickColor, SPCustomQueryEditorVariableColor, SPCustomQueryEditorHighlightQueryColor, SPCustomQueryEditorSelectionColor, nil] retain]; editorNameForColors = [[NSArray arrayWithObjects: NSLocalizedString(@"Text", @"text label for color table (Prefs > Editor)"), NSLocalizedString(@"Background", @"background label for color table (Prefs > Editor)"), NSLocalizedString(@"Caret", @"caret label for color table (Prefs > Editor)"), NSLocalizedString(@"Comment", @"comment label"), NSLocalizedString(@"Keyword", @"keyword label for color table (Prefs > Editor)"), NSLocalizedString(@"Numeric", @"numeric label for color table (Prefs > Editor)"), NSLocalizedString(@"Quote", @"quote label for color table (Prefs > Editor)"), NSLocalizedString(@"Backtick Quote", @"backtick quote label for color table (Prefs > Editor)"), NSLocalizedString(@"Variable", @"variable label for color table (Prefs > Editor)"), NSLocalizedString(@"Query Background", @"query background label for color table (Prefs > Editor)"), NSLocalizedString(@"Selection", @"selection label for color table (Prefs > Editor)"), nil] retain]; } return self; } /** * Initialise the UI, specifically the colours table view. */ - (void)awakeFromNib { [NSColor setIgnoresAlpha:NO]; NSTableColumn *column = [[colorSettingTableView tableColumns] objectAtIndex:0]; NSTextFieldCell *textCell = [[[NSTextFieldCell alloc] init] autorelease]; [textCell setFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorFont]]]; column = [[colorSettingTableView tableColumns] objectAtIndex: 1]; SPColorWellCell *colorCell = [[[SPColorWellCell alloc] init] autorelease]; [colorCell setEditable:YES]; [colorCell setTarget:self]; [colorCell setAction:@selector(colorClick:)]; [column setDataCell:colorCell]; } #pragma mark - #pragma mark IB action methods - (IBAction)exportColorScheme:(id)sender { NSSavePanel *panel = [NSSavePanel savePanel]; [panel setAllowedFileTypes:[NSArray arrayWithObject:SPColorThemeFileExtension]]; [panel setExtensionHidden:NO]; [panel setAllowsOtherFileTypes:NO]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel beginSheetForDirectory:nil file:[SP_EXPORT_COLOR_SCHEME_NAME_STRING stringByAppendingPathExtension:SPColorThemeFileExtension] modalForWindow:[[self view] window] modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:SPExportColorScheme]; } - (IBAction)importColorScheme:(id)sender { if (![self _checkForUnsavedTheme]) return; NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanSelectHiddenExtension:YES]; [panel setDelegate:self]; [panel setCanChooseDirectories:NO]; [panel setAllowsMultipleSelection:NO]; [panel beginSheetForDirectory:nil file:@"" types:[NSArray arrayWithObjects:SPColorThemeFileExtension, @"tmTheme", nil] modalForWindow:[[self view] window] modalDelegate:self didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) contextInfo:SPImportColorScheme]; } - (IBAction)loadColorScheme:(id)sender { if (![self _checkForUnsavedTheme]) return; if ([self _loadColorSchemeFromFile:[NSString stringWithFormat:@"%@/%@.%@", themePath, [sender title], SPColorThemeFileExtension]]) { [prefs setObject:[sender title] forKey:SPCustomQueryEditorThemeName]; [self updateDisplayColorThemeName]; } } - (IBAction)saveAsColorScheme:(id)sender { [[NSColorPanel sharedColorPanel] close]; [enterNameAlertField setHidden:YES]; [enterNameInputField setStringValue:@""]; [enterNameLabel setStringValue:NSLocalizedString(@"Theme Name:", @"theme name label")]; [NSApp beginSheet:enterNameWindow modalForWindow:[[self view] window] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:SPSaveColorScheme]; } - (IBAction)duplicateTheme:(id)sender { if ([editThemeListTable numberOfSelectedRows] != 1) return; NSString *selectedPath = [NSString stringWithFormat:@"%@/%@_copy.%@", themePath, [editThemeListItems objectAtIndex:[editThemeListTable selectedRow]], SPColorThemeFileExtension]; NSFileManager *fm = [NSFileManager defaultManager]; if (![fm fileExistsAtPath:selectedPath isDirectory:nil]) { if ([fm copyItemAtPath:[NSString stringWithFormat:@"%@/%@.%@", themePath, [editThemeListItems objectAtIndex:[editThemeListTable selectedRow]], SPColorThemeFileExtension] toPath:selectedPath error:nil]) { if (editThemeListItems) [editThemeListItems release], editThemeListItems = nil; editThemeListItems = [[NSArray arrayWithArray:[self _getAvailableThemes]] retain]; [editThemeListTable reloadData]; [self updateDisplayColorThemeName]; [self updateColorSchemeSelectionMenu]; return; } } NSBeep(); [editThemeListTable reloadData]; } - (IBAction)removeTheme:(id)sender { if ([editThemeListTable numberOfSelectedRows] != 1) return; NSString *selectedPath = [NSString stringWithFormat:@"%@/%@.%@", themePath, [editThemeListItems objectAtIndex:[editThemeListTable selectedRow]], SPColorThemeFileExtension]; NSFileManager *fm = [NSFileManager defaultManager]; if ([fm fileExistsAtPath:selectedPath isDirectory:nil]) { if ([fm removeItemAtPath:selectedPath error:nil]) { // Refresh current color theme setting name if ([[[prefs objectForKey:SPCustomQueryEditorThemeName] lowercaseString] isEqualToString:[[editThemeListItems objectAtIndex:[editThemeListTable selectedRow]] lowercaseString]]) { [prefs setObject:SPCustomColorSchemeName forKey:SPCustomQueryEditorThemeName]; } if (editThemeListItems) [editThemeListItems release], editThemeListItems = nil; editThemeListItems = [[NSArray arrayWithArray:[self _getAvailableThemes]] retain]; [editThemeListTable reloadData]; [self updateDisplayColorThemeName]; [self updateColorSchemeSelectionMenu]; return; } } NSBeep(); [editThemeListTable reloadData]; } - (IBAction)closePanelSheet:(id)sender { [NSApp endSheet:[sender window] returnCode:[sender tag]]; [[sender window] orderOut:self]; } /** * Opens the font panel. */ - (IBAction)showCustomQueryFontPanel:(id)sender { [(SPPreferenceController *)[[[self view] window] delegate] setFontChangeTarget:2]; [[NSFontPanel sharedFontPanel] setPanelFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorFont]] isMultiple:NO]; [[NSFontPanel sharedFontPanel] makeKeyAndOrderFront:self]; } /** * Sets the syntax colours back to there defaults. */ - (IBAction)setDefaultColors:(id)sender { if (![self _checkForUnsavedTheme]) return; [[NSColorPanel sharedColorPanel] close]; [prefs setObject:SPDefaultColorSchemeName forKey:SPCustomQueryEditorThemeName]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.000f green:0.455f blue:0.000f alpha:1.000f]] forKey:SPCustomQueryEditorCommentColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.769f green:0.102f blue:0.086f alpha:1.000f]] forKey:SPCustomQueryEditorQuoteColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.200f green:0.250f blue:1.000f alpha:1.000f]] forKey:SPCustomQueryEditorSQLKeywordColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.000f green:0.000f blue:0.658f alpha:1.000f]] forKey:SPCustomQueryEditorBacktickColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.506f green:0.263f blue:0.000f alpha:1.000f]] forKey:SPCustomQueryEditorNumericColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.500f green:0.500f blue:0.500f alpha:1.000f]] forKey:SPCustomQueryEditorVariableColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.950f green:0.950f blue:0.950f alpha:1.000f]] forKey:SPCustomQueryEditorHighlightQueryColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithDeviceRed:0.7098f green:0.8352f blue:1.000f alpha:1.000f]] forKey:SPCustomQueryEditorSelectionColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor blackColor]] forKey:SPCustomQueryEditorTextColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor blackColor]] forKey:SPCustomQueryEditorCaretColor]; [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor whiteColor]] forKey:SPCustomQueryEditorBackgroundColor]; [colorSettingTableView reloadData]; [self updateDisplayColorThemeName]; } /** * Opens the theme liste sheet. */ - (IBAction)editThemeList:(id)sender { [[NSColorPanel sharedColorPanel] close]; if (editThemeListItems) [editThemeListItems release], editThemeListItems = nil; editThemeListItems = [[NSArray arrayWithArray:[self _getAvailableThemes]] retain]; [editThemeListTable reloadData]; [NSApp beginSheet:editThemeListWindow modalForWindow:[[self view] window] modalDelegate:self didEndSelector:nil contextInfo:nil]; } #pragma mark - #pragma mark Public API /** * Updates the displayed font according to the user's preferences. */ - (void)updateDisplayedEditorFontName { NSFont *font = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorFont]]; [editorFontName setFont:font]; [colorSettingTableView reloadData]; } /** * Updates the colour scheme selection menu according to the available schemes. */ - (void)updateColorSchemeSelectionMenu { NSMenuItem *defaultItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Default", @"default label") action:@selector(setDefaultColors:) keyEquivalent:@""]; [defaultItem setTarget:self]; // Build theme selection submenu [themeSelectionMenu compatibleRemoveAllItems]; [themeSelectionMenu addItem:defaultItem]; [themeSelectionMenu addItem:[NSMenuItem separatorItem]]; [defaultItem release]; NSArray *foundThemes = [self _getAvailableThemes]; if ([foundThemes count]) { for (NSString* item in foundThemes) { NSMenuItem *loadItem = [[NSMenuItem alloc] initWithTitle:item action:@selector(loadColorScheme:) keyEquivalent:@""]; [loadItem setTarget:self]; [themeSelectionMenu addItem:loadItem]; [loadItem release]; } [themeSelectionMenu addItem:[NSMenuItem separatorItem]]; } NSMenuItem *editItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Theme List…", @"edit theme list label") action:@selector(editThemeList:) keyEquivalent:@""]; [editItem setTarget:self]; [themeSelectionMenu addItem:editItem]; [editItem release]; } /** * Updates the currently selected colour scheme theme name. */ - (void)updateDisplayColorThemeName { if (![prefs objectForKey:SPCustomQueryEditorThemeName]) { [colorThemeName setHidden:YES]; [colorThemeNameLabel setHidden:YES]; return; } if ([[[prefs objectForKey:SPCustomQueryEditorThemeName] lowercaseString] isEqualToString:SPCustomColorSchemeNameLC]) { [colorThemeName setHidden:YES]; [colorThemeNameLabel setHidden:YES]; return; } NSString *currentThemeName = [[prefs objectForKey:SPCustomQueryEditorThemeName] lowercaseString]; if ([currentThemeName isEqualToString:SPDefaultColorSchemeNameLC]) { [colorThemeName setHidden:NO]; [colorThemeNameLabel setHidden:NO]; return; } BOOL nameValid = NO; for (NSString* item in [self _getAvailableThemes]) { if ([[item lowercaseString] isEqualToString:currentThemeName]) { nameValid = YES; break; } } if (nameValid) { [colorThemeName setHidden:NO]; [colorThemeNameLabel setHidden:NO]; return; } else { [prefs setObject:SPCustomColorSchemeName forKey:SPCustomQueryEditorThemeName]; [colorThemeName setHidden:YES]; [colorThemeNameLabel setHidden:YES]; [self updateColorSchemeSelectionMenu]; return; } [colorThemeName setHidden:NO]; [colorThemeNameLabel setHidden:NO]; } #pragma mark - #pragma mark Font panel methods /** * Invoked when the user clicks a colour cell. */ - (void)colorClick:(id)sender { colorRow = [sender clickedRow]; NSColorPanel *panel = [NSColorPanel sharedColorPanel]; [panel setTarget:self]; [panel setAction:@selector(colorChanged:)]; [panel setColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:[editorColors objectAtIndex:colorRow]]]]; [colorSettingTableView deselectAll:nil]; [panel makeKeyAndOrderFront:self]; } /** * Invoked when the user changes and editor colour. */ - (void)colorChanged:(id)sender { if (![[NSColorPanel sharedColorPanel] isVisible]) return; [prefs setObject:[NSArchiver archivedDataWithRootObject:[sender color]] forKey:[editorColors objectAtIndex:colorRow]]; [colorSettingTableView reloadData]; [prefs setObject:SPCustomColorSchemeName forKey:SPCustomQueryEditorThemeName]; [self updateDisplayColorThemeName]; } /** * Sets the font panel's valid modes. */ - (NSUInteger)validModesForFontPanel:(NSFontPanel *)fontPanel { return (NSFontPanelSizeModeMask | NSFontPanelCollectionModeMask); } #pragma mark - #pragma mark Sheet callbacks - (void)checkForUnsavedThemeDidEndSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { checkForUnsavedThemeSheetStatus = returnCode; } - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { // Order out current sheet to suppress overlapping of sheets if ([sheet respondsToSelector:@selector(orderOut:)]) { [sheet orderOut:nil]; } else if ([sheet respondsToSelector:@selector(window)]) { [[sheet window] orderOut:nil]; } if ([contextInfo isEqualToString:SPSaveColorScheme]) { if (returnCode == NSOKButton) { NSFileManager *fm = [NSFileManager defaultManager]; if (![fm fileExistsAtPath:themePath isDirectory:nil]) { if (![fm createDirectoryAtPath:themePath withIntermediateDirectories:YES attributes:nil error:nil]) { NSBeep(); return; } } [self _saveColorThemeAtPath:[NSString stringWithFormat:@"%@/%@.%@", themePath, [enterNameInputField stringValue], SPColorThemeFileExtension]]; [self updateColorSchemeSelectionMenu]; [prefs setObject:[enterNameInputField stringValue] forKey:SPCustomQueryEditorThemeName]; [self updateDisplayColorThemeName]; } } } - (void)panelDidEnd:(NSOpenPanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { if ([contextInfo isEqualToString:SPExportColorScheme]) { if (returnCode == NSOKButton) { [self _saveColorThemeAtPath:[[panel URL] path]]; } } else if ([contextInfo isEqualToString:SPImportColorScheme]) { if (returnCode == NSOKButton) { if ([self _loadColorSchemeFromFile:[[panel filenames] objectAtIndex:0]]) { [prefs setObject:SPCustomColorSchemeName forKey:SPCustomQueryEditorThemeName]; [self updateDisplayColorThemeName]; } } } } #pragma mark - #pragma mark TableView datasource methods - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { if (tableView == colorSettingTableView) { return [editorColors count]; } else if (tableView == editThemeListTable) { return [editThemeListItems count]; } return 0; } - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { if (tableView == colorSettingTableView) { return ([[tableColumn identifier] isEqualToString:@"name"]) ? [editorNameForColors objectAtIndex:rowIndex] : [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:[editorColors objectAtIndex:rowIndex]]]; } else if (tableView == editThemeListTable) { return [editThemeListItems objectAtIndex:rowIndex]; } return nil; } - (void)tableView:(NSTableView *)tableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { if (tableView == editThemeListTable) { // Theme name editing NSString *newName = (NSString*)anObject; // Check for non-valid names if (![newName length] || [[newName lowercaseString] isEqualToString:SPDefaultColorSchemeNameLC] || [[newName lowercaseString] isEqualToString:SPCustomColorSchemeNameLC]) { NSBeep(); [editThemeListTable reloadData]; return; } // Check if new name already exists for (NSString* item in editThemeListItems) { if ([[item lowercaseString] isEqualToString:newName]) { NSBeep(); [editThemeListTable reloadData]; return; } } // Rename theme file NSFileManager *fm = [NSFileManager defaultManager]; if (![fm moveItemAtPath:[NSString stringWithFormat:@"%@/%@.%@", themePath, [editThemeListItems objectAtIndex:rowIndex], SPColorThemeFileExtension] toPath:[NSString stringWithFormat:@"%@/%@.%@", themePath, newName, SPColorThemeFileExtension] error:nil]) { NSBeep(); [editThemeListTable reloadData]; return; } // Refresh current color theme setting name if ([[[prefs objectForKey:SPCustomQueryEditorThemeName] lowercaseString] isEqualToString:[[editThemeListItems objectAtIndex:rowIndex] lowercaseString]]) { [prefs setObject:newName forKey:SPCustomQueryEditorThemeName]; } // Reload everything needed if (editThemeListItems) [editThemeListItems release], editThemeListItems = nil; editThemeListItems = [[NSArray arrayWithArray:[self _getAvailableThemes]] retain]; [editThemeListTable reloadData]; [self updateDisplayColorThemeName]; [self updateColorSchemeSelectionMenu]; } } #pragma mark - #pragma mark TableView delegate methods - (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { if(aTableView == colorSettingTableView) { NSColorPanel* panel; colorRow = rowIndex; panel = [NSColorPanel sharedColorPanel]; [panel setTarget:self]; [panel setAction:@selector(colorChanged:)]; [panel setColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:[editorColors objectAtIndex:colorRow]]]]; [colorSettingTableView deselectAll:nil]; [panel makeKeyAndOrderFront:self]; return NO; } return YES; } - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)index { if (tableView == colorSettingTableView && [[tableColumn identifier] isEqualToString:@"name"]) { if ([cell isKindOfClass:[NSTextFieldCell class]]) { [cell setDrawsBackground:YES]; NSFont *nf = [NSFont fontWithName:[[[NSFontPanel sharedFontPanel] panelConvertFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorFont]]] fontName] size:13.0f]; [cell setFont:nf]; switch (index) { case 1: [cell setTextColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorTextColor]]]; [cell setBackgroundColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorBackgroundColor]]]; break; case 9: [cell setTextColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorTextColor]]]; [cell setBackgroundColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorHighlightQueryColor]]]; break; case 10: [cell setTextColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorTextColor]]]; [cell setBackgroundColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorSelectionColor]]]; break; default: [cell setTextColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:[editorColors objectAtIndex:index]]]]; [cell setBackgroundColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorBackgroundColor]]]; } } } } #pragma mark - #pragma mark TextField delegate methods /** * Trap and control the 'name' field of the selected favorite. If the user pressed * 'Add Favorite' the 'name' field is set to "New Favorite". If the user do not * change the 'name' field or delete that field it will be set to user@host automatically. */ - (void)controlTextDidChange:(NSNotification *)aNotification { id field = [aNotification object]; // Validate 'Save' button for entering a valid theme name if (field == enterNameInputField) { NSString *name = [[enterNameInputField stringValue] lowercaseString]; if (![name length] || [name isEqualToString:SPDefaultColorSchemeNameLC] || [name isEqualToString:SPCustomColorSchemeNameLC]) { [themeNameSaveButton setEnabled:NO]; } else { BOOL hide = YES; for (NSString* item in [self _getAvailableThemes]) { if ([[item lowercaseString] isEqualToString:name]) { hide = NO; break; } } [enterNameAlertField setHidden:hide]; [themeNameSaveButton setEnabled:YES]; } return; } } #pragma mark - #pragma mark Preference pane protocol methods - (NSView *)preferencePaneView { return [self view]; } - (NSImage *)preferencePaneIcon { return [NSImage imageNamed:@"toolbar-preferences-queryeditor"]; } - (NSString *)preferencePaneName { return NSLocalizedString(@"Query Editor", @"query editor preference pane name"); } - (NSString *)preferencePaneIdentifier { return SPPreferenceToolbarEditor; } - (NSString *)preferencePaneToolTip { return NSLocalizedString(@"Query Editor Preferences", @"query editor preference pane tooltip"); } - (BOOL)preferencePaneAllowsResizing { return NO; } #pragma mark - #pragma mark Private API - (BOOL)_checkForUnsavedTheme { if (![prefs objectForKey:SPCustomQueryEditorThemeName] || [[[prefs objectForKey:SPCustomQueryEditorThemeName] lowercaseString] isEqualToString:SPCustomColorSchemeNameLC]) { [[NSColorPanel sharedColorPanel] close]; SPBeginWaitingAlertSheet(@"title", NSLocalizedString(@"Proceed", @"proceed button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, NSWarningAlertStyle, [[self view] window], self, @selector(checkForUnsavedThemeDidEndSheet:returnCode:contextInfo:), nil, NSLocalizedString(@"Unsaved Theme", @"unsaved theme message"), NSLocalizedString(@"The current color theme is unsaved. Do you want to proceed without saving it?", @"unsaved theme informative message"), &checkForUnsavedThemeSheetStatus ); return (checkForUnsavedThemeSheetStatus == NSAlertDefaultReturn); } [[NSColorPanel sharedColorPanel] close]; return YES; } - (NSArray *)_getAvailableThemes { // Read ~/Library/Application Support/Sequel Pro/Themes NSFileManager *fm = [NSFileManager defaultManager]; if ([fm fileExistsAtPath:themePath isDirectory:nil]) { NSArray *allItemsRaw = [fm contentsOfDirectoryAtPath:themePath error:NULL]; if(!allItemsRaw) return [NSArray array]; // Filter out all themes NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF ENDSWITH %@", [NSString stringWithFormat:@".%@", SPColorThemeFileExtension]]; NSMutableArray *allItems = [NSMutableArray arrayWithArray:allItemsRaw]; [allItems filterUsingPredicate:predicate]; allItemsRaw = [NSArray arrayWithArray:allItems]; [allItems removeAllObjects]; // Remove file extension for (NSString* item in allItemsRaw) { [allItems addObject:[item substringToIndex:[item length]-[SPColorThemeFileExtension length]-1]]; } return (NSArray *)allItems; } return [NSArray array]; } - (void)_saveColorThemeAtPath:(NSString *)path { // Build plist dictionary NSMutableDictionary *scheme = [NSMutableDictionary dictionary]; NSMutableDictionary *mainsettings = [NSMutableDictionary dictionary]; NSMutableArray *settings = [NSMutableArray array]; [prefs synchronize]; NSColor *aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorBackgroundColor]]; [mainsettings setObject:[aColor rgbHexString] forKey:@"background"]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorCaretColor]]; [mainsettings setObject:[aColor rgbHexString] forKey:@"caret"]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorTextColor]]; [mainsettings setObject:[aColor rgbHexString] forKey:@"foreground"]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorHighlightQueryColor]]; [mainsettings setObject:[aColor rgbHexString] forKey:@"lineHighlight"]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorSelectionColor]]; [mainsettings setObject:[aColor rgbHexString] forKey:@"selection"]; [settings addObject:[NSDictionary dictionaryWithObjectsAndKeys:mainsettings, @"settings", nil]]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorCommentColor]]; [settings addObject:[NSDictionary dictionaryWithObjectsAndKeys: @"Comment", @"name", [NSDictionary dictionaryWithObjectsAndKeys: [aColor rgbHexString], @"foreground", nil ], @"settings", nil ]]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorQuoteColor]]; [settings addObject:[NSDictionary dictionaryWithObjectsAndKeys: @"String", @"name", [NSDictionary dictionaryWithObjectsAndKeys: [aColor rgbHexString], @"foreground", nil ], @"settings", nil ]]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorSQLKeywordColor]]; [settings addObject:[NSDictionary dictionaryWithObjectsAndKeys: @"Keyword", @"name", [NSDictionary dictionaryWithObjectsAndKeys: [aColor rgbHexString], @"foreground", nil ], @"settings", nil ]]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorBacktickColor]]; [settings addObject:[NSDictionary dictionaryWithObjectsAndKeys: @"User-defined constant", @"name", [NSDictionary dictionaryWithObjectsAndKeys: [aColor rgbHexString], @"foreground", nil ], @"settings", nil ]]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorNumericColor]]; [settings addObject:[NSDictionary dictionaryWithObjectsAndKeys: @"Number", @"name", [NSDictionary dictionaryWithObjectsAndKeys: [aColor rgbHexString], @"foreground", nil ], @"settings", nil ]]; aColor = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorVariableColor]]; [settings addObject:[NSDictionary dictionaryWithObjectsAndKeys: @"Variable", @"name", [NSDictionary dictionaryWithObjectsAndKeys: [aColor rgbHexString], @"foreground", nil ], @"settings", nil ]]; [scheme setObject:settings forKey:@"settings"]; NSString *err = nil; NSData *plist = [NSPropertyListSerialization dataFromPropertyList:scheme format:NSPropertyListXMLFormat_v1_0 errorDescription:&err]; if(err != nil) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while converting color scheme data", @"error while converting color scheme data")] defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil otherButton:nil informativeTextWithFormat:err]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; return; } NSError *error = nil; [plist writeToFile:path options:NSAtomicWrite error:&error]; if (error) [[NSAlert alertWithError:error] runModal]; } - (BOOL)_loadColorSchemeFromFile:(NSString *)filename { NSError *readError = nil; NSString *convError = nil; NSPropertyListFormat format; NSDictionary *theme = nil; NSData *pData = [NSData dataWithContentsOfFile:filename options:NSUncachedRead error:&readError]; theme = [[NSPropertyListSerialization propertyListFromData:pData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; if (!theme || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading data file", @"error while reading data file")] defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil otherButton:nil informativeTextWithFormat:NSLocalizedString(@"File couldn't be read.", @"error while reading data file")]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; if (theme) [theme release]; [self updateDisplayColorThemeName]; return NO; } if ([theme objectForKey:@"settings"] && [[theme objectForKey:@"settings"] isKindOfClass:[NSArray class]] && [[theme objectForKey:@"settings"] count] && [[[theme objectForKey:@"settings"] objectAtIndex:0] isKindOfClass:[NSDictionary class]] && [[[theme objectForKey:@"settings"] objectAtIndex:0] objectForKey:@"settings"]) { NSInteger counter = 0; for (NSDictionary *dict in [theme objectForKey:@"settings"]) { if (counter == 0) { if ([dict objectForKey:@"settings"]) { NSDictionary *dic = [dict objectForKey:@"settings"]; if([dic objectForKey:@"background"]) [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithRGBHexString:[dic objectForKey:@"background"]]] forKey:SPCustomQueryEditorBackgroundColor]; if([dic objectForKey:@"caret"]) [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithRGBHexString:[dic objectForKey:@"caret"]]] forKey:SPCustomQueryEditorCaretColor]; if([dic objectForKey:@"foreground"]) [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithRGBHexString:[dic objectForKey:@"foreground"]]] forKey:SPCustomQueryEditorTextColor]; if([dic objectForKey:@"lineHighlight"]) [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithRGBHexString:[dic objectForKey:@"lineHighlight"]]] forKey:SPCustomQueryEditorHighlightQueryColor]; if([dic objectForKey:@"selection"]) [prefs setObject:[NSArchiver archivedDataWithRootObject:[NSColor colorWithRGBHexString:[dic objectForKey:@"selection"]]] forKey:SPCustomQueryEditorSelectionColor]; } else { continue; } } else { if ([dict objectForKey:@"name"] && [dict objectForKey:@"settings"] && [[dict objectForKey:@"settings"] isKindOfClass:[NSDictionary class]] && [[dict objectForKey:@"settings"] objectForKey:@"foreground"]) { if ([[dict objectForKey:@"name"] isEqualToString:@"Comment"]) [prefs setObject:[NSArchiver archivedDataWithRootObject: [NSColor colorWithRGBHexString:[[dict objectForKey:@"settings"] objectForKey:@"foreground"]]] forKey:SPCustomQueryEditorCommentColor]; else if ([[dict objectForKey:@"name"] isEqualToString:@"String"]) [prefs setObject:[NSArchiver archivedDataWithRootObject: [NSColor colorWithRGBHexString:[[dict objectForKey:@"settings"] objectForKey:@"foreground"]]] forKey:SPCustomQueryEditorQuoteColor]; else if ([[dict objectForKey:@"name"] isEqualToString:@"Keyword"]) [prefs setObject:[NSArchiver archivedDataWithRootObject: [NSColor colorWithRGBHexString:[[dict objectForKey:@"settings"] objectForKey:@"foreground"]]] forKey:SPCustomQueryEditorSQLKeywordColor]; else if ([[dict objectForKey:@"name"] isEqualToString:@"User-defined constant"]) [prefs setObject:[NSArchiver archivedDataWithRootObject: [NSColor colorWithRGBHexString:[[dict objectForKey:@"settings"] objectForKey:@"foreground"]]] forKey:SPCustomQueryEditorBacktickColor]; else if ([[dict objectForKey:@"name"] isEqualToString:@"Number"]) [prefs setObject:[NSArchiver archivedDataWithRootObject: [NSColor colorWithRGBHexString:[[dict objectForKey:@"settings"] objectForKey:@"foreground"]]] forKey:SPCustomQueryEditorNumericColor]; else if ([[dict objectForKey:@"name"] isEqualToString:@"Variable"]) [prefs setObject:[NSArchiver archivedDataWithRootObject: [NSColor colorWithRGBHexString:[[dict objectForKey:@"settings"] objectForKey:@"foreground"]]] forKey:SPCustomQueryEditorVariableColor]; } } counter++; } [theme release]; [colorSettingTableView reloadData]; } else { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading data file", @"error while reading data file")] defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil otherButton:nil informativeTextWithFormat:NSLocalizedString(@"No color theme data found.", @"error that no color theme found")]; [alert setAlertStyle:NSInformationalAlertStyle]; [alert runModal]; [theme release]; return NO; } return YES; } #pragma mark - /** * Dealloc. */ - (void)dealloc { if (themePath) [themePath release], themePath = nil; if (editThemeListItems) [editThemeListItems release], editThemeListItems = nil; if (editorColors) [editorColors release], editorColors = nil; if (editorNameForColors) [editorNameForColors release], editorNameForColors = nil; [super dealloc]; } @end