#include #include #include #import "ZoomSkein.h" #import "ZoomMetadata.h" #import "ZoomBabel.h" /* ----------------------------------------------------------------------------- Generate a preview for file This function's job is to create preview for designated file ----------------------------------------------------------------------------- */ static NSString* zoomConfigDirectory() { NSArray* libraryDirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSEnumerator* libEnum; NSString* libDir; libEnum = [libraryDirs objectEnumerator]; while (libDir = [libEnum nextObject]) { BOOL isDir; NSString* zoomLib = [[libDir stringByAppendingPathComponent: @"Preferences"] stringByAppendingPathComponent: @"uk.org.logicalshift.zoom"]; if ([[NSFileManager defaultManager] fileExistsAtPath: zoomLib isDirectory: &isDir]) { if (isDir) { return zoomLib; } } } libEnum = [libraryDirs objectEnumerator]; while (libDir = [libEnum nextObject]) { NSString* zoomLib = [[libDir stringByAppendingPathComponent: @"Preferences"] stringByAppendingPathComponent: @"uk.org.logicalshift.zoom"]; if ([[NSFileManager defaultManager] createDirectoryAtPath: zoomLib attributes:nil]) { return zoomLib; } } return nil; } OSStatus GeneratePreviewForBabel(void *thisInterface, QLPreviewRequestRef preview, CFURLRef cfUrl, CFStringRef contentTypeUTI, CFDictionaryRef options) { // Can't deal with file URLs. NSURL* url = (NSURL*)cfUrl; if (![url isFileURL]) return noErr; // Get the metadata for the story ZoomBabel* babel = [[[ZoomBabel alloc] initWithFilename: [url path]] autorelease]; ZoomStory* story = [babel metadata]; ZoomStoryID* storyID = [story storyID]; NSImage* image = [babel coverImage]; if (image == nil) { // If there's no image, then we need to use a default one image = [[[NSImage alloc] initWithContentsOfFile: [[NSBundle bundleWithIdentifier: @"uk.org.logicalshift.zoom.save.quicklook"] pathForResource: @"zoom-game" ofType: @"icns"]] autorelease]; } // Try to use babel to work out the story ID, if we have no metadata if (!story || !storyID) { storyID = [babel storyID]; } // Give up if the ID is still nil if (storyID == nil) { return noErr; } // Try to load Zoom's built-in metadata if we can ZoomMetadata* metadata = nil; NSData* userData = [NSData dataWithContentsOfFile: [zoomConfigDirectory() stringByAppendingPathComponent: @"metadata.iFiction"]]; if (userData) metadata = [[[ZoomMetadata alloc] initWithData: userData] autorelease]; if (metadata) { story = [metadata containsStoryWithIdent: storyID]?[metadata findOrCreateStory: storyID]:story; } // If there's no metadata returned, then give up if (story == nil) return; // Generate an attributed string describing the story NSFont* titleFont = [NSFont boldSystemFontOfSize: 24]; NSFont* descriptionFont = [NSFont systemFontOfSize: 11]; NSFont* smallFont = [NSFont systemFontOfSize: 10]; NSFont* ifidFont = [NSFont boldSystemFontOfSize: 9]; NSColor* foreground = [NSColor whiteColor]; NSColor* background = [NSColor clearColor]; NSDictionary* titleAttr = [NSDictionary dictionaryWithObjectsAndKeys: titleFont, NSFontAttributeName, foreground, NSForegroundColorAttributeName, background, NSBackgroundColorAttributeName, nil]; NSDictionary* smallAttr = [NSDictionary dictionaryWithObjectsAndKeys: smallFont, NSFontAttributeName, foreground, NSForegroundColorAttributeName, background, NSBackgroundColorAttributeName, nil]; NSDictionary* ifidAttr = [NSDictionary dictionaryWithObjectsAndKeys: ifidFont, NSFontAttributeName, foreground, NSForegroundColorAttributeName, background, NSBackgroundColorAttributeName, nil]; NSDictionary* descrAttr = [NSDictionary dictionaryWithObjectsAndKeys: descriptionFont, NSFontAttributeName, foreground, NSForegroundColorAttributeName, background, NSBackgroundColorAttributeName, nil]; NSMutableAttributedString* description = [[[NSMutableAttributedString alloc] init] autorelease]; if ([story title]) { [description appendAttributedString: [[[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"%@\n", [story title]] attributes: titleAttr] autorelease]]; } [description appendAttributedString: [[[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"IFID: %@\n", [storyID description]] attributes: ifidAttr] autorelease]]; if ([story author] && [[story author] length] > 0) { NSString* publication = @""; if ([story year] > 0) { publication = [NSString stringWithFormat: @", published %i", [story year]]; } [description appendAttributedString: [[[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"by %@%@\n", [story author], publication] attributes: smallAttr] autorelease]]; } if ([story description] && [[story description] length] > 0) { [description appendAttributedString: [[[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"\n%@", [story description]] attributes: descrAttr] autorelease]]; } else if ([story teaser] && [[story teaser] length] > 0) { [description appendAttributedString: [[[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"\n%@", [story teaser]] attributes: descrAttr] autorelease]]; } // Decide on the size of the graphics context CGSize previewSize; previewSize.width = 760; previewSize.height = 320; NSRect descriptionRect = [description boundingRectWithSize: NSMakeSize((previewSize.width - previewSize.height) - 16, 1e8) options: 0]; if (image != nil) { previewSize.height = [image size].height; if (previewSize.height < 180) previewSize.height = 180; if (previewSize.height < descriptionRect.size.height + 32) previewSize.height = descriptionRect.size.height + 32; if (previewSize.height > 320) previewSize.height = 320; previewSize.width *= previewSize.height / 320.0; if (previewSize.width < 560) previewSize.width = 560; } // Create a graphics context to draw into CGContextRef cgContext = QLPreviewRequestCreateContext(preview, previewSize, false, NULL); NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort: cgContext flipped: NO]; // Start drawing [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext: context]; [context setImageInterpolation: NSImageInterpolationHigh]; // Draw the image NSRect imageRect = NSMakeRect(8,8, 0,0); if (image) { NSSize imageSize = [image size]; imageRect = NSMakeRect(8,8, previewSize.height - 16, previewSize.height - 16); float ratio = imageSize.height / imageSize.width; if (ratio < 1) { imageRect.size.height *= ratio; } else { double oldWidth = imageRect.size.width; imageRect.size.width /= ratio; } [image drawInRect: imageRect fromRect: NSMakeRect(0,0, imageSize.width, imageSize.height) operation: NSCompositeSourceOver fraction: 1.0]; } // Draw the description NSRect descRect = NSMakeRect(imageRect.size.width + 24, 8, (previewSize.width - previewSize.height) - 16, previewSize.height - 16); [description drawInRect: descRect]; // Done with the drawing [NSGraphicsContext restoreGraphicsState]; // Finish up with the context QLPreviewRequestFlushContext(preview, cgContext); CFRelease(cgContext); return noErr; } OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef cfUrl, CFStringRef contentTypeUTI, CFDictionaryRef options) { NSURL* url = (NSURL*)cfUrl; NSData* skeinData = nil; ZoomSkein* skein = nil; ZoomStoryID* storyID = nil; // Read the data for this file if ([(NSString*)contentTypeUTI isEqualToString: @"uk.org.logicalshift.zoomsave"]) { // .zoomsave package // Read in the skein NSURL* skeinUrl = [NSURL URLWithString: [[url absoluteString] stringByAppendingString: @"/Skein.skein"]]; skeinData = [NSData dataWithContentsOfURL: skeinUrl]; // Work out the story ID NSURL* plistUrl = [NSURL URLWithString: [[url absoluteString] stringByAppendingString: @"/Info.plist"]]; NSData* plist = [NSData dataWithContentsOfURL: plistUrl]; if (plist != nil) { NSDictionary* plistDict = [NSPropertyListSerialization propertyListFromData: plist mutabilityOption: NSPropertyListImmutable format: nil errorDescription: nil]; NSString* idString = [plistDict objectForKey: @"ZoomStoryId"]; if (idString != nil) { storyID = [[[ZoomStoryID alloc] initWithIdString: idString] autorelease]; } } } else if ([(NSString*)contentTypeUTI isEqualToString: @"uk.org.logicalshift.glksave"]) { // .glksave package // Read in the skein NSURL* skeinUrl = [NSURL URLWithString: [[url absoluteString] stringByAppendingString: @"/Skein.skein"]]; skeinData = [NSData dataWithContentsOfURL: skeinUrl]; // Work out the story ID NSURL* plistUrl = [NSURL URLWithString: [[url absoluteString] stringByAppendingString: @"/Info.plist"]]; NSData* plist = [NSData dataWithContentsOfURL: plistUrl]; if (plist != nil) { NSDictionary* plistDict = [NSPropertyListSerialization propertyListFromData: plist mutabilityOption: NSPropertyListImmutable format: nil errorDescription: nil]; NSString* idString = [plistDict objectForKey: @"ZoomGlkGameId"]; if (idString != nil) { storyID = [[[ZoomStoryID alloc] initWithIdString: idString] autorelease]; } } } else { // Generate a babel preview return GeneratePreviewForBabel(thisInterface, preview, cfUrl, contentTypeUTI, options); } // Try to parse the skein if (skeinData) { skein = [[[ZoomSkein alloc] init] autorelease]; if (![skein parseXmlData: skeinData]) { skein = nil; } } // If we've got a skein, then generate an attributed string to represent the transcript of play if (skein && [skein activeItem]) { NSMutableAttributedString* result = [[[NSMutableAttributedString alloc] init] autorelease]; ZoomSkeinItem* activeItem = [skein activeItem]; // Set up the attributes for the fonts NSFont* transcriptFont = [[NSFontManager sharedFontManager] fontWithFamily: @"Gill Sans" traits: NSUnboldFontMask weight: 5 size: 12]; NSFont* inputFont = [[NSFontManager sharedFontManager] fontWithFamily: @"Gill Sans" traits: NSBoldFontMask weight: 9 size: 12]; NSFont* titleFont = [[NSFontManager sharedFontManager] fontWithFamily: @"Gill Sans" traits: NSBoldFontMask weight: 9 size: 18]; if (!transcriptFont) transcriptFont = [NSFont systemFontOfSize: 12]; if (!inputFont) inputFont = [NSFont systemFontOfSize: 12]; if (!titleFont) titleFont = [NSFont boldSystemFontOfSize: 12]; NSDictionary* transcriptAttributes = [NSDictionary dictionaryWithObjectsAndKeys: transcriptFont, NSFontAttributeName, nil]; NSDictionary* inputAttributes = [NSDictionary dictionaryWithObjectsAndKeys: inputFont, NSFontAttributeName, nil]; NSDictionary* titleAttributes = [NSDictionary dictionaryWithObjectsAndKeys: titleFont, NSFontAttributeName, nil]; NSAttributedString* newline = [[[NSAttributedString alloc] initWithString: @"\n" attributes: transcriptAttributes] autorelease]; // Build the transcript while (activeItem != nil) { // Append this string NSAttributedString* inputString = nil; NSAttributedString* responseString = nil; if ([activeItem command]) { inputString = [[NSAttributedString alloc] initWithString: [activeItem command] attributes: inputAttributes]; } if ([activeItem result]) { responseString = [[NSAttributedString alloc] initWithString: [activeItem result] attributes: transcriptAttributes]; } if (responseString) { [result insertAttributedString: responseString atIndex: 0]; } if (inputString && [activeItem parent]) { [result insertAttributedString: newline atIndex: 0]; [result insertAttributedString: inputString atIndex: 0]; } // Finish up [inputString release]; [responseString release]; // Move up the tree activeItem = [activeItem parent]; } // Add a title indicating which game this came from if (storyID) { // Write out the story ID [result insertAttributedString: newline atIndex: 0]; [result insertAttributedString: newline atIndex: 0]; [result insertAttributedString: [[[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"IFID: %@", [storyID description]] attributes: inputAttributes] autorelease] atIndex: 0]; // Try to read the metadata for this story, if there is any ZoomMetadata* metadata = nil; ZoomStory* story = nil; NSData* userData = [NSData dataWithContentsOfFile: [zoomConfigDirectory() stringByAppendingPathComponent: @"metadata.iFiction"]]; if (userData) metadata = [[[ZoomMetadata alloc] initWithData: userData] autorelease]; if (metadata) { story = [metadata containsStoryWithIdent: storyID]?[metadata findOrCreateStory: storyID]:nil; } if (story && [[story title] length] > 0) { [result insertAttributedString: newline atIndex: 0]; [result insertAttributedString: [[[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"Saved game from %@", [story title]] attributes: titleAttributes] autorelease] atIndex: 0]; } } // Set the quicklook data NSData *theRTF = [result RTFFromRange:NSMakeRange(0, [result length]-1) documentAttributes:nil]; QLPreviewRequestSetDataRepresentation(preview, (CFDataRef)theRTF, kUTTypeRTF, NULL); } return noErr; } void CancelPreviewGeneration(void* thisInterface, QLPreviewRequestRef preview) { // implement only if supported }