aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2009-04-18 01:30:52 +0000
committerrowanbeentje <rowan@beent.je>2009-04-18 01:30:52 +0000
commite739aab4fc249fac5c82ebf8e2ac5e603341118c (patch)
tree7bed9e80c6a92e2634103bb99a093956efb6d498
parent6646f022ec631d24afe58e8fbe4a7824f4a9c498 (diff)
downloadsequelpro-e739aab4fc249fac5c82ebf8e2ac5e603341118c.tar.gz
sequelpro-e739aab4fc249fac5c82ebf8e2ac5e603341118c.tar.bz2
sequelpro-e739aab4fc249fac5c82ebf8e2ac5e603341118c.zip
- Rework the query console logic. This fixes a memory leak caused by a mutableCopy when toggling SELECT/SHOWS; it allows the SELECT/SHOWS toggle and text filters to stack together whatever order they are changed in; and it allows both filtering and hiding SELECT/SHOWs to be applied 'live', ie messages added to the console will have the same logic applied to them.
- Clean up trailing whitespace in the file
-rw-r--r--Source/SPQueryConsole.h5
-rw-r--r--Source/SPQueryConsole.m292
2 files changed, 162 insertions, 135 deletions
diff --git a/Source/SPQueryConsole.h b/Source/SPQueryConsole.h
index 155517f7..cbe89662 100644
--- a/Source/SPQueryConsole.h
+++ b/Source/SPQueryConsole.h
@@ -31,7 +31,10 @@
IBOutlet NSButton *includeTimeStampsButton, *saveConsoleButton, *clearConsoleButton;
NSFont *consoleFont;
- NSMutableArray *messages, *messagesActiveSet, *messagesFilterSet, *messagesSubset;
+ NSMutableArray *messagesFullSet, *messagesFilteredSet, *messagesVisibleSet;
+ BOOL showSelectStatementsAreDisabled;
+ BOOL filterIsActive;
+ NSMutableString *activeFilterString;
}
@property (readwrite, retain) NSFont *consoleFont;
diff --git a/Source/SPQueryConsole.m b/Source/SPQueryConsole.m
index 0ba6c982..7106f49a 100644
--- a/Source/SPQueryConsole.m
+++ b/Source/SPQueryConsole.m
@@ -39,9 +39,9 @@
- (NSString *)_getConsoleStringWithTimeStamps:(BOOL)timeStamps;
-- (void)_hideSelectShowStatements:(BOOL)show;
-- (void)_filterConsoleUsingSearchString:(NSString *)string;
+- (void)_updateFilterState;
- (void)_addMessageToConsole:(NSString *)message isError:(BOOL)error;
+- (BOOL)_messageMatchesCurrentFilters:(NSString *)message;
@end
@@ -81,15 +81,17 @@ static SPQueryConsole *sharedQueryConsole = nil;
- (id)init
{
if ((self = [super initWithWindowNibName:@"Console"])) {
- messages = [[NSMutableArray alloc] init];
- messagesSubset = [[NSMutableArray alloc] init];
- messagesFilterSet = [[NSMutableArray alloc] init];
-
- // Weak reference
- messagesActiveSet = messages;
- messagesFilterSet = messagesActiveSet;
+ messagesFullSet = [[NSMutableArray alloc] init];
+ messagesFilteredSet = [[NSMutableArray alloc] init];
+
+ showSelectStatementsAreDisabled = NO;
+ filterIsActive = NO;
+ activeFilterString = [[NSMutableString alloc] init];
+
+ // Weak reference to active messages set - starts off as full set
+ messagesVisibleSet = messagesFullSet;
}
-
+
return self;
}
@@ -121,37 +123,37 @@ static SPQueryConsole *sharedQueryConsole = nil;
- (void)copy:(id)sender
{
NSResponder *firstResponder = [[self window] firstResponder];
-
+
if ((firstResponder == consoleTableView) && ([consoleTableView numberOfSelectedRows] > 0)) {
-
+
NSString *string = @"";
NSIndexSet *rows = [consoleTableView selectedRowIndexes];
-
+
NSUInteger i = [rows firstIndex];
-
+
while (i != NSNotFound)
- {
- if (i < [messagesFilterSet count]) {
- SPConsoleMessage *message = [messagesFilterSet objectAtIndex:i];
-
+ {
+ if (i < [messagesVisibleSet count]) {
+ SPConsoleMessage *message = [messagesVisibleSet objectAtIndex:i];
+
NSString *consoleMessage = [message message];
-
+
// If the timestamp column is not hidden we need to include them in the copy
if (![[consoleTableView tableColumnWithIdentifier:TABLEVIEW_DATE_COLUMN_IDENTIFIER] isHidden]) {
-
+
NSString *dateString = [[message messageDate] descriptionWithCalendarFormat:MESSAGE_TIME_STAMP_FORMAT timeZone:nil locale:nil];
-
+
consoleMessage = [NSString stringWithFormat:@"/* MySQL %@ */ %@", dateString, consoleMessage];
}
-
+
string = [string stringByAppendingFormat:@"%@\n", consoleMessage];
}
-
+
i = [rows indexGreaterThanIndex:i];
}
-
+
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
-
+
// Copy the string to the pasteboard
[pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil];
[pasteBoard setString:string forType:NSStringPboardType];
@@ -163,8 +165,9 @@ static SPQueryConsole *sharedQueryConsole = nil;
*/
- (IBAction)clearConsole:(id)sender
{
- [messages removeAllObjects];
-
+ [messagesFullSet removeAllObjects];
+ [messagesFilteredSet removeAllObjects];
+
[consoleTableView reloadData];
}
@@ -174,15 +177,15 @@ static SPQueryConsole *sharedQueryConsole = nil;
- (IBAction)saveConsoleAs:(id)sender
{
NSSavePanel *panel = [NSSavePanel savePanel];
-
+
[panel setRequiredFileType:DEFAULT_CONSOLE_LOG_FILE_EXTENSION];
-
+
[panel setExtensionHidden:NO];
[panel setAllowsOtherFileTypes:YES];
[panel setCanSelectHiddenExtension:YES];
-
+
[panel setAccessoryView:saveLogView];
-
+
[panel beginSheetForDirectory:nil file:DEFAULT_CONSOLE_LOG_FILENAME modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
[panel beginSheetForDirectory:nil
file:DEFAULT_CONSOLE_LOG_FILENAME
@@ -205,7 +208,10 @@ static SPQueryConsole *sharedQueryConsole = nil;
*/
- (IBAction)toggleShowSelectShowStatements:(id)sender
{
- [self _hideSelectShowStatements:(![sender intValue])];
+ // Store the state of the toggle for later quick reference
+ showSelectStatementsAreDisabled = ![sender intValue];
+
+ [self _updateFilterState];
}
/**
@@ -229,7 +235,7 @@ static SPQueryConsole *sharedQueryConsole = nil;
*/
- (NSUInteger)consoleMessageCount
{
- return [messages count];
+ return [messagesFullSet count];
}
/**
@@ -250,40 +256,40 @@ static SPQueryConsole *sharedQueryConsole = nil;
*/
- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
- return [messagesFilterSet count];
+ return [messagesVisibleSet count];
}
/**
* Table view delegate method. Returns the specific object for the request column and row.
*/
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSUInteger)row
-{
+{
NSString *returnValue = nil;
-
- id object = [[messagesFilterSet objectAtIndex:row] valueForKey:[tableColumn identifier]];
-
+
+ id object = [[messagesVisibleSet objectAtIndex:row] valueForKey:[tableColumn identifier]];
+
if ([[tableColumn identifier] isEqualToString:TABLEVIEW_DATE_COLUMN_IDENTIFIER]) {
-
+
NSString *dateString = [(NSDate *)object descriptionWithCalendarFormat:MESSAGE_TIME_STAMP_FORMAT timeZone:nil locale:nil];
-
+
returnValue = [NSString stringWithFormat:@"/* MySQL %@ */", dateString];
}
else {
if ([(NSString *)object length] > MESSAGE_TRUNCATE_CHARACTER_LENGTH) {
object = [NSString stringWithFormat:@"%@...", [object substringToIndex:MESSAGE_TRUNCATE_CHARACTER_LENGTH]];
}
-
+
returnValue = object;
}
-
+
NSMutableDictionary *stringAtributes = nil;
-
+
if (consoleFont) {
stringAtributes = [NSMutableDictionary dictionaryWithObject:consoleFont forKey:NSFontAttributeName];
}
-
+
// If this is an error message give it a red colour
- if ([(SPConsoleMessage *)[messagesFilterSet objectAtIndex:row] isError]) {
+ if ([(SPConsoleMessage *)[messagesVisibleSet objectAtIndex:row] isError]) {
if (stringAtributes) {
[stringAtributes setObject:[NSColor redColor] forKey:NSForegroundColorAttributeName];
}
@@ -304,9 +310,14 @@ static SPQueryConsole *sharedQueryConsole = nil;
- (void)controlTextDidChange:(NSNotification *)notification
{
id object = [notification object];
-
- if ([object isEqualTo:consoleSearchField]) {
- [self _filterConsoleUsingSearchString:[[object stringValue] lowercaseString]];
+
+ if ([object isEqualTo:consoleSearchField]) {
+
+ // Store the state of the text filter and the current filter string for later quick reference
+ [activeFilterString setString:[[object stringValue] lowercaseString]];
+ filterIsActive = [activeFilterString length]?YES:NO;
+
+ [self _updateFilterState];
}
}
@@ -314,15 +325,15 @@ static SPQueryConsole *sharedQueryConsole = nil;
* Menu item validation for console table view contextual menu.
*/
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
-{
+{
if ([menuItem action] == @selector(copy:)) {
return ([consoleTableView numberOfSelectedRows] > 0);
}
-
+
if ([menuItem action] == @selector(clearConsole:)) {
return ([self consoleMessageCount] > 0);
}
-
+
return [[self window] validateMenuItem:menuItem];
}
@@ -331,12 +342,12 @@ static SPQueryConsole *sharedQueryConsole = nil;
*/
- (void)dealloc
{
- messagesSubset = nil;
-
- [messages release], messages = nil;
- [messagesSubset release], messagesSubset = nil;
- [messagesFilterSet release], messagesFilterSet = nil;
-
+ messagesVisibleSet = nil;
+
+ [messagesFullSet release], messagesFullSet = nil;
+ [messagesFilteredSet release], messagesFilteredSet = nil;
+ [activeFilterString release], activeFilterString = nil;
+
[super dealloc];
}
@@ -351,107 +362,86 @@ static SPQueryConsole *sharedQueryConsole = nil;
- (NSString *)_getConsoleStringWithTimeStamps:(BOOL)timeStamps
{
NSMutableString *consoleString = [[[NSMutableString alloc] init] autorelease];
-
- for (SPConsoleMessage *message in messagesFilterSet)
+
+ for (SPConsoleMessage *message in messagesVisibleSet)
{
if (timeStamps) {
NSString *dateString = [[message messageDate] descriptionWithCalendarFormat:MESSAGE_TIME_STAMP_FORMAT timeZone:nil locale:nil];
-
+
[consoleString appendString:[NSString stringWithFormat:@"/* MySQL %@ */ ", dateString]];
}
-
+
[consoleString appendString:[NSString stringWithFormat:@"%@\n", [message message]]];
}
-
- return consoleString;
-}
-/**
- * Either hides or shows all SELECT and SHOW statements within the console.
- */
-- (void)_hideSelectShowStatements:(BOOL)show
-{
- if (!show) {
- messagesActiveSet = messages;
- messagesFilterSet = messagesActiveSet;
-
- [consoleTableView reloadData];
-
- return;
- }
-
- messagesActiveSet = [messages mutableCopy];
-
- // Filter out messages that have a prefix of either SELECT or SHOW
- for (SPConsoleMessage *message in messages)
- {
- if ([[message message] hasPrefix:@"SELECT"] || [[message message] hasPrefix:@"SHOW"]) {
- [messagesActiveSet removeObject:message];
- }
- }
-
- messagesFilterSet = messagesActiveSet;
-
- [consoleTableView reloadData];
+ return consoleString;
}
+
/**
- * Filters the messages array using the supplued search string.
+ * Updates the filtered result set based on any filter string and whether or not
+ * all SELECT nd SHOW statements should be shown within the console.
*/
-- (void)_filterConsoleUsingSearchString:(NSString *)searchString
+- (void)_updateFilterState
{
+
// Display start progress spinner
[progressIndicator setHidden:NO];
[progressIndicator startAnimation:self];
-
+
// Don't allow clearing the console while filtering its content
[saveConsoleButton setEnabled:NO];
[clearConsoleButton setEnabled:NO];
-
- [saveConsoleButton setTitle:@"Save View As..."];
-
- // If there's no search string assign the active messages array back to the message array
- if ([searchString length] == 0) {
- [messagesFilterSet removeAllObjects];
-
- messagesFilterSet = messagesActiveSet;
-
+
+ [messagesFilteredSet removeAllObjects];
+
+ // If filtering is disabled and all show/selects are shown, empty the filtered
+ // result set and set the full set to visible.
+ if (!filterIsActive && !showSelectStatementsAreDisabled) {
+ messagesVisibleSet = messagesFullSet;
+
[consoleTableView reloadData];
- [consoleTableView scrollRowToVisible:([messagesFilterSet count] - 1)];
-
+ [consoleTableView scrollRowToVisible:([messagesVisibleSet count] - 1)];
+
[saveConsoleButton setEnabled:YES];
[clearConsoleButton setEnabled:YES];
-
+
[saveConsoleButton setTitle:@"Save As..."];
-
- // Display start progress spinner
+
+ // Hide progress spinner
[progressIndicator setHidden:YES];
[progressIndicator stopAnimation:self];
-
return;
- }
-
- // Remove all objects in the subset
- [messagesSubset removeAllObjects];
-
- // Filter the messages
- for (SPConsoleMessage *message in messagesActiveSet)
- {
- if ([[message message] rangeOfString:searchString options:NSCaseInsensitiveSearch].location != NSNotFound) {
- [messagesSubset addObject:message];
+ }
+
+ // Cache frequently used selector, avoiding dynamic binding overhead
+ IMP messageMatchesFilters = [self methodForSelector:@selector(_messageMatchesCurrentFilters:)];
+
+ // Loop through all the messages in the full set to determine which should be
+ // added to the filtered set.
+ for (SPConsoleMessage *message in messagesFullSet) {
+
+ // Add a reference to the message to the filtered set if filters are active and the
+ // current message matches them
+ if ((messageMatchesFilters)(self, @selector(_messageMatchesCurrentFilters:), [message message])) {
+ [messagesFilteredSet addObject:message];
}
}
-
- messagesFilterSet = messagesSubset;
-
- [consoleTableView reloadData];
- [consoleTableView scrollRowToVisible:([messagesFilterSet count] - 1)];
-
- if ([messagesFilterSet count] > 0) {
+
+ // Ensure that the filtered set is marked as the currently visible set.
+ messagesVisibleSet = messagesFilteredSet;
+
+ [consoleTableView reloadData];
+ [consoleTableView scrollRowToVisible:([messagesVisibleSet count] - 1)];
+
+ if ([messagesVisibleSet count] > 0) {
[saveConsoleButton setEnabled:YES];
+ [clearConsoleButton setEnabled:YES];
}
-
- // Display start progress spinner
+
+ [saveConsoleButton setTitle:@"Save View As..."];
+
+ // Hide progress spinner
[progressIndicator setHidden:YES];
[progressIndicator stopAnimation:self];
}
@@ -460,15 +450,49 @@ static SPQueryConsole *sharedQueryConsole = nil;
* Adds the supplied message to the query console.
*/
- (void)_addMessageToConsole:(NSString *)message isError:(BOOL)error
-{
+{
SPConsoleMessage *consoleMessage = [SPConsoleMessage consoleMessageWithMessage:[[message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] stringByAppendingString:@";"] date:[NSDate date]];
-
+
[consoleMessage setIsError:error];
-
- [messages addObject:consoleMessage];
-
+
+ [messagesFullSet addObject:consoleMessage];
+
+ // If filtering is active, determine whether to add a reference to the filtered set
+ if ((showSelectStatementsAreDisabled || filterIsActive)
+ && [self _messageMatchesCurrentFilters:[consoleMessage message]])
+ {
+ [messagesFilteredSet addObject:[messagesFullSet lastObject]];
+ }
+
+ // Reload the table and scroll to the new message
[consoleTableView reloadData];
- [consoleTableView scrollRowToVisible:([messages count] - 1)];
+ [consoleTableView scrollRowToVisible:([messagesVisibleSet count] - 1)];
+}
+
+/**
+ * Checks whether the supplied message text matches the current filter text, if any,
+ * and whether it should be hidden if the SELECT/SHOW toggle is off.
+ */
+- (BOOL)_messageMatchesCurrentFilters:(NSString *)message
+{
+ BOOL messageMatchesCurrentFilters = YES;
+
+ // Check whether to hide the message based on the current filter text, if any
+ if (filterIsActive
+ && [message rangeOfString:activeFilterString options:NSCaseInsensitiveSearch].location == NSNotFound)
+ {
+ messageMatchesCurrentFilters = NO;
+ }
+
+ // If hiding SELECTs and SHOWs is toggled to on, check whether the message is a SELECT or SHOW
+ if (messageMatchesCurrentFilters
+ && showSelectStatementsAreDisabled
+ && ([message hasPrefix:@"SELECT"] || [message hasPrefix:@"SHOW"]))
+ {
+ messageMatchesCurrentFilters = NO;
+ }
+
+ return messageMatchesCurrentFilters;
}
@end