// Copyright 2003 - 2004 Nathan Hamblen
// Modified BSD license:
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import "FillController.h"
#import "MyDocument.h"
// Controller for the dynmaic location fill in windows. The fill in windows are a read-only view of the dynamic location's data (a url with parameters) where users can specify values for those parameters and then open the location. Though the primary data is read-only, there is metadata specific to the fill in window of each dynamic location that is saved whenever the window closes: window position, window width, and the parameter history. Metadata is invisibly saved using NSDocument standard methods; the Save menus and keyboard commands are disabled.
@implementation FillController
#pragma mark Initilization
- (id)init
{
self = [super initWithWindowNibName:@"Fill"];
return self;
}
// Set up the window's contents, size, and position based firstly on the specified dynamic location and secondly on the saved metadata.
- (void)windowDidLoad
{
float heightBefore, heightAfter;
NSWindow *wind = [self window];
NSRect frame = [wind frame];
NSArray *urlParams;
NSEnumerator *params;
NSString *param;
MyDocument *doc = [self document];
NSArray *origin = [[doc specificPreferences] objectForKey:@"Origin"];
NSNumber *width = [[doc specificPreferences] objectForKey:@"Width"];
NSNumber *stayOpenPref = [[doc specificPreferences] objectForKey:@"StayOpen"];
NSDictionary *formHistory =
[[doc specificPreferences] objectForKey:@"FormHistory"];
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
NSEnumerator *formCells;
CompletingFormCell *currCell;
if (![doc dynamicLocation])
{
NSLog(@"Dynamic URL fill-in with no initial URL.");
return;
}
urlParams = [[doc dynamicLocation] parameterNameArray];
if (!urlParams || [urlParams count] < 1)
{
NSLog(@"Invalid dynamic location string.");
return;
}
// remember the height of the form specified in the resource
heightBefore = ([mInputForm frame]).size.height;
// NSForm adds one row all on its own, which we need to get rid of
[mInputForm removeRow:0];
params = [urlParams objectEnumerator];
while (param = [params nextObject])
[mInputForm addEntry:[param stringByAppendingString:@":"]];
[mInputForm setTitleAlignment:NSRightTextAlignment];
[mInputForm sizeToFit];
heightAfter = ([mInputForm frame]).size.height;
// use the origin from the metadata, if available
if (origin) {
[self setShouldCascadeWindows:NO];
frame.origin.x = [[origin objectAtIndex:0] floatValue];
frame.origin.y = [[origin objectAtIndex:1] floatValue];
}
// adjust the height of the window by the change in height of the form alone. The button controls at the bottom of the window are set to properly track the window size.
frame.size.height = frame.size.height - heightBefore + heightAfter;
// sets equal min/max values for the height, and leaves the width adjustable (see setMaxSize below)
[wind setMinSize:frame.size];
if (width)
frame.size.width = [width floatValue];
// finally effects the change
[wind setFrame:frame display:YES];
frame.size.width = FLT_MAX;
[wind setMaxSize:frame.size];
// used saved "Stay open" state if avail
[mStayOpenToggle setState:stayOpenPref ? [stayOpenPref intValue] : NSOffState];
// work through the form and set up the history and current values based on metadata
formCells = [[mInputForm cells] objectEnumerator];
while (currCell = [formCells nextObject])
{
NSArray *history =
(formHistory) ? [formHistory objectForKey:[currCell title]] : nil;
NSString *lastValue = (history && [history count]) ?
[history objectAtIndex:0] : @"";
[currCell setObjectValue:lastValue];
if (history)
[currCell addItemsWithObjectValues:history];
}
// These are both needed to save the metadata when closing the fill in window
[noteCenter addObserver:self selector:@selector(updateDocument:)
name:@"NeedsUpdate" object:[self document]];
[noteCenter addObserver:self selector:@selector(handleClose:)
name:@"ClosingDocument" object:[self document]];
}
#pragma mark Record view metadata
// Set metadata for this view into the document object, which is responsible for saving it.
- (void)updateDocument:(NSNotification *)notification
{
// [self document] will have expired if a close notification triggered this update, so use the doc object passed in the notification.
MyDocument *doc = [notification object];
NSMutableDictionary* prefs = [doc specificPreferences];
NSRect frame = [[self window] frame];
NSArray *coords = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:frame.origin.x],
[NSNumber numberWithFloat:frame.origin.y],
nil];
NSNumber *width = [NSNumber numberWithFloat:frame.size.width];
NSEnumerator *formCells = [[mInputForm cells] objectEnumerator];
NSMutableDictionary *formHistory = [[NSMutableDictionary alloc] init];
CompletingFormCell *currCell;
[prefs setObject:coords forKey:@"Origin"];
[prefs setObject:width forKey:@"Width"];
[prefs setObject:[NSNumber numberWithInt:[mStayOpenToggle state]]
forKey:@"StayOpen"];
while (currCell = [formCells nextObject])
[formHistory setObject:[currCell objectValues] forKey:[currCell title]];
[prefs setObject:formHistory forKey:@"FormHistory"];
[formHistory release];
}
// Sent by MyDocument's overridden close method. Some things have already been dismantled at this point, like [self document], but the doc object itself is intact and can be updated and told to save itself.
- (void)handleClose:(NSNotification *)notification
{
// [self document] has already expired
MyDocument *doc = [notification object];
[doc saveDocument:self];
}
#pragma mark Disable menu items
// Prevent the user from saving the document manually, since we do it automatically and invisibly when the window closes.
- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
if ([anItem action] == @selector(saveDocument:)
|| [anItem action] == @selector(saveDocumentAs:)
|| [anItem action] == @selector(revertDocumentToSaved:))
return NO;
return YES;
}
#pragma mark Respond to UI events
// Fills in the dynamic location parameters with data in the input form and opens the completed URL in the default web browser. Closes the fill in window in "Stay Open" is not checked.
- (IBAction)goToLocation:(id)sender
{
MyDocument *doc = [self document];
unsigned int count = [mInputForm numberOfRows], i;
NSString *filledUrl;
NSMutableArray *paramAry = [NSMutableArray arrayWithCapacity:count];
for (i=0; i < count; i++)
{
CompletingFormCell *cell = [mInputForm cellAtIndex:i];
NSString* val = [cell stringValue];
NSString* escapedVal = (NSString*)
CFURLCreateStringByAddingPercentEscapes(nil, (CFStringRef)val, nil,
nil, kCFStringEncodingUTF8);
[cell insertItemWithObjectValue:val atIndex:0];
[paramAry addObject:escapedVal];
}
filledUrl = [[doc dynamicLocation] stringFilledWithParameters:paramAry];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:filledUrl]];
if ([mStayOpenToggle state] == NSOffState)
// this will, of course, trigger saving the metadata
[self close];
else
// nice to have that first item selected when the user returns to this app.
[mInputForm selectTextAtIndex:0];
}
// This will close this window and open the window for editing the primary data (the URL / dynamic location). The close will trigger a metadata save, though that
- (IBAction)editLocation:(id)sender
{
MyDocument *doc = [self document];
[doc switchToFillin:NO];
}
#pragma mark Deallocation
- (void)dealloc
{
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter removeObserver:self];
[super dealloc];
}
@end