// // $Id$ // // SPPrintController.m // sequel-pro // // Created by Stuart Connolly (stuconnolly.com) on March 11, 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 #import "SPPrintController.h" #import "SPTableContent.h" #import "SPTableStructure.h" #import "SPCustomQuery.h" #import "SPConstants.h" #import "SPTableRelations.h" #import "SPPrintAccessory.h" #import "MGTemplateEngine.h" #import "ICUTemplateMatcher.h" #import "SPConnectionController.h" #import "SPExtendedTableInfo.h" #import "SPTableTriggers.h" @implementation SPDatabaseDocument (SPPrintController) /** * WebView delegate method. */ - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame { // Because we need the webFrame loaded (for preview), we've moved the actual printing here NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo]; NSSize paperSize = [printInfo paperSize]; NSRect printableRect = [printInfo imageablePageBounds]; // Calculate page margins CGFloat marginL = printableRect.origin.x; CGFloat marginR = paperSize.width - (printableRect.origin.x + printableRect.size.width); CGFloat marginB = printableRect.origin.y; CGFloat marginT = paperSize.height - (printableRect.origin.y + printableRect.size.height); // Make sure margins are symetric and positive CGFloat marginLR = MAX(0, MAX(marginL, marginR)); CGFloat marginTB = MAX(0, MAX(marginT, marginB)); // Set the margins [printInfo setLeftMargin:marginLR]; [printInfo setRightMargin:marginLR]; [printInfo setTopMargin:marginTB]; [printInfo setBottomMargin:marginTB]; [printInfo setHorizontalPagination:NSFitPagination]; [printInfo setVerticalPagination:NSAutoPagination]; [printInfo setVerticallyCentered:NO]; NSPrintOperation *op = [NSPrintOperation printOperationWithView:[[[printWebView mainFrame] frameView] documentView] printInfo:printInfo]; // Perform the print operation on a background thread [op setCanSpawnSeparateThread:YES]; // Add the ability to select the orientation to print panel NSPrintPanel *printPanel = [op printPanel]; [printPanel setOptions:[printPanel options] + NSPrintPanelShowsOrientation + NSPrintPanelShowsScaling + NSPrintPanelShowsPaperSize]; SPPrintAccessory *printAccessory = [[SPPrintAccessory alloc] initWithNibName:@"PrintAccessory" bundle:nil]; [printAccessory setPrintView:printWebView]; [printPanel addAccessoryController:printAccessory]; [[NSPageLayout pageLayout] addAccessoryController:printAccessory]; [printAccessory release]; [op setPrintPanel:printPanel]; [op runOperationModalForWindow:[self parentWindow] delegate:self didRunSelector:nil contextInfo:nil]; if ([self isWorking]) [self endTask]; } /** * Loads the print document interface. The actual printing is done in the doneLoading delegate. */ - (IBAction)printDocument:(id)sender { // Only display warning for the 'Table Content' view if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1) { NSInteger rowLimit = [prefs integerForKey:SPPrintWarningRowLimit]; // Result count minus one because the first element is the column names NSUInteger resultRows = ([[tableContentInstance currentResult] count] - 1); if (resultRows > rowLimit) { NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease]; [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Continue to print?", @"continue to print message") defaultButton:NSLocalizedString(@"Print", @"print button") alternateButton:NSLocalizedString(@"Cancel", @"cancel button") otherButton:nil informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to print the current content view of the table '%@'?\n\nIt currently contains %@ rows, which may take a significant amount of time to print.", @"continue to print informative message"), [self table], [numberFormatter stringFromNumber:[NSNumber numberWithLongLong:resultRows]]]]; NSArray *buttons = [alert buttons]; // Change the alert's cancel button to have the key equivalent of return [[buttons objectAtIndex:0] setKeyEquivalent:@"p"]; [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; [alert beginSheetModalForWindow:[self parentWindow] modalDelegate:self didEndSelector:@selector(printWarningDidEnd:returnCode:contextInfo:) contextInfo:NULL]; return; } } [self startPrintDocumentOperation]; } /** * Called when the print warning dialog is dismissed. */ - (void)printWarningDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { if (returnCode == NSAlertDefaultReturn) { [self startPrintDocumentOperation]; } } /** * Starts tge print document operation by spawning a new thread if required. */ - (void)startPrintDocumentOperation { [self startTaskWithDescription:NSLocalizedString(@"Generating print document...", @"generating print document status message")]; BOOL isTableInformation = ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 3); if ([NSThread isMainThread]) { printThread = [[NSThread alloc] initWithTarget:self selector:(isTableInformation) ? @selector(generateTableInfoHTMLForPrinting) : @selector(generateHTMLForPrinting) object:nil]; [self enableTaskCancellationWithTitle:NSLocalizedString(@"Cancel", @"cancel button") callbackObject:self callbackFunction:@selector(generateHTMLForPrintingCallback)]; [printThread start]; } else { (isTableInformation) ? [self generateTableInfoHTMLForPrinting] : [self generateHTMLForPrinting]; } } /** * HTML generation thread callback method. */ - (void)generateHTMLForPrintingCallback { [self setTaskDescription:NSLocalizedString(@"Cancelling...", @"cancelling task status message")]; // Cancel the print thread [printThread cancel]; } /** * Loads the supplied HTML string in the print WebView. */ - (void)loadPrintWebViewWithHTMLString:(NSString *)HTMLString { [[printWebView mainFrame] loadHTMLString:HTMLString baseURL:nil]; if (printThread) [printThread release]; } /** * Generates the HTML for the current view that is being printed. */ - (void)generateHTMLForPrinting { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Set up template engine with your chosen matcher MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; NSMutableDictionary *connection = [self connectionInformation]; NSString *heading = @""; NSArray *rows, *indexes, *indexColumns = nil; NSArray *columns = [self columnNames]; NSMutableDictionary *printData = [NSMutableDictionary dictionary]; // Table source view if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 0) { NSDictionary *tableSource = [tableSourceInstance tableSourceForPrinting]; SPExportMode tableType = [tablesListInstance tableType]; switch (tableType) { case SPTableTypeTable: heading = NSLocalizedString(@"Table Structure", @"table structure print heading"); break; case SPTableTypeView: heading = NSLocalizedString(@"View Structure", @"view structure print heading"); break; } rows = [[NSArray alloc] initWithArray: [[tableSource objectForKey:@"structure"] objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"structure"] count] - 1)]] ]; indexes = [[NSArray alloc] initWithArray: [[tableSource objectForKey:@"indexes"] objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"indexes"] count] - 1)]] ]; indexColumns = [[tableSource objectForKey:@"indexes"] objectAtIndex:0]; [printData setObject:rows forKey:@"rows"]; [printData setObject:indexes forKey:@"indexes"]; [printData setObject:indexColumns forKey:@"indexColumns"]; if ([indexes count]) [printData setObject:[NSNumber numberWithInteger:1] forKey:@"hasIndexes"]; [rows release]; [indexes release]; } // Table content view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1) { NSArray *data = [tableContentInstance currentDataResult]; heading = NSLocalizedString(@"Table Content", @"table content print heading"); rows = [[NSArray alloc] initWithArray: [data objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] ]; [printData setObject:rows forKey:@"rows"]; [connection setValue:[tableContentInstance usedQuery] forKey:@"query"]; [rows release]; } // Custom query view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 2) { NSArray *data = [customQueryInstance currentResult]; heading = NSLocalizedString(@"Query Result", @"query result print heading"); rows = [[NSArray alloc] initWithArray: [data objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] ]; [printData setObject:rows forKey:@"rows"]; [connection setValue:[customQueryInstance usedQuery] forKey:@"query"]; [rows release]; } // Table relations view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 4) { NSArray *data = [tableRelationsInstance relationDataForPrinting]; heading = NSLocalizedString(@"Table Relations", @"toolbar item label for switching to the Table Relations tab"); rows = [[NSArray alloc] initWithArray: [data objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] ]; [printData setObject:rows forKey:@"rows"]; [rows release]; } // Table triggers view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 5) { NSArray *data = [tableTriggersInstance triggerDataForPrinting]; heading = NSLocalizedString(@"Table Triggers", @"toolbar item label for switching to the Table Triggers tab"); rows = [[NSArray alloc] initWithArray: [data objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] ]; [printData setObject:rows forKey:@"rows"]; [rows release]; } [engine setObject:connection forKey:@"c"]; [printData setObject:heading forKey:@"heading"]; [printData setObject:columns forKey:@"columns"]; [printData setObject:([prefs boolForKey:SPUseMonospacedFonts]) ? SPDefaultMonospacedFontName : @"Lucida Grande" forKey:@"font"]; [printData setObject:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? @"1px solid #CCCCCC" : @"none" forKey:@"gridlines"]; NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLPrintTemplate ofType:@"html"] withVariables:printData]; // Check if the operation has been cancelled if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { [self endTask]; [pool drain]; [NSThread exit]; return; } [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; [pool drain]; } /** * Generates the HTML for the table information view that is to be printed. */ - (void)generateTableInfoHTMLForPrinting { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Set up template engine with your chosen matcher MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; NSMutableDictionary *connection = [self connectionInformation]; NSMutableDictionary *printData = [NSMutableDictionary dictionary]; NSString *heading = NSLocalizedString(@"Table Information", @"table information print heading"); [engine setObject:connection forKey:@"c"]; [engine setObject:[extendedTableInfoInstance tableInformationForPrinting] forKey:@"i"]; [printData setObject:heading forKey:@"heading"]; [printData setObject:[[NSUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPCustomQueryEditorFont]] fontName] forKey:@"font"]; NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLTableInfoPrintTemplate ofType:@"html"] withVariables:printData]; // Check if the operation has been cancelled if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { [self endTask]; [pool drain]; [NSThread exit]; return; } [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; [pool drain]; } /** * Returns an array of columns for whichever view is being printed. */ - (NSArray *)columnNames { NSArray *columns = nil; // Table source view if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 0 && [[tableSourceInstance tableSourceForPrinting] count] > 0) { columns = [[NSArray alloc] initWithArray:[[[tableSourceInstance tableSourceForPrinting] objectForKey:@"structure"] objectAtIndex:0] copyItems:YES]; } // Table content view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 1 && [[tableContentInstance currentResult] count] > 0) { columns = [[NSArray alloc] initWithArray:[[tableContentInstance currentResult] objectAtIndex:0] copyItems:YES]; } // Custom query view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 2 && [[customQueryInstance currentResult] count] > 0) { columns = [[NSArray alloc] initWithArray:[[customQueryInstance currentResult] objectAtIndex:0] copyItems:YES]; } // Table relations view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 4 && [[tableRelationsInstance relationDataForPrinting] count] > 0) { columns = [[NSArray alloc] initWithArray:[[tableRelationsInstance relationDataForPrinting] objectAtIndex:0] copyItems:YES]; } // Table triggers view else if ([tableTabView indexOfTabViewItem:[tableTabView selectedTabViewItem]] == 5 && [[tableTriggersInstance triggerDataForPrinting] count] > 0) { columns = [[NSArray alloc] initWithArray:[[tableTriggersInstance triggerDataForPrinting] objectAtIndex:0] copyItems:YES]; } if (columns) [columns autorelease]; return columns; } /** * Generates a dictionary of connection information that is used for printing. */ - (NSMutableDictionary *)connectionInformation { NSString *versionForPrint = [NSString stringWithFormat:@"%@ %@ (%@ %@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"], NSLocalizedString(@"build", @"build label"), [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] ]; NSMutableDictionary *connection = [NSMutableDictionary dictionary]; if ([[self user] length]) { [connection setValue:[self user] forKey:@"username"]; } if ([[self table] length]) { [connection setValue:[self table] forKey:@"table"]; } if ([connectionController port] && [[connectionController port] length]) { [connection setValue:[connectionController port] forKey:@"port"]; } [connection setValue:[self host] forKey:@"hostname"]; [connection setValue:selectedDatabase forKey:@"database"]; [connection setValue:versionForPrint forKey:@"version"]; return connection; } @end