diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb549ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.pbxuser +*.mode1v3 +SproutedInterface.framework +SproutedUtilities.framework +build \ No newline at end of file diff --git a/ABBarSmall.png b/ABBarSmall.png new file mode 100644 index 0000000..acc681b Binary files /dev/null and b/ABBarSmall.png differ diff --git a/ActualSize.png b/ActualSize.png new file mode 100644 index 0000000..5d764c4 Binary files /dev/null and b/ActualSize.png differ diff --git a/ActualSizePressed.png b/ActualSizePressed.png new file mode 100644 index 0000000..001c78f Binary files /dev/null and b/ActualSizePressed.png differ diff --git a/AppleScriptActionBarSmall.png b/AppleScriptActionBarSmall.png new file mode 100644 index 0000000..8853c20 Binary files /dev/null and b/AppleScriptActionBarSmall.png differ diff --git a/AppleScriptAlert.h b/AppleScriptAlert.h new file mode 100644 index 0000000..1ab6c8b --- /dev/null +++ b/AppleScriptAlert.h @@ -0,0 +1,23 @@ +// +// AppleScriptAlert.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface AppleScriptAlert : NSWindowController +{ + IBOutlet NSTextView *errorView; + IBOutlet NSTextView *sourceView; + + id source; + id error; +} + +- (id) initWithSource:(id)source error:(id)error; + +@end diff --git a/AppleScriptAlert.m b/AppleScriptAlert.m new file mode 100644 index 0000000..81d3d82 --- /dev/null +++ b/AppleScriptAlert.m @@ -0,0 +1,66 @@ +// +// AppleScriptAlert.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation AppleScriptAlert + +- (id) initWithSource:(id)theSource error:(id)theError +{ + if ( self = [super initWithWindowNibName:@"AppleScriptAlert"] ) + { + source = [theSource retain]; + error = [theError retain]; + + [self retain]; + } + + return self; +} + +- (void) windowDidLoad +{ + if ( [source isKindOfClass:[NSString class]] ) + [sourceView setString:source]; + + else if ( [source isKindOfClass:[NSAttributedString class]] ) + { + [[sourceView textStorage] beginEditing]; + [[sourceView textStorage] setAttributedString:source]; + [[sourceView textStorage] endEditing]; + } + + if ( error == nil ) + [errorView setString:[NSString string]]; + else + [errorView setString:[error description]]; +} + +- (void) dealloc +{ + [source release]; + [error release]; + + [super dealloc]; +} + +- (void) windowWillClose:(NSNotification*)aNotification +{ + [self autorelease]; +} + +#pragma mark - + +- (IBAction) showWindow:(id)sender +{ + [[self window] center]; + [super showWindow:sender]; +} + +@end diff --git a/Back.tif b/Back.tif new file mode 100644 index 0000000..19bb116 Binary files /dev/null and b/Back.tif differ diff --git a/BackDisabled.tif b/BackDisabled.tif new file mode 100644 index 0000000..f5c0adf Binary files /dev/null and b/BackDisabled.tif differ diff --git a/BackPressed.tif b/BackPressed.tif new file mode 100644 index 0000000..8cde6cd Binary files /dev/null and b/BackPressed.tif differ diff --git a/Bigger.png b/Bigger.png new file mode 100644 index 0000000..379d064 Binary files /dev/null and b/Bigger.png differ diff --git a/BiggerDisabled.png b/BiggerDisabled.png new file mode 100644 index 0000000..2fa0ce9 Binary files /dev/null and b/BiggerDisabled.png differ diff --git a/BiggerPressed.png b/BiggerPressed.png new file mode 100644 index 0000000..bba7aee Binary files /dev/null and b/BiggerPressed.png differ diff --git a/CollectionManagerView.h b/CollectionManagerView.h new file mode 100644 index 0000000..7852170 --- /dev/null +++ b/CollectionManagerView.h @@ -0,0 +1,25 @@ + +// +// CollectionManagerView.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface CollectionManagerView : NSView +{ + BOOL bordered; + int numConditions; +} + +- (int) numConditions; +- (void) setNumConditions:(int)num; + +- (BOOL) bordered; +- (void) setBordered:(BOOL)drawsBorder; + +@end \ No newline at end of file diff --git a/CollectionManagerView.m b/CollectionManagerView.m new file mode 100644 index 0000000..e3f6fb9 --- /dev/null +++ b/CollectionManagerView.m @@ -0,0 +1,80 @@ + +// +// CollectionManagerView.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation CollectionManagerView + +- (id)initWithFrame:(NSRect)frameRect +{ + if ((self = [super initWithFrame:frameRect]) != nil) { + // Add initialization code here + bordered = YES; + } + return self; +} + +#pragma mark - + +- (int) numConditions +{ + return numConditions; +} + +- (void) setNumConditions:(int)num +{ + numConditions = num; +} + +- (BOOL) bordered +{ + return bordered; +} + +- (void) setBordered:(BOOL)drawsBorder +{ + bordered = drawsBorder; +} + +#pragma mark - + +- (BOOL) isFlipped +{ + return YES; +} + +- (void)drawRect:(NSRect)rect +{ + int i; + NSRect bds = [self bounds]; + + for ( i = 0; i < [self numConditions]; i++ ) + { + if ( i % 2 == 0 ) + { + [[NSColor whiteColor] set]; + NSRectFillUsingOperation(NSMakeRect( 0, (kConditionViewHeight*i), bds.size.width, kConditionViewHeight), NSCompositeSourceOver); + } + else + { + [[[NSColor controlAlternatingRowBackgroundColors] objectAtIndex:1] set]; + NSRectFillUsingOperation(NSMakeRect( 0, (kConditionViewHeight*i), bds.size.width, kConditionViewHeight), NSCompositeSourceOver); + } + } + + if ( [self bordered] ) + { + [[NSColor lightGrayColor] set]; + NSFrameRect(bds); + } +} + +@end diff --git a/ComposeMailBarSmall.png b/ComposeMailBarSmall.png new file mode 100644 index 0000000..b1a4fb1 Binary files /dev/null and b/ComposeMailBarSmall.png differ diff --git a/ConditionController.h b/ConditionController.h new file mode 100644 index 0000000..34d4dd0 --- /dev/null +++ b/ConditionController.h @@ -0,0 +1,98 @@ +// +// ConditionController.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#define kConditionViewHeight 26 + +#define PDConditionContains 0 +#define PDConditionNotContains 1 +#define PDConditionBeginsWith 2 +#define PDConditionEndsWith 3 +#define PDConditionIs 4 + +#define PDConditionBefore 0 +#define PDConditionAfter 1 +#define PDConditionBetween 2 +#define PDConditionInTheLast 3 +#define PDConditionInTheNext 4 + +#define PDConditionDay 0 +#define PDConditionWeek 1 +#define PDConditionMonth 2 + +@interface ConditionController : NSObject +{ + IBOutlet NSView *conditionView; + IBOutlet NSView *specifiedConditionView; + IBOutlet NSPopUpButton *keyPop; + + IBOutlet NSButton *removeButton; + + int tag; + id target; + + BOOL _allowsEmptyCondition; + BOOL _autogeneratesDynamicDates; + BOOL _sendsLiveUpdate; + +} + +- (id) initWithTarget:(id)anObject; +- (void) setInitialPredicate:(NSPredicate*)aPredicate; +- (void) setInitialCondition:(NSString*)condition; + +- (NSView*) conditionView; +- (NSPredicate*) predicate; +- (NSString*) predicateString; + +- (int) tag; +- (void) setTag:(int)newTag; + +- (id) target; +- (void) setTarget:(id)anObject; + +- (BOOL) sendsLiveUpdate; +- (void) setSendsLiveUpdate:(BOOL)updates; + +- (BOOL) autogeneratesDynamicDates; +- (void) setAutogeneratesDynamicDates:(BOOL)autogenerate; + +- (BOOL) allowsEmptyCondition; +- (void) setAllowsEmptyCondition:(BOOL)allowsEmpty; + +- (BOOL) removeButtonEnabled; +- (void) setRemoveButtonEnabled:(BOOL)enabled; + +- (IBAction) addCondition:(id)sender; +- (IBAction) removeCondition:(id)sender; +- (IBAction) changeCondition:(id)sender; +- (IBAction) changeConditionKey:(id)sender; + +- (id) selectableView; +- (void) appropriateFirstResponder:(NSWindow*)aWindow; +- (void) removeFromSuper; + +- (void) _sendUpdateIfRequested; + +// utility for creating spotlight based conditions - subclasses feel free to use +- (NSString*) _spotlightConditionStringWithAttribute:(NSString*)anAttribute condition:(NSString*)aCondition operation:(int)anOperation; +- (NSString*) _spotlightConditionStringWithAttributes:(NSArray*)theAttributes condition:(NSString*)aCondition operation:(int)anOperation; + +- (BOOL) validatePredicate; +@end + + +#pragma mark - + +@interface NSObject (ConditionControllerDelegate) + +- (void) conditionDidChange:(id)condition; + +@end \ No newline at end of file diff --git a/ConditionController.m b/ConditionController.m new file mode 100644 index 0000000..e8a4f1b --- /dev/null +++ b/ConditionController.m @@ -0,0 +1,384 @@ + +// +// ConditionController.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation ConditionController + +- (id) initWithTarget:(id)anObject { + + if ( self = [super init] ) { + + //[NSBundle loadNibNamed:@"Condition" owner:self]; + + target = anObject; + tag = 0; + + _sendsLiveUpdate = YES; + _allowsEmptyCondition = NO; + _autogeneratesDynamicDates = NO; + } + + return self; +} + +- (void) dealloc { + + [conditionView release]; + conditionView = nil; + + [super dealloc]; + +} + +- (void) awakeFromNib +{ + +} + +#pragma mark - + +- (NSView*) conditionView +{ + return conditionView; +} + +#pragma mark - + +- (int) tag +{ + return tag; +} + +- (void) setTag:(int)newTag +{ + tag = newTag; +} + +- (id) target +{ + return target; +} + +- (void) setTarget:(id)anObject +{ + target = anObject; +} + +- (BOOL) sendsLiveUpdate +{ + return _sendsLiveUpdate; +} + +- (void) setSendsLiveUpdate:(BOOL)updates +{ + _sendsLiveUpdate = updates; +} + +- (BOOL) autogeneratesDynamicDates +{ + return _autogeneratesDynamicDates; +} + +- (void) setAutogeneratesDynamicDates:(BOOL)autogenerate +{ + _autogeneratesDynamicDates = autogenerate; +} + +- (BOOL) allowsEmptyCondition +{ + return _allowsEmptyCondition; +} + +- (void) setAllowsEmptyCondition:(BOOL)allowsEmpty +{ + _allowsEmptyCondition = allowsEmpty; +} + +- (BOOL) removeButtonEnabled +{ + return [removeButton isEnabled]; +} + +- (void) setRemoveButtonEnabled:(BOOL)enabled +{ + [removeButton setEnabled:enabled]; +} + +#pragma mark - + +- (void)controlTextDidChange:(NSNotification *)aNotification +{ + [self _sendUpdateIfRequested]; +} + +- (IBAction) changeCondition:(id)sender +{ + [self _sendUpdateIfRequested]; +} + +- (void) _sendUpdateIfRequested +{ + if ( [self sendsLiveUpdate] && [target respondsToSelector:@selector(conditionDidChange:)] ) + [target conditionDidChange:self]; +} + +#pragma mark - + +- (void) setInitialPredicate:(NSPredicate*)aPredicate +{ + // + // used to reproduce the saved condition + [self setInitialCondition:[aPredicate predicateFormat]]; +} + +- (void) setInitialCondition:(NSString*)condition +{ + + // + // used to reproduce the saved condition + +} + +- (NSPredicate*) predicate +{ + // + // derive a predicate from the specified condition + NSString *predicateString = [self predicateString]; + return ( predicateString != nil ? [NSPredicate predicateWithFormat:predicateString] : nil ); +} + +- (NSString*) predicateString { + + // + // build the predicate string from our current conditions + return nil; +} + +#pragma mark - + +- (IBAction)addCondition:(id)sender +{ + if ( [target respondsToSelector:@selector(addCondition:)] ) + [target performSelector:@selector(addCondition:) withObject:self]; + //[self _sendUpdateIfRequested]; +} + +- (IBAction)removeCondition:(id)sender +{ + if ( [target respondsToSelector:@selector(removeCondition:)] ) + [target performSelector:@selector(removeCondition:) withObject:self]; + //[self _sendUpdateIfRequested]; +} + +- (IBAction)changeConditionKey:(id)sender +{ + // + // remove and add superviews as needed + [self _sendUpdateIfRequested]; +} + +- (id) selectableView +{ + // + // return the a responder that may become first responder + return nil; +} + +- (void) appropriateFirstResponder:(NSWindow*)aWindow +{ + // should depend on the condition +} + +- (void) removeFromSuper +{ + [conditionView removeFromSuperviewWithoutNeedingDisplay]; +} + +#pragma mark - + +- (NSString*) _spotlightConditionStringWithAttribute:(NSString*)anAttribute condition:(NSString*)aCondition operation:(int)anOperation +{ + // handy utility method for creating text based spotlight conditions + + NSString *predicateString = nil; + NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:@"\"'"]; + + if ( [aCondition length] == 0 ) + { + if ( _allowsEmptyCondition == NO ) + return nil; + else if ( anOperation == PDConditionNotContains ) + predicateString = [NSString stringWithFormat:@"%@ != \"%@\"", anAttribute, @"^"]; + else + predicateString = [NSString stringWithFormat:@"%@ == %@", anAttribute, @"^"]; + } + + else if ( [aCondition rangeOfCharacterFromSet:charSet].location != NSNotFound ) + return nil; + + switch ( anOperation ) + { + + case PDConditionContains: + predicateString = [NSString stringWithFormat:@"%@ like[cd] \"*%@*\"", anAttribute, aCondition]; + break; + + case PDConditionNotContains: + predicateString = [NSString stringWithFormat: @"not %@ like[cd] \"*%@*\"", anAttribute, aCondition]; + break; + + case PDConditionBeginsWith: + predicateString = [NSString stringWithFormat:@"%@ like[cd] \"%@*\"", anAttribute, aCondition]; + break; + + case PDConditionEndsWith: + predicateString = [NSString stringWithFormat:@"%@ like[cd] \"*%@\"", anAttribute, aCondition]; + break; + + case PDConditionIs: + //predicateString = [NSString stringWithFormat:@"%@ ==[cd] \"%@\"", anAttribute, aCondition]; + predicateString = [NSString stringWithFormat:@"%@ like[cd] \"%@\"", anAttribute, aCondition]; + break; + + } + + return predicateString; +} + +- (NSString*) _spotlightConditionStringWithAttributes:(NSArray*)theAttributes condition:(NSString*)aCondition operation:(int)anOperation +{ + NSMutableArray *theStrings = [NSMutableArray array]; + + NSString *predicateString = nil; + NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:@"\"'"]; + + NSString *anAttribute; + NSEnumerator *enumerator = [theAttributes objectEnumerator]; + + while ( anAttribute = [enumerator nextObject] ) + { + NSString *aPredicateString = nil; + + if ( [aCondition length] == 0 ) + { + if ( _allowsEmptyCondition == NO ) + return nil; + else if ( anOperation == PDConditionNotContains ) + aPredicateString = [NSString stringWithFormat:@"%@ != \"%@\"", anAttribute, @"^"]; + else + aPredicateString = [NSString stringWithFormat:@"%@ == %@", anAttribute, @"^"]; + } + + else if ( [aCondition rangeOfCharacterFromSet:charSet].location != NSNotFound ) + return nil; + + switch ( anOperation ) + { + + case PDConditionContains: + aPredicateString = [NSString stringWithFormat:@"%@ like[cd] \"*%@*\"", anAttribute, aCondition]; + break; + + case PDConditionNotContains: + aPredicateString = [NSString stringWithFormat: @"not %@ like[cd] \"*%@*\"", anAttribute, aCondition]; + break; + + case PDConditionBeginsWith: + aPredicateString = [NSString stringWithFormat:@"%@ like[cd] \"%@*\"", anAttribute, aCondition]; + break; + + case PDConditionEndsWith: + aPredicateString = [NSString stringWithFormat:@"%@ like[cd] \"*%@\"", anAttribute, aCondition]; + break; + + case PDConditionIs: + //aPredicateString = [NSString stringWithFormat:@"%@ ==[cd] \"%@\"", anAttribute, aCondition]; + aPredicateString = [NSString stringWithFormat:@"%@ like[cd] \"%@\"", anAttribute, aCondition]; + break; + + } + + if ( aPredicateString != nil ) + [theStrings addObject:aPredicateString]; + + } + + // put them all together + if ( [aCondition length] == 0 ) + predicateString = [theStrings componentsJoinedByString:@" && "]; + else if ( anOperation == PDConditionNotContains ) + predicateString = [theStrings componentsJoinedByString:@" && "]; + else + predicateString = [theStrings componentsJoinedByString:@" || "]; + + return predicateString; + +} + +- (BOOL) validatePredicate +{ + NSLog(@"%@ %s - subclasses must override to validate their own predicate formats", [self className], _cmd); + return NO; +} + +/* +if ( [[stringConditionValue stringValue] length] == 0 ) +{ + if ( _allowsEmptyCondition == NO ) + return nil; + else if ( [[stringOperationPop selectedItem] tag] == PDConditionNotContains ) + predicateString = [NSString stringWithFormat: + @"kMDItemAuthorEmailAddresses != \"%@\" && kMDItemAuthors != \"%@\"", @"^", @"^"]; + else + predicateString = [NSString stringWithFormat: + @"kMDItemAuthorEmailAddresses == %@ && kMDItemAuthors == %@", @"^", @"^"]; +} + +else if ( [[stringConditionValue stringValue] rangeOfCharacterFromSet:charSet].location != NSNotFound ) + return nil; + +switch ( [[stringOperationPop selectedItem] tag] ) +{ + +case PDConditionContains: + predicateString = [NSString stringWithFormat: + @"kMDItemAuthorEmailAddresses like[cd] \"*%@*\" || kMDItemAuthors like[cd] \"*%@*\"", + [stringConditionValue stringValue], [stringConditionValue stringValue]]; + break; + +case PDConditionNotContains: + predicateString = [NSString stringWithFormat: + @"not kMDItemAuthorEmailAddresses like[cd] \"*%@*\" && not kMDItemAuthors like[cd] \"*%@*\"", + [stringConditionValue stringValue], [stringConditionValue stringValue]]; + break; + +case PDConditionBeginsWith: + predicateString = [NSString stringWithFormat: + @"kMDItemAuthorEmailAddresses like[cd] \"%@*\" || kMDItemAuthors like[cd] \"%@*\"", + [stringConditionValue stringValue], [stringConditionValue stringValue]]; + break; + +case PDConditionEndsWith: + predicateString = [NSString stringWithFormat: + @"kMDItemAuthorEmailAddresses like[cd] \"*%@\" || kMDItemAuthors like[cd] \"*%@\"", + [stringConditionValue stringValue], [stringConditionValue stringValue]]; + break; + +case PDConditionIs: + predicateString = [NSString stringWithFormat: + @"kMDItemAuthorEmailAddresses ==[cd] \"%@\" || kMDItemAuthors ==[cd] \"%@\"", + [stringConditionValue stringValue], [stringConditionValue stringValue]]; + break; + +} + */ + +@end diff --git a/CustomFindPanel.h b/CustomFindPanel.h new file mode 100644 index 0000000..b5eb9e1 --- /dev/null +++ b/CustomFindPanel.h @@ -0,0 +1,17 @@ +// +// CustomFindPanel.h +// SproutedInterface +// +// Created by Philip Dow on 1/7/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface CustomFindPanel : NSWindow { + +} + +@end diff --git a/CustomFindPanel.m b/CustomFindPanel.m new file mode 100644 index 0000000..054ae5c --- /dev/null +++ b/CustomFindPanel.m @@ -0,0 +1,28 @@ +// +// CustomFindPanel.m +// SproutedInterface +// +// Created by Philip Dow on 1/7/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation CustomFindPanel + +- (BOOL)canBecomeMainWindow +{ + return NO; +} + +- (void)keyDown:(NSEvent *)theEvent +{ + if ( [theEvent keyCode] == 53 ) + [self close]; + else + [super keyDown:theEvent]; +} + +@end diff --git a/Display.png b/Display.png new file mode 100644 index 0000000..9e70cc0 Binary files /dev/null and b/Display.png differ diff --git a/DisplayPressed.png b/DisplayPressed.png new file mode 100644 index 0000000..770b4f6 Binary files /dev/null and b/DisplayPressed.png differ diff --git a/DocumentMakerController.h b/DocumentMakerController.h new file mode 100644 index 0000000..e9ec226 --- /dev/null +++ b/DocumentMakerController.h @@ -0,0 +1,45 @@ +// +// DocumentMakerController.h +// SproutedInterface +// +// Created by Philip Dow on 9/28/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +extern NSString *kSproutedAppHelperURL; +extern NSString *kSproutedAppHelperFlags; +extern NSString *kSproutedAppHelperMetadata; +extern NSString *kSproutedAppHelperIcon; +extern NSString *kSproutedAppHelperEntityName; + +@interface DocumentMakerController : NSObject { + + id delegate; + id representedObject; + + NSManagedObjectContext *managedObjectContext; +} + +- (id) initWithOwner:(id)anObject managedObjectContext:(NSManagedObjectContext*)moc; + +- (int) numberOfViews; +- (NSView*) contentViewAtIndex:(int)index; +- (void) willSelectViewAtIndex:(int)index; +- (void) didSelectViewAtIndex:(int)index; +- (NSString*) titleOfViewAtIndex:(int)index; + +- (id) delegate; +- (void) setDelegate:(id)anObject; + +- (NSManagedObjectContext*) managedObjectContext; +- (void) setManagedObjectContext:(NSManagedObjectContext*)moc; + +- (id) representedObject; +- (void) setRepresentedObject:(id)anObject; + +- (NSArray*) documentDictionaries:(NSError**)error; + +@end diff --git a/DocumentMakerController.m b/DocumentMakerController.m new file mode 100644 index 0000000..4aab1db --- /dev/null +++ b/DocumentMakerController.m @@ -0,0 +1,148 @@ +// +// DocumentMakerController.m +// SproutedInterface +// +// Created by Philip Dow on 9/28/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation DocumentMakerController + +- (id) init +{ + return [self initWithOwner:nil managedObjectContext:nil]; +} + +- (id) initWithOwner:(id)anObject managedObjectContext:(NSManagedObjectContext*)moc +{ + if ( self = [super init] ) + { + // subclasses should call super's implementation + [self setDelegate:anObject]; + [self setManagedObjectContext:moc]; + } + return self; +} + +- (void) dealloc +{ + // local variables + [representedObject release]; + [managedObjectContext release]; + + [super dealloc]; +} + +#pragma mark - + +- (int) numberOfViews +{ + NSLog(@"%@ %s - **** subclasses must override ****", [self className], _cmd); + return 0; + + // subclasses must override and return the number of views/steps + // invovled in creating this particular kind of document +} + +- (NSView*) contentViewAtIndex:(int)index +{ + // subclasses should override and return the content view for the given index + // this allows subclasses to walk the user through + // the custom creation of a document however they see fit. + // the index is 1 based. 0 is where the user choose the kind of + // document they want to create. + + NSLog(@"%@ %s - **** subclasses must override ****", [self className], _cmd); + return nil; +} + +- (void) willSelectViewAtIndex:(int)index +{ + // subclasses may override to perform setup + // before the view at index is display + // index is 1 based + + return; +} + +- (void) didSelectViewAtIndex:(int)index +{ + // subclasses may override to perform postprocessing + // for example setting the new first responder, + // which is recommended. + // index is 1 based + + return; +} + +- (NSString*) titleOfViewAtIndex:(int)index +{ + // subclasses must override to provide a localized + // description for the name of the view at index. + // index is one based. + + NSLog(@"%@ %s - **** subclasses must override ****", [self className], _cmd); + return [NSString string]; +} + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate:(id)anObject +{ + delegate = anObject; +} + +- (NSManagedObjectContext*) managedObjectContext +{ + return managedObjectContext; +} + +- (void) setManagedObjectContext:(NSManagedObjectContext*)moc +{ + if ( managedObjectContext != moc ) + { + [managedObjectContext release]; + managedObjectContext = [moc retain]; + } +} + +- (id) representedObject +{ + return representedObject; +} + +- (void) setRepresentedObject:(id)anObject +{ + if ( representedObject != anObject ) + { + [representedObject release]; + representedObject = [anObject retain]; + } +} + +#pragma mark - + +- (NSArray*) documentDictionaries:(NSError**)error +{ + NSLog(@"%@ %s - **** subclasses must override ****", [self className], _cmd); + return nil; + + // return an array of dictionaries describing how the pasteboard data + // should be handled by Lex. each dictionary should include + // 1. kSproutedAppHelperURL -> NSURL, uniformly and uniquely indicating the data + // 2. kSproutedAppHelperFlags -> NSNumber, a set of flags indicating how the + // data should be handled, eg "force copy" + // 3. kSproutedAppHelperMetadata -> NSDictionary, contains key-value pairs + // of Spotlight metadata attributes + // + // These keys are defined in the plugin framework +} + +@end diff --git a/DragView.h b/DragView.h new file mode 100644 index 0000000..6159db6 --- /dev/null +++ b/DragView.h @@ -0,0 +1,17 @@ +// +// DragView.h +// SproutedInterface +// +// Created by Philip Dow on 9/14/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface DragView : NSView { + +} + +@end diff --git a/DragView.m b/DragView.m new file mode 100644 index 0000000..cdd834d --- /dev/null +++ b/DragView.m @@ -0,0 +1,30 @@ +// +// DragView.m +// SproutedInterface +// +// Created by Philip Dow on 9/14/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation DragView + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + } + return self; +} + +- (void)drawRect:(NSRect)rect { + // Drawing code here. +} + +- (BOOL)mouseDownCanMoveWindow { + return NO; +} + +@end diff --git a/EmailBarSmall.png b/EmailBarSmall.png new file mode 100644 index 0000000..11a905d Binary files /dev/null and b/EmailBarSmall.png differ diff --git a/English.lproj/FileInfo.strings b/English.lproj/FileInfo.strings new file mode 100644 index 0000000..533e43e --- /dev/null +++ b/English.lproj/FileInfo.strings @@ -0,0 +1,10 @@ + +"mditem title name" = "Title"; +"mditem kind name" = "Kind"; +"mditem size name" = "Size"; +"mditem size kb" = "KB on disk"; +"mditem size mb" = "MB on disk"; +"mditem size gb" = "GB on disk"; +"mditem created name" = "Created"; +"mditem modified name" = "Modified"; +"mditem lastopened name" = "Last opened"; \ No newline at end of file diff --git a/English.lproj/InfoPlist.strings b/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..befb935 Binary files /dev/null and b/English.lproj/InfoPlist.strings differ diff --git a/English.lproj/InsertLink.nib/classes.nib b/English.lproj/InsertLink.nib/classes.nib new file mode 100644 index 0000000..8687110 --- /dev/null +++ b/English.lproj/InsertLink.nib/classes.nib @@ -0,0 +1,17 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + ACTIONS = {genericCancel = id; genericOkay = id; }; + CLASS = LinkController; + LANGUAGE = ObjC; + OUTLETS = { + linkField = NSTextField; + objectController = NSObjectController; + urlField = NSTextField; + }; + SUPERCLASS = NSWindowController; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/English.lproj/InsertLink.nib/info.nib b/English.lproj/InsertLink.nib/info.nib new file mode 100644 index 0000000..cc50b92 --- /dev/null +++ b/English.lproj/InsertLink.nib/info.nib @@ -0,0 +1,14 @@ + + + + + IBDocumentLocation + 370 131 356 240 0 0 1280 778 + IBFramework Version + 446.1 + IBOldestOS + 3 + IBSystem Version + 8P2137 + + diff --git a/English.lproj/InsertLink.nib/keyedobjects.nib b/English.lproj/InsertLink.nib/keyedobjects.nib new file mode 100644 index 0000000..96ac6c5 Binary files /dev/null and b/English.lproj/InsertLink.nib/keyedobjects.nib differ diff --git a/English.lproj/IntegrationFileCopy.nib/classes.nib b/English.lproj/IntegrationFileCopy.nib/classes.nib new file mode 100644 index 0000000..4360a16 --- /dev/null +++ b/English.lproj/IntegrationFileCopy.nib/classes.nib @@ -0,0 +1,12 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + CLASS = IntegrationCopyFiles; + LANGUAGE = ObjC; + OUTLETS = {controller = NSObjectController; progress = NSProgressIndicator; }; + SUPERCLASS = NSWindowController; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/English.lproj/IntegrationFileCopy.nib/info.nib b/English.lproj/IntegrationFileCopy.nib/info.nib new file mode 100644 index 0000000..f061466 --- /dev/null +++ b/English.lproj/IntegrationFileCopy.nib/info.nib @@ -0,0 +1,12 @@ + + + + + IBDocumentLocation + 52 56 356 240 0 0 1280 778 + IBFramework Version + 446.1 + IBSystem Version + 8P2137 + + diff --git a/English.lproj/IntegrationFileCopy.nib/keyedobjects.nib b/English.lproj/IntegrationFileCopy.nib/keyedobjects.nib new file mode 100644 index 0000000..14cc8f7 Binary files /dev/null and b/English.lproj/IntegrationFileCopy.nib/keyedobjects.nib differ diff --git a/English.lproj/Mediabar.strings b/English.lproj/Mediabar.strings new file mode 100644 index 0000000..19992d5 --- /dev/null +++ b/English.lproj/Mediabar.strings @@ -0,0 +1,27 @@ + +"add item title" = "Add Custom Item…"; +"edit item title" = "Edit Custom Item…"; +"remove item title" = "Remove Custom Item…"; + +"get info title" = "Get Info"; +"get info tip" = "Useful information about the selected resource"; + +"reveal in finder title" = "Reveal"; +"reveal in finder tip" = "Show the selected resource in the Finder"; + +"open in finder title" = "Open"; +"open in finder tip" = "Open the selected resource with the default application"; +"open with tip" = "Open the selection with %@"; + +"address record show title" = "Show"; +"address record show tip" = "Open in Address Book"; + +"address record email title" = "Email"; +"address record email tip" = "Begin a new email to the sender"; + +"address record browse title" = "Browse"; +"address record browse tip" = "Browse the entry's associated web site"; + + +"mail message compose title" = "Compose"; +"mail message compose tip" = "Begin a new email to the sender"; \ No newline at end of file diff --git a/English.lproj/NewMediabarItem.nib/classes.nib b/English.lproj/NewMediabarItem.nib/classes.nib new file mode 100644 index 0000000..5d633ca --- /dev/null +++ b/English.lproj/NewMediabarItem.nib/classes.nib @@ -0,0 +1,36 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + {CLASS = JournlerGradientView; LANGUAGE = ObjC; SUPERCLASS = NSView; }, + { + CLASS = MediabarItemApplicationPicker; + LANGUAGE = ObjC; + OUTLETS = {delegate = id; }; + SUPERCLASS = JournlerGradientView; + }, + {CLASS = NSObject; LANGUAGE = ObjC; }, + { + ACTIONS = { + cancel = id; + chooseApplication = id; + help = id; + save = id; + verifyDraggedImage = id; + }; + CLASS = NewMediabarItemController; + LANGUAGE = ObjC; + OUTLETS = { + appImageView = NSImageView; + applicationField = MediabarItemApplicationPicker; + appnameField = NSTextField; + delegate = id; + objectController = NSObjectController; + representedObject = id; + scriptText = NSTextView; + titleField = NSTextField; + }; + SUPERCLASS = NSWindowController; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/English.lproj/NewMediabarItem.nib/info.nib b/English.lproj/NewMediabarItem.nib/info.nib new file mode 100644 index 0000000..7a21a01 --- /dev/null +++ b/English.lproj/NewMediabarItem.nib/info.nib @@ -0,0 +1,16 @@ + + + + + IBDocumentLocation + 69 14 356 240 0 0 1280 778 + IBFramework Version + 446.1 + IBOpenObjects + + 5 + + IBSystem Version + 8P2137 + + diff --git a/English.lproj/NewMediabarItem.nib/keyedobjects.nib b/English.lproj/NewMediabarItem.nib/keyedobjects.nib new file mode 100644 index 0000000..9d5e4fb Binary files /dev/null and b/English.lproj/NewMediabarItem.nib/keyedobjects.nib differ diff --git a/English.lproj/PDFavoritesBar.strings b/English.lproj/PDFavoritesBar.strings new file mode 100644 index 0000000..6bd7a9d --- /dev/null +++ b/English.lproj/PDFavoritesBar.strings @@ -0,0 +1,5 @@ +"Favorites Name" = "Favorite's Name:"; +"Cancel" = "Cancel"; +"OK" = "OK"; + +"draw labels" = "Draw Labels"; \ No newline at end of file diff --git a/English.lproj/PDFontPreview.strings b/English.lproj/PDFontPreview.strings new file mode 100644 index 0000000..49df2e7 --- /dev/null +++ b/English.lproj/PDFontPreview.strings @@ -0,0 +1 @@ +"set font title" = "Set Font…"; \ No newline at end of file diff --git a/English.lproj/PDTabsView.strings b/English.lproj/PDTabsView.strings new file mode 100644 index 0000000..b46dc01 --- /dev/null +++ b/English.lproj/PDTabsView.strings @@ -0,0 +1,4 @@ +"new tab" = "New Tab"; +"close tab" = "Close Tab"; +"close other tabs" = "Close Other Tabs"; +"close tab tip" = "Close this tab"; \ No newline at end of file diff --git a/English.lproj/SproutedAboutBox.nib/classes.nib b/English.lproj/SproutedAboutBox.nib/classes.nib new file mode 100644 index 0000000..41e769e --- /dev/null +++ b/English.lproj/SproutedAboutBox.nib/classes.nib @@ -0,0 +1,98 @@ + + + + + IBClasses + + + CLASS + PDGradientView + LANGUAGE + ObjC + SUPERCLASS + PDBorderedView + + + ACTIONS + + relaunch + id + + CLASS + NSApplication + LANGUAGE + ObjC + SUPERCLASS + NSResponder + + + CLASS + NSObject + LANGUAGE + ObjC + + + CLASS + PDBorderedView + LANGUAGE + ObjC + SUPERCLASS + NSView + + + CLASS + NSView + LANGUAGE + ObjC + SUPERCLASS + NSResponder + + + CLASS + FirstResponder + LANGUAGE + ObjC + SUPERCLASS + NSObject + + + ACTIONS + + doSomething + id + showAboutBox + id + + CLASS + SproutedAboutBoxController + LANGUAGE + ObjC + OUTLETS + + aboutText + NSTextView + additionalText + NSTextView + appnameField + NSTextField + imageView + NSImageView + versionField + NSTextField + + SUPERCLASS + NSWindowController + + + CLASS + NSCell + LANGUAGE + ObjC + SUPERCLASS + NSObject + + + IBVersion + 1 + + diff --git a/English.lproj/SproutedAboutBox.nib/info.nib b/English.lproj/SproutedAboutBox.nib/info.nib new file mode 100644 index 0000000..b5d3140 --- /dev/null +++ b/English.lproj/SproutedAboutBox.nib/info.nib @@ -0,0 +1,18 @@ + + + + + IBFramework Version + 670 + IBLastKnownRelativeProjectPath + ../Journler.xcodeproj + IBOldestOS + 5 + IBOpenObjects + + IBSystem Version + 9E17 + targetFramework + IBCocoaFramework + + diff --git a/English.lproj/SproutedAboutBox.nib/keyedobjects.nib b/English.lproj/SproutedAboutBox.nib/keyedobjects.nib new file mode 100644 index 0000000..2091f9b Binary files /dev/null and b/English.lproj/SproutedAboutBox.nib/keyedobjects.nib differ diff --git a/English.lproj/Stats.nib/classes.nib b/English.lproj/Stats.nib/classes.nib new file mode 100644 index 0000000..776d57c --- /dev/null +++ b/English.lproj/Stats.nib/classes.nib @@ -0,0 +1,14 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + {CLASS = PDGradientView; LANGUAGE = ObjC; SUPERCLASS = NSView; }, + { + ACTIONS = {genericStop = id; }; + CLASS = StatsController; + LANGUAGE = ObjC; + OUTLETS = {charsField = NSTextField; parsField = NSTextField; wordsField = NSTextField; }; + SUPERCLASS = NSWindowController; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/English.lproj/Stats.nib/info.nib b/English.lproj/Stats.nib/info.nib new file mode 100644 index 0000000..2999e87 --- /dev/null +++ b/English.lproj/Stats.nib/info.nib @@ -0,0 +1,18 @@ + + + + + IBDocumentLocation + 95 15 356 240 0 0 1280 778 + IBFramework Version + 446.1 + IBOldestOS + 3 + IBOpenObjects + + 5 + + IBSystem Version + 8P2137 + + diff --git a/English.lproj/Stats.nib/keyedobjects.nib b/English.lproj/Stats.nib/keyedobjects.nib new file mode 100644 index 0000000..5d976ec Binary files /dev/null and b/English.lproj/Stats.nib/keyedobjects.nib differ diff --git a/EtchedPopUpButton.h b/EtchedPopUpButton.h new file mode 100644 index 0000000..56e2d81 --- /dev/null +++ b/EtchedPopUpButton.h @@ -0,0 +1,15 @@ + +/* EtchedPopUpButton */ +// NOTE TO SELF: where did this come from? + +#import + + +@interface EtchedPopUpButton : NSPopUpButton { + +} + ++ (Class)cellClass; +-(void)setShadowColor:(NSColor *)color; + +@end diff --git a/EtchedPopUpButton.m b/EtchedPopUpButton.m new file mode 100644 index 0000000..5f42cb9 --- /dev/null +++ b/EtchedPopUpButton.m @@ -0,0 +1,43 @@ + +/* EtchedPopUpButton */ +// NOTE TO SELF: where did this come from? + +#import +#import + +@implementation EtchedPopUpButton + ++ (Class)cellClass +{ + return [EtchedPopUpButtonCell class]; +} + +- initWithCoder: (NSCoder *)origCoder +{ + if(![origCoder isKindOfClass: [NSKeyedUnarchiver class]]){ + self = [super initWithCoder: origCoder]; + } else { + NSKeyedUnarchiver *coder = (id)origCoder; + + NSString *oldClassName = [[[self superclass] cellClass] className]; + Class oldClass = [coder classForClassName: oldClassName]; + if(!oldClass) + oldClass = [[super superclass] cellClass]; + [coder setClass: [[self class] cellClass] forClassName: oldClassName]; + self = [super initWithCoder: coder]; + [coder setClass: oldClass forClassName: oldClassName]; + + [self setShadowColor:[NSColor whiteColor]]; + } + + return self; +} + +-(void)setShadowColor:(NSColor *)color +{ + EtchedPopUpButtonCell *cell = [self cell]; + [cell setShadowColor:color]; +} + + +@end diff --git a/EtchedPopUpButtonCell.h b/EtchedPopUpButtonCell.h new file mode 100644 index 0000000..a10585a --- /dev/null +++ b/EtchedPopUpButtonCell.h @@ -0,0 +1,14 @@ + +/* EtchedPopUpButtonCell */ +// NOTE TO SELF: where did this come from? + +#import + + +@interface EtchedPopUpButtonCell : NSPopUpButtonCell { + NSColor *mShadowColor; +} + +-(void)setShadowColor:(NSColor *)aColor; + +@end diff --git a/EtchedPopUpButtonCell.m b/EtchedPopUpButtonCell.m new file mode 100644 index 0000000..186b195 --- /dev/null +++ b/EtchedPopUpButtonCell.m @@ -0,0 +1,43 @@ + +/* EtchedPopUpButtonCell */ +// NOTE TO SELF: where did this come from? + +#import + + +@implementation EtchedPopUpButtonCell + +- (void) dealloc +{ + [mShadowColor release]; + [super dealloc]; +} + +-(void)setShadowColor:(NSColor *)aColor +{ + if ( mShadowColor != aColor ) + { + [mShadowColor release]; + mShadowColor = [aColor retain]; + } +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(id)controlView +{ + [NSGraphicsContext saveGraphicsState]; + NSShadow* theShadow = [[NSShadow alloc] init]; + [theShadow setShadowOffset:NSMakeSize(0, -1)]; + [theShadow setShadowBlurRadius:0.3]; + + [theShadow setShadowColor:mShadowColor]; + + [theShadow set]; + + [super drawInteriorWithFrame:cellFrame inView:controlView]; + + [NSGraphicsContext restoreGraphicsState]; + [theShadow release]; +} + + +@end diff --git a/EtchedText.h b/EtchedText.h new file mode 100644 index 0000000..4891467 --- /dev/null +++ b/EtchedText.h @@ -0,0 +1,14 @@ + +/* EtchedText */ +// NOTE TO SELF: where did this come from? + +#import + +@interface EtchedText : NSTextField +{ +} + ++ (Class)cellClass; +-(void)setShadowColor:(NSColor *)color; + +@end diff --git a/EtchedText.m b/EtchedText.m new file mode 100644 index 0000000..97e43e8 --- /dev/null +++ b/EtchedText.m @@ -0,0 +1,42 @@ + +/* EtchedText */ +// NOTE TO SELF: where did this come from? + +#import +#import + +@implementation EtchedText + ++ (Class)cellClass +{ + return [EtchedTextCell class]; +} + +- initWithCoder: (NSCoder *)origCoder +{ + if(![origCoder isKindOfClass: [NSKeyedUnarchiver class]]){ + self = [super initWithCoder: origCoder]; + } else { + NSKeyedUnarchiver *coder = (id)origCoder; + + NSString *oldClassName = [[[self superclass] cellClass] className]; + Class oldClass = [coder classForClassName: oldClassName]; + if(!oldClass) + oldClass = [[super superclass] cellClass]; + [coder setClass: [[self class] cellClass] forClassName: oldClassName]; + self = [super initWithCoder: coder]; + [coder setClass: oldClass forClassName: oldClassName]; + + [self setShadowColor:[NSColor whiteColor]]; + } + + return self; +} + +-(void)setShadowColor:(NSColor *)color +{ + EtchedTextCell *cell = [self cell]; + [cell setShadowColor:color]; +} + +@end diff --git a/EtchedTextCell.h b/EtchedTextCell.h new file mode 100644 index 0000000..debfecc --- /dev/null +++ b/EtchedTextCell.h @@ -0,0 +1,14 @@ + +/* EtchedTextCell */ +// NOTE TO SELF: where did this come from? + +#import + +@interface EtchedTextCell : NSTextFieldCell +{ + NSColor *mShadowColor; +} + +-(void)setShadowColor:(NSColor *)aColor; + +@end diff --git a/EtchedTextCell.m b/EtchedTextCell.m new file mode 100644 index 0000000..2012cda --- /dev/null +++ b/EtchedTextCell.m @@ -0,0 +1,41 @@ + +/* EtchedTextCell */ +// NOTE TO SELF: where did this come from? + +#import + +@implementation EtchedTextCell + +- (void) dealloc +{ + [mShadowColor release]; + [super dealloc]; +} + +-(void)setShadowColor:(NSColor *)aColor +{ + if ( mShadowColor != aColor ) + { + [mShadowColor release]; + mShadowColor = [aColor retain]; + } +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(id)controlView +{ + [NSGraphicsContext saveGraphicsState]; + NSShadow* theShadow = [[NSShadow alloc] init]; + [theShadow setShadowOffset:NSMakeSize(0, -1)]; + [theShadow setShadowBlurRadius:0.3]; + + [theShadow setShadowColor:mShadowColor]; + + [theShadow set]; + + [super drawInteriorWithFrame:cellFrame inView:controlView]; + + [NSGraphicsContext restoreGraphicsState]; + [theShadow release]; +} + +@end diff --git a/Forward.tif b/Forward.tif new file mode 100644 index 0000000..d178fec Binary files /dev/null and b/Forward.tif differ diff --git a/ForwardDisabled.tif b/ForwardDisabled.tif new file mode 100644 index 0000000..22a2cca Binary files /dev/null and b/ForwardDisabled.tif differ diff --git a/ForwardPressed.tif b/ForwardPressed.tif new file mode 100644 index 0000000..172efad Binary files /dev/null and b/ForwardPressed.tif differ diff --git a/HUDWindow.h b/HUDWindow.h new file mode 100644 index 0000000..ef075db --- /dev/null +++ b/HUDWindow.h @@ -0,0 +1,33 @@ +// +// HUDWindow.h +// HUDWindow +// +// Created by Matt Gemmell on 12/02/2006. +// Copyright 2006 Magic Aubergine. All rights reserved. +// + +#import +#import + +@interface HUDWindow : NSPanel { + BOOL forceDisplay; + BOOL closesOnEvent; + BOOL closesOnEscape; +} + +- (BOOL) closesOnEvent; +- (void) setClosesOnEvent:(BOOL)closes; + +- (BOOL) closesOnEscape; +- (void) setClosesOnEscape:(BOOL)closes; + +- (NSColor *)sizedHUDBackground; +- (void)addCloseWidget; + +@end + +@interface NSObject (HUDWindowDelegate) + +- (IBAction) runClose:(id)sender; + +@end diff --git a/HUDWindow.m b/HUDWindow.m new file mode 100644 index 0000000..5c12a9a --- /dev/null +++ b/HUDWindow.m @@ -0,0 +1,296 @@ +// +// HUDWindow.m +// HUDWindow +// +// Created by Matt Gemmell on 12/02/2006. +// Copyright 2006 Magic Aubergine. All rights reserved. +// + +#import + +@implementation HUDWindow + + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(unsigned int)styleMask + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag +{ + unsigned int replacementMask = NSBorderlessWindowMask; + if ( styleMask & NSClosableWindowMask ) replacementMask |= NSClosableWindowMask; + + if (self = [super initWithContentRect:contentRect + styleMask:replacementMask + backing:bufferingType + defer:flag]) { + + [self setBackgroundColor: [NSColor clearColor]]; + [self setAlphaValue:1.0]; + [self setOpaque:NO]; + [self setHasShadow:YES]; + [self setMovableByWindowBackground:YES]; + forceDisplay = NO; + [self setBackgroundColor:[self sizedHUDBackground]]; + + if ( styleMask & NSClosableWindowMask ) + [self addCloseWidget]; + + [self setReleasedWhenClosed:YES]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowDidResize:) + name:NSWindowDidResizeNotification + object:self]; + + closesOnEvent = NO; + closesOnEscape = NO; + + return self; + } + return nil; +} + +- (void)awakeFromNib +{ + if ( [self styleMask] & NSClosableWindowMask ) + [self addCloseWidget]; +} + +- (void)addCloseWidget +{ + NSButton *closeButton = [[NSButton alloc] initWithFrame:NSMakeRect(3.0, [self frame].size.height - 16.0, 13.0, 13.0)]; + + [[self contentView] addSubview:closeButton]; + [closeButton setBezelStyle:NSRoundedBezelStyle]; + [closeButton setButtonType:NSMomentaryChangeButton]; + [closeButton setBordered:NO]; + [closeButton setImage:BundledImageWithName(@"hud_titlebar-close",@"com.sprouted.interface")]; + [closeButton setTitle:@""]; + [closeButton setImagePosition:NSImageBelow]; + [closeButton setTarget:self]; + [closeButton setFocusRingType:NSFocusRingTypeNone]; + + if ( [[self delegate] respondsToSelector:@selector(runClose:)] ) + { + [closeButton setTarget:[self delegate]]; + [closeButton setAction:@selector(runClose:)]; + } + else + { + [closeButton setTarget:self]; + [closeButton setAction:@selector(close)]; + } + [closeButton release]; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResizeNotification object:self]; + [super dealloc]; +} + +- (void)windowDidResize:(NSNotification *)aNotification +{ + [self setBackgroundColor:[self sizedHUDBackground]]; + if (forceDisplay) { + [self display]; + } +} + +- (void)setFrame:(NSRect)frameRect display:(BOOL)displayFlag animate:(BOOL)animationFlag +{ + forceDisplay = YES; + [super setFrame:frameRect display:displayFlag animate:animationFlag]; + forceDisplay = NO; +} + +- (NSColor *)sizedHUDBackground +{ + float alpha = 0.85; + float titlebarHeight = 19.0; + NSImage *bg = [[NSImage alloc] initWithSize:[self frame].size]; + [bg lockFocus]; + + // Make background path + NSRect bgRect = NSMakeRect(0, 0, [bg size].width, [bg size].height - titlebarHeight); + int minX = NSMinX(bgRect); + int midX = NSMidX(bgRect); + int maxX = NSMaxX(bgRect); + int minY = NSMinY(bgRect); + int midY = NSMidY(bgRect); + int maxY = NSMaxY(bgRect); + float radius = 6.0; + NSBezierPath *bgPath = [NSBezierPath bezierPath]; + + // Bottom edge and bottom-right curve + [bgPath moveToPoint:NSMakePoint(midX, minY)]; + [bgPath appendBezierPathWithArcFromPoint:NSMakePoint(maxX, minY) + toPoint:NSMakePoint(maxX, midY) + radius:radius]; + + [bgPath lineToPoint:NSMakePoint(maxX, maxY)]; + [bgPath lineToPoint:NSMakePoint(minX, maxY)]; + + // Top edge and top-left curve + [bgPath appendBezierPathWithArcFromPoint:NSMakePoint(minX, maxY) + toPoint:NSMakePoint(minX, midY) + radius:radius]; + + // Left edge and bottom-left curve + [bgPath appendBezierPathWithArcFromPoint:bgRect.origin + toPoint:NSMakePoint(midX, minY) + radius:radius]; + [bgPath closePath]; + + // Composite background color into bg + [[NSColor colorWithCalibratedWhite:0.1 alpha:alpha] set]; + [bgPath fill]; + + // Make titlebar path + NSRect titlebarRect = NSMakeRect(0, [bg size].height - titlebarHeight, [bg size].width, titlebarHeight); + minX = NSMinX(titlebarRect); + midX = NSMidX(titlebarRect); + maxX = NSMaxX(titlebarRect); + minY = NSMinY(titlebarRect); + midY = NSMidY(titlebarRect); + maxY = NSMaxY(titlebarRect); + NSBezierPath *titlePath = [NSBezierPath bezierPath]; + + // Bottom edge and bottom-right curve + [titlePath moveToPoint:NSMakePoint(minX, minY)]; + [titlePath lineToPoint:NSMakePoint(maxX, minY)]; + + // Right edge and top-right curve + [titlePath appendBezierPathWithArcFromPoint:NSMakePoint(maxX, maxY) + toPoint:NSMakePoint(midX, maxY) + radius:radius]; + + // Top edge and top-left curve + [titlePath appendBezierPathWithArcFromPoint:NSMakePoint(minX, maxY) + toPoint:NSMakePoint(minX, minY) + radius:radius]; + + [titlePath closePath]; + + // Titlebar + NSColor *titlebarColor = [NSColor colorWithCalibratedWhite:0.25 alpha:1.0]; + [titlebarColor set]; + [titlePath fill]; + + // Title + NSFont *titleFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + NSMutableParagraphStyle *paraStyle = [[NSMutableParagraphStyle alloc] init]; + [paraStyle setParagraphStyle:[NSParagraphStyle defaultParagraphStyle]]; + [paraStyle setAlignment:NSCenterTextAlignment]; + [paraStyle setLineBreakMode:NSLineBreakByTruncatingTail]; + NSMutableDictionary *titleAttrs = [NSMutableDictionary dictionaryWithObjectsAndKeys: + titleFont, NSFontAttributeName, + [NSColor whiteColor], NSForegroundColorAttributeName, + [[paraStyle copy] autorelease], NSParagraphStyleAttributeName, + nil]; + + NSSize titleSize = [[self title] sizeWithAttributes:titleAttrs]; + // We vertically centre the title in the titlbar area, and we also horizontally + // inset the title by 19px, to allow for the 3px space from window's edge to close-widget, + // plus 13px for the close widget itself, plus another 3px space on the other side of + // the widget. + NSRect titleRect = NSInsetRect(titlebarRect, 19.0, (titlebarRect.size.height - titleSize.height) / 2.0); + [[self title] drawInRect:titleRect withAttributes:titleAttrs]; + [bg unlockFocus]; + + return [NSColor colorWithPatternImage:[bg autorelease]]; +} + +- (void)setTitle:(NSString *)value { + [super setTitle:value]; + [self windowDidResize:nil]; +} + +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + +#pragma mark - + +- (BOOL) closesOnEvent +{ + return closesOnEvent; +} + +- (void) setClosesOnEvent:(BOOL)closes +{ + closesOnEvent = closes; +} + +- (BOOL) closesOnEscape +{ + return closesOnEscape; +} + +- (void) setClosesOnEscape:(BOOL)closes +{ + closesOnEscape = closes; +} + +#pragma mark - + +- (void)keyDown:(NSEvent *)theEvent +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + if ( closesOnEvent || ( closesOnEscape && [theEvent keyCode] == 53 ) ) + if ( [[self delegate] respondsToSelector:@selector(runClose:)] ) + [[self delegate] runClose:self]; + else + [self close]; + else + [super keyDown:theEvent]; +} + +- (void) mouseDown:(NSEvent*)theEvent +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + if ( closesOnEvent ) + if ( [[self delegate] respondsToSelector:@selector(runClose:)] ) + [[self delegate] runClose:self]; + else + [self close]; + else + [super mouseDown:theEvent]; +} + +#pragma mark - + +- (void)resignKeyWindow +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + if ( closesOnEvent ) + [[self delegate] runClose:self]; + else + [super resignKeyWindow]; + //[self close]; +} + +- (void)resignMainWindow +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + if ( closesOnEvent ) + [[self delegate] runClose:self]; + else + [super resignMainWindow]; + //[self close]; +} + +@end diff --git a/Home.tif b/Home.tif new file mode 100644 index 0000000..b0e0893 Binary files /dev/null and b/Home.tif differ diff --git a/HomePressed.tif b/HomePressed.tif new file mode 100644 index 0000000..cf4116b Binary files /dev/null and b/HomePressed.tif differ diff --git a/ImageAndTextCell.h b/ImageAndTextCell.h new file mode 100644 index 0000000..7d56103 --- /dev/null +++ b/ImageAndTextCell.h @@ -0,0 +1,50 @@ +// +// ImageAndTextCell.h +// +// Copyright (c) 2001-2002, Apple. All rights reserved. +// + +#import + +@interface ImageAndTextCell : NSTextFieldCell { + + BOOL separatorCell; + BOOL updating; + + BOOL dim; + BOOL selected; + BOOL boldsWhenSelected; + int contentCount; + NSSize imageSize; + NSImage *image; + NSMutableParagraphStyle *_paragraph; +} + +- (BOOL) dim; +- (void) setDim:(BOOL)isDim; + +- (BOOL) updating; +- (void) setUpdating:(BOOL)isUpdating; + +- (BOOL) isSeparatorCell; +- (void) setIsSeparatorCell:(BOOL)separator; + +- (BOOL) isSelected; +- (void) setSelected:(BOOL)isSelected; + +- (BOOL) boldsWhenSelected; +- (void) setBoldsWhenSelected:(BOOL)doesBold; + +- (int) contentCount; +- (void) setContentCount:(int)count; + +- (NSSize) imageSize; +- (void) setImageSize:(NSSize)aSize; + +- (NSImage *)image; +- (void)setImage:(NSImage *)anImage; + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView; +- (NSSize)cellSize; + +@end diff --git a/ImageAndTextCell.m b/ImageAndTextCell.m new file mode 100644 index 0000000..8a3eef1 --- /dev/null +++ b/ImageAndTextCell.m @@ -0,0 +1,588 @@ +/* + ImageAndTextCell.m + Copyright (c) 2001-2004, Apple Computer, Inc., all rights reserved. + Author: Chuck Pisula + + Milestones: + Initially created 3/1/01 + + Subclass of NSTextFieldCell which can display text and an image simultaneously. +*/ + +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation, + modification or redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in + this original Apple software (the "Apple Software"), to use, reproduce, modify and + redistribute the Apple Software, with or without modifications, in source and/or binary + forms; provided that if you redistribute the Apple Software in its entirety and without + modifications, you must retain this notice and the following text and disclaimers in all + such redistributions of the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Computer, Inc. may be used to endorse or promote products derived from + the Apple Software without specific prior written permission from Apple. Except as expressly + stated in this notice, no other rights or licenses, express or implied, are granted by Apple + herein, including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#import + +#import +#import + +@implementation ImageAndTextCell + +static int kMinBackgroundWidth = 20; + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) + { + imageSize = NSMakeSize(32,32); + contentCount = -1; + boldsWhenSelected = YES; + } + + return self; +} + +- (id)initTextCell:(NSString *)aString { + + if ( self = [super initTextCell:aString] ) + { + imageSize = NSMakeSize(32,32); + contentCount = -1; + boldsWhenSelected = YES; + } + + return self; +} + +- (void)dealloc { + + [image release]; + image = nil; + + if ( _paragraph ) [_paragraph release]; + _paragraph = nil; + + [super dealloc]; +} + +- copyWithZone:(NSZone *)zone { + ImageAndTextCell *cell = (ImageAndTextCell *)[super copyWithZone:zone]; + + cell->image = [image retain]; + cell->_paragraph = [_paragraph retain]; + + cell->imageSize = imageSize; + cell->separatorCell = separatorCell; + cell->updating = updating; + cell->contentCount = contentCount; + cell->selected = selected; + cell->dim = dim; + cell->boldsWhenSelected = boldsWhenSelected; + + return cell; +} + +#pragma mark - + +- (BOOL) dim +{ + return dim; +} + +- (void) setDim:(BOOL)isDim +{ + dim = isDim; +} + +- (BOOL) boldsWhenSelected +{ + return boldsWhenSelected; +} + +- (void) setBoldsWhenSelected:(BOOL)doesBold +{ + boldsWhenSelected = doesBold; +} + +- (int) contentCount +{ + return contentCount; +} + +- (void) setContentCount:(int)count +{ + contentCount = count; +} + +- (BOOL) updating +{ + return updating; +} + +- (void) setUpdating:(BOOL)isUpdating +{ + updating = isUpdating; +} + +- (BOOL) isSeparatorCell +{ + return separatorCell; +} + +- (void) setIsSeparatorCell:(BOOL)separator +{ + separatorCell = separator; +} + +- (NSSize) imageSize { + return imageSize; +} + +- (void) setImageSize:(NSSize)aSize { + imageSize = aSize; +} + +- (void)setImage:(NSImage *)anImage { + if (anImage != image) { + [image release]; + image = [anImage retain]; + } +} + +- (NSImage *)image { + return image; +} + +- (BOOL) isSelected +{ + return selected; +} + +- (void) setSelected:(BOOL)isSelected +{ + selected = isSelected; +} + +#pragma mark - + +- (NSRect)imageFrameForCellFrame:(NSRect)cellFrame { + if (image != nil) { + NSRect imageFrame; + imageFrame.size = [self imageSize]; + imageFrame.origin = cellFrame.origin; + imageFrame.origin.x += 3; + imageFrame.origin.y += ceil((cellFrame.size.height - imageFrame.size.height) / 2); + return imageFrame; + } + else + return NSZeroRect; +} + +- (NSSize)cellSize { + NSSize cellSize = [super cellSize]; + cellSize.width += (image ? [self imageSize].width : 0) + 3; + return cellSize; +} + +#pragma mark - + +- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent { + + NSRect textFrame, imageFrame; + NSDivideRect (aRect, &imageFrame, &textFrame, 3 + [self imageSize].width, NSMinXEdge); + + NSMutableDictionary *attrs; + NSAttributedString *attrStringValue = [self attributedStringValue]; + + if ( attrStringValue != nil && [attrStringValue length] != 0) + attrs = [[[attrStringValue attributesAtIndex:0 effectiveRange:NULL] mutableCopyWithZone:[self zone]] autorelease]; + else + attrs = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, nil]; + + if ([self isSelected]) { + // prepare the text in white. + //[attrs setValue:[NSColor grayColor] forKey:NSForegroundColorAttributeName]; + [attrs setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName]; + + NSFont *originalFont = [attrs objectForKey:NSFontAttributeName]; + if ( originalFont ) { + NSFont *boldedFont = [[NSFontManager sharedFontManager] convertFont:originalFont toHaveTrait:NSBoldFontMask]; + if ( boldedFont ) + [attrs setValue:boldedFont forKey:NSFontAttributeName]; + } + + } + else { + // prepare the text in black. + [attrs setValue:[self textColor] forKey:NSForegroundColorAttributeName]; + } + + + //[textObj setTextColor:[NSColor blackColor]]; + //[textObj setTextColor:[NSColor blackColor] range:NSMakeRange(0,[[textObj string] length])]; + + // center the text and take into account the required inset + NSSize textSize = [[self stringValue] sizeWithAttributes:attrs]; + + int textHeight = textSize.height; + //int textWidth = textSize.width; + + textFrame.origin.y = textFrame.origin.y + (textFrame.size.height/2 - textHeight/2); + textFrame.size.height = textHeight; + //textFrame.size.width = textWidth; + + [super editWithFrame: textFrame inView: controlView editor:textObj delegate:anObject event: theEvent]; +} + +- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(int)selStart length:(int)selLength { + + NSRect textFrame, imageFrame; + NSDivideRect (aRect, &imageFrame, &textFrame, 3 + [self imageSize].width, NSMinXEdge); + + NSMutableDictionary *attrs; + NSAttributedString *attrStringValue = [self attributedStringValue]; + + if ( attrStringValue != nil && [attrStringValue length] != 0) + attrs = [[[attrStringValue attributesAtIndex:0 effectiveRange:NULL] mutableCopyWithZone:[self zone]] autorelease]; + else + attrs = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, nil]; + + if ([self isSelected]) { + // prepare the text in white. + //[attrs setValue:[NSColor grayColor] forKey:NSForegroundColorAttributeName]; + [attrs setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName]; + + NSFont *originalFont = [attrs objectForKey:NSFontAttributeName]; + if ( originalFont ) { + NSFont *boldedFont = [[NSFontManager sharedFontManager] convertFont:originalFont toHaveTrait:NSBoldFontMask]; + if ( boldedFont ) + [attrs setValue:boldedFont forKey:NSFontAttributeName]; + } + + } + else { + // prepare the text in black. + [attrs setValue:[self textColor] forKey:NSForegroundColorAttributeName]; + } + + + //[textObj setTextColor:[NSColor blackColor]]; + //[textObj setTextColor:[NSColor blackColor] range:NSMakeRange(0,[[textObj string] length])]; + + // center the text and take into account the required inset + NSSize textSize = [[self stringValue] sizeWithAttributes:attrs]; + + int textHeight = textSize.height; + //int textWidth = textSize.width; + + textFrame.origin.y = textFrame.origin.y + (textFrame.size.height/2 - textHeight/2); + textFrame.size.height = textHeight; + //textFrame.size.width = textWidth; + + [super selectWithFrame: textFrame inView: controlView editor:textObj delegate:anObject start:selStart length:selLength]; +} + +#pragma mark - + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + if ( ![self isSeparatorCell] ) + { + /* + if ([self isHighlighted]) { + + NSColor *gradientStart, *gradientEnd; + + NSRect controlBds = [controlView bounds]; + //NSRect gradientBounds = NSMakeRect(controlBds.origin.x, cellFrame.origin.y+1, + // controlBds.size.width, cellFrame.size.height-1); + + NSRect gradientBounds = NSMakeRect(controlBds.origin.x, cellFrame.origin.y, + controlBds.size.width, cellFrame.size.height-1); + + if (([[controlView window] firstResponder] == controlView) && [[controlView window] isMainWindow] && + [[controlView window] isKeyWindow]) { + + //bottomColor = [NSColor colorWithCalibratedRed:91.0/255.0 green:129.0/255.0 blue:204.0/255.0 alpha:1.0]; + gradientStart = [NSColor colorWithCalibratedRed:136.0/255.0 green:165.0/255.0 blue:212.0/255.0 alpha:1.0]; + gradientEnd = [NSColor colorWithCalibratedRed:102.0/255.0 green:133.0/255.0 blue:183.0/255.0 alpha:1.0]; + + } else { + + //bottomColor = [NSColor colorWithCalibratedRed:140.0/255.0 green:152.0/255.0 blue:176.0/255.0 alpha:0.9]; + gradientStart = [NSColor colorWithCalibratedRed:172.0/255.0 green:186.0/255.0 blue:207.0/255.0 alpha:0.9]; + gradientEnd = [NSColor colorWithCalibratedRed:152.0/255.0 green:170.0/255.0 blue:196.0/255.0 alpha:0.9]; + } + + [[NSBezierPath bezierPathWithRect:gradientBounds] linearGradientFillWithStartColor: + gradientStart endColor:gradientEnd]; + } + */ + + if (image != nil) { + + NSSize myImageSize; + NSRect imageFrame; + + myImageSize = [self imageSize]; + NSDivideRect(cellFrame, &imageFrame, &cellFrame, 3 + myImageSize.width, NSMinXEdge); + + imageFrame.origin.x += 3; + imageFrame.size = myImageSize; + + if ([controlView isFlipped]) + imageFrame.origin.y += ceil((cellFrame.size.height + imageFrame.size.height) / 2); + else + imageFrame.origin.y += ceil((cellFrame.size.height - imageFrame.size.height) / 2); + + //[image compositeToPoint:imageFrame.origin operation:NSCompositeSourceOver]; + + //#warning this seems like a lot of extra work just because a newly created item draws flipped + + NSImage *imageCopy = [[[NSImage alloc] initWithSize:[self imageSize]] autorelease]; + //[imageCopy setFlipped:[controlView isFlipped]]; + + [imageCopy lockFocus]; + + [[NSGraphicsContext currentContext] saveGraphicsState]; + [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; + + [image drawInRect:NSMakeRect(0,0,[self imageSize].width,[self imageSize].height) fromRect:NSMakeRect(0,0,[image size].width,[image size].height) + operation:NSCompositeSourceOver fraction:( [self dim] ? 0.5 : 1.0 )]; + + [[NSGraphicsContext currentContext] restoreGraphicsState]; + + [imageCopy unlockFocus]; + + //[imageCopy drawInRect:imageFrame fromRect:NSMakeRect(0,0,[imageCopy size].width,[imageCopy size].height) operation:NSCompositeSourceOver fraction:1.0]; + [imageCopy compositeToPoint:imageFrame.origin operation:NSCompositeSourceOver]; + } + + } + + [super drawWithFrame:cellFrame inView:controlView]; +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + if ( [self isSeparatorCell] ) + { + NSRect controlBds = [controlView bounds]; + NSRect sepBounds = NSMakeRect(controlBds.origin.x + 4, cellFrame.origin.y + cellFrame.size.height/2 - 1, + controlBds.size.width - 8, 1); + + //[controlView lockFocus]; + + [[NSColor colorWithCalibratedWhite:0.94 alpha:0.7] set]; + NSRectFillUsingOperation(sepBounds, NSCompositeSourceOver); + + //[controlView unlockFocus]; + } + else + { + int textHeight; + int countWidth; + + NSRect inset = cellFrame; + NSMutableDictionary *attrs; + NSAttributedString *attrStringValue = [self attributedStringValue]; + + if ( attrStringValue != nil && [attrStringValue length] != 0) + attrs = [[[attrStringValue attributesAtIndex:0 effectiveRange:NULL] mutableCopyWithZone:[self zone]] autorelease]; + else + attrs = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, nil]; + + // paragraph attribute + + if ( _paragraph == nil ) { + _paragraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopyWithZone:[self zone]]; + [_paragraph setLineBreakMode:NSLineBreakByTruncatingTail]; + } + + [attrs setValue:_paragraph forKey:NSParagraphStyleAttributeName]; + + // shadow attribute + + //NSShadow *textShadow = [[[NSShadow alloc] init] autorelease]; + //[textShadow setShadowColor:[NSColor colorWithCalibratedWhite:0.4 alpha:0.6]]; + //[textShadow setShadowOffset:NSMakeSize(0,-1)]; + + //[attrs setValue:textShadow forKey:NSShadowAttributeName]; + //[attrs setValue:( [self dim] ? [[self textColor] colorWithAlphaComponent:0.5] : [self textColor] ) forKey:NSForegroundColorAttributeName]; + + + if ([self isSelected]) + { + // prepare the text in white. + [attrs setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName]; + + // bold the text if that option has been requested + if ( [self boldsWhenSelected] ) + { + NSFont *originalFont = [attrs objectForKey:NSFontAttributeName]; + if ( originalFont ) { + NSFont *boldedFont = [[NSFontManager sharedFontManager] convertFont:originalFont toHaveTrait:NSBoldFontMask]; + if ( boldedFont ) + [attrs setValue:boldedFont forKey:NSFontAttributeName]; + } + } + } + else { + // prepare the text in black. + [attrs setValue:( [self dim] ? [[self textColor] colorWithAlphaComponent:0.5] : [self textColor] ) forKey:NSForegroundColorAttributeName]; + } + + + // modify the inset same + inset.origin.x += 2; + inset.size.width -= 4; + + // draw some status info + + if ( [self updating] ) + { + float height = cellFrame.size.height - cellFrame.size.height/5; + NSRect badgeFrame = NSMakeRect( cellFrame.origin.x + cellFrame.size.width - height - 4, + cellFrame.origin.y + cellFrame.size.height/2 - height/2, + height, height ); + + + NSBezierPath *updatingBadge = [NSBezierPath bezierPathWithOvalInRect:badgeFrame]; + [[NSColor colorWithCalibratedWhite:0.8 alpha:0.6] set]; + [updatingBadge fill]; + + NSBezierPath *arc = [NSBezierPath bezierPath]; + + [arc moveToPoint:NSMakePoint(badgeFrame.origin.x + badgeFrame.size.width/2, badgeFrame.origin.y + badgeFrame.size.height/2)]; + [arc appendBezierPathWithArcWithCenter:[arc currentPoint] radius:badgeFrame.size.height/2 + startAngle:-45 endAngle:-90 clockwise:NO]; + [arc closePath]; + + [[NSColor colorWithCalibratedWhite:1.0 alpha:0.8] set]; + [arc fill]; + + // modify the inset + inset.size.width -= ( cellFrame.size.height - cellFrame.size.height/5 + 8 ); + } + + else if ( [self contentCount] > 0 ) + { + NSMutableDictionary *countAttrs; + NSColor *countBackground; + + NSFont *countFont = [NSFont fontWithName:@"LucidaGrande" size:10.0]; + if ( countFont == nil ) + countFont = [NSFont boldSystemFontOfSize:10]; + + NSFont *boldedFont = [[NSFontManager sharedFontManager] convertFont:countFont toHaveTrait:NSBoldFontMask]; + + countAttrs = [NSMutableDictionary dictionaryWithObjectsAndKeys: + boldedFont, NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, nil]; + + if ( [self isSelected] ) + { + countBackground = [NSColor whiteColor]; + [countAttrs setObject:[NSColor colorWithCalibratedRed:105/255.0 green:122/255.0 blue:145/255.0 alpha:1.0] forKey:NSForegroundColorAttributeName]; + } + else + { + [countAttrs setObject:( [self dim] ? [[NSColor whiteColor] colorWithAlphaComponent:0.8] : [NSColor whiteColor] ) forKey:NSForegroundColorAttributeName]; + countBackground = [NSColor colorWithCalibratedRed:141/255.0 green:160/255.0 blue:186/255.0 alpha:1.0]; + if ( [self dim] ) countBackground = [countBackground colorWithAlphaComponent:0.5]; + } + + NSString *countString = [NSString stringWithFormat:@"%i", [self contentCount]]; + NSSize countSize = [countString sizeWithAttributes:countAttrs]; + + countWidth = ( countSize.width + 10 ); + NSRect countRect = NSMakeRect( cellFrame.origin.x + cellFrame.size.width - countWidth, + cellFrame.origin.y + cellFrame.size.height/2 - countSize.height/2, + countSize.width, countSize.height ); + + if ( [controlView isFlipped] ) + countRect.origin.y++; + else + countRect.origin.y--; + + NSRect backgroundRect = countRect; + backgroundRect.origin.x -= 5; backgroundRect.size.width += 10; + backgroundRect.origin.y -= 1.5; backgroundRect.size.height += 1; + + if ( backgroundRect.size.width < kMinBackgroundWidth ) + { + // re-align the background + backgroundRect.origin.x = cellFrame.origin.x + cellFrame.size.width - kMinBackgroundWidth - 5; + backgroundRect.size.width = kMinBackgroundWidth; + + // recenter the text horizontally on the background + countRect.origin.x = cellFrame.origin.x + cellFrame.size.width - backgroundRect.size.width - 5 + ( backgroundRect.size.width/2 - countRect.size.width/2 ); + } + + // recenter the text vertically on the background + //countRect.origin.y = backgroundRect.origin.y + ( backgroundRect.size.height/2 - countSize.height/2 ); + + [countBackground set]; + [[NSBezierPath bezierPathWithRoundedRect:backgroundRect cornerRadius:7.4] fill]; + + [countString drawInRect:countRect withAttributes:countAttrs]; + + // modify the inset + inset.size.width -= (backgroundRect.size.width + 10); + } + + + // center the text and take into account the required inset + textHeight = [[self stringValue] sizeWithAttributes:attrs].height; + inset.origin.y = inset.origin.y + (inset.size.height/2 - textHeight/2); + inset.size.height = textHeight; + + // actually draw the title + [[self stringValue] drawInRect:inset withAttributes:attrs]; + } +} + +- (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + return nil; +} + +/* +- (NSColor*) textColor { + + if ( [self isHighlighted] ) + return [NSColor whiteColor]; + else + return [super textColor]; +} +*/ + +@end + diff --git a/Info.plist b/Info.plist new file mode 100644 index 0000000..23b4502 --- /dev/null +++ b/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleName + ${PRODUCT_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.sprouted.interface + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleSignature + ???? + CFBundleVersion + 101 + NSPrincipalClass + + + diff --git a/InfoBarSmall.png b/InfoBarSmall.png new file mode 100644 index 0000000..595a077 Binary files /dev/null and b/InfoBarSmall.png differ diff --git a/IntegrationCopyFiles.h b/IntegrationCopyFiles.h new file mode 100644 index 0000000..bfd94b0 --- /dev/null +++ b/IntegrationCopyFiles.h @@ -0,0 +1,26 @@ +// +// IntegrationCopyFiles.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface IntegrationCopyFiles : NSWindowController +{ + IBOutlet NSObjectController *controller; + IBOutlet NSProgressIndicator *progress; + + NSString *noticeText; +} + +- (NSString*)noticeText; +- (void) setNoticeText:(NSString*)aString; + +- (void) runNotice; +- (void) endNotice; + +@end diff --git a/IntegrationCopyFiles.m b/IntegrationCopyFiles.m new file mode 100644 index 0000000..11720ff --- /dev/null +++ b/IntegrationCopyFiles.m @@ -0,0 +1,80 @@ + +// +// IntegrationCopyFiles.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation IntegrationCopyFiles + +- (id) init { + if ( self = [super initWithWindowNibName:@"IntegrationFileCopy"] ) + { + [self setNoticeText:NSLocalizedString(@"integration copying files",@"")]; + [self retain]; + } + + return self; +} + +- (void) windowDidLoad { + + [[self window] setHidesOnDeactivate:NO]; + //[progress setUsesThreadedAnimation:YES]; + +} + +- (void) dealloc +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + [noticeText release]; + [super dealloc]; +} + +#pragma mark - + +- (NSString*)noticeText +{ + return noticeText; +} + +- (void) setNoticeText:(NSString*)aString +{ + if ( noticeText != aString ) + { + [noticeText release]; + noticeText = [aString copyWithZone:[self zone]]; + } +} + +- (void) runNotice { + + [[self window] center]; + [[self window] makeKeyAndOrderFront:self]; + + [progress startAnimation:self]; + +} + +- (void) endNotice { + [progress stopAnimation:self]; + [[self window] close]; +} + +- (void)windowWillClose:(NSNotification *)aNotification +{ + [controller unbind:@"contentObject"]; + [controller setContent:nil]; + + [self autorelease]; +} + +@end diff --git a/JRLRFooter.h b/JRLRFooter.h new file mode 100644 index 0000000..11c83c3 --- /dev/null +++ b/JRLRFooter.h @@ -0,0 +1,15 @@ +// +// JRLRFooter.h +// SproutedInterface +// +// Created by Philip Dow on 7/30/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface JRLRFooter : NSView { +} + +@end diff --git a/JRLRFooter.m b/JRLRFooter.m new file mode 100644 index 0000000..2d3f848 --- /dev/null +++ b/JRLRFooter.m @@ -0,0 +1,47 @@ +// +// JRLRFooter.m +// SproutedInterface +// +// Created by Philip Dow on 7/30/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +@implementation JRLRFooter + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + + } + return self; +} + +- (void) dealloc { + + [super dealloc]; +} + +#pragma mark - + +- (void)drawRect:(NSRect)rect { + // Drawing code here. + + NSRect bds = [self bounds]; + NSColor *gradientEnd = [NSColor colorWithCalibratedRed:254.0/255.0 green:254.0/255.0 blue:254.0/255.0 alpha:1.0]; + NSColor *gradientStart = [NSColor colorWithCalibratedRed:235.0/255.0 green:235.0/255.0 blue:235.0/255.0 alpha:1.0]; + + [[NSBezierPath bezierPathWithRect:bds] + linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + [[NSColor lightGrayColor] set]; + NSFrameRect([self bounds]); +} + +@end diff --git a/JournlerGradientView.h b/JournlerGradientView.h new file mode 100644 index 0000000..1b7b0a7 --- /dev/null +++ b/JournlerGradientView.h @@ -0,0 +1,63 @@ +// +// JournlerGradientView.h +// SproutedInterface +// +// Created by Philip Dow on 10/20/06. +// Copyright 2006 Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import // needed for Core Image + +@interface JournlerGradientView : NSView { + + NSColor *gradientStartColor; + NSColor *gradientEndColor; + NSColor *backgroundColor; + + int borders[4]; + BOOL bordered; + + BOOL usesBezierPath; + BOOL drawsGradient; + + NSColor *fillColor; + NSColor *borderColor; + + NSControlTint controlTint; +} + ++ (void) drawGradientInView:(NSView*)aView rect:(NSRect)aRect highlight:(BOOL)highlight shadow:(float)shadowLevel; + +- (int*) borders; +- (void) setBorders:(int*)sides; + +- (BOOL) bordered; +- (void) setBordered:(BOOL)flag; + +- (BOOL) drawsGradient; +- (void) setDrawsGradient:(BOOL)draws; + +- (BOOL) usesBezierPath; +- (void) setUsesBezierPath:(BOOL)bezier; + +- (NSColor*) fillColor; +- (void) setFillColor:(NSColor*)aColor; + +- (NSColor*) borderColor; +- (void) setBorderColor:(NSColor*)aColor; + +- (NSControlTint) controlTint; +- (void) setControlTint:(NSControlTint)aTint; + +- (NSColor *)gradientStartColor; +- (void)setGradientStartColor:(NSColor *)newGradientStartColor; +- (NSColor *)gradientEndColor; +- (void)setGradientEndColor:(NSColor *)newGradientEndColor; +- (NSColor *)backgroundColor; +- (void)setBackgroundColor:(NSColor *)newBackgroundColor; + +- (void) resetGradient; + +@end diff --git a/JournlerGradientView.m b/JournlerGradientView.m new file mode 100644 index 0000000..5b78275 --- /dev/null +++ b/JournlerGradientView.m @@ -0,0 +1,476 @@ +// +// JournlerGradientView.m +// SproutedInterface +// +// Created by Philip Dow on 10/20/06. +// Copyright 2006 Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import "JournlerGradientView.h" + +#import +#import + +@implementation JournlerGradientView + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + + [self setControlTint:[NSColor currentControlTint]]; + [self resetGradient]; + + fillColor = [[NSColor whiteColor] retain]; + borderColor = [[NSColor colorWithCalibratedRed:157.0/255.0 green:157.0/255.0 blue:157.0/255.0 alpha:1.0] retain]; + + bordered = NO; + + borders[0] = 0; // top + borders[1] = 0; // right + borders[2] = 0; // bottom + borders[3] = 0; // left + + drawsGradient = YES; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_controlTintChanged:) + name:NSControlTintDidChangeNotification object:NSApp]; + } + return self; +} + +- (void) dealloc { + [gradientStartColor release]; + [gradientEndColor release]; + [backgroundColor release]; + [fillColor release]; + [borderColor release]; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [super dealloc]; +} + +#pragma mark - + ++ (void) drawGradientInView:(NSView*)aView rect:(NSRect)aRect highlight:(BOOL)highlight shadow:(float)shadowLevel +{ + // Construct rounded rect path + NSRect boxRect = aRect; + NSRect bgRect = boxRect; + NSRect rect = aRect; + + int minX = NSMinX(bgRect); + int maxX = NSMaxX(bgRect); + int minY = NSMinY(bgRect); + int maxY = NSMaxY(bgRect); + + float fraction = ( highlight ? 0.5 : 0.5 ); + + NSBezierPath *bgPath = [NSBezierPath bezierPathWithRect:rect]; + NSGraphicsContext *nsContext = [NSGraphicsContext currentContext]; + + NSColor *backgroundColor = [NSColor windowBackgroundColor]; + NSColor *gradientStartColor, *gradientEndColor; + + if ( [aView isFlipped] ) + { + gradientEndColor = [[[[NSColor colorWithCalibratedWhite:0.92 alpha:0.6] + colorUsingColorSpaceName:NSCalibratedRGBColorSpace] + blendedColorWithFraction:fraction ofColor:[NSColor colorForControlTint:[NSColor currentControlTint]]] + shadowWithLevel:shadowLevel]; + + gradientStartColor = [[[[NSColor colorWithCalibratedWhite:0.82 alpha:0.6] + colorUsingColorSpaceName:NSCalibratedRGBColorSpace] + blendedColorWithFraction:fraction ofColor:[NSColor colorForControlTint:[NSColor currentControlTint]]] + shadowWithLevel:shadowLevel]; + } + else + { + gradientStartColor = [[[[NSColor colorWithCalibratedWhite:0.92 alpha:0.6] + colorUsingColorSpaceName:NSCalibratedRGBColorSpace] + blendedColorWithFraction:fraction ofColor:[NSColor colorForControlTint:[NSColor currentControlTint]]] + shadowWithLevel:shadowLevel]; + + gradientEndColor = [[[[NSColor colorWithCalibratedWhite:0.82 alpha:0.6] + colorUsingColorSpaceName:NSCalibratedRGBColorSpace] + blendedColorWithFraction:fraction ofColor:[NSColor colorForControlTint:[NSColor currentControlTint]]] + shadowWithLevel:shadowLevel]; + } + + // Draw solid color background + [backgroundColor set]; + [bgPath fill]; + + // Draw gradient background using Core Image + CIColor *startColor = [[[CIColor alloc] initWithColor:gradientStartColor] autorelease]; + CIColor *endColor = [[[CIColor alloc] initWithColor:gradientEndColor] autorelease]; + + CIFilter *myFilter = [CIFilter filterWithName:@"CILinearGradient"]; + [myFilter setDefaults]; + [myFilter setValue:[CIVector vectorWithX:(minX) + Y:(minY)] + forKey:@"inputPoint0"]; + [myFilter setValue:[CIVector vectorWithX:(minX) + Y:(maxY)] + forKey:@"inputPoint1"]; + [myFilter setValue:startColor + forKey:@"inputColor0"]; + [myFilter setValue:endColor + forKey:@"inputColor1"]; + CIImage *theImage = [myFilter valueForKey:@"outputImage"]; + + + // Get a CIContext from the NSGraphicsContext, and use it to draw the CIImage + CGRect dest = CGRectMake(minX, minY, maxX - minX, maxY - minY); + + CGPoint pt = CGPointMake(bgRect.origin.x, bgRect.origin.y); + + [nsContext saveGraphicsState]; + + [bgPath addClip]; + + [[nsContext CIContext] drawImage:theImage + atPoint:pt + fromRect:dest]; + + [nsContext restoreGraphicsState]; +} + +- (void)drawRect:(NSRect)rect { + + // Construct rounded rect path + NSRect boxRect = [self bounds]; + NSRect bgRect = boxRect; + + int minX = NSMinX(bgRect); + int maxX = NSMaxX(bgRect); + int minY = NSMinY(bgRect); + int maxY = NSMaxY(bgRect); + + NSBezierPath *bgPath = [NSBezierPath bezierPathWithRect:rect]; + NSGraphicsContext *nsContext = [NSGraphicsContext currentContext]; + + // Draw solid color background + [backgroundColor set]; + [bgPath fill]; + + // Draw gradient background using Core Image + + if ( [self drawsGradient] ) + { + + if ( [self usesBezierPath] ) + { + bgPath = [NSBezierPath bezierPathWithRect:boxRect]; + [bgPath linearGradientFillWithStartColor:gradientStartColor endColor:gradientEndColor]; + } + else + { + + CIColor *startColor = [[[CIColor alloc] initWithColor:gradientStartColor] autorelease]; + CIColor *endColor = [[[CIColor alloc] initWithColor:gradientEndColor] autorelease]; + + CIFilter *myFilter = [CIFilter filterWithName:@"CILinearGradient"]; + [myFilter setDefaults]; + [myFilter setValue:[CIVector vectorWithX:(minX) + Y:(minY)] + forKey:@"inputPoint0"]; + [myFilter setValue:[CIVector vectorWithX:(minX) + Y:(maxY)] + forKey:@"inputPoint1"]; + [myFilter setValue:startColor + forKey:@"inputColor0"]; + [myFilter setValue:endColor + forKey:@"inputColor1"]; + CIImage *theImage = [myFilter valueForKey:@"outputImage"]; + + + // Get a CIContext from the NSGraphicsContext, and use it to draw the CIImage + CGRect dest = CGRectMake(minX, minY, maxX - minX, maxY - minY); + + CGPoint pt = CGPointMake(bgRect.origin.x, bgRect.origin.y); + + [nsContext saveGraphicsState]; + + [bgPath addClip]; + + [[nsContext CIContext] drawImage:theImage + atPoint:pt + fromRect:dest]; + + [nsContext restoreGraphicsState]; + } + } + + if ( [self bordered] ) { + + NSPoint topLeft, topRight, bottomRight, bottomLeft; + + topLeft = NSMakePoint(0.5, boxRect.size.height-0.5); + topRight = NSMakePoint(boxRect.size.width, boxRect.size.height-0.5); + + bottomRight = NSMakePoint(boxRect.size.width-0.5, 0.5); + bottomLeft = NSMakePoint(0.5, 0.5); + + // does this actually work? + float scaleFactor = [[NSScreen mainScreen] userSpaceScaleFactor]; + if ( scaleFactor != 1.0 ) { + + // apply the scale factor + topLeft.x *= scaleFactor; + topLeft.y *= scaleFactor; + + topRight.x *= scaleFactor; + topRight.y *= scaleFactor; + + bottomRight.x *= scaleFactor; + bottomRight.y *= scaleFactor; + + bottomLeft.x *= scaleFactor; + bottomLeft.y *= scaleFactor; + + // adjust the points to integral boundaries + topLeft.x = ceil(topLeft.x); + topLeft.y = ceil(topLeft.y); + + topRight.x = ceil(topRight.x); + topRight.y = ceil(topRight.y); + + bottomRight.x = ceil(bottomRight.x); + bottomRight.y = ceil(bottomRight.y); + + bottomLeft.x = ceil(bottomLeft.x); + bottomLeft.y = ceil(bottomLeft.y); + + // convert back to user space + topLeft.x /= scaleFactor; + topLeft.y /= scaleFactor; + + topRight.x /= scaleFactor; + topRight.y /= scaleFactor; + + bottomRight.x /= scaleFactor; + bottomRight.y /= scaleFactor; + + bottomLeft.x /= scaleFactor; + bottomLeft.y /= scaleFactor; + } + + // + //draws an outline around the guy, just like with other views + NSBezierPath *borderPath = [NSBezierPath bezierPath]; + if ( borders[0] ) { + [borderPath moveToPoint:topLeft]; + [borderPath lineToPoint:topRight]; + } + if ( borders[1] ) { + [borderPath moveToPoint:topRight]; + [borderPath lineToPoint:bottomRight]; + } + if ( borders[2] ) { + [borderPath moveToPoint:bottomRight]; + [borderPath lineToPoint:bottomLeft]; + } + if ( borders[3] ) { + [borderPath moveToPoint:bottomLeft]; + [borderPath lineToPoint:topLeft]; + } + + [[self borderColor] set ]; + + [nsContext saveGraphicsState]; + [nsContext setShouldAntialias:NO]; + + [borderPath setLineWidth:1.0]; + [borderPath stroke]; + + [nsContext restoreGraphicsState]; + } +} + + +#pragma mark - + +- (void) resetGradient +{ + if ( [self respondsToSelector:@selector(addTrackingArea:)] ) + { + //10.5 - ends up no different that the pdgradientview + [self setGradientStartColor:[NSColor colorWithCalibratedWhite:0.82 alpha:0.8]]; // 1.0 + [self setGradientEndColor:[NSColor colorWithCalibratedWhite:0.92 alpha:0.8]]; // 1.0 + } + else + { + //10.4 + [self setGradientStartColor:[NSColor colorWithCalibratedWhite:0.92 alpha:0.6]]; // 1.0 + [self setGradientEndColor:[NSColor colorWithCalibratedWhite:0.82 alpha:0.6]]; // 1.0 + } + + [self setBackgroundColor:[NSColor windowBackgroundColor]]; // 1.0 +} + +#pragma mark - + +- (int*) borders { + return borders; +} + +- (void) setBorders:(int*)sides { + + borders[0] = sides[0]; + borders[1] = sides[1]; + borders[2] = sides[2]; + borders[3] = sides[3]; +} + +- (BOOL) bordered { + return bordered; +} + +#pragma mark - + +- (BOOL) drawsGradient +{ + return drawsGradient; +} + +- (void) setDrawsGradient:(BOOL)draws +{ + drawsGradient = draws; +} + +- (BOOL) usesBezierPath +{ + return usesBezierPath; +} + +- (void) setUsesBezierPath:(BOOL)bezier +{ + usesBezierPath = bezier; +} + +#pragma mark - + +- (void) setBordered:(BOOL)flag { + bordered = flag; +} + +- (NSColor*) fillColor { + return fillColor; +} + +- (void) setFillColor:(NSColor*)aColor { + + if ( fillColor != aColor ) { + [fillColor release]; + fillColor = [aColor copyWithZone:[self zone]]; + } +} + +- (NSColor*) borderColor { + return borderColor; +} + +- (void) setBorderColor:(NSColor*)aColor { + + if ( borderColor != aColor ) { + [borderColor release]; + borderColor = [aColor copyWithZone:[self zone]]; + } +} + +- (NSControlTint) controlTint +{ + return controlTint; +} + +- (void) setControlTint:(NSControlTint)aTint +{ + controlTint = aTint; + [self resetGradient]; +} + +#pragma mark - + +- (NSColor *)gradientStartColor +{ + return gradientStartColor; +} + +- (void)setGradientStartColor:(NSColor *)newGradientStartColor +{ + // Must ensure gradient colors are in NSCalibratedRGBColorSpace, or Core Image gets angry. + NSColor *newCalibratedGradientStartColor; + if ( [self respondsToSelector:@selector(addTrackingArea:)] ) + { + newCalibratedGradientStartColor = [newGradientStartColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + } + else + { + newCalibratedGradientStartColor = [[newGradientStartColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace] + blendedColorWithFraction:0.5 ofColor:[NSColor colorForControlTint:[self controlTint]]]; + } + + [newCalibratedGradientStartColor retain]; + [gradientStartColor release]; + gradientStartColor = newCalibratedGradientStartColor; + +} + + +- (NSColor *)gradientEndColor +{ + return gradientEndColor; +} + + +- (void)setGradientEndColor:(NSColor *)newGradientEndColor +{ + // Must ensure gradient colors are in NSCalibratedRGBColorSpace, or Core Image gets angry. + NSColor *newCalibratedGradientEndColor; + + if ( [self respondsToSelector:@selector(addTrackingArea:)] ) + { + newCalibratedGradientEndColor = [newGradientEndColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + } + else + { + newCalibratedGradientEndColor = [[newGradientEndColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace] + blendedColorWithFraction:0.5 ofColor:[NSColor colorForControlTint:[self controlTint]]]; + } + + [newCalibratedGradientEndColor retain]; + [gradientEndColor release]; + gradientEndColor = newCalibratedGradientEndColor; +} + + +- (NSColor *)backgroundColor +{ + return backgroundColor; +} + + +- (void)setBackgroundColor:(NSColor *)newBackgroundColor +{ + [newBackgroundColor retain]; + [backgroundColor release]; + backgroundColor = newBackgroundColor; + [self setNeedsDisplay:YES]; +} + +#pragma mark - + +- (void) _controlTintChanged:(NSNotification*)aNotification +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + [self resetGradient]; + [self setNeedsDisplay:YES]; +} + +@end diff --git a/LabelPicker.h b/LabelPicker.h new file mode 100644 index 0000000..827975a --- /dev/null +++ b/LabelPicker.h @@ -0,0 +1,50 @@ +// +// LabelPicker.h +// SproutedInterface +// +// Created by Philip Dow on 11/12/05. +// Copyright 2005 Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface LabelPicker : NSView { + + NSInteger _tag; + + id _target; + SEL _selector; + + NSImage *_labelImage; + NSImage *_labelSelectedImage; + NSImage *_labelHoverImage; + + NSInteger _labelSelection; + + NSRect _clearRect; + NSRect _redRect; + NSRect _orangeRect; + NSRect _yellowRect; + NSRect _greenRect; + NSRect _blueRect; + NSRect _purpleRect; + NSRect _greyRect; +} + +- (int) tag; +- (void) setTag:(int)aTag; + +- (id) target; +- (void) setTarget:(id)targetObject; + +- (SEL) action; +- (void) setAction:(SEL)targetSelector; + +- (NSInteger) labelSelection; +- (void) setLabelSelection:(NSInteger)value; + ++ (NSInteger) finderEquivalentForPickerLabel:(NSInteger)value; ++ (NSInteger) pickerEquivalentForFinderLabel:(NSInteger)value; + +@end diff --git a/LabelPicker.m b/LabelPicker.m new file mode 100644 index 0000000..93a27ad --- /dev/null +++ b/LabelPicker.m @@ -0,0 +1,238 @@ +// +// LabelPicker.m +// SproutedInterface +// +// Created by Philip Dow on 11/12/05. +// Copyright 2005 Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import +#import + +@implementation LabelPicker + ++ (void)initialize +{ + if ( self == [LabelPicker class] ) + { + [self exposeBinding:@"labelSelection"]; + } +} + +- (id)initWithFrame:(NSRect)frame +{ + if ( self = [super initWithFrame:frame] ) + { + // Initialization code here. + _labelImage = [BundledImageWithName(@"labelall.tif", @"com.sprouted.interface") retain]; + if ( _labelImage == nil ) NSLog(@"%@ %s - labelall.tif not found!",[self className],_cmd); + + _labelSelectedImage = [BundledImageWithName(@"labelselected.tif", @"com.sprouted.interface") retain]; + if ( _labelSelectedImage == nil ) NSLog(@"%@ %s - labelselected.tif not found!",[self className],_cmd); + + _labelHoverImage = [BundledImageWithName(@"labelhover.tif", @"com.sprouted.interface") retain]; + if ( _labelSelectedImage == nil ) NSLog(@"%@ %s - labelhover.tif not found!",[self className],_cmd); + + _clearRect = NSMakeRect(0,0,17,16); + + _redRect = NSMakeRect(22,0,17,16); + _orangeRect = NSMakeRect(40,0,17,16); + _yellowRect = NSMakeRect(58,0,17,16); + + _greenRect = NSMakeRect(76,0,17,16); + _blueRect = NSMakeRect(94,0,17,16); + _purpleRect = NSMakeRect(112,0,17,16); + _greyRect = NSMakeRect(130,0,17,16); + + _target = nil; + _tag = 0; + } + + return self; +} + +- (void) dealloc +{ + [_labelImage release], _labelImage = nil; + [_labelSelectedImage release], _labelSelectedImage = nil; + [_labelHoverImage release], _labelHoverImage = nil; + + [super dealloc]; +} + +#pragma mark - + +- (NSInteger) tag +{ + return _tag; +} + +- (void) setTag:(NSInteger)aTag +{ + _tag = aTag; +} + +- (id) target +{ + return _target; +} + +- (void) setTarget:(id)targetObject +{ + _target = targetObject; +} + +- (SEL) action { + return _selector; +} + +- (void) setAction:(SEL)targetSelector +{ + _selector = targetSelector; +} + +- (NSInteger) labelSelection +{ + return _labelSelection; +} + +- (void) setLabelSelection:(NSInteger)value +{ + [self willChangeValueForKey:@"labelSelection"]; + _labelSelection = value; + [self didChangeValueForKey:@"labelSelection"]; + [self setNeedsDisplay:YES]; +} + +#pragma mark - + +- (void)drawRect:(NSRect)rect +{ + NSRect bds = [self bounds]; + NSRect targetSelectionRect; + + NSRect selectionRect = NSMakeRect( 0, 0, + [_labelSelectedImage size].width, + [_labelSelectedImage size].height ); + + NSRect sourceRect = NSMakeRect( 0, 0, + [_labelImage size].width, + [_labelImage size].height) ; + + NSRect destinationRect = NSMakeRect(0, + bds.size.height/2 - sourceRect.size.height/2, + sourceRect.size.width, + sourceRect.size.height ); + + switch ( [self labelSelection] ) + { + case 1: + targetSelectionRect = _redRect; + break; + case 2: + targetSelectionRect = _orangeRect; + break; + case 3: + targetSelectionRect = _yellowRect; + break; + case 4: + targetSelectionRect = _greenRect; + break; + case 5: + targetSelectionRect = _blueRect; + break; + case 6: + targetSelectionRect = _purpleRect; + break; + case 7: + targetSelectionRect = _greyRect; + break; + } + + if ( [self labelSelection] ) + { + // target the selection image + targetSelectionRect.size.height += ( bds.size.height/2 - targetSelectionRect.size.height/2 ); + [_labelSelectedImage drawInRect:targetSelectionRect + fromRect:selectionRect + operation:NSCompositeSourceOver + fraction:1.0]; + + // draw this particular color back over it + } + + [_labelImage drawInRect:destinationRect + fromRect:sourceRect + operation:NSCompositeSourceOver + fraction:1.0]; +} + +#pragma mark - + +- (void)mouseMoved:(NSEvent *)theEvent +{ + NSPoint mouseLoc; + NSInteger myX, myY; + + mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + myX = mouseLoc.x; + myY = mouseLoc.y; +} + +- (BOOL)mouseDownCanMoveWindow +{ + return NO; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + NSPoint mouseLoc; + NSInteger myX, myY; + NSInteger newPoint = -1; + + mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + myX = mouseLoc.x; + myY = mouseLoc.y; + + if ( _labelSelection != 0 && NSPointInRect(mouseLoc,_clearRect) ) + newPoint = 0; + else if ( _labelSelection != 1 && NSPointInRect(mouseLoc,_redRect) ) + newPoint = 1; + else if ( _labelSelection != 2 && NSPointInRect(mouseLoc,_orangeRect) ) + newPoint = 2; + else if ( _labelSelection != 3 && NSPointInRect(mouseLoc,_yellowRect) ) + newPoint = 3; + else if ( _labelSelection != 4 && NSPointInRect(mouseLoc,_greenRect) ) + newPoint = 4; + else if ( _labelSelection != 5 && NSPointInRect(mouseLoc,_blueRect) ) + newPoint = 5; + else if ( _labelSelection != 6 && NSPointInRect(mouseLoc,_purpleRect) ) + newPoint = 6; + else if ( _labelSelection != 7 && NSPointInRect(mouseLoc,_greyRect) ) + newPoint = 7; + + if ( newPoint != -1 ) + { + [self setLabelSelection:newPoint]; + + if ( _target && [_target respondsToSelector:_selector] ) + [_target performSelector:_selector withObject:self]; + } +} + +#pragma mark - + ++ (NSInteger) finderEquivalentForPickerLabel:(NSInteger)value +{ + return [SproutedLabelConverter finderEquivalentForSproutedLabel:value]; +} + ++ (NSInteger) pickerEquivalentForFinderLabel:(NSInteger)value +{ + return [SproutedLabelConverter sproutedEquivalentForFinderLabel:value]; +} + + +@end diff --git a/LinkController.h b/LinkController.h new file mode 100644 index 0000000..b61a2af --- /dev/null +++ b/LinkController.h @@ -0,0 +1,36 @@ +// +// LinkController.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface LinkController : NSWindowController +{ + IBOutlet NSObjectController *objectController; + + IBOutlet NSTextField *linkField; + IBOutlet NSTextField *urlField; + + NSString *_linkString; + NSString *_URLString; +} + +- (id) initWithLink:(NSString*)link URL:(NSString*)url; + +- (int) runAsSheetForWindow:(NSWindow*)window attached:(BOOL)sheet; + +- (NSString*) linkString; +- (void) setLinkString:(NSString*)string; + +- (NSString*) URLString; +- (void) setURLString:(NSString*)string; + +- (IBAction)genericCancel:(id)sender; +- (IBAction)genericOkay:(id)sender; + +@end diff --git a/LinkController.m b/LinkController.m new file mode 100644 index 0000000..ca5976e --- /dev/null +++ b/LinkController.m @@ -0,0 +1,110 @@ +// +// LinkController.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation LinkController + +- (id) initWithLink:(NSString*)link URL:(NSString*)url { + + //if ( self = [super init] ) { + if ( self = [super initWithWindowNibName:@"InsertLink"] ) { + //[self window]; + + //[self setWindowFrameAutosaveName:@"InsertLink"]; + + _linkString = [link copyWithZone:[self zone]]; + _URLString = [url copyWithZone:[self zone]]; + + //[self setLinkString:link]; + //[self setURLString:url]; + + //[NSBundle loadNibNamed:@"InsertLink" owner:self]; + + } + + return self; + +} + +- (void) dealloc { + + [_linkString release]; + _linkString = nil; + [_URLString release]; + _URLString = nil; + + [super dealloc]; + +} + +- (void)windowWillClose:(NSNotification *)aNotification { + + [objectController unbind:@"objectContent"]; + [objectController setContent:nil]; + +} + +#pragma mark - + +- (NSString*) linkString { return _linkString; } + +- (void) setLinkString:(NSString*)string { + if ( _linkString != string ) { + [_linkString release]; + _linkString = [string copyWithZone:[self zone]]; + } +} + +- (NSString*) URLString { return _URLString; } + +- (void) setURLString:(NSString*)string { + if ( _URLString != string ) { + [_URLString release]; + _URLString = [string copyWithZone:[self zone]]; + } +} + +#pragma mark - + +- (int) runAsSheetForWindow:(NSWindow*)window attached:(BOOL)sheet { + + int result; + + if ( sheet ) + [NSApp beginSheet: [self window] modalForWindow: window modalDelegate: nil + didEndSelector: nil contextInfo: nil]; + + result = [NSApp runModalForWindow: [self window]]; + + if ( ![objectController commitEditing] ) + NSLog(@"%@ %s - unable to commit editing", [self className], _cmd); + + if ( sheet ) + [NSApp endSheet: [self window]]; + + [self close]; + + return result; + +} + +#pragma mark - + +- (IBAction)genericCancel:(id)sender +{ + [NSApp abortModal]; +} + +- (IBAction)genericOkay:(id)sender +{ + [NSApp stopModal]; +} + +@end diff --git a/MNLineNumberingRulerView.h b/MNLineNumberingRulerView.h new file mode 100644 index 0000000..36bf037 --- /dev/null +++ b/MNLineNumberingRulerView.h @@ -0,0 +1,101 @@ +// +// MNLineNumberingRulerView.h +// +// +// Created by Masatoshi Nishikata on 29/10/05. +// Copyright 2005 Masatoshi Nishikata. All rights reserved. +// http://homepage.mac.com/mnishikata/iblog/ + +//ORIGINAL +// +// MyTextView.m +// LineNumbering +// +// Created by Koen van der Drift on Sat May 01 2004. +// Copyright (c) 2004 Koen van der Drift. All rights reserved. +// + + +#import + +@class MNLineNumberingTextStorage; + +extern const int MNNoLineNumbering; +extern const int MNParagraphNumber; +extern const int MNCharacterNumber; +extern const int MNLineNumber; +extern const int MNDrawBookmarks; + + + +@interface MNLineNumberingRulerView : NSRulerView { + + //nib outlets + IBOutlet id dialogueView; + IBOutlet id radioButtons; + IBOutlet id textField; + + + NSTextView* textView; + NSLayoutManager* layoutManager; + + NSImage* markerImage; + + int rulerOption; + + NSMutableDictionary* marginAttributes; + + BOOL markerDeleteReservationFlag; + +} + +-(void)startSheet; +// Start 'Jump To...' sheet. + +-(BOOL)showParagraph:(unsigned)paragraphNum; + +-(BOOL)showLine:(unsigned)lineNum; + +-(BOOL)showCharacter:(unsigned)charIndex granularity:(NSSelectionGranularity)granularity; + // show line in document text view + // Granularity is one of NSSelectByCharacter, NSSelectByWord, NSSelectByParagraph, or -1(select by line) + +-(void)setVisible:(BOOL)flag; +-(BOOL)isVisible; +// Set and Return Ruler View visiblity. + + +-(void)setOption:(unsigned)option; +// option is +/* + extern const int MNNoLineNumbering; + extern const int MNParagraphNumber; + extern const int MNCharacterNumber; + extern const int MNLineNumber; + extern const int MNDrawBookmarks; +*/ + + + + + +////// private + + +-(IBAction)jumpButtonClicked:(id)sender; +-(unsigned)lineNumberAtIndex:(unsigned)charIndex; +-(unsigned)charIndexOfLineNumber:(unsigned)lineNumber; + +-(void)drawEmptyMargin; +-(void) drawParagraphNumbersInMargin; +-(void) drawNumbersInMargin; +-(void)drawOneNumberInMargin:(unsigned) aNumber inRect:(NSRect)r ; +-(void)drawMarkerInRect:(NSRect)lineRect; + +-(unsigned)characterIndexAtLocation:(float)pos; +-(NSRulerMarker*)newMarker; + + + + +@end diff --git a/MNLineNumberingRulerView.m b/MNLineNumberingRulerView.m new file mode 100644 index 0000000..ac87a82 --- /dev/null +++ b/MNLineNumberingRulerView.m @@ -0,0 +1,1095 @@ + + +#import +#import + +// Ruler thickness value +#define RULER_THICKNESS 25 + +#define MarkerAttributeName NSToolTipAttributeName + +// Margin of displaying bookmarked line in a context menu. +#define STRIP_PREVIEW_MARGIN 15 + +// Default +#define DEFAULT_OPTION MNParagraphNumber | MNDrawBookmarks + + +const int MNNoLineNumbering = 0x00; +const int MNParagraphNumber = 0x03; +const int MNCharacterNumber = 0x02; +const int MNLineNumber = 0x01; +const int MNDrawBookmarks = 0x10; + + + + +@implementation MNLineNumberingRulerView + +/* +- (id)initWithScrollView:(NSScrollView *)aScrollView orientation:(NSRulerOrientation)orientation +{ + + if ( self = [super initWithScrollView:(NSScrollView *)aScrollView + orientation:(NSRulerOrientation)orientation]) + { + + //load nib + [NSBundle loadNibNamed:@"MNLineNumbering" owner:self]; + + + + // Set default width + [self setRuleThickness:RULER_THICKNESS]; + + // Marker config + [self setReservedThicknessForMarkers:0]; + [self setClientView:self]; // Markers ask me if I can add a marker. + + // Add a dummy marker to draw properly + + markerImage = [[NSImage alloc] initByReferencingFile: + [[NSBundle bundleForClass:[self class]] pathForResource:@"marker" ofType:@"tiff"]]; + + NSRulerMarker* aMarker = [self newMarker]; + [self addMarker:aMarker]; + [self removeMarker:aMarker]; + + + // Set letter attributes + marginAttributes = [[NSMutableDictionary alloc] init]; + [marginAttributes setObject:[NSFont labelFontOfSize:9] forKey: NSFontAttributeName]; + [marginAttributes setObject:[NSColor darkGrayColor] forKey: NSForegroundColorAttributeName]; + + + + rulerOption = DEFAULT_OPTION; + + + markerDeleteReservationFlag = NO; + + // + textView = [aScrollView documentView]; + layoutManager = [textView layoutManager]; + + + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowDidUpdate:) + name:NSWindowDidUpdateNotification + object:[aScrollView window]]; + + + + + } + + return self; +} +*/ + +- (void)windowDidUpdate:(NSNotification *)notification +{ + [self display]; +} + +-(void)startSheet +{ + [[NSApplication sharedApplication] beginSheet:dialogueView + modalForWindow:[self window] + modalDelegate:self + didEndSelector:NULL + contextInfo:NULL]; +} + +- (IBAction)jumpButtonClicked:(id)sender +{ + if( [sender tag] != 1 ) // jump & close or cancel + { + [dialogueView orderOut:self]; + [[NSApplication sharedApplication] endSheet:dialogueView]; + } + + if( [sender tag] == -1 ) //cancel + return; + + //check radio buttons + int tag = [radioButtons selectedRow]; + + int number = [textField intValue]; + + if( tag == 0 ) //paragraph + [self showParagraph:number]; + + else if( tag == 1 )//line + [self showLine:number]; + + else if( tag == 2 ) + [self showCharacter:number -1 granularity:NSSelectByCharacter]; + + [self display]; +} + +-(BOOL)showParagraph:(unsigned)paragraphNum +{ + //search paragraph number .... can be faster + + + unsigned charIndex = 0; + unsigned paragraph = 0; + + for( charIndex = 0; charIndex < [ [textView textStorage] length]; charIndex++ ) + { + unichar characterToCheck = [[[textView textStorage] string] characterAtIndex:charIndex]; + + + if (characterToCheck == '\n' || characterToCheck == '\r' || + characterToCheck == 0x2028 || characterToCheck == 0x2029) + paragraph++; + + if( paragraph == paragraphNum ) + break; + } + + + + return [self showCharacter:charIndex granularity:NSSelectByParagraph]; +} + + + +-(BOOL)showLine:(unsigned)lineNum +{ + + unsigned targetCharIndex = [self charIndexOfLineNumber:lineNum ]; + + return [self showCharacter:targetCharIndex granularity:-1]; +} + +-(unsigned)lineNumberAtIndex:(unsigned)charIndex +{ + unsigned index = 0; + unsigned lineNumber = 1; + NSRange lineRange; + + //convert charindex to glyphIndex + + unsigned glyphIndex = [layoutManager glyphRangeForCharacterRange:NSMakeRange(charIndex,1) + actualCharacterRange:NULL].location; + + // Skip all lines that are visible at the top of the text view (if any) + while ( index < glyphIndex ) + { + ++lineNumber; + + [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange]; + index = NSMaxRange( lineRange ); + } + + return lineNumber; +} + + +-(unsigned)charIndexOfLineNumber:(unsigned)lineNumber +{ + unsigned indexLine = 0; + unsigned charIndex = 0; + NSRange lineRange; + + // Skip all lines that are visible at the top of the text view (if any) + while ( indexLine < lineNumber ) + { + ++indexLine; + + [layoutManager lineFragmentRectForGlyphAtIndex:charIndex effectiveRange:&lineRange]; + charIndex = NSMaxRange( lineRange ); + } + + return charIndex -1; +} + +-(BOOL)showCharacter:(unsigned)charIndex granularity:(NSSelectionGranularity)granularity + // show line in document text view + // Granularity is one of NSSelectByCharacter, NSSelectByWord, NSSelectByParagraph, or -1(select by line) +{ + NSRange lineRange; + + + // Return if text view is empty + if([[textView textStorage] length] < charIndex +1 ) return NO; + + + // Show in textView + if( granularity == -1 ) + { + + [layoutManager lineFragmentRectForGlyphAtIndex: + [layoutManager glyphRangeForCharacterRange:NSMakeRange(charIndex,1) + actualCharacterRange:NULL].location effectiveRange:&lineRange]; + + + // Now lineRange is glyph range of the line + // Convert lineRange(glyph range) --> lineRange(char range) + lineRange = [layoutManager characterRangeForGlyphRange: lineRange + actualGlyphRange:NULL]; + [textView setSelectedRange:lineRange]; + } + else + { + [textView setSelectedRange: + [textView selectionRangeForProposedRange:NSMakeRange(charIndex,1) granularity:granularity]]; + + } + + [textView scrollRangeToVisible: [textView selectedRange]]; + return YES; +} + + +-(void)setVisible:(BOOL)flag +{ + if( flag == YES ) + [self setRuleThickness:RULER_THICKNESS]; + else + [self setRuleThickness:0]; + +} +-(BOOL)isVisible +{ + if( [self ruleThickness] == 0 ) + return NO; + else + return YES; + +} +-(void)setOption:(unsigned)option +{ + rulerOption = option; + [self display]; +} + +- (void) dealloc +{ + //NSLog(@"view dealloc"); + [layoutManager setDelegate:NULL]; + + [[NSNotificationCenter defaultCenter] removeObserver:self ]; + + + textView = NULL; + layoutManager = NULL; + [markerImage release]; + + [marginAttributes release]; + + [super dealloc]; +} + +#pragma mark Drawing + +-(void)drawRect:(NSRect)rect +{ + if( ! [[self window] isKeyWindow] ) return; + + [super drawRect:rect]; + + +} + +- (void)drawHashMarksAndLabelsInRect:(NSRect)aRect + //Draw numbers +{ + + + + + + if( [self isVisible] ) + { + + // *** (1) draw background *** + [self drawEmptyMargin ]; + + // *** (2) draw numbers *** + [self drawNumbersInMargin ]; + + + } + +} + + + + + +-(void)drawEmptyMargin +{ + + + NSRect aRect = NSMakeRect(0,0,[self ruleThickness],[self frame].size.height); + /* + These values control the color of our margin. Giving the rect the 'clear' + background color is accomplished using the windowBackgroundColor. Change + the color here to anything you like to alter margin contents. + */ + + aRect.origin.x += 1; + [[NSColor controlHighlightColor] set]; + [NSBezierPath fillRect: aRect]; + + + // These points should be set to the left margin width. + NSPoint top = NSMakePoint([self frame].size.width, aRect.origin.y + aRect.size.height); + NSPoint bottom = NSMakePoint([self frame].size.width, aRect.origin.y); + + + // This draws the dark line separating the margin from the text area. + [[NSColor darkGrayColor] set]; + [NSBezierPath setDefaultLineWidth:1.0]; + [NSBezierPath strokeLineFromPoint:top toPoint:bottom]; + + +} + + + +-(void) drawParagraphNumbersInMargin:(unsigned)startParagraph start:(unsigned)start_index end:(unsigned)end_index +{ + + + unsigned index; + for ( index = start_index; index < end_index; ) + { + NSRange paragraphRange = + [textView selectionRangeForProposedRange:NSMakeRange(index, 1) granularity:NSSelectByParagraph]; + + + + unsigned glyphIndex = [layoutManager glyphRangeForCharacterRange:NSMakeRange(paragraphRange.location,1) + actualCharacterRange:NULL].location; + + NSRect drawingRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange: NULL]; + + + + [self drawOneNumberInMargin:startParagraph inRect:drawingRect]; + + index = NSMaxRange( [layoutManager glyphRangeForCharacterRange:paragraphRange + actualCharacterRange:NULL] ); + + startParagraph++; + + } + + + +} + + +-(void) drawNumbersInMargin +{ + //NSLog(@"drawNumbersInMargin"); + + UInt32 index, lineNumber; + NSRange lineRange; + NSRect lineRect; + + NSTextContainer* textContainer = [[layoutManager firstTextView] textContainer]; + + // Only get the visible part of the scroller view + NSRect documentVisibleRect = [[[layoutManager firstTextView] enclosingScrollView] documentVisibleRect]; + + // Find the glyph range for the visible glyphs + NSRange glyphRange = [layoutManager glyphRangeForBoundingRect: documentVisibleRect inTextContainer: textContainer]; + + + // Calculate the start and end indexes for the glyphs + unsigned start_index = glyphRange.location; + unsigned end_index = glyphRange.location + glyphRange.length; + + // + NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + // Calculate the start and end char indexes + unsigned start_charIndex = charRange.location; + unsigned end_charIndex = charRange.location + charRange.length; + + + index = 0; + lineNumber = 1; + + unsigned start_paragraphNumber; + start_paragraphNumber = [(MNLineNumberingTextStorage*)[textView textStorage] paragraphNumberAtIndex:start_charIndex]; + + // Skip all lines that are visible at the top of the text view (if any) + while (index < start_index) + { + lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange]; + index = NSMaxRange( lineRange ); + ++lineNumber; + } + + for ( index = start_index; index < end_index; lineNumber++ ) + { + lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange]; + + + if ( ( rulerOption & 0x0F ) == MNParagraphNumber ) + { + + + + } + else if( ( rulerOption & 0x0F ) == MNLineNumber ) + { + [self drawOneNumberInMargin:lineNumber inRect:lineRect]; + + + } + else if ( ( rulerOption & 0x0F ) == MNCharacterNumber ) // draw character numbers + { + [self drawOneNumberInMargin:index +1 inRect:lineRect]; + } + + + index = NSMaxRange( lineRange ); + + + + } + + ///paragraph + + if ( ( rulerOption & 0x0F ) == MNParagraphNumber ) + { + [self drawParagraphNumbersInMargin:start_paragraphNumber start:(unsigned)start_charIndex end:(unsigned)end_charIndex ]; + } +} + + +-(void)drawOneNumberInMargin:(unsigned) aNumber inRect:(NSRect)r +{ + + + //draw a number + r = [textView convertRect:r toView:self]; //Convert coordinates + + NSString *s; + NSSize stringSize; + + s = [NSString stringWithFormat:@"%d", aNumber, nil]; + if( aNumber == 0 ) + s = @"-"; + stringSize = [s sizeWithAttributes:marginAttributes]; + + // Simple algorithm to center the line number next to the glyph. + [s drawAtPoint: NSMakePoint( [self ruleThickness] - stringSize.width, + r.origin.y + ((r.size.height / 2) - (stringSize.height / 2))) + withAttributes:marginAttributes]; + + +} + + +- (void)drawMarkersInRect:(NSRect)aRect +{ + //NSLog(@"drawMarkersInRect %@",NSStringFromRect(aRect)); + + if( (rulerOption & 0x10) == 0 ) + return; + + + + + // *** (0) remove existing markers *** + // Delete markers unless while dragging. + + NSArray* existingMarkers = [self markers]; + + unsigned hoge = 0; + for( hoge = 0; hoge < [existingMarkers count]; hoge++) + { + if( ! [[existingMarkers objectAtIndex:hoge] isDragging] ) + [self removeMarker:[existingMarkers objectAtIndex:hoge]]; + + } + + + + + // Only get the visible part of the scroller view + NSRect documentVisibleRect = [[[layoutManager firstTextView] enclosingScrollView] documentVisibleRect]; + + // Find the glyph range for the visible glyphs + NSRange glyphRange = [layoutManager glyphRangeForBoundingRect: documentVisibleRect inTextContainer: [textView textContainer]]; + + NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + + + + for( hoge = charRange.location; hoge < NSMaxRange(charRange) ; hoge ++ ) + { + + if ( [(MNLineNumberingTextStorage*)[textView textStorage] hasBookmarkAtIndex:hoge inTextView:textView] ) + { + + NSRange paragraphRange = + [textView selectionRangeForProposedRange:NSMakeRange(hoge, 1) granularity:NSSelectByParagraph]; + + + + unsigned glyphIndex = [layoutManager glyphRangeForCharacterRange:NSMakeRange(paragraphRange.location,1) + actualCharacterRange:NULL].location; + + NSRect drawingRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange: NULL]; + drawingRect.size.height = [markerImage size].height; + + [self drawMarkerInRect:drawingRect ]; + + + + } + } + + + +} + +-(void)drawMarkerInRect:(NSRect)lineRect + // check if a marker should be drawn and draw it if necessary +{ + + lineRect = [textView convertRect:lineRect toView:self]; + + + NSArray* markerObjects = [self markers]; + unsigned hoge; + BOOL exist = NO; + for(hoge = 0; hoge < [markerObjects count]; hoge++) + { + //get represented object + NSRulerMarker* marker = [markerObjects objectAtIndex:hoge]; + + + if( [[marker representedObject] isEqualToString:NSStringFromRect(lineRect) ] ) + { + //if( ! [marker isDragging] ) + // [marker setMarkerLocation: lineRect.origin.y + (lineRect.size.height / 2) ]; + [marker drawRect:lineRect]; + + exist = YES; + } + + + } + + if( exist == NO ) + { + + NSRulerMarker* aMarker = [self newMarker]; + [aMarker setMarkerLocation: lineRect.origin.y + (lineRect.size.height / 2) ]; + [aMarker drawRect:lineRect]; + [aMarker setMovable:YES]; + [aMarker setRemovable:YES]; + [aMarker setRepresentedObject: NSStringFromRect(lineRect) ]; + [self addMarker:aMarker]; + + } +} + + + +/////////// + +-(unsigned)characterIndexAtLocation:(float)pos +{ + + //convert + float viewPos = [textView convertPoint:NSMakePoint(0,pos) fromView:[[self window] contentView]].y; + + NSRect sweepRect = NSMakeRect( 0,viewPos,100,viewPos+1); + + NSRange glyphRange = [layoutManager glyphRangeForBoundingRect:sweepRect inTextContainer:[textView textContainer] ]; + NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + //NSLog(@"characterIndexAtLocation = %d",charRange.location); + return charRange.location; +} + + + +#pragma mark Adding, moving, and removing markers + + +-(NSRulerMarker*)newMarker +{ + + NSRulerMarker* aMarker = [[NSRulerMarker alloc] initWithRulerView:self + markerLocation:-10 //invisible at first + image:markerImage + imageOrigin:NSMakePoint(0,8)]; + [aMarker autorelease]; + return aMarker; +} + + +- (void)mouseDown:(NSEvent *)theEvent +{ + //NSLog(@"mouseDown"); + + // add a new marker + if( (rulerOption & 0x10) != MNDrawBookmarks ) return; + + //retrieve mouse location + markerDeleteReservationFlag = NO; + NSPoint mousePosition = [theEvent locationInWindow]; + + //adding or removing a marker + unsigned clickedIndex; + clickedIndex = [self characterIndexAtLocation:mousePosition.y]; + + + if( [(MNLineNumberingTextStorage*)[textView textStorage] hasBookmarkAtIndex:clickedIndex inTextView:textView]== NO) // adding a new marker + { + [(MNLineNumberingTextStorage*)[textView textStorage] setBookmarkAtIndex:clickedIndex flag:YES inTextView:textView]; + + + }else + markerDeleteReservationFlag = YES; + // if clicked on an existing marker, turn flag on + // This flag is used in mouseUp to delete existing marker + +} + + + + + +-(void)mouseUp:(NSEvent *)theEvent + // deleting a marker +{ + + //NSLog(@"mouseUp"); + + if(markerDeleteReservationFlag == YES) + { + //delete + NSPoint mousePosition = [theEvent locationInWindow]; + + unsigned clickedIndex; + clickedIndex = [self characterIndexAtLocation:mousePosition.y]; + + [(MNLineNumberingTextStorage*)[textView textStorage] setBookmarkAtIndex:clickedIndex flag:NO inTextView:textView]; + } + + //[self display]; + markerDeleteReservationFlag = NO; + +} + + +- (void)mouseDragged:(NSEvent *)theEvent + // dragging marker +{ + //NSLog(@"mouseDragged"); + + + + if( (rulerOption & 0x10) != MNDrawBookmarks ) return; + + NSPoint mousePosition = [theEvent locationInWindow]; + + unsigned clickedIndex; + clickedIndex = [self characterIndexAtLocation:mousePosition.y]; + + if( [(MNLineNumberingTextStorage*)[textView textStorage] hasBookmarkAtIndex:clickedIndex inTextView:textView]== YES) //start moving + { + + NSArray* markerObjects = [self markers]; + unsigned hoge; + for(hoge = 0; hoge < [markerObjects count]; hoge++) + { + //get represented object + id ii = [[markerObjects objectAtIndex:hoge] representedObject]; + NSRect identifyingRect = NSRectFromString( ii ); + + float yy = [self convertPoint:mousePosition fromView:[[self window] contentView]].y; + + if( identifyingRect.origin.y <= yy && yy <= NSMaxY(identifyingRect) ) break; + + + } + //NSLog(@"%d, %d", [markerObjects count], hoge); + [ [markerObjects objectAtIndex:hoge] trackMouse:theEvent adding:NO]; + + } + markerDeleteReservationFlag = NO; // turn delete flag off +} + +#pragma mark Defining marker behaviour + +// These are answers to the ruler marker which is asking the client (this ruler view) +// for approvals. + +// ADD +- (BOOL)rulerView:(NSRulerView *)aRulerView shouldAddMarker:(NSRulerMarker *)aMarker +{ + //display update + + return YES; +} + +/* + - (float)rulerView:(NSRulerView *)aRulerView willAddMarker:(NSRulerMarker *)aMarker atLocation:(float)location + { + + + }*/ + +- (void)rulerView:(NSRulerView *)aRulerView didAddMarker:(NSRulerMarker *)aMarker +{ + + // add dictionary + NSPoint mousePosition = [[self window] mouseLocationOutsideOfEventStream]; + + + unsigned clickedIndex; + clickedIndex = [self characterIndexAtLocation:mousePosition.y]; + + [(MNLineNumberingTextStorage*)[textView textStorage] setBookmarkAtIndex:clickedIndex flag:YES inTextView:textView]; + + + + //[self display]; + //NSLog(@"** ADD **"); +} + +//MOVE +- (BOOL)rulerView:(NSRulerView *)aRulerView shouldMoveMarker:(NSRulerMarker *)aMarker +{ + //display update + + NSPoint mousePosition = [[self window] mouseLocationOutsideOfEventStream]; + + + unsigned clickedIndex; + clickedIndex = [self characterIndexAtLocation:mousePosition.y]; + + [(MNLineNumberingTextStorage*)[textView textStorage] setBookmarkAtIndex:clickedIndex flag:NO inTextView:textView]; + + + + return YES; +} + +/* + - (float)rulerView:(NSRulerView *)aRulerView willMoveMarker:(NSRulerMarker *)aMarker toLocation:(float)location + { + + + }*/ + +- (void)rulerView:(NSRulerView *)aRulerView didMoveMarker:(NSRulerMarker *)aMarker +{ + // add dictionary + NSPoint mousePosition = [[self window] mouseLocationOutsideOfEventStream]; + + + unsigned clickedIndex; + clickedIndex = [self characterIndexAtLocation:mousePosition.y]; + + [(MNLineNumberingTextStorage*)[textView textStorage] setBookmarkAtIndex:clickedIndex flag:YES inTextView:textView]; + + + + + //[self display]; //NSLog(@"** BOOKMARK MOVED **"); + + +} + +//REMOVE +- (BOOL)rulerView:(NSRulerView *)aRulerView shouldRemoveMarker:(NSRulerMarker *)aMarker +{ + return YES; +} + +- (void)rulerView:(NSRulerView *)aRulerView didRemoveMarker:(NSRulerMarker *)aMarker +{ + //Do nothing here because the marker was already removed when the moving started. + + //NSLog(@"** BOOKMARK REMOVED 2**"); + + +} + + +#pragma mark Context Menu + +-(void)menu_selected {} // dummy method. +-(void)menu_selected_main:(NSNotification *)notification + // this is called when contextual menu is selected +{ + + NSMenuItem* aMenuItem = [[notification userInfo] objectForKey:@"MenuItem"]; + int tag = [aMenuItem tag]; + if( tag == -2 ) // clear bookmarks + { + + [[textView textStorage] removeAttribute:MarkerAttributeName range:NSMakeRange(0,[[textView textStorage] length]) ]; + + [self display]; + + + }else if( tag >= 0 ) + { + + + [self showCharacter:tag granularity:-1]; + + + }else if( tag == -10 ) + { + [self setOption:(rulerOption & 0x10) | MNNoLineNumbering]; + } + else if( tag == -11 ) + { + [self setOption:(rulerOption & 0x10) | MNLineNumber]; + } + else if( tag == -12 ) + { + [self setOption:(rulerOption & 0x10) | MNCharacterNumber]; + } + else if( tag == -13 ) + { + [self setOption:(rulerOption & 0x10) | MNParagraphNumber]; + } + else if( tag == -14 ) + { + if( (rulerOption & 0x10) == MNDrawBookmarks ) + [self setOption:(rulerOption & 0x0F) ]; + else + [self setOption:(rulerOption & 0x0F) | MNDrawBookmarks ]; + } + else if( tag == -15 ) + { + [self setVisible:![self isVisible]]; + } + else if( tag == -7 ) + { + [self startSheet]; + } + +} + +- (NSMenu *)menuForEvent:(NSEvent *)theEvent +{ + NSMenuItem* aMenuItem; + NSMenu* menu = [[NSMenu alloc] init]; + + + //create bookmark array + NSMutableArray* bookmarks = [[[NSMutableArray alloc] init] autorelease]; + + unsigned hoge; + //NSRange __range; + for( hoge = 0; hoge < [[textView textStorage] length] ;hoge++ ) + { + + unichar characterToCheck = [[[textView textStorage] string] characterAtIndex:hoge]; + + + if (characterToCheck == '\n' || characterToCheck == '\r' || + characterToCheck == 0x2028 || characterToCheck == 0x2029) + { + id marker = [[textView textStorage] attribute:MarkerAttributeName atIndex:hoge + longestEffectiveRange:NULL inRange:NSMakeRange(0,[[textView textStorage] length])]; + + if( marker != NULL ) + { + if( [marker boolValue] ) + { + NSRange paragraphRange = + [textView selectionRangeForProposedRange:NSMakeRange(hoge, 1) granularity:NSSelectByParagraph]; + + [bookmarks addObject:NSStringFromRange(paragraphRange)]; + } + + } + + } + } + + + + + //set up menu + + if( (rulerOption & 0x10) == MNDrawBookmarks ) + { + if([bookmarks count] == 0) + { + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"No Bookmarks" action:@selector(dummy) + keyEquivalent:@""]; + [aMenuItem setEnabled:NO]; + [menu addItem:[aMenuItem autorelease]]; + }else + { + // Add each bookmark + unsigned hoge; + for(hoge = 0; hoge < [bookmarks count]; hoge++) + { + unsigned charIndex = NSRangeFromString( [bookmarks objectAtIndex:hoge] ).location; + NSMenuItem* aMenuItem = [[NSMenuItem alloc] initWithTitle:@"" + action:@selector(menu_selected) + keyEquivalent:@""]; + + unsigned glyphIndex = [[textView layoutManager] glyphRangeForCharacterRange:NSMakeRange(charIndex,1) actualCharacterRange:NULL].location; + //prepare bookmark preview stip + + //set target rect + NSRect aRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:NULL]; + + aRect.origin.y -= STRIP_PREVIEW_MARGIN; + aRect.size.height += STRIP_PREVIEW_MARGIN * 2; + if( aRect.size.width > 500 ) aRect.size.width = 500; + + // strip preview + NSImage* stripImage = [[NSImage alloc] initWithSize:aRect.size]; + [stripImage setFlipped:YES]; + [stripImage lockFocus]; + + NSRect clipViewRect = [[[textView enclosingScrollView] contentView] bounds]; + + + NSPoint originalOrigin = [textView bounds].origin; + NSRect originalFrame = [textView frame]; + + + [textView setBoundsOrigin:NSMakePoint(0, -aRect.origin.y )]; + [textView setFrame:NSMakeRect(0, -aRect.origin.y ,originalFrame.size.width, originalFrame.size.height)]; + [textView drawRect:NSMakeRect(0,0,aRect.size.width,aRect.size.height)]; + + + [textView setBoundsOrigin:originalOrigin]; + [textView setFrame:originalFrame]; + [[[textView enclosingScrollView] contentView] setBounds:clipViewRect]; + + + [[NSColor darkGrayColor] set]; + [NSBezierPath setDefaultLineWidth:2.0]; + [NSBezierPath strokeRect: NSMakeRect(0,0,aRect.size.width, aRect.size.height)]; + + [stripImage unlockFocus]; + + + + [aMenuItem setImage:[stripImage autorelease]]; + + [aMenuItem setTag:charIndex]; // Tag as character index + [aMenuItem setTarget:self]; + [aMenuItem setToolTip:[NSString stringWithFormat:@"Char:%d Line:%d Paragraph:%d", + charIndex +1, [self lineNumberAtIndex:charIndex],[(MNLineNumberingTextStorage*)[textView textStorage] paragraphNumberAtIndex:charIndex]]]; + [menu addItem:[aMenuItem autorelease]]; + } + } + + //////// + [menu addItem:[NSMenuItem separatorItem]]; + + + //////// + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"Clear Bookmarks" action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-2]; + [aMenuItem setTarget:self]; + [menu addItem:[aMenuItem autorelease]]; + } + + //Add other items + + + //////// + NSMenu* submenu = [[NSMenu alloc] init]; + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"No Line Numbering" action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-10]; + [aMenuItem setTarget:self]; + [aMenuItem setState:( (rulerOption & 0x0F) == MNNoLineNumbering ? NSOnState : NSOffState)]; + [submenu addItem:[aMenuItem autorelease]]; + + + //////// + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"Line Number" action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-11]; + [aMenuItem setTarget:self]; + [aMenuItem setState:( (rulerOption & 0x0F) == MNLineNumber ? NSOnState : NSOffState)]; + [submenu addItem:[aMenuItem autorelease]]; + + + //////// + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"Character Number" action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-12]; + [aMenuItem setTarget:self]; + [aMenuItem setState:( (rulerOption & 0x0F) == MNCharacterNumber ? NSOnState : NSOffState)]; + [submenu addItem:[aMenuItem autorelease]]; + + + //////// + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"Paragraph Number" action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-13]; + [aMenuItem setTarget:self]; + [aMenuItem setState:( (rulerOption & 0x0F) == MNParagraphNumber ? NSOnState : NSOffState)]; + [submenu addItem:[aMenuItem autorelease]]; + + //////// + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"Show Bookmarks" action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-14]; + [aMenuItem setTarget:self]; + [aMenuItem setState:( (rulerOption & 0x10) == MNDrawBookmarks ? NSOnState : NSOffState)]; + [submenu addItem:[aMenuItem autorelease]]; + + + //////// + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide Ruler" action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-15]; + [aMenuItem setTarget:self]; + [aMenuItem setState:( ![self isVisible] ? NSOnState : NSOffState)]; + [submenu addItem:[aMenuItem autorelease]]; + + + //////// + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"View as" action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-6]; + [aMenuItem setTarget:self]; + [menu addItem:[aMenuItem autorelease]]; + [menu setSubmenu:[submenu autorelease] forItem:aMenuItem]; + + + //////// + aMenuItem = [[NSMenuItem alloc] initWithTitle:@"Jump to..." action:@selector(menu_selected) + keyEquivalent:@""]; + [aMenuItem setTag:-7]; + [aMenuItem setTarget:self]; + [menu addItem:[aMenuItem autorelease]]; + + + //////// + //OBSERVE CONTEXT MENU + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menu_selected_main:) + name:NSMenuDidSendActionNotification object:menu]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menu_selected_main:) + name:NSMenuDidSendActionNotification object:submenu]; + + return [menu autorelease]; + +} + + +@end diff --git a/MNLineNumberingTextStorage.h b/MNLineNumberingTextStorage.h new file mode 100644 index 0000000..4aa5f67 --- /dev/null +++ b/MNLineNumberingTextStorage.h @@ -0,0 +1,32 @@ + +#import + +extern const NSString* MarkerAttributeName; + +@interface MNLineNumberingTextStorage : NSTextStorage +{ + NSMutableAttributedString *m_attributedString; + + + + +} + +-(BOOL)hasBookmarkAtIndex:(unsigned)index inTextView:(NSTextView*)textView; + // Check if the paragraph contains index is bookmarked. + +-(void)setBookmarkAtIndex:(unsigned)index flag:(BOOL)flag inTextView:(NSTextView*)textView; + // Set bookmark to the paragraph contains index. + + // ** note ** + // Bookmarks are added to paragraphs, not characters. + // A bookmark is stored as an attribute 'MarkerAttributeName' embedded to return code. + + +-(unsigned)paragraphNumberAtIndex:(unsigned)index; + // return paragraph number contains index + + + + +@end diff --git a/MNLineNumberingTextStorage.m b/MNLineNumberingTextStorage.m new file mode 100644 index 0000000..33c42cf --- /dev/null +++ b/MNLineNumberingTextStorage.m @@ -0,0 +1,140 @@ +// +// FugoCompletionTextStorage.m +// SampleApp +// +// Created by Masatoshi Nishikata on 13/02/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import + +#define UNIQUECODE [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]] + + +//const NSString* MarkerAttributeName = @"MarkerAttributeName"; +#define MarkerAttributeName NSToolTipAttributeName + +@interface MNLineNumberingTextStorage ( NSTextStorage ) + + +- (NSString *)string; +- (NSDictionary *)attributesAtIndex:(unsigned)index effectiveRange:(NSRangePointer)aRange; +- (void)replaceCharactersInRange:(NSRange)aRange withString:(NSString *)str; +- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)aRange; + +@end + + + +@implementation MNLineNumberingTextStorage + +- (id)init { + self = [super init]; + if (self) { + + // fundamental + m_attributedString = [[NSMutableAttributedString alloc] init]; + + + } + + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [m_attributedString release]; + + + [super dealloc]; +} + + + +-(BOOL)hasBookmarkAtIndex:(unsigned)index inTextView:(NSTextView*)textView +{ + NSRange paragraphRange = + [textView selectionRangeForProposedRange:NSMakeRange(index, 1) granularity:NSSelectByParagraph]; + + id attribute = [self attribute:MarkerAttributeName atIndex:NSMaxRange(paragraphRange) -1 effectiveRange:NULL]; + if( attribute != NULL ) + { + return [attribute boolValue] ; + } + return NO; +} + +-(void)setBookmarkAtIndex:(unsigned)index flag:(BOOL)flag inTextView:(NSTextView*)textView +{ + NSRange paragraphRange = + [textView selectionRangeForProposedRange:NSMakeRange(index, 1) granularity:NSSelectByParagraph]; + + [self addAttribute:MarkerAttributeName value:[NSNumber numberWithBool:flag] range:NSMakeRange(NSMaxRange(paragraphRange)-1,1)]; +} + + + +-(unsigned)paragraphNumberAtIndex:(unsigned)index +{ + int paragraphNumber = 1; + NSString* str = [self string]; + + unsigned hoge; + for( hoge = 0; hoge < [str length]; hoge ++ ) + { + if( index <= hoge ) + break; + + unichar characterToCheck = [str characterAtIndex:hoge]; + + if (characterToCheck == '\n' || characterToCheck == '\r' || + characterToCheck == 0x2028 || characterToCheck == 0x2029) + + paragraphNumber++; + + } + + return paragraphNumber; + +} + +@end + + +// ######## fundamental subclassing ########## +@implementation MNLineNumberingTextStorage (NSTextStorage) + +- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str +{ + + [m_attributedString replaceCharactersInRange:range withString:str]; + + int lengthChange = [str length] - range.length; + [self edited:NSTextStorageEditedCharacters range:range changeInLength:lengthChange]; + +} + + +- (NSString *)string +{ + return [m_attributedString string]; +} + +- (NSDictionary *)attributesAtIndex:(unsigned)index effectiveRange:(NSRangePointer)aRange +{ + return [m_attributedString attributesAtIndex:index effectiveRange:aRange]; +} + + + +- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range +{ + [m_attributedString setAttributes:attributes range:range]; + [self edited:NSTextStorageEditedAttributes range:range changeInLength:0]; + +} + + +@end + diff --git a/MNLineNumberingTextView.h b/MNLineNumberingTextView.h new file mode 100644 index 0000000..f12837f --- /dev/null +++ b/MNLineNumberingTextView.h @@ -0,0 +1,52 @@ +// +// MNLineNumberingRulerView.h +// +// +// Created by Masatoshi Nishikata on 29/10/05. +// Copyright 2005 Masatoshi Nishikata. All rights reserved. +// http://homepage.mac.com/mnishikata/iblog/ + +//ORIGINAL +// +// MyTextView.m +// LineNumbering +// +// Created by Koen van der Drift on Sat May 01 2004. +// Copyright (c) 2004 Koen van der Drift. All rights reserved. +// + +/* + + + LineNumberingRulerView shows paragraph number, line number and character number. + Furthermore, it can handle bookmarks like debug marker in Xcode. + + Bookmarks are embedded to return code as an attribute 'MarkerAttributeName' (Bool value YES as NSNumber). + As a default text view saving process using RTFFromRange... , the bookmark attribute is not saved. + If you want to save them, extract that attribute and save separatory. + + + */ + + +#import + +@class MNLineNumberingRulerView; +@class MNLineNumberingTextStorage; + +@interface MNLineNumberingTextView : NSTextView { + +} + +-(void)toggleGutterVisiblity; +// Show and Hide gutter (ruler view). + +-(void)jumpTo; +// Show 'Jump To...' sheet + + +- (void)textStorageDidProcessEditing:(NSNotification *)aNotification; +-(int)paragraphNumberAtIndex:(int)index; + + +@end diff --git a/MNLineNumberingTextView.m b/MNLineNumberingTextView.m new file mode 100644 index 0000000..3c1e677 --- /dev/null +++ b/MNLineNumberingTextView.m @@ -0,0 +1,128 @@ +// +// LineNumberingTextView.m +// SampleApp +// +// Created by Masatoshi Nishikata on 06/03/24. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import + + +@implementation MNLineNumberingTextView + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + + + + + + + } + return self; +} + +- (void) awakeFromNib +{ + MNLineNumberingTextStorage* ts = [[MNLineNumberingTextStorage alloc] init]; + + [[self layoutManager] replaceTextStorage:ts]; + + [[self textStorage] setDelegate:self]; + + + NSScrollView* scrollView = [self enclosingScrollView]; + + + + // *** set up main text View *** // + //textView setting -- add ruler to textView + MNLineNumberingRulerView* aNumberingRulerView = + [[MNLineNumberingRulerView alloc] initWithScrollView:scrollView + orientation:NSVerticalRuler]; + + [scrollView setVerticalRulerView:aNumberingRulerView ]; + + //configuration + [scrollView setHasVerticalRuler:YES]; + [scrollView setHasHorizontalRuler:NO]; + + [scrollView setRulersVisible:YES]; + + [aNumberingRulerView release]; + +} + +/* +- (void)awakeFromNib +{ + + + if ([[self superclass] instancesRespondToSelector:@selector(awakeFromNib)]) { + [super awakeFromNib]; + } + +} +*/ + +-(void)dealloc +{ + + [super dealloc]; +} + + + + +- (NSMenu *)menuForEvent:(NSEvent *)theEvent +{ + NSMenuItem* customMenuItem; + NSMenu* aContextMenu = [super menuForEvent:theEvent]; + + + //add separator + customMenuItem = [NSMenuItem separatorItem]; + [customMenuItem setRepresentedObject:@"MN"]; + [aContextMenu addItem:customMenuItem ]; + + + + // + customMenuItem = [[NSMenuItem alloc] initWithTitle:@"Show/Hide Gutter" + action:@selector(toggleGutterVisiblity) keyEquivalent:@""]; + [customMenuItem setRepresentedObject:@"MN"]; + [aContextMenu addItem:customMenuItem ]; + [customMenuItem release]; + // + + // + customMenuItem = [[NSMenuItem alloc] initWithTitle:@"Jump to..." + action:@selector(jumpTo) keyEquivalent:@""]; + [customMenuItem setRepresentedObject:@"MN"]; + [aContextMenu addItem:customMenuItem ]; + [customMenuItem release]; + + + return aContextMenu; +} + +-(void)toggleGutterVisiblity +{ + NSRulerView* rv = [[self enclosingScrollView] verticalRulerView]; + [rv setVisible:![rv isVisible]]; +} + +-(void)jumpTo +{ + [[[self enclosingScrollView] verticalRulerView] startSheet]; +} + +- (void)textStorageDidProcessEditing:(NSNotification *)aNotification +{ + [[[self enclosingScrollView] verticalRulerView] setNeedsDisplay:YES]; +} + +@end diff --git a/MUPhotoCell.h b/MUPhotoCell.h new file mode 100644 index 0000000..a5867ee --- /dev/null +++ b/MUPhotoCell.h @@ -0,0 +1,23 @@ +// +// MUPhotoCell.h +// SproutedInterface +// +// Created by Philip Dow on 1/19/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// +// Or was this originally part of the MUPhotoView package? +// I subclassed to PDPhotoView and don't recall if I added this as well + +#import + + +@interface MUPhotoCell : NSImageCell { + + NSString *imageTitle; +} + +- (NSString*) title; +- (void) setTitle:(NSString*)aString; + +@end diff --git a/MUPhotoCell.m b/MUPhotoCell.m new file mode 100644 index 0000000..9a01654 --- /dev/null +++ b/MUPhotoCell.m @@ -0,0 +1,137 @@ +// +// MUPhotoCell.m +// SproutedInterface +// +// Created by Philip Dow on 1/19/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// +// Or was this originally part of the MUPhotoView package? +// I subclassed to PDPhotoView and don't recall if I added this as well + +#import + + +@implementation MUPhotoCell + +- (id)initImageCell:(NSImage *)anImage +{ + if ( self = [super initImageCell:anImage] ) + { + [self setImageFrameStyle:NSImageFrameNone]; + [self setImageScaling:NSScaleProportionally]; + + [self setAlignment:NSCenterTextAlignment]; + [self setImageAlignment:NSImageAlignTop]; + + [self setBezeled:NO]; + [self setBordered:YES]; + } + + return self; +} + +- (void) dealloc +{ + [imageTitle release]; + [super dealloc]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + MUPhotoCell *newObject = [[[self class] allocWithZone:zone] initImageCell:[self image]]; + + [newObject setTitle:[self title]]; + [newObject setImageFrameStyle:[self imageFrameStyle]]; + [newObject setImageScaling:[self imageScaling]]; + + [newObject setAlignment:[self alignment]]; + [newObject setImageAlignment:[self imageAlignment]]; + + [newObject setBezeled:[self isBezeled]]; + [newObject setBordered:[self isBordered]]; + + return newObject; +} + + +- (BOOL)isOpaque +{ + return NO; +} + +- (NSString*) title +{ + return imageTitle; +} + +- (void) setTitle:(NSString*)aString +{ + if ( imageTitle != aString ) + { + [imageTitle release]; + imageTitle = [aString copyWithZone:[self zone]]; + } +} + +#pragma mark - +// #warning should take into account font metrics, etc + +- (NSRect)imageRectForBounds:(NSRect)theRect +{ + NSRect adjustedImageFrame = theRect; + if ( [self title] != nil ) + adjustedImageFrame.size.height -= 30; + + return adjustedImageFrame; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect +{ + NSRect adjustedTitleFrame = theRect; + if ( [self objectValue] != nil ) + { + adjustedTitleFrame.origin.y = adjustedTitleFrame.origin.y + adjustedTitleFrame.size.height - 30; + adjustedTitleFrame.size.height = 30; + } + + return adjustedTitleFrame; +} + +#pragma mark - + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + // that draws the frame and photo interior (drawInterior is called, can't override) in a restricted space, leaving us room for the title + [super drawWithFrame:[self imageRectForBounds:cellFrame] inView:controlView]; + + NSMutableParagraphStyle *paragraph = [[[NSParagraphStyle defaultParagraphStyle] mutableCopyWithZone:[self zone]] autorelease]; + [paragraph setLineBreakMode:NSLineBreakByWordWrapping]; + [paragraph setAlignment:NSCenterTextAlignment]; + + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:11], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, + paragraph, NSParagraphStyleAttributeName, nil]; + + if ( [self title] != nil ) + { + NSRect targetTitleRect; + NSRect originalTitleRect = [self titleRectForBounds:cellFrame]; + NSSize titleSize = [[self title] sizeWithAttributes:attributes]; + + if ( titleSize.width > originalTitleRect.size.width ) + targetTitleRect = NSMakeRect( originalTitleRect.origin.x, + originalTitleRect.origin.y + ( originalTitleRect.size.height/2 - titleSize.height/2 ), + originalTitleRect.size.width, 30 ); + + else + targetTitleRect = NSMakeRect( originalTitleRect.origin.x + ( originalTitleRect.size.width/2 - titleSize.width/2 ), + originalTitleRect.origin.y + ( originalTitleRect.size.height/2 - titleSize.height/2 ), + titleSize.width, 30 ); + + [[self title] drawInRect:targetTitleRect withAttributes:attributes]; + } +} + +@end diff --git a/MUPhotoView.h b/MUPhotoView.h new file mode 100644 index 0000000..bd09ac8 --- /dev/null +++ b/MUPhotoView.h @@ -0,0 +1,317 @@ +// +// +// MUPhotoView +// +// Copyright (c) 2006 Blake Seely +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// * You include a link to http://www.blakeseely.com in your final product. +// +// Version History: +// +// Version 1.0 - April 17, 2006 - Initial Release +// Version 1.1 - April 29, 2006 - Photo removal support, Added support for reduced-size drawing during live resize +// Version 1.2 - September 24, 2006 - Updated selection behavior, Changed to MIT license, Fixed issue where no images would show, fixed autoscroll + +// Modified for the iMedia project http://imedia.karelia.com/ + +#import + +extern NSString *ShowCaptionChangedNotification; + +//! MUPhotoView displays a grid of photos similar to iPhoto's main photo view. The class gives developers several options for providing images - via bindings or delegation. + +//! MUPhotoView displays a resizeable grid of photos, similar to iPhoto's photo view functionality. MUPhotoView provides developers with two different options for passing photo information to the view +//! Most importantly, MUPhotoView currently only deals with an array of photos. It also does not know how to find NSImage objects +//! that are inside another object - it expects NSImage objects. The first method for providing those objects it by binding an array of NSImage objects to the "photosArray" key of the view. +//! If this key has been bound, MUPhotoView will fetch all the images it displays from that binding. The second method is to have a delegate object provide the photos. MUPhotoView will only +//! call the delegate's photo methods if the photosArray key has not been bound. Please see the MUPhotoViewDelegate category documentation for descriptions of the methods. +@interface MUPhotoView : NSView { + // Please do not access ivars directly - use the accessor methods documented below + IBOutlet id delegate; + + BOOL sendsLiveSelectionUpdates; + BOOL useHighQualityResize; + BOOL showCaptions; + + NSMutableArray *photosArray; + NSMutableArray *photosFastArray; + NSIndexSet *selectedPhotoIndexes; + NSMutableIndexSet *dragSelectedPhotoIndexes; + + NSColor *backgroundColor; + BOOL useShadowBorder; + BOOL useOutlineBorder; + NSShadow *borderShadow; + NSShadow *noShadow; + NSColor *borderOutlineColor; + + BOOL useBorderSelection; + BOOL useShadowSelection; + NSColor *selectionBorderColor; + NSColor *shadowBoxColor; + float selectionBorderWidth; + + // spacing photos + float photoSize; + float photoVerticalSpacing; + float photoHorizontalSpacing; + + + NSSize gridSize; + unsigned columns; + unsigned rows; + + BOOL mouseDown; + BOOL potentialDragDrop; + NSPoint mouseDownPoint; + NSPoint mouseCurrentPoint; + NSTimer *autoscrollTimer; + NSTimer *photoResizeTimer; + NSDate *photoResizeTime; + BOOL isDonePhotoResizing; + + NSArray *liveResizeSubviews; + + + BOOL drawDropHilite; +} + +#pragma mark - +// Delegate Methods +#pragma mark Delegate Methods +/** Returns the current delegate **/ +- (id)delegate; +/** Sets the delegate. See the MUPhotoViewDelegate category for information about which delegate methods will get called and when.**/ +- (void)setDelegate:(id)del; + +#pragma mark - +// Photos Methods +#pragma mark Photo Methods +/** Returns the array of NSImage objects that MUPhotoView is currently drawing from. If nothing has been bound to the "photosArray" key and + there has not been a call to -setPhotosArray, then this will probably return nil. If this method returns nil, then at draw time, the MUPhotoView will attempt to ask + its delegate for the count of photos and for photos at each index. **/ +- (NSArray *)photosArray; +/** Sets the array of NSImage objects that MUPhotoView uses to draw itself. If you call this method and pass nil, and the delegate is NOT nil, it will ask the delegate for + the photos. **/ +- (void)setPhotosArray:(NSArray *)aPhotosArray; + +#pragma mark - +// Selection Management +#pragma mark Selection Management + /** Returns the current NSIndexSet indicating which photos are currently selected. If this is nil, then the view is asking its delegate for the selection index information. **/ +- (NSIndexSet *)selectedPhotoIndexes; +/** Sets the NSIndexSet that the view will use to indicate which photos need to appear selected in the view. By setting this value to nil, MUPhotoView will ask the delegate for + this information. **/ +- (void)setSelectedPhotoIndexes:(NSIndexSet *)aSelectedPhotoIndexes; + +#pragma mark - +// Selection Style +#pragma mark Selection Style + /** Indicates whether the view is drawing "selected" photos with a 3px border around the photo. The appearnce is similar to iPhoto's selection style. The default value is YES. **/ +- (BOOL)useBorderSelection; +/** Tells the view whether or not to indicated "selected" photos by drawing a 3px border around the photo. The appearnce is similar to iPhoto's selection style. + The default value is YES. **/ +- (void)setUseBorderSelection:(BOOL)flag; +/** Returns the current color that thew view is using to draw selection borders. If -useBorderSelection returns NO, it doesn't matter what color is returned from this method. + The initial value for selectionBorderColor is the user's current selection color. **/ +- (NSColor *)selectionBorderColor; +/** Tells the view what color border should be drawn around a "selected" photo. If -useBorderSelection returns NO, calling this method will not have any effect until + -setUseBorderSelection:YES is callled. The selection border color defaults to the user's current selection color. **/ +- (void)setSelectionBorderColor:(NSColor *)aSelectionBorderColor; +/** Indicates whether the view indicates "selected" photos by drawing a semi-transparent rounded box around the photo. The default value is NO. **/ +- (BOOL)useShadowSelection; + /** By setting this value to YES, you tell MUPhotoView to indicate a "selected" photo by drawing a semi-transparent rounded rectangle around the photo. The color and opacity + of the rounded rectangle depend on the current background color of the view: for lighter colors, MUPhotoView will use a semi-transparent black; for darker colors, the color will + be a semi-transparent white.**/ +- (void)setUseShadowSelection:(BOOL)flag; + + /** Indicates whether the view is drawing with a high-quality, though possibly slower, algorithm for image interpolation. The default value is NO; the default setting for the machine is used. **/ +- (BOOL)useHighQualityResize; + /** By setting this value to YES, you tell MUPhotoView to draw with a higher-quality image, sacrificing speed. **/ +- (void)setUseHighQualityResize:(BOOL)flag; + +/** Indicates whether captions should be shown below the photo */ +- (BOOL)showCaptions; +/** By setting this value to YES, you tell MUPhotoView to draw captions below the thumbnails. **/ +- (void)setShowCaptions:(BOOL)flag; + +#pragma mark - +// Appearance +#pragma mark Appearance + /** Indicates whether the view is drawing a drop-shadow around each photo. The default value is YES. **/ +- (BOOL)useShadowBorder; +/** Passing YES to this method will cause the view to draw a drop shadow around each photo. The default value is YES. **/ +- (void)setUseShadowBorder:(BOOL)flag; +/** Indicates whether the view is currently set to draw a 1px, 50% white border around each photo. The default value is YES. **/ +- (BOOL)useOutlineBorder; +/** Tells the view whether or not to draw a 1px, 50% white border around each photo. The default value is YES. **/ +- (void)setUseOutlineBorder:(BOOL)flag; +/** Returns the current color being used to paint the background before drawing photos. The default value is [NSColor whiteColor]. **/ +- (NSColor *)backgroundColor; +/** Tells the view to use a new color when drawing the background. If -useShadowSelection is YES, updating the background color may also affect the color being used to draw + the shadow selection indicator. **/ +- (void)setBackgroundColor:(NSColor *)aBackgroundColor; +/** Returns the current pixel size that photos are scaled to. When drawing, a photo is scalled proportionately so it's longest side is this number of pixels. **/ +- (float)photoSize; +/** Tells the view to draw photos scaled so their longest side is aPhotoSize pixels long. This will cause the visible area of the view to be redrawn - and the view will attempt to + keep the currently-visible photos near the center of the scroll area. **/ +- (void)setPhotoSize:(float)aPhotoSize; + +- (IBAction)takePhotoSizeFrom:(id)sender; + +#pragma mark - +// Seriously, Don't Mess With Texas +#pragma mark Seriously, Don't Mess With Texas +// haven't tested changing these behaviors yet - there's no reason they shouldn't work... but use at your own risk. +- (float)photoVerticalSpacing; +- (void)setPhotoVerticalSpacing:(float)aPhotoVerticalSpacing; +- (float)photoHorizontalSpacing; +- (void)setPhotoHorizontalSpacing:(float)aPhotoHorizontalSpacing; +- (NSColor *)borderOutlineColor; +- (void)setBorderOutlineColor:(NSColor *)aBorderOutlineColor; +- (NSColor *)shadowBoxColor; +- (void)setShadowBoxColor:(NSColor *)aShadowBoxColor; +- (float)selectionBorderWidth; +- (void)setSelectionBorderWidth:(float)aSelectionBorderWidth; + +@end + +#pragma mark - +// Delegate Methods +#pragma mark Delegate Methods +/// The MUPhotoViewDelegate category defines the methods that a MUPhotoView may call, and that you can use provide drag and drop, double-click, selection and even photo display support +/** The MUPhotoViewDelegate category provides default implementations of all the methods the MUPhotoView may call. Overriding each of them is optional - the default implementations + return no results, nil results, or zero as appropriate. **/ +@interface NSObject (MUPhotoViewDelegate) + +/** The view will call this method if all of the following are true: (a) a valid array of NSImage objects has not been bound to the @"photosArray" key, (b) + -setPhotosArray: has not been called and passed a valid, non-nil array, and (c) the delegate is not nil. **/ +- (unsigned)photoCountForPhotoView:(MUPhotoView *)view; +/** The view will call this method if all of the following are true: (a) a valid array of NSImage objects has not been bound to the @"photosArray" key, (b) + -setPhotosArray: has not been called and passed a valid, non-nil array, and (c) the delegate is not nil. The delegate should return the NSImage appropriate + to draw at the specified index. **/ +- (NSImage *)photoView:(MUPhotoView *)view photoAtIndex:(unsigned)index; + +/** If the view depends on the delegate for photos (instead of bindings), it will call this method during a live resize operation. It expects a very small version + of the photo at the specified index in order to speed drawing in the live resize. Overriding this method is optional. The default implementation returns nil, + which forces the view to use the regular photos during resize. Avoid doing any time-consuming image manipulation in this method or there will be no benefit + to drawing the small images. Ideally, you would only create a small version once - either ahead of time or during this call - and then reuse it. **/ +- (NSImage *)photoView:(MUPhotoView *)view fastPhotoAtIndex:(unsigned)index; + +- (NSString *)photoView:(MUPhotoView *)view titleForPhotoAtIndex:(unsigned)index; + // If a title is returned it will be drawn under the photo, if that option is set. + +// selection methods - will only get called if photoSelectionIndexes has not been bound +/** The view will call this method if all of the following are true: (a) a valid NSIndexSet has not been bound to the @"photoSelectionIndexes" key, (b) + -setPhotoSelectionIndexes: has not been called and passed a valid, non-nil NSIndexSet, and (c) the delegate is not nil. The delegate should return an NSIndexSet filled + with indexes appropriately representing which photos should be drawn as "selected" .**/ +- (NSIndexSet *)selectionIndexesForPhotoView:(MUPhotoView *)view; +/** The view will call this method if all of the following are true: (a) a valid NSIndexSet has not been bound to the @"photoSelectionIndexes" key, (b) + -setPhotoSelectionIndexes: has not been called and passed a valid, non-nil NSIndexSet, and (c) the delegate is not nil. If the delegate implements this method, + it can modify the proposed selection and return an appropriate NSIndexSet. You should only implement this method if, for some reason, you want to manipulate or look + at the selection indexes before the view marks them as selected. **/ +- (NSIndexSet *)photoView:(MUPhotoView *)view willSetSelectionIndexes:(NSIndexSet *)indexes; +/** The view will call this method if all of the following are true: (a) a valid NSIndexSet has not been bound to the @"photoSelectionIndexes" key, (b) + -setPhotoSelectionIndexes: has not been called and passed a valid, non-nil NSIndexSEt, and (c) the delegate is not nil. The delegate should do whatever work necessary to + mark the specified indexes as selected. (i.e. a subsequent call to -selectionIndexesForPhotoView: should most likely return this set or an identical one. **/ +- (void)photoView:(MUPhotoView *)view didSetSelectionIndexes:(NSIndexSet *)indexes; + +// drag and drop +/** A delegate would use this method to specify whether the view should support drag operations. (i.e. whether the view should allow photos to be dragged out of the view. + The semantics are identical to the -[NSDraggingSource draggingSourceOperationmaskForLocal] **/ +- (unsigned int)photoView:(MUPhotoView *)view draggingSourceOperationMaskForLocal:(BOOL)isLocal; +/** The view will call this method at drag time. The delegate should pass an array indicating the types that it will put on the pasteboard for a given set of images. If + you provide an implementation for -photoView:draggingSourceOperationMaskForLocal: that returns anything other than NO, you should also implement this method and indicate + which types you will support. **/ +//- (NSArray *)pasteboardDragTypesForPhotoView:(MUPhotoView *)view; +/** The view will call this method when it is about to initiate a drag. It will call this method once for *each* combination type returned from -pasteboardDragTypesForPhotoView: + and each photo currently being dragged. The delegate should return the appropriate data for the given type. If you provide any implementation of + -photoView:draggingSourceOperationMaskForLocal that returns anything other than NO, you should also implement this method. **/ +//- (NSData *)photoView:(MUPhotoView *)view pasteboardDataForPhotoAtIndex:(unsigned)index dataType:(NSString *)type; +- (void)photoView:(MUPhotoView *)view fillPasteboardForDrag:(NSPasteboard *)pboard; + +// drag and drop receiving - Equivalent to the NSDraggingDestination methods +- (NSDragOperation)photoView:(MUPhotoView *)view draggingEntered:(id )sender; +- (void)photoView:(MUPhotoView *)view draggingExited:(id )sender; +- (BOOL)photoView:(MUPhotoView *)view performDragOperation:(id )sender; +- (BOOL)photoView:(MUPhotoView *)view prepareForDragOperation:(id )sender; +- (void)photoView:(MUPhotoView *)view concludeDragOperation:(id )sender; + +// double-click support +/** The view will call this delegate method when the user double-clicks on the photo at the specified index. If you do not wish to support any double-click behavior, then you + don't need to override this method. **/ +- (void)photoView:(MUPhotoView *)view doubleClickOnPhotoAtIndex:(unsigned)index withFrame:(NSRect)frame; + +// photo removal support +/** The view will call this delegate method when the user selects photos and presses the delete key. The delegate should use this method to alter the photos that will + be removed by returning an appropriate NSIndexSet. The delegate will get this method even if the photos array is set via bindings. If the photos array is not set + via bindings, overriding this method is still optional. The default implementation of this method simply returns the an empty index set. **/ +- (NSIndexSet *)photoView:(MUPhotoView *)view willRemovePhotosAtIndexes:(NSIndexSet *)indexes; + +/** The view will call this delegate method when the user selects photos and presses the delete key. The view will first call the willRemovePhotosAtIndexes: method to give + the delegate a chance to modify the behavior. If the photo array is set via bindings, the view will remove the specified photos and then call this method. Note that in that + case, the indexes passed to this method no longer have meaning because the array has already been modified. If the photos array is NOT set via bindings, the delegate should + override this method and do the appropriate removals itself. The default implementation does nothing. **/ +- (void)photoView:(MUPhotoView *)view didRemovePhotosAtIndexes:(NSIndexSet *)indexes; + +// Tool tip support +- (NSString *)photoView:(MUPhotoView *)view tooltipForPhotoAtIndex:(unsigned)index; + +@end + + +// private methods. Do not call or override. +@interface MUPhotoView (PrivateAPI) + +// set internal grid and my frame based on array size, photo size, and width +- (void)updateGridAndFrame; + +// will fetch from the internal array if not nil, from delegate otherwise +- (unsigned)photoCount; +- (NSImage *)photoAtIndex:(unsigned)index; +- (NSImage *)fastPhotoAtIndex:(unsigned)index; +- (void)updatePhotoResizing; +- (NSString *)titleAtIndex:(unsigned)index; + +// This may be the actual photo, or a fast photo, or a placeholder image +- (NSImage*) currentDisplayImageAtIndex:(unsigned)thisPhotoIndex allowsShadows:(BOOL *)allowShadows; + +// placement and hit detection + +// The central powerhouse for determining geometry distribution on a given index, with a given photo and title. +// By consolidating a lot of this stuff into one freakin' method name, we are assured that hit detection matches +// up with drawing, even as optional features like caption titles bloom... - DCJ, July 2007 +- (void)getDrawingRectsAtIndex:(unsigned)photoIndex withPhoto:(NSImage *)cellPhoto withTitle:(NSString*)title outGridRect:(NSRect *)outGridRect outPhotoRect:(NSRect *)outPhotoRect outTitleRect:(NSRect *)outTitleRect; + +- (NSSize)scaledPhotoSizeForSize:(NSSize)size; +- (NSImage *)scalePhoto:(NSImage *)image; +- (unsigned)photoIndexForPoint:(NSPoint)point; +- (NSRange)photoIndexRangeForRect:(NSRect)rect; +- (NSRect)gridRectForIndex:(unsigned)index; +- (NSRect)rectCenteredInRect:(NSRect)rect withSize:(NSSize)size; +- (NSRect)photoRectForIndex:(unsigned)index; +- (NSSize)sizeOfTitleWithCurrentAttributes:(NSString*)title; + +// selection +- (BOOL)isPhotoSelectedAtIndex:(unsigned)index; +- (NSIndexSet *)selectionIndexes; +- (void)setSelectionIndexes:(NSIndexSet *)indexes; +- (NSBezierPath *)shadowBoxPathForRect:(NSRect)rect; + +// photo removal +- (void)removePhotosAtIndexes:(NSIndexSet *)indexes; + +// scaling +- (NSImage *)scaleImage:(NSImage *)image toSize:(float)size; + +// For fast re-drawing during drag and selection +- (void)dirtyDisplayRectsForNewSelection:(NSIndexSet *)newSelection oldSelection:(NSIndexSet *)oldSelection; +@end diff --git a/MUPhotoView.m b/MUPhotoView.m new file mode 100644 index 0000000..2518dd3 --- /dev/null +++ b/MUPhotoView.m @@ -0,0 +1,2168 @@ +// +// MUPhotoView +// +// Copyright (c) 2006 Blake Seely +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// * You include a link to http://www.blakeseely.com in your final product. +// +// Version History: +// +// Version 1.0 - April 17, 2006 - Initial Release +// Version 1.1 - April 29, 2006 - Photo removal support, Added support for reduced-size drawing during live resize +// Version 1.2 - September 24, 2006 - Updated selection behavior, Changed to MIT license, Fixed issue where no images would show, fixed autoscroll + +// Modified for the iMedia project http://imedia.karelia.com/ + +#import "MUPhotoView.h" + +NSString *ShowCaptionChangedNotification = @"ShowCaptionChangedNotification"; + +@implementation MUPhotoView + +#pragma mark - +// Initializers and Dealloc +#pragma mark Initializers and Dealloc + ++ (void)initialize +{ + if ( self == [MUPhotoView class] ) + { + // Only do some work when not called because one of our subclasses does not implement +initialize + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *defaultsBase = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithFloat:75.0], @"MUPhotoSize", nil]; + [defaults registerDefaults:defaultsBase]; + + [self exposeBinding:@"photosArray"]; + [self exposeBinding:@"selectedPhotoIndexes"]; + [self exposeBinding:@"backgroundColor"]; + [self exposeBinding:@"photoSize"]; + [self exposeBinding:@"useShadowBorder"]; + [self exposeBinding:@"useOutlineBorder"]; + [self exposeBinding:@"useShadowSelection"]; + [self exposeBinding:@"useOutlineSelection"]; + + [self setKeys:[NSArray arrayWithObject:@"backgroundColor"] triggerChangeNotificationsForDependentKey:@"shadowBoxColor"]; + + [pool release]; +} +} + +- (id)initWithFrame:(NSRect)frameRect +{ + if ((self = [super initWithFrame:frameRect]) != nil) { + + delegate = nil; + sendsLiveSelectionUpdates = NO; + useHighQualityResize = NO; + photosArray = nil; + photosFastArray = nil; + selectedPhotoIndexes = nil; + dragSelectedPhotoIndexes = [[NSMutableIndexSet alloc] init]; + + [self setBackgroundColor:[NSColor grayColor]]; + + useShadowBorder = YES; + useOutlineBorder = YES; + + borderShadow = [[NSShadow alloc] init]; + [borderShadow setShadowColor:[NSColor colorWithCalibratedWhite:0. alpha:.5]]; + [borderShadow setShadowOffset:NSMakeSize(0.0, -2.0)]; + [borderShadow setShadowBlurRadius:4.0]; + + noShadow = [[NSShadow alloc] init]; + [noShadow setShadowOffset:NSMakeSize(0,0)]; + [noShadow setShadowBlurRadius:0.0]; + [self setBorderOutlineColor:[NSColor colorWithCalibratedWhite:0.5 alpha:1.0]]; + + + useShadowSelection = NO; + useBorderSelection = YES; + [self setSelectionBorderColor:[NSColor selectedControlColor]]; + selectionBorderWidth = 3.0; + [self setShadowBoxColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.5]]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + photoSize = [defaults floatForKey:@"MUPhotoSize"]; + photoVerticalSpacing = 25.0; + photoHorizontalSpacing = 25.0; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(resetCaptionShowing:) + name:ShowCaptionChangedNotification + object:nil]; + + photoResizeTimer = nil; + photoResizeTime = [[NSDate date] retain]; + isDonePhotoResizing = YES; + } + + return self; +} + +- (void)dealloc +{ + [dragSelectedPhotoIndexes release], dragSelectedPhotoIndexes = nil; + [photoResizeTime release], photoResizeTime = nil; + + [borderShadow release], borderShadow = nil; + [noShadow release], noShadow = nil; + + [self setBorderOutlineColor:nil]; + [self setSelectionBorderColor:nil]; + [self setShadowBoxColor:nil]; + [self setBackgroundColor:nil]; + [self setPhotosArray:nil]; + [self setSelectedPhotoIndexes:nil]; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [super dealloc]; +} + +#pragma mark - +// Drawing Methods +#pragma mark Drawing Methods + +- (BOOL)isOpaque +{ + return YES; +} + +- (BOOL)isFlipped +{ + return YES; +} + +static NSDictionary *sTitleAttributes = nil; + +- (void)drawRect:(NSRect)rect +{ + [self removeAllToolTips]; + + // draw the background color + [[self backgroundColor] set]; + [NSBezierPath fillRect:rect]; + + // get the number of photos + unsigned photoCount = [self photoCount]; + if (0 == photoCount) + return; + + // update internal grid size, adjust height based on the new grid size + // because I may not find out that the photos array has changed until I draw and read the photos from the delegate, this call has to stay here + [self updateGridAndFrame]; + + // any other setup + if (useHighQualityResize) { + [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; + } + + /**** BEGIN Drawing Photos ****/ + NSRange rangeToDraw = [self photoIndexRangeForRect:rect]; // adjusts for photoCount if the rect goes outside my range + unsigned thisPhotoIndex; + unsigned lastIndex = rangeToDraw.location + rangeToDraw.length; + // Our version of photoIndexRangeForRect: returns one item more in the range than the MUPhotoView 1.2 version. Hence we also + // must do one less iteration so here we do < instead of <= + for (thisPhotoIndex = rangeToDraw.location; thisPhotoIndex < lastIndex; thisPhotoIndex++) { + + // Get the image at the current index - a gray bezier anywhere in the view means it asked for an image, but got nil for that index + BOOL allowShadows = YES; + NSImage* photo = [self currentDisplayImageAtIndex:thisPhotoIndex allowsShadows:&allowShadows]; + + // set it to draw correctly in a flipped view (will restore it after drawing) + BOOL isFlipped = [photo isFlipped]; + [photo setFlipped:YES]; + + NSString* title = [self titleAtIndex:thisPhotoIndex]; + NSRect gridRect = NSZeroRect; + NSRect photoRect = NSZeroRect; + NSRect titleRect = NSZeroRect; + + // Note this will automatically cause the photo to scale as necessary + [self getDrawingRectsAtIndex:thisPhotoIndex withPhoto:photo withTitle:title outGridRect:&gridRect outPhotoRect:&photoRect outTitleRect:&titleRect]; + + //**** BEGIN Background Drawing - any drawing that technically goes under the image ****/ + #if 0 // Debugging Aid - Enable this to fill each gridRect with a different gray + [[NSColor colorWithCalibratedWhite:(float)(thisPhotoIndex % 5) / 4.0 alpha:0.8] set]; + [NSBezierPath fillRect:gridRect]; + #endif + // kSelectionStyleShadowBox draws a semi-transparent rounded rect behind/around the image + if ([self isPhotoSelectedAtIndex:thisPhotoIndex] && [self useShadowSelection]) { + NSBezierPath *shadowBoxPath = [self shadowBoxPathForRect:gridRect]; + [shadowBoxColor set]; + [shadowBoxPath fill]; + } + + //**** END Background Drawing ****/ + + // kBorderStyleShadow - set the appropriate shadow + // Don't draw the shadow if we have a border and the item is selected, or if shadows are disabled for this image + if ([self useShadowBorder] && (allowShadows == YES) && + ([self useBorderSelection] == NO || [[self selectionIndexes] containsIndex:thisPhotoIndex] == NO)) { + + [borderShadow set]; + } + + // draw the current photo + NSRect imageRect = NSMakeRect(0, 0, [photo size].width, [photo size].height); + [photo drawInRect:photoRect fromRect:imageRect operation:NSCompositeSourceOver fraction:1.0]; + + // register the tooltip area + [self addToolTipRect:photoRect owner:self userData:nil]; + + // restore the photo's flipped status + [photo setFlipped:isFlipped]; + + // kBorderStyleShadow - remove the shadow after drawing the image + [noShadow set]; + + //**** BEGIN Foreground Drawing - includes outline borders, selection rectangles ****/ + if ([self isPhotoSelectedAtIndex:thisPhotoIndex] && [self useBorderSelection]) { + NSBezierPath *selectionBorder = [NSBezierPath bezierPathWithRect:NSInsetRect(photoRect,-3.0,-3.0)]; + [selectionBorder setLineWidth:[self selectionBorderWidth]]; + [[self selectionBorderColor] set]; + [selectionBorder stroke]; + } else if ([self useOutlineBorder]) { + photoRect = NSInsetRect(photoRect,0.5,0.5); // line up the 1px border so it completely fills a single row of pixels + NSBezierPath *outlinePath = [NSBezierPath bezierPathWithRect:photoRect]; + [outlinePath setLineWidth:1.0]; + [borderOutlineColor set]; + [outlinePath stroke]; + } + + //**** END Foreground Drawing ****// + + // draw title + if (title) + { + // center rect + NSMutableString *s1 = [NSMutableString stringWithString:[title substringToIndex:[title length] / 2]]; + NSMutableString *s2 = [NSMutableString stringWithString:[title substringFromIndex:[title length] / 2]]; + NSSize titleSize = [self sizeOfTitleWithCurrentAttributes:title]; + while (titleSize.width > NSWidth(titleRect)) + { + [s1 deleteCharactersInRange:NSMakeRange([s1 length] - 1, 1)]; + [s2 deleteCharactersInRange:NSMakeRange(0, 1)]; + + title = [NSString stringWithFormat:@"%@...%@", s1, s2]; + titleSize = [title sizeWithAttributes:sTitleAttributes]; + } + titleRect.origin.x = NSMidX(titleRect) - (titleSize.width / 2); + titleRect.size.width = titleSize.width; + + [title drawInRect:titleRect withAttributes:sTitleAttributes]; + } + + } + + //**** END Drawing Photos ****// + + //**** BEGIN Selection Rectangle ****// + if (mouseDown) { + [noShadow set]; + [[NSColor whiteColor] set]; + + float minX = (mouseDownPoint.x < mouseCurrentPoint.x) ? mouseDownPoint.x : mouseCurrentPoint.x; + float minY = (mouseDownPoint.y < mouseCurrentPoint.y) ? mouseDownPoint.y : mouseCurrentPoint.y; + float maxX = (mouseDownPoint.x > mouseCurrentPoint.x) ? mouseDownPoint.x : mouseCurrentPoint.x; + float maxY = (mouseDownPoint.y > mouseCurrentPoint.y) ? mouseDownPoint.y : mouseCurrentPoint.y; + NSRect selectionRectangle = NSMakeRect(minX,minY,maxX-minX,maxY-minY); + [NSBezierPath strokeRect:selectionRectangle]; + + [[NSColor colorWithDeviceRed:0.8 green:0.8 blue:0.8 alpha:0.5] set]; + [NSBezierPath fillRect:selectionRectangle]; + } + //**** END Selection Rectangle ****// + +} + +- (void)forceRedisplay +{ + [self setNeedsDisplay:YES]; +} + +- (void)setNeedsDisplayInRect:(NSRect)invalidatedRect +{ + // Make the view redraw some more pixels, to avoid the "disappearing shadows on scroll" problem + if ([[borderShadow shadowColor] alphaComponent]!=0.0) { + NSRect shadowRect = invalidatedRect; + shadowRect.origin.x += [borderShadow shadowOffset].width; + shadowRect.origin.y -= [borderShadow shadowOffset].height; + shadowRect = NSInsetRect(shadowRect, -[borderShadow shadowBlurRadius], -[borderShadow shadowBlurRadius]); + invalidatedRect = NSUnionRect(invalidatedRect, shadowRect); + } + [super setNeedsDisplayInRect:invalidatedRect]; +} + + +- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)userData +{ + unsigned idx = [self photoIndexForPoint:point]; + if (idx < [self photoCount]) + { + return [delegate photoView:self tooltipForPhotoAtIndex:[self photoIndexForPoint:point]]; + } + return nil; +} + + + +#pragma mark - +// Delegate Accessors +#pragma mark Delegate Accessors + +- (id)delegate +{ + return delegate; +} + +- (void)setDelegate:(id)del +{ + [self willChangeValueForKey:@"delegate"]; + delegate = del; + [self didChangeValueForKey:@"delegate"]; +} + +#pragma mark - +// Photos Methods +#pragma mark Photo Methods + +- (NSArray *)photosArray +{ + //NSLog(@"in -photosArray, returned photosArray = %@", photosArray); + return photosArray; +} + +- (void)setPhotosArray:(NSArray *)aPhotosArray +{ + //NSLog(@"in -setPhotosArray:, old value of photosArray: %@, changed to: %@", photosArray, aPhotosArray); + if (photosArray != aPhotosArray) { + [photosArray release]; + [self willChangeValueForKey:@"photosArray"]; + photosArray = [aPhotosArray mutableCopy]; + [self didChangeValueForKey:@"photosArray"]; + + // update live resize array + if (nil != photosFastArray) { + [photosFastArray release]; + } + photosFastArray = [[NSMutableArray alloc] initWithCapacity:[aPhotosArray count]]; + unsigned i; + for (i = 0; i < [photosArray count]; i++) + { + [photosFastArray addObject:[NSNull null]]; + } + + // update internal grid size, adjust height based on the new grid size + [self scrollPoint:([self frame].origin)]; + [self setNeedsDisplayInRect:[self visibleRect]]; + } +} + +#pragma mark - +// Selection Management +#pragma mark Selection Management + +- (NSIndexSet *)selectedPhotoIndexes +{ + //NSLog(@"in -selectedPhotoIndexes, returned selectedPhotoIndexes = %@", selectedPhotoIndexes); + return selectedPhotoIndexes; +} + +- (void)setSelectedPhotoIndexes:(NSIndexSet *)aSelectedPhotoIndexes +{ + //NSLog(@"in -setSelectedPhotoIndexes:, old value of selectedPhotoIndexes: %@, changed to: %@", selectedPhotoIndexes, aSelectedPhotoIndexes); + if ((selectedPhotoIndexes != aSelectedPhotoIndexes) && (![selectedPhotoIndexes isEqualToIndexSet:aSelectedPhotoIndexes])) { + + // Set the selection and send KVO + [selectedPhotoIndexes release]; + [self willChangeValueForKey:@"selectedPhotoIndexes"]; + selectedPhotoIndexes = [aSelectedPhotoIndexes copy]; + [self didChangeValueForKey:@"selectedPhotoIndexes"]; + + } +} + +#pragma mark - +// Selection Style +#pragma mark Selection Style + +- (BOOL)useBorderSelection +{ + //NSLog(@"in -useBorderSelection, returned useBorderSelection = %@", useBorderSelection ? @"YES": @"NO"); + return useBorderSelection; +} + +- (void)setUseBorderSelection:(BOOL)flag +{ + //NSLog(@"in -setUseBorderSelection, old value of useBorderSelection: %@, changed to: %@", (useBorderSelection ? @"YES": @"NO"), (flag ? @"YES": @"NO")); + [self willChangeValueForKey:@"useBorderSelection"]; + useBorderSelection = flag; + [self didChangeValueForKey:@"useBorderSelection"]; + + [self setNeedsDisplayInRect:[self visibleRect]]; +} + +- (NSColor *)selectionBorderColor +{ + //NSLog(@"in -selectionBorderColor, returned selectionBorderColor = %@", selectionBorderColor); + return selectionBorderColor; +} + +- (void)setSelectionBorderColor:(NSColor *)aSelectionBorderColor +{ + //NSLog(@"in -setSelectionBorderColor:, old value of selectionBorderColor: %@, changed to: %@", selectionBorderColor, aSelectionBorderColor); + if (selectionBorderColor != aSelectionBorderColor) { + [selectionBorderColor release]; + [self willChangeValueForKey:@"selectionBorderColor"]; + selectionBorderColor = [aSelectionBorderColor copy]; + [self didChangeValueForKey:@"selectionBorderColor"]; + } +} + +- (BOOL)useShadowSelection +{ + //NSLog(@"in -useShadowSelection, returned useShadowSelection = %@", useShadowSelection ? @"YES": @"NO"); + return useShadowSelection; +} + +- (void)setUseShadowSelection:(BOOL)flag +{ + //NSLog(@"in -setUseShadowSelection, old value of useShadowSelection: %@, changed to: %@", (useShadowSelection ? @"YES": @"NO"), (flag ? @"YES": @"NO")); + [self willChangeValueForKey:@"useShadowSelection"]; + useShadowSelection = flag; + [self willChangeValueForKey:@"useShadowSelection"]; + + [self setNeedsDisplayInRect:[self visibleRect]]; +} + +#pragma mark - +// Appearance +#pragma mark Appearance + +- (BOOL)useShadowBorder +{ + //NSLog(@"in -useShadowBorder, returned useShadowBorder = %@", useShadowBorder ? @"YES": @"NO"); + return useShadowBorder; +} + +- (void)setUseShadowBorder:(BOOL)flag +{ + //NSLog(@"in -setUseShadowBorder, old value of useShadowBorder: %@, changed to: %@", (useShadowBorder ? @"YES": @"NO"), (flag ? @"YES": @"NO")); + [self willChangeValueForKey:@"useShadowBorder"]; + useShadowBorder = flag; + [self didChangeValueForKey:@"useShadowBorder"]; + + [self setNeedsDisplayInRect:[self visibleRect]]; +} + +- (BOOL)useOutlineBorder +{ + //NSLog(@"in -useOutlineBorder, returned useOutlineBorder = %@", useOutlineBorder ? @"YES": @"NO"); + return useOutlineBorder; +} + +- (void)setUseOutlineBorder:(BOOL)flag +{ + //NSLog(@"in -setUseOutlineBorder, old value of useOutlineBorder: %@, changed to: %@", (useOutlineBorder ? @"YES": @"NO"), (flag ? @"YES": @"NO")); + [self willChangeValueForKey:@"useOutlineBorder"]; + useOutlineBorder = flag; + [self didChangeValueForKey:@"useOutlineBorder"]; + + [self setNeedsDisplayInRect:[self visibleRect]]; +} + +- (NSColor *)backgroundColor +{ + //NSLog(@"in -backgroundColor, returned backgroundColor = %@", backgroundColor); + return [[backgroundColor retain] autorelease]; +} + +- (void)setBackgroundColor:(NSColor *)aBackgroundColor +{ + //NSLog(@"in -setBackgroundColor:, old value of backgroundColor: %@, changed to: %@", backgroundColor, aBackgroundColor); + if (backgroundColor != aBackgroundColor) { + [backgroundColor release]; + [self willChangeValueForKey:@"backgroundColor"]; + backgroundColor = [aBackgroundColor copy]; + [self didChangeValueForKey:@"backgroundColor"]; + + // adjust the shadow box selection color based on the background color. values closer to white use black and vice versa + NSColor *newShadowBoxColor; + float whiteValue = 0.0; + if ([backgroundColor numberOfComponents] >= 3) { + float red, green, blue; + [backgroundColor getRed:&red green:&green blue:&blue alpha:NULL]; + whiteValue = (red + green + blue) / 3; + } else if ([backgroundColor numberOfComponents] >= 1) { + [backgroundColor getWhite:&whiteValue alpha:NULL]; + } + + if (0.5 > whiteValue) + newShadowBoxColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.5]; + else + newShadowBoxColor = [NSColor colorWithDeviceWhite:0.0 alpha:0.5]; + [self setShadowBoxColor:newShadowBoxColor]; + } +} + +- (BOOL)useHighQualityResize +{ + return useHighQualityResize; +} + +- (void)setUseHighQualityResize:(BOOL)flag +{ + useHighQualityResize = flag; +} + +- (BOOL)showCaptions +{ + return showCaptions; +} + +- (void)setShowCaptions:(BOOL)flag +{ + showCaptions = flag; +} + +- (void) resetCaptionShowing:(NSNotification *)notification +{ + NSDictionary *ui = [notification userInfo]; + BOOL flag = [[ui objectForKey:@"flag"] boolValue]; + [self setShowCaptions:flag]; +} + +- (float)photoSize +{ + //NSLog(@"in -photoSize, returned photoSize = %f", photoSize); + return photoSize; +} + +- (void)setPhotoSize:(float)aPhotoSize +{ + //NSLog(@"in -setPhotoSize, old value of photoSize: %f, changed to: %f", photoSize, aPhotoSize); + [self willChangeValueForKey:@"photoSize"]; + photoSize = aPhotoSize; + [self didChangeValueForKey:@"photoSize"]; + + // update internal grid size, adjust height based on the new grid size + // to make sure the same photos stay in view, get a visible photos' index, then scroll to that photo after the update + NSRect visibleRect = [self visibleRect]; + float heightRatio = visibleRect.origin.y / [self frame].size.height; + visibleRect.origin.y = heightRatio * [self frame].size.height; + [self scrollRectToVisible:visibleRect]; + + [self viewWillStartLiveResize]; + + [self setNeedsDisplayInRect:[self visibleRect]]; + + // update time for live resizing + if (nil != photoResizeTime) { + [photoResizeTime release]; + photoResizeTime = nil; + } + isDonePhotoResizing = NO; + photoResizeTime = [[NSDate date] retain]; + + + if (photoResizeTimer) { + [photoResizeTimer invalidate]; + photoResizeTimer = nil; + } + + // note that the timer retains the target + photoResizeTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 + target:self + selector:@selector(updatePhotoResizing) + userInfo:nil + repeats:YES]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setFloat:aPhotoSize forKey:@"MUPhotoSize"]; +} + +- (IBAction)takePhotoSizeFrom:(id)sender // allow hooking up to a slider +{ + if ([sender respondsToSelector:@selector(doubleValue)]) + { + mouseCurrentPoint = mouseDownPoint = NSZeroPoint; + [self setPhotoSize:[sender doubleValue]]; + //fake a bounds resize notification + [[NSNotificationCenter defaultCenter] postNotificationName:NSViewFrameDidChangeNotification + object:self]; + if ([[self selectionIndexes] count] > 0) + { + unsigned lastSelectedIndex = [[self selectionIndexes] lastIndex]; + NSRect r = [self photoRectForIndex:lastSelectedIndex]; + r.origin.y -= photoVerticalSpacing; + r.size.height += photoVerticalSpacing; + + [self scrollRectToVisible:r]; + } + } +} + +#pragma mark - +// Don't Mess With Texas +#pragma mark Don't Mess With Texas +// haven't tested changing these behaviors yet - there's no reason they shouldn't work... but use at your own risk. + +- (float)photoVerticalSpacing +{ + //NSLog(@"in -photoVerticalSpacing, returned photoVerticalSpacing = %f", photoVerticalSpacing); + return photoVerticalSpacing; +} + +- (void)setPhotoVerticalSpacing:(float)aPhotoVerticalSpacing +{ + //NSLog(@"in -setPhotoVerticalSpacing, old value of photoVerticalSpacing: %f, changed to: %f", photoVerticalSpacing, aPhotoVerticalSpacing); + [self willChangeValueForKey:@"photoVerticalSpacing"]; + photoVerticalSpacing = aPhotoVerticalSpacing; + [self didChangeValueForKey:@"photoVertificalSpacing"]; + + // update internal grid size, adjust height based on the new grid size + NSRect visibleRect = [self visibleRect]; + float heightRatio = visibleRect.origin.y / [self frame].size.height; + visibleRect.origin.y = heightRatio * [self frame].size.height; + [self scrollRectToVisible:visibleRect]; + [self setNeedsDisplayInRect:[self visibleRect]]; + + + // update time for live resizing + if (nil != photoResizeTime) { + [photoResizeTime release]; + photoResizeTime = nil; + } + isDonePhotoResizing = NO; + photoResizeTime = [[NSDate date] retain]; + if (nil == photoResizeTimer) { + // the timer retains the target + photoResizeTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 + target:self + selector:@selector(updatePhotoResizing) + userInfo:nil + repeats:YES]; + } +} + +- (float)photoHorizontalSpacing +{ + //NSLog(@"in -photoHorizontalSpacing, returned photoHorizontalSpacing = %f", photoHorizontalSpacing); + return photoHorizontalSpacing; +} + +- (void)setPhotoHorizontalSpacing:(float)aPhotoHorizontalSpacing +{ + //NSLog(@"in -setPhotoHorizontalSpacing, old value of photoHorizontalSpacing: %f, changed to: %f", photoHorizontalSpacing, aPhotoHorizontalSpacing); + [self willChangeValueForKey:@"photoHorizontalSpacing"]; + photoHorizontalSpacing = aPhotoHorizontalSpacing; + [self didChangeValueForKey:@"photoHorizontalSpacing"]; + + // update internal grid size, adjust height based on the new grid size + NSRect visibleRect = [self visibleRect]; + float heightRatio = visibleRect.origin.y / [self frame].size.height; + visibleRect.origin.y = heightRatio * [self frame].size.height; + [self scrollRectToVisible:visibleRect]; + [self setNeedsDisplayInRect:[self visibleRect]]; + + // update time for live resizing + if (nil != photoResizeTime) { + [photoResizeTime release]; + photoResizeTime = nil; + } + isDonePhotoResizing = NO; + photoResizeTime = [[NSDate date] retain]; + if (nil == photoResizeTimer) { + // the timer retains the target + photoResizeTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 + target:self + selector:@selector(updatePhotoResizing) + userInfo:nil + repeats:YES]; + } +} + + +- (NSColor *)borderOutlineColor +{ + //NSLog(@"in -borderOutlineColor, returned borderOutlineColor = %@", borderOutlineColor); + return borderOutlineColor; +} + +- (void)setBorderOutlineColor:(NSColor *)aBorderOutlineColor +{ + //NSLog(@"in -setBorderOutlineColor:, old value of borderOutlineColor: %@, changed to: %@", borderOutlineColor, aBorderOutlineColor); + if (borderOutlineColor != aBorderOutlineColor) { + [borderOutlineColor release]; + [self willChangeValueForKey:@"borderOutlineColor"]; + borderOutlineColor = [aBorderOutlineColor copy]; + [self didChangeValueForKey:@"borderOutlineColor"]; + + [self setNeedsDisplayInRect:[self visibleRect]]; + } +} + +- (NSColor *)shadowBoxColor +{ + //NSLog(@"in -shadowBoxColor, returned shadowBoxColor = %@", shadowBoxColor); + return shadowBoxColor; +} + +- (void)setShadowBoxColor:(NSColor *)aShadowBoxColor +{ + //NSLog(@"in -setShadowBoxColor:, old value of shadowBoxColor: %@, changed to: %@", shadowBoxColor, aShadowBoxColor); + if (shadowBoxColor != aShadowBoxColor) { + [shadowBoxColor release]; + shadowBoxColor = [aShadowBoxColor copy]; + + [self setNeedsDisplayInRect:[self visibleRect]]; + } + +} +- (float)selectionBorderWidth +{ + //NSLog(@"in -selectionBorderWidth, returned selectionBorderWidth = %f", selectionBorderWidth); + return selectionBorderWidth; +} + +- (void)setSelectionBorderWidth:(float)aSelectionBorderWidth +{ + //NSLog(@"in -setSelectionBorderWidth, old value of selectionBorderWidth: %f, changed to: %f", selectionBorderWidth, aSelectionBorderWidth); + selectionBorderWidth = aSelectionBorderWidth; +} + + +#pragma mark - +// Mouse Event Methods +#pragma mark Mouse Event Methods + +- (void) mouseDown:(NSEvent *) event +{ + mouseDown = YES; + mouseDownPoint = [self convertPoint:[event locationInWindow] fromView:nil]; + mouseCurrentPoint = mouseDownPoint; + + unsigned clickedIndex = [self photoIndexForPoint:mouseDownPoint]; + NSRect photoRect = [self photoRectForIndex:clickedIndex]; + unsigned int flags = [event modifierFlags]; + NSMutableIndexSet* indexes = [[self selectionIndexes] mutableCopy]; + BOOL imageHit = NSPointInRect(mouseDownPoint, photoRect); + + if (imageHit) { + if (flags & NSCommandKeyMask) { + // Flip current image selection state. + if ([indexes containsIndex:clickedIndex]) { + [indexes removeIndex:clickedIndex]; + } else { + [indexes addIndex:clickedIndex]; + } + } else { + if (flags & NSShiftKeyMask) { + // Add range to selection. + if ([indexes count] == 0) { + [indexes addIndex:clickedIndex]; + } else { + unsigned int origin = (clickedIndex < [indexes lastIndex]) ? clickedIndex :[indexes lastIndex]; + unsigned int length = (clickedIndex < [indexes lastIndex]) ? [indexes lastIndex] - clickedIndex : clickedIndex - [indexes lastIndex]; + + length++; + [indexes addIndexesInRange:NSMakeRange(origin, length)]; + } + } else { + if (![self isPhotoSelectedAtIndex:clickedIndex]) { + // Photo selection without modifiers. + [indexes removeAllIndexes]; + [indexes addIndex:clickedIndex]; + } + } + } + + potentialDragDrop = YES; + } else { + if ((flags & NSShiftKeyMask) == 0) { + [indexes removeAllIndexes]; + } + potentialDragDrop = NO; + } + + [self setSelectionIndexes:indexes]; + [indexes release]; +} + +- (void)mouseDragged:(NSEvent *)event +{ + if (0 == columns) return; + mouseCurrentPoint = [self convertPoint:[event locationInWindow] fromView:nil]; + + // if the mouse has moved less than 5px in either direction, don't register the drag yet + float xFromStart = fabs((mouseDownPoint.x - mouseCurrentPoint.x)); + float yFromStart = fabs((mouseDownPoint.y - mouseCurrentPoint.y)); + if ((xFromStart < 5) && (yFromStart < 5)) { + return; + + } else if (potentialDragDrop && (nil != delegate)) { + // create a drag image + unsigned clickedIndex = [self photoIndexForPoint:mouseDownPoint]; + NSImage *clickedImage = [self photoAtIndex:clickedIndex]; + BOOL flipped = [clickedImage isFlipped]; + [clickedImage setFlipped:YES]; + NSSize scaledSize = [self scaledPhotoSizeForSize:[clickedImage size]]; + if (nil == clickedImage) { // creates a red image, which should let the user/developer know something is wrong + clickedImage = [[[NSImage alloc] initWithSize:NSMakeSize(photoSize,photoSize)] autorelease]; + [clickedImage lockFocus]; + [[NSColor redColor] set]; + [NSBezierPath fillRect:NSMakeRect(0,0,photoSize,photoSize)]; + [clickedImage unlockFocus]; + } + NSImage *dragImage = [[NSImage alloc] initWithSize:scaledSize]; + + // draw the drag image as a semi-transparent copy of the image the user dragged, and optionally a red badge indicating the number of photos + [dragImage lockFocus]; + [clickedImage drawInRect:NSMakeRect(0,0,scaledSize.width,scaledSize.height) fromRect:NSMakeRect(0,0,[clickedImage size].width,[clickedImage size].height) operation:NSCompositeCopy fraction:0.7]; + [dragImage unlockFocus]; + + [clickedImage setFlipped:flipped]; + + // if there's more than one image, put a badge on the photo + if ([[self selectionIndexes] count] > 1) { + NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init]; + [attributes setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName]; + [attributes setObject:[NSFont fontWithName:@"Helvetica" size:14] forKey:NSFontAttributeName]; + NSAttributedString *badgeString = [[NSAttributedString alloc] initWithString:[[NSNumber numberWithInt:[[self selectionIndexes] count]] stringValue] attributes:attributes]; + NSSize stringSize = [badgeString size]; + int diameter = stringSize.width; + if (stringSize.height > diameter) diameter = stringSize.height; + diameter += 5; + + // calculate the badge circle + int maxY = [dragImage size].height - 5; + int maxX = [dragImage size].width - 5; + int minY = maxY - diameter; + int minX = maxX - diameter; + NSBezierPath *circle = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(minX,minY,maxX-minX,maxY-minY)]; + + // draw the string + NSPoint point; + point.x = maxX - ((maxX - minX) / 2) - 1 - (stringSize.width / 2); + point.y = maxY - diameter + (stringSize.height / 2) - 6; + + NSAffineTransform *t = [NSAffineTransform transform]; + [t translateXBy:0 yBy:maxY]; + [t scaleXBy:1 yBy:-1]; + + [dragImage lockFocus]; + [t concat]; + [[NSColor colorWithDeviceRed:1 green:0.1 blue:0.1 alpha:0.7] set]; + [circle fill]; + [badgeString drawAtPoint:point]; + [t invert]; + [t concat]; + [dragImage unlockFocus]; + + [badgeString release]; + [attributes release]; + } + + [dragImage setFlipped:YES]; + + // get the pasteboard and register the returned types with delegate as the owner + NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSDragPboard]; + [pb declareTypes:[NSArray array] owner:nil]; // clear the pasteboard + [delegate photoView:self fillPasteboardForDrag:pb]; + + // place the cursor in the center of the drag image + NSPoint p = [self convertPoint:[event locationInWindow] fromView:nil]; + NSSize imageSize = [dragImage size]; + p.x = p.x - imageSize.width / 2; + p.y = p.y + imageSize.height / 2; + + [self dragImage:dragImage at:p offset:NSZeroSize event:event pasteboard:pb source:self slideBack:YES]; + + [dragImage release]; + + } else { + // adjust the mouse current point so that it's not outside the frame + NSRect frameRect = [self frame]; + if (mouseCurrentPoint.x < NSMinX(frameRect)) + mouseCurrentPoint.x = NSMinX(frameRect); + if (mouseCurrentPoint.x > NSMaxX(frameRect)) + mouseCurrentPoint.x = NSMaxX(frameRect); + if (mouseCurrentPoint.y < NSMinY(frameRect)) + mouseCurrentPoint.y = NSMinY(frameRect); + if (mouseCurrentPoint.y > NSMaxY(frameRect)) + mouseCurrentPoint.y = NSMaxY(frameRect); + + // determine the rect for the current drag area + float minX, maxX, minY, maxY; + minX = (mouseCurrentPoint.x < mouseDownPoint.x) ? mouseCurrentPoint.x : mouseDownPoint.x; + minY = (mouseCurrentPoint.y < mouseDownPoint.y) ? mouseCurrentPoint.y : mouseDownPoint.y; + maxX = (mouseCurrentPoint.x > mouseDownPoint.x) ? mouseCurrentPoint.x : mouseDownPoint.x; + maxY = (mouseCurrentPoint.y > mouseDownPoint.y) ? mouseCurrentPoint.y : mouseDownPoint.y; + if (maxY > NSMaxY(frameRect)) + maxY = NSMaxY(frameRect); + if (maxX > NSMaxX(frameRect)) + maxX = NSMaxX(frameRect); + + NSRect selectionRect = NSMakeRect(minX,minY,maxX-minX,maxY-minY); + + unsigned minIndex = [self photoIndexForPoint:NSMakePoint(minX, minY)]; + unsigned xRun = [self photoIndexForPoint:NSMakePoint(maxX, minY)] - minIndex + 1; + unsigned yRun = [self photoIndexForPoint:NSMakePoint(minX, maxY)] - minIndex + 1; + unsigned selectedRows = (yRun / columns); + + // Save the current selection (if any), then populate the drag indexes + // this allows us to shift band select to add to the current selection. + [dragSelectedPhotoIndexes removeAllIndexes]; + [dragSelectedPhotoIndexes addIndexes:[self selectionIndexes]]; + + // add indexes in the drag rectangle + unsigned i; + for (i = 0; i <= selectedRows; i++) { + unsigned rowStartIndex = (i * columns) + minIndex; + unsigned j; + for (j = rowStartIndex; j < (rowStartIndex + xRun); j++) { + if (NSIntersectsRect([self photoRectForIndex:j],selectionRect)) + [dragSelectedPhotoIndexes addIndex:j]; + } + } + + // if requested, set the selection. this could cause a rapid series of KVO notifications, so if this is false, the view tracks + // the selection internally, but doesn't pass it to the bindings or the delegates until the drag is over. + // This will cause an appropriate redraw. + if (sendsLiveSelectionUpdates) + { + [self setSelectionIndexes:dragSelectedPhotoIndexes]; + } + + // autoscrolling + if (autoscrollTimer == nil) { + // the timer retains the target: must invalidate or dealloc wont be called on the photo view! + autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 + target:self + selector:@selector(autoscroll) + userInfo:nil + repeats:YES]; + } + + [[self superview] autoscroll:event]; + + [self setNeedsDisplayInRect:[self visibleRect]]; + } + +} + + +- (void)mouseUp:(NSEvent *)event +{ + // Double-click Handling + if ([event clickCount] == 2) { + // There could be more than one selected photo. In that case, call the delegates doubleClickOnPhotoAtIndex routine for + // each selected photo. + unsigned int selectedIndex = [[self selectionIndexes] firstIndex]; + while (selectedIndex != NSNotFound) { + [delegate photoView:self doubleClickOnPhotoAtIndex:selectedIndex withFrame:[self photoRectForIndex:selectedIndex]]; + selectedIndex = [[self selectionIndexes] indexGreaterThanIndex:selectedIndex]; + } + } + else if (0 < [dragSelectedPhotoIndexes count]) { // finishing a drag selection + // move the drag indexes into the main selection indexes - firing off KVO messages or delegate messages + [self setSelectionIndexes:dragSelectedPhotoIndexes]; + [dragSelectedPhotoIndexes removeAllIndexes]; + } + + if (autoscrollTimer != nil) { + [autoscrollTimer invalidate]; + autoscrollTimer = nil; + } + + mouseDown = NO; + mouseCurrentPoint = mouseDownPoint = NSZeroPoint; + + [self setNeedsDisplayInRect:[self visibleRect]]; +} + +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal +{ + if (nil != delegate) + return [delegate photoView:self draggingSourceOperationMaskForLocal:isLocal]; + else + return NSDragOperationNone; +} + +- (void)autoscroll +{ + mouseCurrentPoint = [self convertPoint:[[NSApp currentEvent] locationInWindow] fromView:nil]; + [[self superview] autoscroll:[NSApp currentEvent]]; + + [self mouseDragged:[NSApp currentEvent]]; +} + + +- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation +{ + if (operation == NSDragOperationDelete) + [self removePhotosAtIndexes:[self selectionIndexes]]; +} + + + +#pragma mark - +#pragma mark Drag Receiving + +- (NSDragOperation)draggingEntered:(id )sender +{ + if ([delegate respondsToSelector:@selector(photoView:draggingEntered:)]) + { + NSDragOperation result = [delegate photoView:self draggingEntered:sender]; + if (result != NSDragOperationNone) + { + drawDropHilite = YES; + [self setNeedsDisplay:YES]; + } + return result; + } + else + return NSDragOperationNone; +} + + +- (void)draggingExited:(id )sender +{ + if ([delegate respondsToSelector:@selector(photoView:draggingExited:)]) + [delegate photoView:self draggingExited:sender]; + + if (drawDropHilite) + { + drawDropHilite = NO; + [self setNeedsDisplay:YES]; + } +} + + +- (BOOL)prepareForDragOperation:(id )sender +{ + BOOL result; + if ([delegate respondsToSelector:@selector(photoView:prepareForDragOperation:)]) + result = [delegate photoView:self prepareForDragOperation:sender]; + else + result = YES; + + if (drawDropHilite) + { + drawDropHilite = NO; + [self setNeedsDisplay:YES]; + } + return result; +} + +- (BOOL)performDragOperation:(id )sender +{ + if ([delegate respondsToSelector:@selector(photoView:performDragOperation:)]) + return [delegate photoView:self performDragOperation:sender]; + else + return NO; +} + +- (void)concludeDragOperation:(id )sender +{ + if ([delegate respondsToSelector:@selector(photoView:concludeDragOperation:)]) + [delegate photoView:self concludeDragOperation:sender]; +} + + + +#pragma mark - +// Responder Method +#pragma mark Responder Methods + +- (BOOL)acceptsFirstResponder +{ + return([self photoCount] > 0); +} + +- (BOOL)resignFirstResponder +{ + [self setNeedsDisplay:YES]; + return YES; +} + +- (BOOL)becomeFirstResponder +{ + [self setNeedsDisplay:YES]; + return YES; +} + +- (void)keyDown:(NSEvent *)theEvent +{ + NSString* eventKey = [theEvent charactersIgnoringModifiers]; + unichar keyChar = 0; + + if ([eventKey length] == 1) + { + keyChar = [eventKey characterAtIndex:0]; + if (keyChar == ' ') + { + unsigned int selectedIndex = [[self selectionIndexes] firstIndex]; + + while (selectedIndex != NSNotFound) + { + [delegate photoView:self doubleClickOnPhotoAtIndex:selectedIndex withFrame:[self photoRectForIndex:selectedIndex]]; + selectedIndex = [[self selectionIndexes] indexGreaterThanIndex:selectedIndex]; + } + return; + } + else if ((keyChar == NSCarriageReturnCharacter) || (keyChar == NSEnterCharacter)) { + [super keyDown:theEvent]; + return; + } + } + + + [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; +} + +- (void)deleteBackward:(id)sender +{ + if (0 < [[self selectionIndexes] count]) { + [self removePhotosAtIndexes:[self selectionIndexes]]; + } +} + +- (void)selectAll:(id)sender +{ + if (0 < [self photoCount]) { + NSIndexSet *allIndexes = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, [self photoCount])]; + [self setSelectionIndexes:allIndexes]; + [allIndexes release]; + } +} + +- (void)insertTab:(id)sender +{ + [[self window] selectKeyViewFollowingView:self]; +} + +- (void)insertBackTab:(id)sender +{ + [[self window] selectKeyViewPrecedingView:self]; +} + +- (void)moveLeft:(id)sender +{ + NSIndexSet* indexes = [self selectionIndexes]; + NSMutableIndexSet* newIndexes = [[NSMutableIndexSet alloc] init]; + + if (([indexes count] > 0) && (![indexes containsIndex:0])) + { + [newIndexes addIndex:[indexes firstIndex] - 1]; + } + else + { + if ((([indexes count] == 0) || ([indexes count] == [self photoCount])) && ([self photoCount] > 0)) + { + [newIndexes addIndex:[self photoCount] - 1]; + } + } + + if ([newIndexes count] > 0) + { + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:[newIndexes firstIndex]]]; + } + + [newIndexes release]; +} + +- (void)moveLeftAndModifySelection:(id)sender +{ + NSIndexSet *indexes = [self selectionIndexes]; + if (([indexes count] > 0) && (![indexes containsIndex:0])) { + NSMutableIndexSet *newIndexes = [indexes mutableCopy]; + [newIndexes addIndex:([newIndexes firstIndex] - 1)]; + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:[newIndexes firstIndex]]]; + [newIndexes release]; + } +} + +- (void)moveRight:(id)sender +{ + NSIndexSet* indexes = [self selectionIndexes]; + NSMutableIndexSet* newIndexes = [[NSMutableIndexSet alloc] init]; + + if (([indexes count] > 0) && (![indexes containsIndex:[self photoCount] - 1])) + { + [newIndexes addIndex:[indexes lastIndex] + 1]; + } + else + { + if ((([indexes count] == 0) || ([indexes count] == [self photoCount])) && ([self photoCount] > 0)) + { + [newIndexes addIndex:0]; + } + } + + if ([newIndexes count] > 0) + { + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:[newIndexes lastIndex]]]; + } + + [newIndexes release]; +} + +- (void)moveRightAndModifySelection:(id)sender +{ + NSIndexSet *indexes = [self selectionIndexes]; + if (([indexes count] > 0) && (![indexes containsIndex:([self photoCount] - 1)])) { + NSMutableIndexSet *newIndexes = [indexes mutableCopy]; + [newIndexes addIndex:([newIndexes lastIndex] + 1)]; + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:[newIndexes lastIndex]]]; + [newIndexes release]; + } +} + +- (void)moveDown:(id)sender +{ + NSIndexSet* indexes = [self selectionIndexes]; + NSMutableIndexSet* newIndexes = [[NSMutableIndexSet alloc] init]; + unsigned int destinationIndex = [indexes lastIndex] + columns; + unsigned int lastIndex = [self photoCount] - 1; + + if (([indexes count] > 0) && (destinationIndex <= lastIndex)) + { + [newIndexes addIndex:destinationIndex]; + } + else + { + if ((([indexes count] == 0) || ([indexes count] == [self photoCount])) && ([self photoCount] > 0)) + { + [newIndexes addIndex:0]; + } + } + + if ([newIndexes count] > 0) + { + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:[newIndexes lastIndex]]]; + } + + [newIndexes release]; +} + +- (void)moveDownAndModifySelection:(id)sender +{ + NSIndexSet *indexes = [self selectionIndexes]; + unsigned int destinationIndex = [indexes lastIndex] + columns; + unsigned int lastIndex = [self photoCount] - 1; + + if (([indexes count] > 0) && (destinationIndex <= lastIndex)) { + NSMutableIndexSet *newIndexes = [indexes mutableCopy]; + NSRange addRange; + addRange.location = [indexes lastIndex] + 1; + addRange.length = columns; + [newIndexes addIndexesInRange:addRange]; + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:[newIndexes lastIndex]]]; + [newIndexes release]; + } +} + +- (void)moveUp:(id)sender +{ + NSIndexSet* indexes = [self selectionIndexes]; + NSMutableIndexSet* newIndexes = [[NSMutableIndexSet alloc] init]; + + if (([indexes count] > 0) && ([indexes firstIndex] >= columns)) + { + [newIndexes addIndex:[indexes firstIndex] - columns]; + } + else + { + if ((([indexes count] == 0) || ([indexes count] == [self photoCount])) && ([self photoCount] > 0)) + { + [newIndexes addIndex:[self photoCount] - 1]; + } + } + + if ([newIndexes count] > 0) + { + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:[newIndexes firstIndex]]]; + } + + [newIndexes release]; +} + +- (void)moveUpAndModifySelection:(id)sender +{ + NSMutableIndexSet *indexes = [[self selectionIndexes] mutableCopy]; + if (([indexes count] > 0) && ([indexes firstIndex] >= columns)) { + [indexes addIndexesInRange:NSMakeRange(([indexes firstIndex] - columns), columns + 1)]; + [self setSelectionIndexes:indexes]; + [self scrollRectToVisible:[self gridRectForIndex:[indexes firstIndex]]]; + } + [indexes release]; +} + +- (void)scrollToEndOfDocument:(id)sender +{ + [self scrollRectToVisible:[self gridRectForIndex:([self photoCount] - 1)]]; +} + +- (void)scrollToBeginningOfDocument:(id)sender +{ + [self scrollPoint:NSZeroPoint]; +} + +- (void)scrollPageDown:(id)sender +{ + NSScrollView* scrollView = [self enclosingScrollView]; + NSRect r = [scrollView documentVisibleRect]; + [self scrollPoint:NSMakePoint(NSMinX(r), NSMaxY(r) - [scrollView verticalPageScroll])]; +} + +- (void)scrollPageUp:(id)sender +{ + NSScrollView* scrollView = [self enclosingScrollView]; + NSRect r = [scrollView documentVisibleRect]; + [self scrollPoint:NSMakePoint(NSMinX(r), (NSMinY(r) - NSHeight(r)) + [scrollView verticalPageScroll])]; +} + +- (void)moveToEndOfLine:(id)sender +{ + NSIndexSet *indexes = [self selectionIndexes]; + if ([indexes count] > 0) { + unsigned int destinationIndex = ([indexes lastIndex] + columns) - ([indexes lastIndex] % columns) - 1; + if (destinationIndex >= [self photoCount]) { + destinationIndex = [self photoCount] - 1; + } + NSIndexSet *newIndexes = [[NSIndexSet alloc] initWithIndex:destinationIndex]; + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:destinationIndex]]; + [newIndexes release]; + } +} + +- (void)moveToEndOfLineAndModifySelection:(id)sender +{ + NSMutableIndexSet *indexes = [[self selectionIndexes] mutableCopy]; + if ([indexes count] > 0) { + unsigned int destinationIndexPlusOne = ([indexes lastIndex] + columns) - ([indexes lastIndex] % columns); + if (destinationIndexPlusOne >= [self photoCount]) { + destinationIndexPlusOne = [self photoCount]; + } + [indexes addIndexesInRange:NSMakeRange(([indexes lastIndex]), (destinationIndexPlusOne - [indexes lastIndex]))]; + [self setSelectionIndexes:indexes]; + [self scrollRectToVisible:[self gridRectForIndex:[indexes lastIndex]]]; + } + [indexes release]; +} + +- (void)moveToBeginningOfLine:(id)sender +{ + NSIndexSet *indexes = [self selectionIndexes]; + if ([indexes count] > 0) { + unsigned int destinationIndex = [indexes firstIndex] - ([indexes firstIndex] % columns); + NSIndexSet *newIndexes = [[NSIndexSet alloc] initWithIndex:destinationIndex]; + [self setSelectionIndexes:newIndexes]; + [self scrollRectToVisible:[self gridRectForIndex:destinationIndex]]; + [newIndexes release]; + } +} + +- (void)moveToBeginningOfLineAndModifySelection:(id)sender +{ + NSMutableIndexSet *indexes = [[self selectionIndexes] mutableCopy]; + if ([indexes count] > 0) { + unsigned int destinationIndex = [indexes firstIndex] - ([indexes firstIndex] % columns); + [indexes addIndexesInRange:NSMakeRange(destinationIndex, ([indexes firstIndex] - destinationIndex))]; + [self setSelectionIndexes:indexes]; + [self scrollRectToVisible:[self gridRectForIndex:destinationIndex]]; + } + [indexes release]; +} + +- (void)moveToBeginningOfDocument:(id)sender +{ + if (0 < [self photoCount]) { + [self setSelectionIndexes:[NSIndexSet indexSetWithIndex:0]]; + [self scrollPoint:NSZeroPoint]; + } +} + +- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender +{ + NSMutableIndexSet *indexes = [[self selectionIndexes] mutableCopy]; + if ([indexes count] > 0) { + [indexes addIndexesInRange:NSMakeRange(0, [indexes firstIndex])]; + [self setSelectionIndexes:indexes]; + [self scrollRectToVisible:NSZeroRect]; + } + [indexes release]; +} + +- (void)moveToEndOfDocument:(id)sender +{ + if (0 < [self photoCount]) { + [self setSelectionIndexes:[NSIndexSet indexSetWithIndex:([self photoCount] - 1)]]; + [self scrollRectToVisible:[self gridRectForIndex:([self photoCount] - 1)]]; + } +} + +- (void)moveToEndOfDocumentAndModifySelection:(id)sender +{ + NSMutableIndexSet *indexes = [[self selectionIndexes] mutableCopy]; + if ([indexes count] > 0) { + [indexes addIndexesInRange:NSMakeRange([indexes lastIndex], ([self photoCount] - [indexes lastIndex]))]; + [self setSelectionIndexes:indexes]; + [self scrollRectToVisible:[self gridRectForIndex:[indexes lastIndex]]]; + } +} + +@end + + +#pragma mark - +// Delegate Default Implementations +#pragma mark Delegate Default Implementations + +@implementation NSObject (MUPhotoViewDelegate) + +// will only get called if photoArray has not been set, or has not been bound +- (unsigned)photoCountForPhotoView:(MUPhotoView *)view +{ + return 0; +} + +- (NSImage *)photoView:(MUPhotoView *)view photoAtIndex:(unsigned)photoIndex +{ + return nil; +} + +- (NSImage *)photoView:(MUPhotoView *)view fastPhotoAtIndex:(unsigned)photoIndex +{ + return [self photoView:view photoAtIndex:photoIndex]; +} + +- (NSString *)photoView:(MUPhotoView *)view titleForPhotoAtIndex:(unsigned)photoIndex +{ + return nil; +} + +// selection +- (NSIndexSet *)selectionIndexesForPhotoView:(MUPhotoView *)view +{ + return [NSIndexSet indexSet]; +} + +- (NSIndexSet *)photoView:(MUPhotoView *)view willSetSelectionIndexes:(NSIndexSet *)indexes +{ + return indexes; +} + +- (void)photoView:(MUPhotoView *)view didSetSelectionIndexes:(NSIndexSet *)indexes +{ +} + +// drag and drop +- (NSDragOperation)photoView:(MUPhotoView *)view draggingSourceOperationMaskForLocal:(BOOL)isLocal +{ + return NSDragOperationNone; +} + +- (NSArray *)pasteboardDragTypesForPhotoView:(MUPhotoView *)view +{ + return [[[NSArray alloc] init] autorelease]; +} + +- (NSData *)photoView:(MUPhotoView *)view pasteboardDataForPhotoAtIndex:(unsigned)photoIndex dataType:(NSString *)type +{ + return nil; +} + +// double-click +- (void)photoView:(MUPhotoView *)view doubleClickOnPhotoAtIndex:(unsigned)photoIndex withFrame:(NSRect)frame +{ + +} + +// photo removal support +- (NSIndexSet *)photoView:(MUPhotoView *)view willRemovePhotosAtIndexes:(NSIndexSet *)indexes +{ + return [NSIndexSet indexSet]; +} + +- (void)photoView:(MUPhotoView *)view didRemovePhotosAtIndexes:(NSIndexSet *)indexes +{ + +} + +- (NSString *)photoView:(MUPhotoView *)view tooltipForPhotoAtIndex:(unsigned)photoIndex +{ + return nil; +} + +- (NSDragOperation)photoView:(MUPhotoView *)view draggingEntered:(id )sender +{ + return NSDragOperationNone; +} + +- (void)photoView:(MUPhotoView *)view draggingExited:(id )sender +{ +} + +- (BOOL)photoView:(MUPhotoView *)view performDragOperation:(id )sender +{ + return NO; +} + +- (BOOL)photoView:(MUPhotoView *)view prepareForDragOperation:(id )sender +{ + return YES; +} + +- (void)photoView:(MUPhotoView *)view concludeDragOperation:(id )sender +{ + +} + +- (void)photoView:(MUPhotoView *)view fillPasteboardForDrag:(NSPasteboard *)pboard +{ + +} + +@end + +#pragma mark - +// Private +#pragma mark Private + +@implementation MUPhotoView (PrivateAPI) + +- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)theEvent +{ + NSPoint mouseEventLocation; + + mouseEventLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + unsigned clickedIndex = [self photoIndexForPoint:mouseEventLocation]; + NSRect photoRect = [self photoRectForIndex:clickedIndex]; + + return(NSPointInRect(mouseEventLocation, photoRect)); +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +{ + // CEsfahani - If acceptsFirstMouse unconditionally returns YES, then it is possible to lose the selection if + // the user clicks in the content of the window without hitting one of the selected images. This is + // the Finder's behavior, and it bothers me. + // It seems I have two options: unconditionally return YES, or only return YES if we clicked in an image. + // But, does anyone rely on losing the selection if I bring a window forward? + + NSPoint mouseEventLocation; + + mouseEventLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + unsigned clickedIndex = [self photoIndexForPoint:mouseEventLocation]; + NSRect photoRect = [self photoRectForIndex:clickedIndex]; + + return NSPointInRect(mouseEventLocation, photoRect); +} + +// This stops the stuttering of the movie view when changing the photo sizes from the slider +- (void)viewWillStartLiveResize +{ + if (nil == liveResizeSubviews) + { + // remove all subviews + liveResizeSubviews = [[NSArray arrayWithArray:[self subviews]] retain]; + NSEnumerator *e = [liveResizeSubviews objectEnumerator]; + NSView *cur; + + while (cur = [e nextObject]) + { + [cur removeFromSuperview]; + } + } +} + +- (void)viewDidEndLiveResize +{ + if (nil != liveResizeSubviews) + { + + NSEnumerator *e = [liveResizeSubviews objectEnumerator]; + NSView *cur; + + while (cur = [e nextObject]) + { + [self addSubview:cur]; + } + [liveResizeSubviews release]; + liveResizeSubviews = nil; + } + [self setNeedsDisplayInRect:[self visibleRect]]; +} + +- (void)setFrame:(NSRect)frame +{ + float width = [self frame].size.width; + [super setFrame:frame]; + + if (width != frame.size.width) { + // update internal grid size, adjust height based on the new grid size + [self setNeedsDisplayInRect:[self visibleRect]]; + } +} + +- (void)updateGridAndFrame +{ + /**** BEGIN Dimension calculations and adjustments ****/ + + // get the number of photos + unsigned photoCount = [self photoCount]; + + // calculate the base grid size + gridSize.height = [self photoSize] + [self photoVerticalSpacing]; + gridSize.width = [self photoSize] + [self photoHorizontalSpacing]; + + if ([self showCaptions]) + { + gridSize.height += [self sizeOfTitleWithCurrentAttributes:@"Example"].height; + } + + // if there are no photos, return + if (0 == photoCount) { + columns = 0; + rows = 0; + float width = [self frame].size.width; + float height = [[[self enclosingScrollView] contentView] frame].size.height; + [self setFrameSize:NSMakeSize(width, height)]; + return; + } + + // calculate the number of columns (ivar) + float width = [self frame].size.width; + columns = width / gridSize.width; + + // minimum 1 column + if (1 > columns) + columns = 1; + + // if we have fewer photos than columns, adjust downward + // This behaviour is incorrect - a single row will be evenly + // spaced instead of left justified. Comment out the + // conditional below to get the arguably correct behav. + if (photoCount < columns) + columns = photoCount; + + // adjust the grid size width for extra space + gridSize.width += (width - (columns * gridSize.width)) / columns; + + // calculate the number of rows of photos based on the total count and the number of columns (ivar) + rows = photoCount / columns; + if (0 < (photoCount % columns)) + rows++; + // adjust my frame height to contain all the photos + float height = rows * gridSize.height; + NSScrollView *scroll = [self enclosingScrollView]; + if ((nil != scroll) && (height < [[scroll contentView] frame].size.height)) + height = [[scroll contentView] frame].size.height; + + // set my new frame size + [self setFrameSize:NSMakeSize(width, height)]; + + /**** END Dimension calculations and adjustments ****/ + +} + +// will fetch from the internal array if not nil, from delegate otherwise +- (unsigned)photoCount +{ + if (nil != [self photosArray]) + return [[self photosArray] count]; + else if (nil != delegate) + return [delegate photoCountForPhotoView:self]; + else + return 0; +} + +- (NSImage *)photoAtIndex:(unsigned)photoIndex +{ + NSImage *result = nil; + if ((nil != [self photosArray]) && (photoIndex < [self photoCount])) + result = [[self photosArray] objectAtIndex:photoIndex]; + else if ((nil != delegate) && (photoIndex < [self photoCount])) + result = [delegate photoView:self photoAtIndex:photoIndex]; + +// commenting out. This is really slow, and maybe not needed. +// if (![result isValid]) +// result = nil; + /* + If the receiver is initialized with an existing image file, but the corresponding image data is not yet loaded into memory, this method loads the data and expands it as needed. If the receiver contains no image representations and no associated image file, this method creates a valid cached image representation and initializes it to the default bit depth. This method returns NO in cases where the file or URL from which it was initialized is nonexistent or when the data in an existing file is invalid. + */ + + return result; +} + +- (NSString *)titleAtIndex:(unsigned)photoIndex +{ + NSString *title = nil; + + if ([self showCaptions]) + { + title = [delegate photoView:self titleForPhotoAtIndex:photoIndex]; + } + return title; +} + +- (void)updatePhotoResizing +{ + NSTimeInterval timeSinceResize = [[NSDate date] timeIntervalSinceReferenceDate] - [photoResizeTime timeIntervalSinceReferenceDate]; + if (timeSinceResize > 1) { + isDonePhotoResizing = YES; + [photoResizeTimer invalidate]; + photoResizeTimer = nil; + } + [self viewDidEndLiveResize]; +} + +- (BOOL)inLiveResize +{ + return ([super inLiveResize]) || (mouseDown) || (!isDonePhotoResizing); +} + +- (NSImage *)fastPhotoAtIndex:(unsigned)photoIndex +{ + NSImage *fastPhoto = nil; + if ((nil != [self photosArray]) && (photoIndex < [[self photosArray] count])) + { + fastPhoto = [photosFastArray objectAtIndex:photoIndex]; + if ((NSNull *)fastPhoto == [NSNull null]) + { + // Change this if you want higher/lower quality fast photos + float fastPhotoSize = 100.0; + + NSImageRep *fullSizePhotoRep = [[self scalePhoto:[self photoAtIndex:photoIndex]] bestRepresentationForDevice:nil]; + + // Figure out what the scaled size is + float longSide = [fullSizePhotoRep pixelsWide]; + if (longSide < [fullSizePhotoRep pixelsHigh]) + longSide = [fullSizePhotoRep pixelsHigh]; + + float scale = fastPhotoSize / longSide; + + NSSize scaledSize; + scaledSize.width = [fullSizePhotoRep pixelsWide] * scale; + scaledSize.height = [fullSizePhotoRep pixelsHigh] * scale; + + // Draw the full-size image into our fast, small image. + fastPhoto = [[NSImage alloc] initWithSize:scaledSize]; + [fastPhoto setFlipped:YES]; + [fastPhoto lockFocus]; + [fullSizePhotoRep drawInRect:NSMakeRect(0.0, 0.0, scaledSize.width, scaledSize.height)]; + [fastPhoto unlockFocus]; + + // Save it off + [photosFastArray replaceObjectAtIndex:photoIndex withObject:fastPhoto]; + + [fastPhoto autorelease]; + } + } else if ((nil != delegate) && ([delegate respondsToSelector:@selector(photoView:fastPhotoAtIndex:)])) { + fastPhoto = [delegate photoView:self fastPhotoAtIndex:photoIndex]; + } + + // if the above calls failed, try to just fetch the full size image + if (0 == fastPhoto || ![fastPhoto isValid]) { + fastPhoto = [self photoAtIndex:photoIndex]; + } + + return fastPhoto; +} + + +// placement and hit detection +- (NSSize)scaledPhotoSizeForSize:(NSSize)size +{ + float longSide = size.width; + if (longSide < size.height) + longSide = size.height; + + float scale = [self photoSize] / longSide; + + NSSize scaledSize = size; + if (scale < 1.0) // do not enlarge (POSSIBLY MAKE THIS A PREFERENCE?) + { + scaledSize.width = size.width * scale; + scaledSize.height = size.height * scale; + } + return scaledSize; +} + +- (NSImage *)scalePhoto:(NSImage *)image +{ + // calculate the new image size based on the scale + NSSize newSize; + NSImageRep *bestRep = [image bestRepresentationForDevice:nil]; + newSize.width = [bestRep pixelsWide]; + newSize.height = [bestRep pixelsHigh]; + + // resize the image + [image setScalesWhenResized:YES]; + [image setSize:newSize]; + + return image; +} + +- (unsigned)photoIndexForPoint:(NSPoint)point +{ + unsigned column = point.x / gridSize.width; + unsigned row = point.y / gridSize.height; + + return ((row * columns) + column); +} + +- (NSRange)photoIndexRangeForRect:(NSRect)rect +{ + unsigned photoCount = [self photoCount]; + if (!photoCount) + return NSMakeRange(NSNotFound, 0); + + unsigned start = [self photoIndexForPoint:rect.origin]; + if (start >= photoCount) + return NSMakeRange(NSNotFound, 0); + + unsigned finish = [self photoIndexForPoint:NSMakePoint(NSMaxX(rect)-1, NSMaxY(rect)-1)]; + if (finish >= photoCount) + finish = photoCount - 1; + + return NSMakeRange(start, (finish + 1) - start); +} + +- (NSRect)gridRectForIndex:(unsigned)photoIndex +{ + if (columns == 0) return NSZeroRect; + + unsigned row = photoIndex / columns; + unsigned column = photoIndex % columns; + float x = column * gridSize.width; + float y = row * gridSize.height; + + return NSMakeRect(x, y, gridSize.width, gridSize.height); +} + +- (NSRect)rectCenteredInRect:(NSRect)rect withSize:(NSSize)size +{ + float x = NSMidX(rect) - (size.width / 2); + float y = NSMidY(rect) - (size.height / 2); + + return NSMakeRect(x, y, size.width, size.height); +} + +- (NSSize)sizeOfTitleWithCurrentAttributes:(NSString*)title +{ + if (!sTitleAttributes) + { // This could be improved by setting the color to something that works well with a non white background color. + sTitleAttributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont labelFontOfSize:[NSFont labelFontSize]], NSFontAttributeName, [NSColor darkGrayColor], NSForegroundColorAttributeName, nil]; + [sTitleAttributes retain]; + } + return [title sizeWithAttributes:sTitleAttributes]; +} + +- (void)getDrawingRectsAtIndex:(unsigned)photoIndex withPhoto:(NSImage *)cellPhoto withTitle:(NSString*)title outGridRect:(NSRect *)outGridRect outPhotoRect:(NSRect *)outPhotoRect outTitleRect:(NSRect *)outTitleRect +{ + NSRect titleRect = NSZeroRect; + NSRect photoRect = NSZeroRect; + NSRect gridRect = NSZeroRect; + + // Only bother if the requested index is within our range + if ((photoIndex + 1) <= [self photoCount]) + { + NSSize titleSize = NSZeroSize; + + gridRect = [self centerScanRect:[self gridRectForIndex:photoIndex]]; + + if (title) + { + titleSize = [self sizeOfTitleWithCurrentAttributes:title]; + NSDivideRect(gridRect, &titleRect, &gridRect, titleSize.height + 6.0f, NSMaxYEdge); + } + + NSSize scaledSize = [self scaledPhotoSizeForSize:[cellPhoto size]]; + photoRect = [self rectCenteredInRect:gridRect withSize:scaledSize]; + +// DCJ - I don't think this is necessary because the title rect is already considered when determining the photoRect +// Furthermore, doing this caused a nasty bug when either dimension is less than 12: it causes the dimension to +// inset to a NEGATIVE dimension :) +// if (!NSEqualSizes(NSZeroSize,titleSize)) +// { +// photoRect = NSInsetRect(photoRect,6,6); +// } + photoRect = [self centerScanRect:photoRect]; + } + + if (outGridRect != nil) *outGridRect = gridRect; + if (outPhotoRect != nil) *outPhotoRect = photoRect; + if (outTitleRect != nil) *outTitleRect = titleRect; +} + +- (NSRect)photoRectForIndex:(unsigned)photoIndex +{ + // get the actual image + NSImage *photo = [self photoAtIndex:photoIndex]; + if (nil == photo) + { + return NSZeroRect; + } + else + { + NSRect photoRect; + [self getDrawingRectsAtIndex:photoIndex withPhoto:photo withTitle:[self titleAtIndex:photoIndex] outGridRect:nil outPhotoRect:&photoRect outTitleRect:nil]; + return photoRect; + } +} + +// selection +- (BOOL)isPhotoSelectedAtIndex:(unsigned)photoIndex; +{ + if (0 < [dragSelectedPhotoIndexes count]) { + if ([dragSelectedPhotoIndexes containsIndex:photoIndex]) + return YES; + } else if ((nil != [self selectedPhotoIndexes]) && [[self selectedPhotoIndexes] containsIndex:photoIndex]) + return YES; + else if (nil != delegate) + return [[delegate selectionIndexesForPhotoView:self] containsIndex:photoIndex]; + + + return NO; +} + +- (NSIndexSet *)selectionIndexes +{ + if (nil != [self selectedPhotoIndexes]) + return [self selectedPhotoIndexes]; + else if (nil != delegate) + return [delegate selectionIndexesForPhotoView:self]; + else + return nil; +} + +- (void)setSelectionIndexes:(NSIndexSet *)indexes +{ + NSMutableIndexSet *oldSelection = nil; + + // Set the new selection, but save the old selection so we know exactly what to redraw + if (nil != [self selectedPhotoIndexes]) + { + oldSelection = [[self selectedPhotoIndexes] retain]; + [self setSelectedPhotoIndexes:indexes]; + } + else if (nil != delegate) + { + // We have to iterate through the photos to figure out which ones the delegate thinks are selected - that's the only way to know the old selection when in delegate mode + oldSelection = [[NSMutableIndexSet alloc] init]; + int i, count = [self photoCount]; + for( i = 0; i < count; i += 1 ) + { + if ([self isPhotoSelectedAtIndex:i]) + { + [oldSelection addIndex:i]; + } + } + + // Now update the selection + indexes = [delegate photoView:self willSetSelectionIndexes:indexes]; + [delegate photoView:self didSetSelectionIndexes:indexes]; + } + + [self dirtyDisplayRectsForNewSelection:indexes oldSelection:oldSelection]; + [oldSelection release]; +} + +- (NSBezierPath *)shadowBoxPathForRect:(NSRect)rect +{ + NSRect inset = NSInsetRect(rect,5.0,5.0); + float radius = 15.0; + + float minX = NSMinX(inset); + float midX = NSMidX(inset); + float maxX = NSMaxX(inset); + float minY = NSMinY(inset); + float midY = NSMidY(inset); + float maxY = NSMaxY(inset); + + NSBezierPath *path = [[NSBezierPath alloc] init]; + [path moveToPoint:NSMakePoint(midX, minY)]; + [path appendBezierPathWithArcFromPoint:NSMakePoint(maxX,minY) toPoint:NSMakePoint(maxX,midY) radius:radius]; + [path appendBezierPathWithArcFromPoint:NSMakePoint(maxX,maxY) toPoint:NSMakePoint(midX,maxY) radius:radius]; + [path appendBezierPathWithArcFromPoint:NSMakePoint(minX,maxY) toPoint:NSMakePoint(minX,midY) radius:radius]; + [path appendBezierPathWithArcFromPoint:NSMakePoint(minX,minY) toPoint:NSMakePoint(midX,minY) radius:radius]; + + return [path autorelease]; + +} + +// photo removal +- (void)removePhotosAtIndexes:(NSIndexSet *)indexes +{ + // let the delegate know that we're about to delete, give it a chance to modify the indexes we'll delete + NSIndexSet *modifiedIndexes = [[indexes copy] autorelease]; + if ((nil != delegate) && ([delegate respondsToSelector:@selector(photoView:willRemovePhotosAtIndexes:)])) { + modifiedIndexes = [delegate photoView:self willRemovePhotosAtIndexes:modifiedIndexes]; + } + + // if using bindings, do the removal + if ((0 < [modifiedIndexes count]) && (nil != [self photosArray])) { + [self willChangeValueForKey:@"photosArray"]; + [photosArray removeObjectsAtIndexes:modifiedIndexes]; + [self didChangeValueForKey:@"photosArray"]; + } + + if ((nil != delegate) && ([delegate respondsToSelector:@selector(photoView:didRemovePhotosAtIndexes:)])) { + [delegate photoView:self didRemovePhotosAtIndexes:modifiedIndexes]; + } + + // update the selection + NSMutableIndexSet *remaining = [[self selectionIndexes] mutableCopy]; + [remaining removeIndexes:modifiedIndexes]; + [self setSelectionIndexes:remaining]; + [remaining release]; +} + +- (NSImage *)scaleImage:(NSImage *)image toSize:(float)size +{ + NSImageRep *fullSizePhotoRep = [[self scalePhoto:image] bestRepresentationForDevice:nil]; + + float longSide = [fullSizePhotoRep pixelsWide]; + if (longSide < [fullSizePhotoRep pixelsHigh]) + longSide = [fullSizePhotoRep pixelsHigh]; + + float scale = size / longSide; + + NSSize scaledSize; + scaledSize.width = [fullSizePhotoRep pixelsWide] * scale; + scaledSize.height = [fullSizePhotoRep pixelsHigh] * scale; + + NSImage *fastPhoto = [[NSImage alloc] initWithSize:scaledSize]; + [fastPhoto setFlipped:YES]; + [fastPhoto lockFocus]; + [fullSizePhotoRep drawInRect:NSMakeRect(0.0, 0.0, scaledSize.width, scaledSize.height)]; + [fastPhoto unlockFocus]; + + return [fastPhoto autorelease]; +} + +- (void)dirtyDisplayRectsForNewSelection:(NSIndexSet *)newSelection oldSelection:(NSIndexSet *)oldSelection +{ + NSRect visibleRect = [self visibleRect]; + + // Figure out how the selection changed and only update those areas of the grid + NSMutableIndexSet *changedIndexes = [NSMutableIndexSet indexSet]; + if (oldSelection && newSelection) + { + // First, see which of the old are different than the new + unsigned int thisIndex = [newSelection firstIndex]; + + while (thisIndex != NSNotFound) + { + if (![oldSelection containsIndex:thisIndex]) + { + [changedIndexes addIndex:thisIndex]; + } + thisIndex = [newSelection indexGreaterThanIndex:thisIndex]; + } + + // Next, see which of the new are different from the old + thisIndex = [oldSelection firstIndex]; + while (thisIndex != NSNotFound) + { + if (![newSelection containsIndex:thisIndex]) + { + [changedIndexes addIndex:thisIndex]; + } + thisIndex = [oldSelection indexGreaterThanIndex:thisIndex]; + } + + // Loop through the changes and dirty the rect for each + thisIndex = [changedIndexes firstIndex]; + while (thisIndex != NSNotFound) + { + NSRect photoRect = [self gridRectForIndex:thisIndex]; + if (NSIntersectsRect(visibleRect, photoRect)) + { + [self setNeedsDisplayInRect:photoRect]; + } + thisIndex = [changedIndexes indexGreaterThanIndex:thisIndex]; + } + + } + else + { + [self setNeedsDisplayInRect:visibleRect]; + } + +} + +- (NSImage*) currentDisplayImageAtIndex:(unsigned)thisPhotoIndex allowsShadows:(BOOL *)allowShadows; +{ + NSImage *photo = nil; + if ([self inLiveResize]) { + photo = [self fastPhotoAtIndex:thisPhotoIndex]; + } + + if (nil == photo) { + photo = [self photoAtIndex:thisPhotoIndex]; + } + BOOL placeholder = NO; + + if (nil == photo) { + placeholder = YES; + photo = [[[NSImage alloc] initWithSize:NSMakeSize(photoSize,photoSize)] autorelease]; + + // Note: it would be nice to have an NSBezierPath category method like bezierPathWithRoundedRect:radius: + const float curve = MIN(photoSize * 0.3, 50); + const int width = 4; + const int margin = width / 2; + const int boxSize = photoSize - margin; + NSBezierPath *p = [NSBezierPath bezierPath]; + [p moveToPoint:NSMakePoint(curve, margin)]; + [p lineToPoint:NSMakePoint(boxSize - curve, margin)]; + [p curveToPoint:NSMakePoint(boxSize, curve) controlPoint1:NSMakePoint(boxSize, margin) controlPoint2:NSMakePoint(boxSize, margin)]; + [p lineToPoint:NSMakePoint(boxSize, boxSize - curve)]; + [p curveToPoint:NSMakePoint(boxSize - curve, boxSize) controlPoint1:NSMakePoint(boxSize,boxSize) controlPoint2:NSMakePoint(boxSize,boxSize)]; + [p lineToPoint:NSMakePoint(curve, boxSize)]; + [p curveToPoint:NSMakePoint(margin, boxSize - curve) controlPoint1:NSMakePoint(margin, boxSize) controlPoint2:NSMakePoint(margin, boxSize)]; + [p lineToPoint:NSMakePoint(margin, curve)]; + [p curveToPoint:NSMakePoint(curve, margin) controlPoint1:NSMakePoint(margin, margin) controlPoint2:NSMakePoint(margin, margin)]; + [p closePath]; + + [photo lockFocus]; + [[NSColor colorWithCalibratedWhite:0.95 alpha:1.0] set]; + [p setLineWidth:width]; + [p stroke]; + + [photo unlockFocus]; + } + + // Disable shadows for placeholder image + if (allowShadows != nil) *allowShadows = (placeholder == NO); + + return photo; +} + +@end + diff --git a/MUPhotoView.zip b/MUPhotoView.zip new file mode 100644 index 0000000..4a21e67 Binary files /dev/null and b/MUPhotoView.zip differ diff --git a/MediaContentController.h b/MediaContentController.h new file mode 100644 index 0000000..078e6b1 --- /dev/null +++ b/MediaContentController.h @@ -0,0 +1,107 @@ +// +// MediaContentController.h +// SproutedInterface +// +// Created by Philip Dow on 6/11/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +#define PDMediabarItemShowInFinder @"PDMediabarItemShowInFinder" +#define PDMediabarItemOpenWithFinder @"PDMediabarItemOpenWithFinder" +#define PDMediaBarItemGetInfo @"PDMediaBarItemGetInfo" + +@class PDMediaBar; +@class PDMediabarItem; +@class JournlerGradientView; + +@interface MediaContentController : NSObject { + + IBOutlet NSView *contentView; + IBOutlet PDMediaBar *bar; + + id delegate; + NSURL *URL; + + id representedObject; + NSString *searchString; + + NSDictionary *fileError; + NSManagedObjectContext *managedObjectContext; +} + +- (id) initWithOwner:(id)anObject managedObjectContext:(NSManagedObjectContext*)moc; + +- (NSView*) contentView; +- (NSUndoManager*) undoManager; + +- (id) delegate; +- (void) setDelegate:(id)anObject; + +- (NSManagedObjectContext*) managedObjectContext; +- (void) setManagedObjectContext:(NSManagedObjectContext*)moc; + +- (id) representedObject; +- (void) setRepresentedObject:(id)anObject; + +- (NSURL*) URL; +- (void) setURL:(NSURL*)aURL; + +- (NSString*) searchString; +- (void) setSearchString:(NSString*)aString; + +- (BOOL) loadURL:(NSURL*)aURL; +- (BOOL) highlightString:(NSString*)aString; + +- (IBAction) printSelection:(id)sender; +- (IBAction) exportSelection:(id)sender; + +- (IBAction) getInfo:(id)sender; +- (IBAction) showInFinder:(id)sender; +- (IBAction) openInFinder:(id)sender; + +- (void) prepareTitleBar; +- (void) setWindowTitleFromURL:(NSURL*)aURL; + +- (NSResponder*) preferredResponder; +- (void) appropriateFirstResponder:(NSWindow*)window; +- (void) appropriateAlternateResponder:(NSWindow*)aWindow; +- (void) establishKeyViews:(NSView*)previous nextKeyView:(NSView*)next; + +- (BOOL) commitEditing; +- (void) ownerWillClose:(NSNotification*)aNotification; + +- (void) updateContent; +- (void) stopContent; + +- (BOOL) handlesFindCommand; +- (void) performCustomFindPanelAction:(id)sender; + +- (BOOL) handlesTextSizeCommand; +- (void) performCustomTextSizeAction:(id)sender; + +- (BOOL) canAnnotateDocumentSelection; +- (IBAction) annotateDocumentSelection:(id)sender; + +- (BOOL) canHighlightDocumentSelection; +- (IBAction) highlightDocumentSelection:(id)sender; + +- (BOOL)validateMenuItem:(NSMenuItem*)menuItem; + +@end + +@interface NSObject (MediaContentDelegate) + +- (void) contentController:(MediaContentController*)controller changedTitle:(NSString*)title; +- (void) contentController:(MediaContentController*)aController showLexiconSelection:(id)anObject term:(NSString*)aTerm; + +@end + +@interface NSView (MediaContentAdditions) + +- (void) setImage:(NSImage*)anImage; + +@end diff --git a/MediaContentController.m b/MediaContentController.m new file mode 100644 index 0000000..4501f77 --- /dev/null +++ b/MediaContentController.m @@ -0,0 +1,658 @@ +// +// MediaContentController.m +// SproutedInterface +// +// Created by Philip Dow on 6/11/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +#import +#import + +#import + +#import +#import + +@implementation MediaContentController + +- (id) init +{ + return [self initWithOwner:nil managedObjectContext:nil]; +} + +- (id) initWithOwner:(id)anObject managedObjectContext:(NSManagedObjectContext*)moc +{ + if ( self = [super init] ) + { + URL = [[NSURL alloc] initWithString:@"http://journler.com"]; + + [self setDelegate:anObject]; + [self setManagedObjectContext:moc]; + + #ifdef __DEBUG__ + NSLog(@"%@ %s", [self className], _cmd); + #endif + } + return self; +} + +- (void) awakeFromNib +{ + // sublcasses should call super's implementation + [self prepareTitleBar]; +} + +- (void) dealloc +{ + //#ifdef __DEBUG__ + NSLog(@"%@ %s", [self className], _cmd); + //#endif + + // top level nib objects + [contentView release]; + + // local variables + [URL release]; + [searchString release]; + [representedObject release]; + [managedObjectContext release]; + + [super dealloc]; +} + +#pragma mark - + +- (NSView*) contentView +{ + return contentView; +} + +- (NSUndoManager*) undoManager +{ + return [[[self contentView] window] undoManager]; +} + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate:(id)anObject +{ + delegate = anObject; +} + +- (NSManagedObjectContext*) managedObjectContext +{ + return managedObjectContext; +} + +- (void) setManagedObjectContext:(NSManagedObjectContext*)moc +{ + if ( managedObjectContext != moc ) + { + [managedObjectContext release]; + managedObjectContext = [moc retain]; + } +} + +- (id) representedObject +{ + return representedObject; +} + +- (void) setRepresentedObject:(id)anObject +{ + if ( representedObject != anObject ) + { + [representedObject release]; + representedObject = [anObject retain]; + } +} + +- (NSURL*) URL +{ + return URL; +} + +- (void) setURL:(NSURL*)aURL +{ + if ( URL != aURL ) + { + [URL release]; + URL = [aURL copyWithZone:[self zone]]; + } +} + +- (NSString*) searchString +{ + return searchString; +} + +- (void) setSearchString:(NSString*)aString +{ + if ( searchString != aString ) + { + [searchString release]; + searchString = [aString retain]; + } +} + +#pragma mark - + +- (BOOL) loadURL:(NSURL*)aURL +{ + // overridden by subclasses to load the given url + // subclasses must set the url, or subclasses should call super's implementation + + [self setURL:aURL]; + [self setupMediabar:bar url:aURL]; + [self setWindowTitleFromURL:aURL]; + + return YES; +} + +- (BOOL) highlightString:(NSString*)aString +{ + if ( aString == nil || [aString length] == 0 ) + return NO; + + // overridden by subclasses to highlight a string in their view + NSPasteboard *findBoard = [NSPasteboard pasteboardWithName:NSFindPboard]; + [findBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; + [findBoard setString:aString forType:NSStringPboardType]; + + return NO; +} + +- (IBAction) printSelection:(id)sender +{ + // overridden by subclasses to handle document type specific printing + return; +} + +- (IBAction) exportSelection:(id)sender +{ + // subclasses should override if the url does not point to a file or if they want special behavior + if ( [[self URL] isFileURL] ) + { + NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setRequiredFileType:nil]; + [savePanel setCanSelectHiddenExtension:YES]; + + //[openPanel setMessage:NSLocalizedString(@"export resources panel text",@"")]; + //[openPanel setTitle:NSLocalizedString(@"export resources panel title",@"")]; + //[openPanel setPrompt:NSLocalizedString(@"export resources panel prompt",@"")]; + + if ( [savePanel runModalForDirectory:nil file:[[[self URL] path] lastPathComponent]] == NSOKButton ) + { + NSString *filename = [savePanel filename]; + if ( ![[NSFileManager defaultManager] copyPath:[[self URL] path] toPath:filename handler:self] ) + { + NSString *errorTitle = NSLocalizedString(@"file manager error title",@""); + NSString *errorMessage = [NSString stringWithFormat:NSLocalizedString(@"file manager error msg",@""), + [fileError objectForKey:@"Error"], [fileError objectForKey:@"Path"]]; + + NSBeep(); + NSRunAlertPanel(errorTitle, errorMessage, nil, nil, nil); + + [fileError release]; + fileError = nil; + } + else + { + NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:[savePanel isExtensionHidden]] forKey:NSFileExtensionHidden]; + [[NSFileManager defaultManager] changeFileAttributes:fileAttributes atPath:[savePanel filename]]; + } + } + } + else + { + NSBeep(); + return; + } +} + +#pragma mark - + +- (IBAction) getInfo:(id)sender +{ + /* + if ( [[self representedObject] isKindOfClass:[JournlerResource class]] ) + [self showInfoForResource:[self representedObject]]; + else + NSBeep(); + */ +} + +- (IBAction) showInFinder:(id)sender +{ + // subclasses may override to provide more specific behavior + if ( [[self URL] isFileURL] ) + { + [[NSWorkspace sharedWorkspace] selectFile:[[self URL] path] + inFileViewerRootedAtPath:[[[self URL] path] stringByDeletingLastPathComponent]]; + } + else + [self openInFinder:sender]; +} + +- (IBAction) openInFinder:(id)sender +{ + // subclasses may override to provide more specific behavior + [[NSWorkspace sharedWorkspace] openURL:[self URL]]; +} + +#pragma mark - + +- (void) prepareTitleBar +{ + // subclasses may override to provide special behavior + int whichBorders[4] = {1,0,1,0}; + [bar setBordered:YES]; + [bar setBorderColor:[NSColor colorWithCalibratedRed:155.0/255.0 green:155.0/255.0 blue:155.0/255.0 alpha:1.0]]; + [bar setBorders:whichBorders]; +} + +- (void) setWindowTitleFromURL:(NSURL*)aURL +{ + // subclasses may override to provide custom behavior + if ( delegate && [delegate respondsToSelector:@selector(contentController:changedTitle:)] ) + { + if ( [aURL isFileURL] ) + [delegate contentController:self changedTitle:[[[aURL path] lastPathComponent] stringByDeletingPathExtension]]; + else + [delegate contentController:self changedTitle:[aURL absoluteString]]; + } +} + +- (NSResponder*) preferredResponder +{ + // meant to be overridden by subclasses + NSLog(@"%@ %s - **** subclasses must override ****", [self className], _cmd); + return nil; +} + +- (void) appropriateFirstResponder:(NSWindow*)window +{ + // meant to be overridden by subclasses + return; +} + +- (void) appropriateAlternateResponder:(NSWindow*)aWindow +{ + // meant to be overridden by subclasses + return; +} + +- (void) establishKeyViews:(NSView*)previous nextKeyView:(NSView*)next +{ + // meant to be overridden by subclasses + // establish a connection between the two provided views if you don't need them + [previous setNextKeyView:next]; + NSLog(@"%@ %s - **** subclasses must override ****", [self className], _cmd); + return; +} + +#pragma mark - +#pragma mark Find Panel + +- (BOOL) handlesFindCommand +{ + // subclasses should override and return yes if they handle the find panel in their own way, including windowing + return NO; +} + +- (void) performCustomFindPanelAction:(id)sender +{ + // subclasses should override and return perform the handling in their own specific way + return; +} + +#pragma mark Text Sizing + +- (BOOL) handlesTextSizeCommand +{ + // subclasses should override and return yes if the handle the cmd +/- menu items (bigger/smaller) + return NO; +} + +- (void) performCustomTextSizeAction:(id)sender +{ + // subclasses should override and return perform the handling in their own specific way + return; +} + +#pragma mark Annotation + +- (BOOL) canAnnotateDocumentSelection +{ + //NSLog(@"%@ %s - **** subclasses must override ****",[self className],_cmd); + return NO; +} + +- (IBAction) annotateDocumentSelection:(id)sender +{ + NSLog(@"%@ %s - **** subclasses must override ****",[self className],_cmd); + return; +} + +#pragma mark Highlight + +- (BOOL) canHighlightDocumentSelection +{ + return NO; +} + +- (IBAction) highlightDocumentSelection:(id)sender +{ + NSLog(@"%@ %s - **** subclasses must override ****",[self className],_cmd); + return; +} + +#pragma mark - + +- (BOOL)validateMenuItem:(NSMenuItem*)menuItem +{ + // subclasses should override and validate/invalidate their own way (specifically for the custom find panel action and +/- actions) + return YES; +} + +#pragma mark - + +- (BOOL) commitEditing +{ + NSLog(@"%@ %s - **** Subclasses should override ****",[self className],_cmd); + return YES; + // subclsses should override and commit editing on the document in their own particular way +} + +- (void) ownerWillClose:(NSNotification*)aNotification +{ + // subclasses should override to unhook bindings, etc. aNotification is currently nil + [self stopContent]; + [contentView removeFromSuperview]; +} + +- (void) updateContent +{ + //meant to be overriddden by subclasses + return; +} + +- (void) stopContent +{ + //meant to be overridden by subclasses + return; +} + +#pragma mark - + +- (BOOL)fileManager:(NSFileManager *)manager shouldProceedAfterError:(NSDictionary *)errorInfo +{ + NSLog(@"%@ %s - file error: %@", [self className], _cmd, errorInfo); + fileError = [errorInfo retain]; + return NO; +} + +- (void)fileManager:(NSFileManager *)manager willProcessPath:(NSString *)path +{ + return; +} + +#pragma mark - +#pragma mark MediaBar Implementation + +- (void) setupMediabar:(PDMediaBar*)aMediabar url:(NSURL*)aURL +{ + if ( ![self canCustomizeMediabar:aMediabar] ) + return; + else + { + [aMediabar setDelegate:self]; + [aMediabar loadItems]; + [aMediabar displayItems]; + } +} + +- (NSImage*) defaultOpenWithFinderImage:(PDMediaBar*)aMediabar +{ + // subclasses should override to provide a default "open with finder" icon + return nil; +} + +- (float) mediabarMinimumWidthForUnmanagedControls:(PDMediaBar*)aMediabar +{ + // subclasses should override to provide the minimum width needed for default controls that aren't managed by the media bar + return 0; +} + +#pragma mark - +#pragma mark Medibar + +- (BOOL) canCustomizeMediabar:(PDMediaBar*)aMediabar +{ + // subclasses should override and return YES if they allow media bar customization + return NO; +} + +- (NSString*) mediabarIdentifier:(PDMediaBar*)aMediabar +{ + // subclasses may override to provide a different classname + return [self className]; +} + + +#pragma mark - + + +- (IBAction) perfomCustomMediabarItemAction:(PDMediabarItem*)anItem +{ + BOOL success = NO; + static NSString *perform_action_handler = @"perform_action"; + + // subclasses may override although it isn't necessary + + if ( [[anItem typeIdentifier] intValue] == kMenubarItemURI ) + { + // throw the uri at the workspace + NSURL *applicationURI = [anItem targetURI]; + NSURL *fileURI = [self URL]; + + if ( [applicationURI isFileURL] && [fileURI isFileURL] ) + { + success = [[NSWorkspace sharedWorkspace] openFile:[fileURI path] withApplication:[applicationURI path]]; + } + else + { + NSLog(@"%@ %s - curretly, only file based urls are supported %@", [self className], _cmd, [applicationURI absoluteString]); + success = NO; + } + } + + else if ( [[anItem typeIdentifier] intValue] == kMenubarItemAppleScript ) + { + NSDictionary *errorDictionary; + NSString *scriptSource = [[anItem targetScript] string]; + + NSString *resourceURI = [[self URL] absoluteString]; + id theRepresentedObject = (id)[NSNull null]; + //id theRepresentedObject = ( [self representedObject] != nil ? [[self representedObject] aeDescriptorValue] : (id)[NSNull null] ); + //id theRepresentedObject = ( [self representedObject] != nil && [[self representedObject] respondsToSelector:@selector(objectSpecifier)] + //? [[self representedObject] objectSpecifier] : (id)[NSNull null] ); + + NSAppleScript *script = [[[NSAppleScript alloc] initWithSource:scriptSource] autorelease]; + if ( script == nil ) + { + success = NO; + NSLog(@"%@ %s - unable to initalize script with source %@", [self className], _cmd, scriptSource); + + NSBeep(); + AppleScriptAlert *scriptAlert = [[[AppleScriptAlert alloc] initWithSource:[anItem targetScript] error:[NSString string]] autorelease]; + + NSBeep(); + [scriptAlert showWindow:self]; + goto bail; + } + + if ( [script compileAndReturnError:&errorDictionary] == NO ) + { + success = NO; + NSLog(@"%@ %s - unable to compile the script %@, error: %@", [self className], _cmd, scriptSource, errorDictionary); + AppleScriptAlert *scriptAlert = [[[AppleScriptAlert alloc] initWithSource:[anItem targetScript] error:errorDictionary] autorelease]; + + NSBeep(); + [scriptAlert showWindow:self]; + goto bail; + } + + if ( ![script executeHandler:perform_action_handler error:&errorDictionary withParameters: resourceURI, theRepresentedObject, nil] ) + { + success = NO; + NSLog(@"%@ %s - unable to execute handler of script %@, error: %@", [self className], _cmd, scriptSource, errorDictionary); + + id theSource = [script richTextSource]; + if ( theSource == nil ) theSource = [script source]; + AppleScriptAlert *scriptAlert = [[[AppleScriptAlert alloc] initWithSource:theSource error:errorDictionary] autorelease]; + + NSBeep(); + [scriptAlert showWindow:self]; + goto bail; + } + + // we made it through! + success = YES; + } + +bail: + + return; +} + +#pragma mark - + +- (PDMediabarItem*) mediabar:(PDMediaBar *)mediabar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoMediabar:(BOOL)flag +{ + // subclasses should override to build the media bar + // call super to get some default support for the get info, show in finder and open with finder items + + NSURL *aURL = [self URL]; + PDMediabarItem *anItem = [[[PDMediabarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease]; + + NSBundle *myBundle = [NSBundle bundleWithIdentifier:@"com.sprouted.interface"]; + + if ( [itemIdentifier isEqualToString:PDMediaBarItemGetInfo] ) + { + [anItem setTitle:NSLocalizedStringFromTableInBundle(@"get info title", @"Mediabar", myBundle, @"")]; + [anItem setTag:kMediaBarGetInfo]; + [anItem setTypeIdentifier:[NSNumber numberWithInt:kMenubarItemDefault]]; + [anItem setToolTip:NSLocalizedStringFromTableInBundle(@"get info tip",@"Mediabar",myBundle,@"")]; + + NSImage *theImage = [[[NSImage alloc] initWithContentsOfFile:[myBundle pathForImageResource:@"InfoBarSmall.png"]] autorelease]; + [anItem setImage:theImage]; + + [anItem setTarget:self]; + [anItem setAction:@selector(getInfo:)]; + } + + else if ( [itemIdentifier isEqualToString:PDMediabarItemShowInFinder] ) + { + [anItem setTitle:NSLocalizedStringFromTableInBundle(@"reveal in finder title", @"Mediabar", myBundle, @"")]; + [anItem setTag:kMediaBarShowInFinder]; + [anItem setTypeIdentifier:[NSNumber numberWithInt:kMenubarItemDefault]]; + [anItem setToolTip:NSLocalizedStringFromTableInBundle(@"reveal in finder tip",@"Mediabar",myBundle,@"")]; + + NSImage *theImage = [[[NSImage alloc] initWithContentsOfFile:[myBundle pathForImageResource:@"RevealInFinderBarSmall.png"]] autorelease]; + [anItem setImage:theImage]; + + [anItem setTarget:self]; + [anItem setAction:@selector(showInFinder:)]; + } + + else if ( [itemIdentifier isEqualToString:PDMediabarItemOpenWithFinder] ) + { + [anItem setTitle:NSLocalizedStringFromTableInBundle(@"open in finder title", @"Mediabar", myBundle, @"")]; + [anItem setTag:kMediaBarOpenWithFinder]; + [anItem setTypeIdentifier:[NSNumber numberWithInt:kMenubarItemDefault]]; + [anItem setToolTip:NSLocalizedStringFromTableInBundle(@"open in finder tip",@"Mediabar",myBundle,@"")]; + + [anItem setTarget:self]; + [anItem setAction:@selector(openInFinder:)]; + + if ( [aURL isFileURL] ) + { + NSImage *imageIcon; + NSString *appName, *fileType, *appPath; + NSString *filename = [aURL path]; + + if ( ![[NSWorkspace sharedWorkspace] getInfoForFile:filename application:&appName type:&fileType] || appName == nil ) + { + NSLog(@"%@ %s - unable to get workspace information for file at path %@", [self className], _cmd, filename); + // use the default image + [anItem setImage:[self defaultOpenWithFinderImage:mediabar]]; + } + else + { + appPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:appName]; + + if ( appPath == nil ) + { + NSLog(@"%@ %s - unable to get application path for file at path %@", [self className], _cmd, filename); + // use the default image + [anItem setImage:[self defaultOpenWithFinderImage:mediabar]]; + } + else + { + #ifdef __DEBUG__ + NSLog(appPath); + #endif + + imageIcon = [[NSWorkspace sharedWorkspace] iconForFile:appPath]; + + // use a more indicative tooltip + NSString *appDisplayName = [[NSFileManager defaultManager] displayNameAtPath:appName]; + [anItem setToolTip:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"open with tip", @"Mediabar", myBundle, @""), appDisplayName]]; + + if ( imageIcon == nil ) + { + NSLog(@"%@ %s - unable to get icon path for application at path %@", [self className], _cmd, appPath); + // use the default image + [anItem setImage:[self defaultOpenWithFinderImage:mediabar]]; + } + else + { + [anItem setImage:[imageIcon imageWithWidth:24 height:24]]; + } + } + } + } + else + { + // use the default image + [anItem setImage:[self defaultOpenWithFinderImage:mediabar]]; + } + } + + else + { + // return nil - the setup method will handle custom attributes + anItem = nil; + } + + return anItem; +} + +- (NSArray*) mediabarDefaultItemIdentifiers:(PDMediaBar *)mediabar +{ + // subclasses should override to provide an array of default items + return nil; +} + +@end \ No newline at end of file diff --git a/MediaViewer.h b/MediaViewer.h new file mode 100644 index 0000000..f4a69f4 --- /dev/null +++ b/MediaViewer.h @@ -0,0 +1,52 @@ +// +// MediaViewer.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class MediaContentController; + +/* +@class AudioViewController; +@class MovieViewController; +@class WebViewController; +@class PDFViewController; +@class ImageViewController; +@class AddressRecordController; +*/ + +@interface MediaViewer : NSWindowController +{ + NSURL *homeURL; + MediaContentController *_contentController; + IBOutlet NSView *contentPlaceholder; +} + ++ (BOOL) canDisplayMediaOfType:(NSString*)uti url:(NSURL*)aURL; +- (id) initWithURL:(NSURL*)url uti:(NSString*)uti; + +- (id) representedObject; +- (void) setRepresentedObject:(id)anObject; + +- (MediaContentController*) contentController; + +- (BOOL) highlightString:(NSString*)aString; + +- (IBAction) printDocument:(id)sender; + +- (IBAction) save:(id)sender; +- (IBAction) exportSelection:(id)sender; + +@end + +@interface MediaViewer (CustomMenuSupport) + +- (void) performCustomFindPanelAction:(id)sender; +- (void) performCustomTextSizeAction:(id)sender; + +@end diff --git a/MediaViewer.m b/MediaViewer.m new file mode 100644 index 0000000..e6ab41a --- /dev/null +++ b/MediaViewer.m @@ -0,0 +1,305 @@ +// +// MediaViewer.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import MediaViewer.h> + +/* +#import "NSWorkspace_PDCategories.h" +#import "MediaContentController.h" + +#import "AudioViewController.h" +#import "MovieViewController.h" +#import "WebViewController.h" +#import "PDFViewController.h" +#import "ImageViewController.h" +#import "AddressRecordController.h" +#import "MailMessageController.h" +#import "WordDocumentController.h" +#import "TextDocumentController.h" +*/ + +//#import "JournlerResource.h" + +@implementation MediaViewer + ++ (BOOL) canDisplayMediaOfType:(NSString*)uti url:(NSURL*)aURL +{ + BOOL can_display = NO; + /* + if ( uti == nil ) + return NO; + + NSString *path = nil; + if ( [aURL isFileURL] ) + path = [aURL path]; + + NSString *mime_type = (NSString*)UTTypeCopyPreferredTagWithClass((CFStringRef)uti,kUTTagClassMIMEType); + + if ( UTTypeConformsTo((CFStringRef)uti,kUTTypeWebArchive) || UTTypeConformsTo( (CFStringRef)uti, kUTTypeHTML) ) + can_display = YES; + else if ( UTTypeConformsTo((CFStringRef)uti,kUTTypePDF) ) + can_display = YES; + else if ( UTTypeConformsTo((CFStringRef)uti,kUTTypeImage) ) + can_display = YES; + else if ( [[NSWorkspace sharedWorkspace] canPlayFile:path] ) + can_display = YES; + else if ( [[NSWorkspace sharedWorkspace] canWatchFile:path] ) + can_display = YES; + + else if ( UTTypeConformsTo((CFStringRef)uti,kUTTypeURL) || UTTypeConformsTo((CFStringRef)uti,(CFStringRef)LexUTTypeURL) ) + can_display = YES; + else if ( UTTypeConformsTo((CFStringRef)uti,(CFStringRef)LexUTTypeABPerson) ) + can_display = YES; + else if ( UTTypeConformsTo((CFStringRef)uti,(CFStringRef)LexUTTypeMailEmail) ) + can_display = YES; + + else if ( UTTypeConformsTo( (CFStringRef)uti, (CFStringRef)@"com.microsoft.word.doc") ) + can_display = YES; + else if ( mime_type != nil && [WebView canShowMIMEType:mime_type] ) + can_display = YES; + else if ( UTTypeConformsTo( (CFStringRef)uti, kUTTypeRTF ) || UTTypeConformsTo( (CFStringRef)uti, kUTTypeRTFD ) || UTTypeConformsTo( (CFStringRef)uti, kUTTypePlainText ) ) + can_display = YES; + else + can_display = NO; + + if ( mime_type != nil ) + [mime_type release]; + */ + return can_display; +} + +#pragma mark - + +- (id) initWithURL:(NSURL*)url uti:(NSString*)uti +{ + _contentController = nil; + Class controllerClass = nil; + + /* + // first determine if we can display the media + if ( [url isFileURL] ) + { + if ( UTTypeConformsTo((CFStringRef)uti,kUTTypeWebArchive) || UTTypeConformsTo( (CFStringRef)uti, kUTTypeHTML) ) + controllerClass = [WebViewController class]; + + else if ( UTTypeConformsTo((CFStringRef)uti,(CFStringRef)kUTTypePDF) ) + controllerClass = [PDFViewController class]; + + else if ( UTTypeConformsTo((CFStringRef)uti,(CFStringRef)kUTTypeImage) ) + controllerClass = [ImageViewController class]; + + else if ( UTTypeConformsTo((CFStringRef)uti,(CFStringRef)kUTTypeAudio) ) + controllerClass = [AudioViewController class]; + + else if ( UTTypeConformsTo((CFStringRef)uti,(CFStringRef)@"public.movie") ) + controllerClass = [MovieViewController class]; + + else if ( UTTypeConformsTo((CFStringRef)uti,(CFStringRef)LexUTTypeMailEmail) ) + controllerClass = [MailMessageController class]; + + else if ( UTTypeConformsTo( (CFStringRef)uti, (CFStringRef)@"com.microsoft.word.doc") ) + controllerClass = [WordDocumentController class]; + + else if ( UTTypeConformsTo( (CFStringRef)uti, kUTTypeRTF ) || UTTypeConformsTo( (CFStringRef)uti, kUTTypeRTFD ) || UTTypeConformsTo( (CFStringRef)uti, kUTTypePlainText ) ) + controllerClass = [TextDocumentController class]; + + else if ( uti != nil ) + { + // check the mime type + NSString *mime_type = (NSString*)UTTypeCopyPreferredTagWithClass((CFStringRef)uti,kUTTagClassMIMEType); + if ( mime_type != nil && [WebView canShowMIMEType:mime_type] ) + controllerClass = [WebViewController class]; + } + } + + + else + { + if ( UTTypeConformsTo((CFStringRef)uti,kUTTypeURL) || UTTypeConformsTo((CFStringRef)uti,(CFStringRef)LexUTTypeURL) ) + controllerClass = [WebViewController class]; + else if ( UTTypeConformsTo((CFStringRef)uti,(CFStringRef)LexUTTypeABPerson) ) + controllerClass = [AddressRecordController class]; + } + */ + + if ( controllerClass == nil ) + { + [self release]; + return nil; + } + + else if ( self = [self initWithWindowNibName:@"MediaViewer"] ) + { + // take control of ourselves + //[self setShouldCascadeWindows:NO]; + + homeURL = [url retain]; + _contentController = [[controllerClass alloc] init]; + [self retain]; + } + + return self; +} + + +- (void) windowDidLoad +{ + [[self window] setFrameUsingName:@"Media Viewer Window" force:YES]; + [[self window] setBackgroundColor:[NSColor whiteColor]]; + + [[_contentController contentView] setFrame:[contentPlaceholder frame]]; + [[[self window] contentView] replaceSubview:contentPlaceholder with:[_contentController contentView]]; + + //[[self window] setContentView:[_contentController contentView]]; + + [_contentController setDelegate:self]; + [_contentController loadURL:homeURL]; + +} + +- (void) dealloc +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s", [self className], _cmd); + #endif + + [_contentController ownerWillClose:nil]; + [_contentController release]; + [homeURL release]; + + [super dealloc]; +} + +- (void)windowWillClose:(NSNotification *)aNotification +{ + [self autorelease]; +} + +#pragma mark - + +- (id) representedObject +{ + return [_contentController representedObject]; +} + +- (void) setRepresentedObject:(id)anObject +{ + [_contentController setRepresentedObject:anObject]; +} + +#pragma mark - + +- (MediaContentController*) contentController +{ + return _contentController; +} + +#pragma mark - + +- (void) contentController:(MediaContentController*)controller changedTitle:(NSString*)title +{ + if ( controller != _contentController ) + return; + + if ( title != nil ) + [[self window] setTitle:title]; +} + +#pragma mark - + +- (BOOL) highlightString:(NSString*)aString +{ + // pass it on to the controller + return [_contentController highlightString:aString]; +} + +- (IBAction) printDocument:(id)sender +{ + // pass it on to the controller + [_contentController printDocument:sender]; +} + +- (IBAction) save:(id)sender +{ + NSBeep(); + return; +} + +- (IBAction) exportSelection:(id)sender +{ + // pass it on to the controller + [_contentController exportSelection:sender]; +} + +- (IBAction) doPageSetup:(id) sender +{ + NSPageLayout *pageLayout = [NSPageLayout pageLayout]; + //[pageLayout setAccessoryView:[[PageSetupController sharedPageSetup] contentView]]; + + if ( [[self window] isMainWindow] ) + [pageLayout beginSheetWithPrintInfo:[NSPrintInfo sharedPrintInfo] modalForWindow:[self window] + delegate:nil didEndSelector:nil contextInfo:nil]; + else + [pageLayout runModalWithPrintInfo:[NSPrintInfo sharedPrintInfo]]; + +} + +- (BOOL)validateMenuItem:(NSMenuItem*)menuItem +{ + BOOL enabled = YES; + SEL action = [menuItem action]; + + if ( action == @selector(printDocument:) ) + enabled = YES; + + else if ( action == @selector(doPageSetup:) ) + enabled = YES; + + else if ( action == @selector(exportSelection:) ) + enabled = YES; + + else if ( action == @selector(save:) ) + enabled = NO; + + else if ( action == @selector(performCustomFindPanelAction:) ) + enabled = [_contentController handlesFindCommand]; + + else if ( action == @selector(performCustomTextSizeAction:) ) + enabled = [_contentController handlesTextSizeCommand]; + + return enabled; +} + +@end + +@implementation MediaViewer (CustomMenuSupport) + +- (void)performCustomFindPanelAction:(id)sender +{ + // the first responder should only make it this far under special circumstances + if ( [_contentController handlesFindCommand] && [_contentController respondsToSelector:@selector(performCustomFindPanelAction:)] ) + [_contentController performSelector:@selector(performCustomFindPanelAction:) withObject:sender]; + else + { + NSBeep(); + } +} + +- (void) performCustomTextSizeAction:(id)sender +{ + // the first responder should only make it this far under special circumstances + if ( [_contentController handlesTextSizeCommand] && [_contentController respondsToSelector:@selector(performCustomTextSizeAction:)] ) + [_contentController performSelector:@selector(performCustomTextSizeAction:) withObject:sender]; + else + { + NSBeep(); + } +} + +@end \ No newline at end of file diff --git a/MediabarItemApplicationPicker.h b/MediabarItemApplicationPicker.h new file mode 100644 index 0000000..d37fe57 --- /dev/null +++ b/MediabarItemApplicationPicker.h @@ -0,0 +1,32 @@ +// +// MediabarItemApplicationPicker.h +// SproutedInterface +// +// Created by Philip Dow on 2/22/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@interface MediabarItemApplicationPicker : JournlerGradientView { + + id delegate; + NSString *filename; +} + +- (id) delegate; +- (void) setDelegate:(id)anObject; + +- (NSString*) filename; +- (void) setFilename:(NSString*)aString; + +@end + +@interface NSObject (MediabarItemApplicationPickerDelegate) + +// return yes if the drop is accepted - do something with it! +- (BOOL) mediabarItemApplicationPicker:(MediabarItemApplicationPicker*)appPicker shouldAcceptDrop:(NSString*)aFilename; + +@end \ No newline at end of file diff --git a/MediabarItemApplicationPicker.m b/MediabarItemApplicationPicker.m new file mode 100644 index 0000000..2e0a7cc --- /dev/null +++ b/MediabarItemApplicationPicker.m @@ -0,0 +1,132 @@ +// +// MediabarItemApplicationPicker.m +// SproutedInterface +// +// Created by Philip Dow on 2/22/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation MediabarItemApplicationPicker + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; + } + return self; +} + +- (void) dealloc +{ + [filename release]; + [self unregisterDraggedTypes]; + + [super dealloc]; +} + +#pragma mark - + +- (void)drawRect:(NSRect)rect { + // Drawing code here.f + [super drawRect:rect]; +} + +#pragma mark - + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate:(id)anObject +{ + delegate = anObject; +} + +- (NSString*) filename +{ + return filename; +} + +- (void) setFilename:(NSString*)aString +{ + if ( filename != aString ) + { + [filename release]; + filename = [aString copyWithZone:[self zone]]; + [self setNeedsDisplay:YES]; + } +} + +#pragma mark - + +- (BOOL)performDragOperation:(id )sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + NSArray *types = [pboard types]; + + if ( ![types containsObject:NSFilenamesPboardType] ) + { + NSBeep(); return NO; + } + + NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; + if ( [filenames count] == 0 ) return NO; + + if ( [[self delegate] respondsToSelector:@selector(mediabarItemApplicationPicker:shouldAcceptDrop:)] ) + return [[self delegate] mediabarItemApplicationPicker:self shouldAcceptDrop:[filenames objectAtIndex:0]]; + else + return NO; +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + NSArray *types = [pboard types]; + + if ( ![types containsObject:NSFilenamesPboardType] ) + return NSDragOperationNone; + + NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; + return ( [filenames count] == 1 ? NSDragOperationCopy : NSDragOperationNone ); +} + +- (NSDragOperation)draggingUpdated:(id )sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + NSArray *types = [pboard types]; + + if ( ![types containsObject:NSFilenamesPboardType] ) + return NSDragOperationNone; + + NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; + return ( [filenames count] == 1 ? NSDragOperationCopy : NSDragOperationNone ); +} + +- (void)draggingExited:(id )sender +{ + return; +} + +- (BOOL)prepareForDragOperation:(id )sender +{ + return YES; +} + +- (void)concludeDragOperation:(id )sender +{ + return; +} + +- (BOOL)ignoreModifierKeysWhileDragging +{ + return YES; +} + + +@end diff --git a/NewMediabarItemController.h b/NewMediabarItemController.h new file mode 100644 index 0000000..a1a0d74 --- /dev/null +++ b/NewMediabarItemController.h @@ -0,0 +1,92 @@ + +// +// NewMediabarItemController.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + + +#import + +@class JournlerGradientView; +@class MediabarItemApplicationPicker; + +@interface NewMediabarItemController : NSWindowController +{ + IBOutlet MediabarItemApplicationPicker *applicationField; + IBOutlet NSTextView *scriptText; + IBOutlet NSTextField *titleField; + + IBOutlet NSTextField *appnameField; + IBOutlet NSImageView *appImageView; + + IBOutlet NSObjectController *objectController; + + BOOL isSheet; + NSRect sheetFrame; + + id delegate; + NSWindow *targetWindow; + + NSImage *icon; + NSString *title; + NSString *helptip; + NSString *filepath; + NSAttributedString *scriptSource; + + id representedObject; + + BOOL wantsScript; + BOOL wantsFile; +} + +- (id) initWithDictionaryRepresentation:(NSDictionary*)aDictionary; +- (NSDictionary*) dictionaryRepresentation; + +- (id) delegate; +- (void) setDelegate:(id)anObject; + +- (NSImage *)icon; +- (void) setIcon:(NSImage*)anImage; + +- (NSString *)title; +- (void) setTitle:(NSString*)aString; + +- (NSString *)helptip; +- (void) setHelptip:(NSString*)aString; + +- (NSString *)filepath; +- (void) setFilepath:(NSString*)aString; + +- (NSAttributedString *)scriptSource; +- (void) setScriptSource:(NSAttributedString*)anAttributedString; + +- (id) representedObject; +- (void) setRepresentedObject:(id)anObject; + +- (BOOL) wantsScript; +- (void) setWantsScript:(BOOL)aBool; + +- (BOOL) wantsFile; +- (void) setWantsFile:(BOOL)aBool; + +- (IBAction)cancel:(id)sender; +- (IBAction)chooseApplication:(id)sender; +- (IBAction)help:(id)sender; +- (IBAction)save:(id)sender; +- (IBAction) verifyDraggedImage:(id)sender; + +- (void) runAsSheetForWindow:(NSWindow*)window attached:(BOOL)sheet location:(NSRect)frame; +- (void) sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; + +@end + +@interface NSObject (MediaBarItemCreatorDelegate) + +- (void) mediabarItemCreateDidCancelAction:(NewMediabarItemController*)mediabarItemCreator; +- (void) mediabarItemCreateDidSaveAction:(NewMediabarItemController*)mediabarItemCreator; + +@end \ No newline at end of file diff --git a/NewMediabarItemController.m b/NewMediabarItemController.m new file mode 100644 index 0000000..94487ca --- /dev/null +++ b/NewMediabarItemController.m @@ -0,0 +1,423 @@ + +// +// NewMediabarItemController.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +#import +#import +#import + +@implementation NewMediabarItemController + +- (id) init +{ + if ( self = [super initWithWindowNibName:@"NewMediabarItem"] ) + { + wantsScript = YES; + + [self window]; + [self retain]; + } + return self; +} + +- (id) initWithDictionaryRepresentation:(NSDictionary*)aDictionary +{ + if ( self = [self init] ) + { + [self setTitle:[aDictionary objectForKey:@"title"]]; + [self setFilepath:[aDictionary objectForKey:@"targetURI"]]; + [self setHelptip:[aDictionary objectForKey:@"tooltip"]]; + + NSData *imageData = [aDictionary objectForKey:@"image"]; + if ( imageData != nil ) [self setIcon:[NSKeyedUnarchiver unarchiveObjectWithData:imageData]]; + + NSData *scriptData = [aDictionary objectForKey:@"targetScript"]; + if ( scriptData != nil ) [self setScriptSource:[NSKeyedUnarchiver unarchiveObjectWithData:scriptData]]; + + int myTypeIdentifier = [[aDictionary objectForKey:@"typeIdentifier"] intValue]; + if ( myTypeIdentifier == kMenubarItemAppleScript ) + [self setWantsScript:YES]; + else if ( myTypeIdentifier == kMenubarItemURI ) + [self setWantsFile:YES]; + else + [self setWantsScript:YES]; + + } + + return self; +} + +- (void) windowDidLoad +{ + // the filepath gradient + int borders[4] = {1,1,1,1}; + [applicationField setBordered:YES]; + [applicationField setBorders:borders]; + + [applicationField setDrawsGradient:NO]; + [applicationField setBackgroundColor:[NSColor whiteColor]]; + + // the default icon + NSImage *scriptIcon = BundledImageWithName(@"AppleScriptActionBarSmall.png", @"com.sprouted.interface"); + [self setIcon:scriptIcon]; + + // the example script + // place DefaultMediabarAction.rtf in your application's main bundle + NSString *examplePath = [[NSBundle mainBundle] pathForResource:@"DefaultMediabarAction" ofType:@"rtf"]; + if ( examplePath == nil ) + { + NSLog(@"%@ %s - unable to locate resource \"DefaultMediabarAction\" of type \"rtf\"", [self className], _cmd); + } + else + { + NSAttributedString *attributedExample = [[[NSAttributedString alloc] initWithPath:examplePath documentAttributes:nil] autorelease]; + if ( attributedExample == nil ) + { + NSLog(@"%@ %s - unable to initialize example from path %@", [self className], _cmd, examplePath); + } + else + { + [self setScriptSource:attributedExample]; + } + } + + //targetWindow = [self window]; +} + +- (void) dealloc +{ + [icon release]; + [title release]; + [helptip release]; + [filepath release]; + [scriptSource release]; + + [super dealloc]; +} + +- (void)windowWillClose:(NSNotification *)aNotification +{ + [objectController unbind:@"contentObject"]; + [objectController setContent:nil]; + + [self autorelease]; +} + +#pragma mark - + +- (NSDictionary*) dictionaryRepresentation +{ + NSNumber *typeIdentifier = nil; + NSMutableString *identifier = [[[self title] mutableCopyWithZone:[self zone]] autorelease]; + NSMutableDictionary *representation = [NSMutableDictionary dictionaryWithCapacity:4]; + + // preapre the type identifier + if ( [self wantsScript] ) + typeIdentifier = [NSNumber numberWithInt:kMenubarItemAppleScript]; + else + typeIdentifier = [NSNumber numberWithInt:kMenubarItemURI]; + + // prepare the identifier + [identifier replaceOccurrencesOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] + withString:@"" options:0 range:NSMakeRange(0,[identifier length])]; + + + if ( icon != nil ) [representation setObject:[NSKeyedArchiver archivedDataWithRootObject:icon] forKey:@"image"]; + if ( title != nil ) [representation setObject:title forKey:@"title"]; + if ( helptip != nil ) [representation setObject:helptip forKey:@"tooltip"]; + + if ( filepath != nil && [self wantsFile] ) [representation setObject:[[NSURL fileURLWithPath:filepath] absoluteString] forKey:@"targetURI"]; + if ( scriptSource != nil && [self wantsScript] ) [representation setObject:[NSKeyedArchiver archivedDataWithRootObject:[self scriptSource]] forKey:@"targetScript"]; + + if ( identifier != nil ) [representation setObject:identifier forKey:@"identifier"]; + if ( typeIdentifier != nil ) [representation setObject:typeIdentifier forKey:@"typeIdentifier"]; + + return representation; + +} + +#pragma mark - + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate:(id)anObject +{ + delegate = anObject; +} + +- (NSImage *)icon +{ + return icon; +} + +- (void) setIcon:(NSImage*)anImage +{ + if ( icon != anImage ) + { + [icon release]; + icon = [anImage copyWithZone:[self zone]]; + } +} + +- (NSString *)title +{ + return title; +} + +- (void) setTitle:(NSString*)aString +{ + if ( title != aString ) + { + [title release]; + title = [aString copyWithZone:[self zone]]; + } +} + +- (NSString *)helptip +{ + return helptip; +} + +- (void) setHelptip:(NSString*)aString +{ + if ( helptip != aString ) + { + [helptip release]; + helptip = [aString copyWithZone:[self zone]]; + } +} + +- (NSString *)filepath +{ + return filepath; +} + +- (void) setFilepath:(NSString*)aString +{ + if ( filepath != aString ) + { + [filepath release]; + filepath = [aString copyWithZone:[self zone]]; + } +} + +- (NSAttributedString *)scriptSource +{ + return scriptSource; +} + +- (void) setScriptSource:(NSAttributedString*)anAttributedString +{ + if ( scriptSource != anAttributedString ) + { + [scriptSource release]; + scriptSource = [anAttributedString copyWithZone:[self zone]]; + } +} + +- (id) representedObject +{ + return representedObject; +} + +- (void) setRepresentedObject:(id)anObject +{ + if ( representedObject != anObject ) + { + [representedObject release]; + representedObject = [anObject retain]; + } +} + +#pragma mark - + +- (BOOL) wantsScript +{ + return wantsScript; +} + +- (void) setWantsScript:(BOOL)aBool +{ + wantsScript = aBool; + if ( wantsScript == YES ) [self setWantsFile:NO]; +} + +- (BOOL) wantsFile +{ + return wantsFile; +} + +- (void) setWantsFile:(BOOL)aBool +{ + wantsFile = aBool; + if ( wantsFile == YES ) [self setWantsScript:NO]; +} + +#pragma mark - + +- (IBAction) save:(id)sender +{ + [objectController commitEditing]; + + if ( [self title] == nil || [[self title] length] == 0 ) + { + NSBeep(); + [[self window] makeFirstResponder:titleField]; + } + else + { + if ( isSheet ) + [NSApp endSheet:targetWindow returnCode:NSRunStoppedResponse]; + else + [NSApp stopModal]; + } +} + +- (IBAction) cancel:(id)sender +{ + [objectController commitEditing]; + + if ( isSheet ) + [NSApp endSheet:targetWindow returnCode:NSRunAbortedResponse]; + else + [NSApp abortModal]; +} + +- (IBAction)chooseApplication:(id)sender +{ + int result; + NSArray *fileTypes = /*[NSArray arrayWithObjects:@"app", @"application", @"scpt", nil]*/ nil; + NSOpenPanel *oPanel = [NSOpenPanel openPanel]; + + result = [oPanel runModalForDirectory:@"/Applications" file:nil types:fileTypes]; + if (result == NSOKButton) + { + NSString *theFilename = [oPanel filename]; + NSString *theAppname = [[NSFileManager defaultManager] displayNameAtPath:theFilename]; + NSImage *theIcon = [[[NSWorkspace sharedWorkspace] iconForFile:theFilename] imageWithWidth:22 height:22]; + + [applicationField setFilename:theFilename]; + [appnameField setStringValue:theAppname]; + [appImageView setImage:theIcon]; + [appImageView setHidden:NO]; + + [self setIcon:theIcon]; + [self setFilepath:theFilename]; + [self setWantsFile:YES]; + } +} + +- (BOOL) mediabarItemApplicationPicker:(MediabarItemApplicationPicker*)appPicker shouldAcceptDrop:(NSString*)aFilename +{ + NSString *theFilename = aFilename; + NSString *theAppname = [[NSFileManager defaultManager] displayNameAtPath:theFilename]; + NSImage *theIcon = [[[NSWorkspace sharedWorkspace] iconForFile:theFilename] imageWithWidth:22 height:22]; + + [applicationField setFilename:theFilename]; + [appnameField setStringValue:theAppname]; + [appImageView setImage:theIcon]; + [appImageView setHidden:NO]; + + [self setIcon:theIcon]; + [self setFilepath:theFilename]; + [self setWantsFile:YES]; + + return YES; +} + +- (IBAction) verifyDraggedImage:(id)sender +{ + NSImage *anImage = [sender image]; + NSImage *resizedImage = [anImage imageWithWidth:22 height:22 inset:0]; + [self setIcon:resizedImage]; +} + + +- (IBAction)help:(id)sender +{ + [[NSHelpManager sharedHelpManager] openHelpAnchor:@"CustomizableMediabar" inBook:@"JournlerHelp"]; +} + +#pragma mark - + +- (void) runAsSheetForWindow:(NSWindow*)window attached:(BOOL)sheet location:(NSRect)frame +{ + int result = NSRunAbortedResponse; + isSheet = sheet; + + id originalDelegate = [window delegate]; + [window setDelegate:self]; + + if ( sheet ) + { + sheetFrame = frame; + sheetFrame.size.height = 0; + + //[NSApp beginSheet: [self window] modalForWindow: window modalDelegate: self + // didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: nil]; + [NSApp beginSheet: [self window] modalForWindow: window modalDelegate: self + didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: nil]; + } + else + { + //result = [NSApp runModalForWindow: [self window]]; + result = [NSApp runModalForWindow: [self window]]; + } + + if ( !isSheet && result == NSRunAbortedResponse ) + { + // only possible if ran as modal and the date was canceled + if ( [delegate respondsToSelector:@selector(mediabarItemCreateDidCancelAction:)] ) + [delegate mediabarItemCreateDidCancelAction:self]; + + //[[self window] close]; + [[self window] close]; + } + else if ( !isSheet && result == NSRunStoppedResponse ) + { + // only possible if ran as modal and the date was saved + if ( [delegate respondsToSelector:@selector(mediabarItemCreateDidSaveAction:)] ) + [delegate mediabarItemCreateDidSaveAction:self]; + + //[[self window] close]; + [[self window] close]; + } + + [window setDelegate:originalDelegate]; +} + +- (void) sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + if ( returnCode == NSRunAbortedResponse && [delegate respondsToSelector:@selector(mediabarItemCreateDidCancelAction:)] ) + [delegate mediabarItemCreateDidCancelAction:self]; + + else if ( returnCode == NSRunStoppedResponse && [delegate respondsToSelector:@selector(mediabarItemCreateDidSaveAction:)] ) + [delegate mediabarItemCreateDidSaveAction:self]; + + //[[self window] close]; + [[self window] close]; +} + +#pragma mark - + +- (NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect +{ + return sheetFrame; +} + + +@end diff --git a/PDAnnotatedRulerView.h b/PDAnnotatedRulerView.h new file mode 100644 index 0000000..0a683b9 --- /dev/null +++ b/PDAnnotatedRulerView.h @@ -0,0 +1,17 @@ +// +// PDAnnotatedRulerView.h +// SproutedInterface +// +// Created by Philip Dow on 5/23/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@interface PDAnnotatedRulerView : MNLineNumberingRulerView { + +} + +@end diff --git a/PDAnnotatedRulerView.m b/PDAnnotatedRulerView.m new file mode 100644 index 0000000..b8ddd2f --- /dev/null +++ b/PDAnnotatedRulerView.m @@ -0,0 +1,230 @@ +// +// PDAnnotatedRulerView.m +// SproutedInterface +// +// Created by Philip Dow on 5/23/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +// Ruler thickness value +#define RULER_THICKNESS 25 + +// Margin of displaying bookmarked line in a context menu. +#define STRIP_PREVIEW_MARGIN 15 + +// Default +#define DEFAULT_OPTION MNParagraphNumber | MNDrawBookmarks + +@implementation PDAnnotatedRulerView + +- (id)initWithScrollView:(NSScrollView *)aScrollView orientation:(NSRulerOrientation)orientation +{ + + if ( self = [super initWithScrollView:(NSScrollView *)aScrollView orientation:(NSRulerOrientation)orientation]) + { + [self setScrollView:aScrollView]; + [self setOrientation:orientation]; + + //load nib + //[NSBundle loadNibNamed:@"PDAnnotatedRuler" owner:self]; + + + + // Set default width + [self setRuleThickness:RULER_THICKNESS]; + + // Marker config + [self setReservedThicknessForMarkers:0]; + [self setClientView:self]; // Markers ask me if I can add a marker. + + // Add a dummy marker to draw properly + + markerImage = [[NSImage alloc] initByReferencingFile: + [[NSBundle bundleForClass:[self class]] pathForResource:@"prioritymedium" ofType:@"tiff"]]; + + NSRulerMarker* aMarker = [self newMarker]; + [self addMarker:aMarker]; + [self removeMarker:aMarker]; + + + // Set letter attributes + marginAttributes = [[NSMutableDictionary alloc] init]; + [marginAttributes setObject:[NSFont labelFontOfSize:9] forKey: NSFontAttributeName]; + [marginAttributes setObject:[NSColor darkGrayColor] forKey: NSForegroundColorAttributeName]; + + rulerOption = DEFAULT_OPTION; + + markerDeleteReservationFlag = NO; + + // + textView = [aScrollView documentView]; + layoutManager = [textView layoutManager]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowDidUpdate:) + name:NSWindowDidUpdateNotification + object:[aScrollView window]]; + + [self setOption:MNDrawBookmarks]; + + } + + return self; +} + +#pragma mark - + +- (void)drawMarkersInRect:(NSRect)aRect +{ + //NSLog(@"drawMarkersInRect %@",NSStringFromRect(aRect)); + + if( (rulerOption & 0x10) == 0 ) + return; + + + + + // *** (0) remove existing markers *** + // Delete markers unless while dragging. + + NSArray* existingMarkers = [self markers]; + + unsigned hoge = 0; + for( hoge = 0; hoge < [existingMarkers count]; hoge++) + { + if( ! [[existingMarkers objectAtIndex:hoge] isDragging] ) + [self removeMarker:[existingMarkers objectAtIndex:hoge]]; + + } + + + + + // Only get the visible part of the scroller view + NSRect documentVisibleRect = [[[layoutManager firstTextView] enclosingScrollView] documentVisibleRect]; + + // Find the glyph range for the visible glyphs + NSRange glyphRange = [layoutManager glyphRangeForBoundingRect: documentVisibleRect inTextContainer: [textView textContainer]]; + + NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + NSRange limitRange; + NSRange effectiveRange; + + limitRange = charRange; + + while (limitRange.length > 0) { + + //for( hoge= charRange.location; hoge < NSMaxRange(charRange) ; hoge ++ ) + //{ + + hoge = limitRange.location; + + id attribute = [[textView textStorage] attribute:NSToolTipAttributeName + atIndex:limitRange.location longestEffectiveRange:&effectiveRange + inRange:limitRange]; + + if ( attribute != nil ) + { + //if ( [[textView textStorage] hasBookmarkAtIndex:hoge inTextView:textView effectiveRange:effectiveRange] ) + //{ + + //NSRange paragraphRange = + //[textView selectionRangeForProposedRange:NSMakeRange(hoge, 1) granularity:NSSelectByParagraph]; + + + + //unsigned glyphIndex = [layoutManager glyphRangeForCharacterRange:NSMakeRange(paragraphRange.location,1) + // actualCharacterRange:NULL].location; + + + unsigned glyphIndex = [layoutManager glyphRangeForCharacterRange:NSMakeRange(hoge,1) + actualCharacterRange:NULL].location; + + NSRect drawingRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange: NULL]; + drawingRect.size.height = [markerImage size].height; + + [self drawMarkerInRect:drawingRect ]; + + + + } + + limitRange = NSMakeRange(NSMaxRange(effectiveRange), NSMaxRange(limitRange) - NSMaxRange(effectiveRange)); + } + + + +} + +-(void)drawMarkerInRect:(NSRect)lineRect + // check if a marker should be drawn and draw it if necessary +{ + + lineRect = [textView convertRect:lineRect toView:self]; + + + NSArray* markerObjects = [self markers]; + unsigned hoge; + BOOL exist = NO; + for(hoge = 0; hoge < [markerObjects count]; hoge++) + { + //get represented object + NSRulerMarker* marker = [markerObjects objectAtIndex:hoge]; + + + if( [[marker representedObject] isEqualToString:NSStringFromRect(lineRect) ] ) + { + //if( ! [marker isDragging] ) + // [marker setMarkerLocation: lineRect.origin.y + (lineRect.size.height / 2) ]; + [[[self scrollView] backgroundColor] set]; + NSRectFillUsingOperation(lineRect, NSCompositeSourceOver); + + [marker drawRect:lineRect]; + + exist = YES; + } + + + } + + if( exist == NO ) + { + + NSRulerMarker* aMarker = [self newMarker]; + [aMarker setMarkerLocation: lineRect.origin.y + (lineRect.size.height / 2) ]; + + [[[self scrollView] backgroundColor] set]; + NSRectFillUsingOperation(lineRect, NSCompositeSourceOver); + [aMarker drawRect:lineRect]; + + [aMarker setMovable:YES]; + [aMarker setRemovable:YES]; + [aMarker setRepresentedObject: NSStringFromRect(lineRect) ]; + [self addMarker:aMarker]; + + } +} + + +#pragma mark - + +- (void)mouseDown:(NSEvent *)theEvent +{ + return; +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + return; +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + return; +} + +@end diff --git a/PDAnnotatedTextStorage.h b/PDAnnotatedTextStorage.h new file mode 100644 index 0000000..b79de16 --- /dev/null +++ b/PDAnnotatedTextStorage.h @@ -0,0 +1,19 @@ +// +// PDAnnotatedTextStorage.h +// SproutedInterface +// +// Created by Philip Dow on 5/24/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@interface PDAnnotatedTextStorage : MNLineNumberingTextStorage { + +} + +-(BOOL)hasBookmarkAtIndex:(unsigned)index inTextView:(NSTextView*)textView effectiveRange:(NSRangePointer)aRange; + +@end diff --git a/PDAnnotatedTextStorage.m b/PDAnnotatedTextStorage.m new file mode 100644 index 0000000..1d70b3b --- /dev/null +++ b/PDAnnotatedTextStorage.m @@ -0,0 +1,53 @@ +// +// PDAnnotatedTextStorage.m +// SproutedInterface +// +// Created by Philip Dow on 5/24/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#define MarkerAttributeName NSToolTipAttributeName + +@implementation PDAnnotatedTextStorage + +-(BOOL)hasBookmarkAtIndex:(unsigned)index inTextView:(NSTextView*)textView +{ + //NSRange paragraphRange = + //[textView selectionRangeForProposedRange:NSMakeRange(index, 1) granularity:NSSelectByParagraph]; + + //NSLog(@"%@ %s - %i, %i", [self className], _cmd, NSMaxRange(paragraphRange) -1); + + //id attribute = [self attribute:MarkerAttributeName atIndex:NSMaxRange(paragraphRange) -1 effectiveRange:NULL]; + id attribute = [self attribute:MarkerAttributeName atIndex:index effectiveRange:NULL]; + if( attribute != NULL ) + return YES; + else + return NO; +} + +-(BOOL)hasBookmarkAtIndex:(unsigned)index inTextView:(NSTextView*)textView effectiveRange:(NSRangePointer)aRange +{ + //NSRange paragraphRange = + //[textView selectionRangeForProposedRange:NSMakeRange(index, 1) granularity:NSSelectByParagraph]; + + //NSLog(@"%@ %s - %i, %i", [self className], _cmd, NSMaxRange(paragraphRange) -1); + + id attribute = [self attribute:MarkerAttributeName atIndex:index effectiveRange:aRange]; + if( attribute != NULL ) + return YES; + else + return NO; +} + +-(void)setBookmarkAtIndex:(unsigned)index flag:(BOOL)flag inTextView:(NSTextView*)textView +{ + NSRange paragraphRange = + [textView selectionRangeForProposedRange:NSMakeRange(index, 1) granularity:NSSelectByParagraph]; + + [self addAttribute:MarkerAttributeName value:@"My Comment" range:NSMakeRange(NSMaxRange(paragraphRange)-1,1)]; +} + +@end diff --git a/PDAnnotatedTextView.h b/PDAnnotatedTextView.h new file mode 100644 index 0000000..fe5190c --- /dev/null +++ b/PDAnnotatedTextView.h @@ -0,0 +1,18 @@ +// +// PDAnnotatedTextView.h +// SproutedInterface +// +// Created by Philip Dow on 5/23/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import + +@interface PDAnnotatedTextView : MNLineNumberingTextView { + +} + +@end diff --git a/PDAnnotatedTextView.m b/PDAnnotatedTextView.m new file mode 100644 index 0000000..59ca1e7 --- /dev/null +++ b/PDAnnotatedTextView.m @@ -0,0 +1,47 @@ +// +// PDAnnotatedTextView.m +// SproutedInterface +// +// Created by Philip Dow on 5/23/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import +#import + +@implementation PDAnnotatedTextView + +- (void) awakeFromNib +{ + PDAnnotatedTextStorage* ts = [[PDAnnotatedTextStorage alloc] init]; + + [[self layoutManager] replaceTextStorage:ts]; + + [[self textStorage] setDelegate:self]; + + + NSScrollView* scrollView = [self enclosingScrollView]; + + + + // *** set up main text View *** // + //textView setting -- add ruler to textView + PDAnnotatedRulerView* aNumberingRulerView = + [[PDAnnotatedRulerView alloc] initWithScrollView:scrollView + orientation:NSVerticalRuler]; + + [scrollView setVerticalRulerView:aNumberingRulerView ]; + + //configuration + [scrollView setHasVerticalRuler:YES]; + [scrollView setHasHorizontalRuler:NO]; + + [scrollView setRulersVisible:YES]; + + [aNumberingRulerView release]; + +} + +@end diff --git a/PDAutoCompleteTextField.h b/PDAutoCompleteTextField.h new file mode 100644 index 0000000..43c5c21 --- /dev/null +++ b/PDAutoCompleteTextField.h @@ -0,0 +1,19 @@ +// +// PDAutoCompleteTextField.h +// SproutedInterface +// +// Created by Philip Dow on 7/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDAutoCompleteTextField : NSTextField { + NSArray *autoCompleteOptions; +} + +- (NSArray*) autoCompleteOptions; +- (void) setAutoCompleteOptions:(NSArray*)options; + +@end diff --git a/PDAutoCompleteTextField.m b/PDAutoCompleteTextField.m new file mode 100644 index 0000000..19b88f0 --- /dev/null +++ b/PDAutoCompleteTextField.m @@ -0,0 +1,67 @@ +// +// PDAutoCompleteTextField.m +// SproutedInterface +// +// Created by Philip Dow on 7/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation PDAutoCompleteTextField + +- (void) awakeFromNib +{ + autoCompleteOptions = [[NSArray alloc] init]; +} + +- (NSArray*) autoCompleteOptions +{ + return autoCompleteOptions; +} + +- (void) setAutoCompleteOptions:(NSArray*)options +{ + if ( autoCompleteOptions != options ) + { + [autoCompleteOptions release]; + autoCompleteOptions = [options copyWithZone:[self zone]]; + } +} + +#pragma mark - + +- (void)textDidChange:(NSNotification *)aNotification +{ + [[[aNotification userInfo] objectForKey:@"NSFieldEditor"] complete:self]; + [super textDidChange:aNotification]; +} + +- (NSArray *)textView:(NSTextView *)textView completions:(NSArray *)words + forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int *)index +{ + + // + // Textview delegate method that responds to the field editor + // When this text view is being edited + // + + // dunno if this is the correct way to do this at all + + NSMutableArray *hits = [[NSMutableArray alloc] init]; + + // grab the view's string + NSString *text = [textView string]; + + int i; + for ( i = 0; i < [autoCompleteOptions count]; i++ ) + { + if ( [[autoCompleteOptions objectAtIndex:i] rangeOfString:text options:NSLiteralSearch].location != NSNotFound ) + [hits addObject:[autoCompleteOptions objectAtIndex:i]]; + } + + return [hits autorelease]; +} + +@end diff --git a/PDBlueHighlightTextCell.h b/PDBlueHighlightTextCell.h new file mode 100644 index 0000000..fa38486 --- /dev/null +++ b/PDBlueHighlightTextCell.h @@ -0,0 +1,16 @@ +// +// PDBlueHighlightTextCell.h +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDBlueHighlightTextCell : NSTextFieldCell { + BOOL _beingEdited; +} + +@end diff --git a/PDBlueHighlightTextCell.m b/PDBlueHighlightTextCell.m new file mode 100644 index 0000000..75f186a --- /dev/null +++ b/PDBlueHighlightTextCell.m @@ -0,0 +1,45 @@ +// +// PDBlueHighlightTextCell.m +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation PDBlueHighlightTextCell + + +- (id)initWithCoder:(NSCoder *)decoder { + self = [super initWithCoder:decoder]; + [self setFocusRingType:NSFocusRingTypeNone]; + [self setShowsFirstResponder:NO]; + return self; +} + + +- (NSColor*) textColor { + + if ( [self isHighlighted] && ([[[self controlView] window] firstResponder] == [self controlView]) && + [[[self controlView] window] isMainWindow] && [[[self controlView] window] isKeyWindow] ) + return [NSColor whiteColor]; + else if ( [self isHighlighted] && + [(NSTableView*)[self controlView] editedRow] != -1 && [(NSTableView*)[self controlView] editedColumn] != -1 ) + return [NSColor whiteColor]; + else + return [super textColor]; + +} + +/* +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + //[super drawWithFrame:cellFrame inView:<#(NSView *)controlView#> + +} +*/ + +@end diff --git a/PDBorderedView.h b/PDBorderedView.h new file mode 100644 index 0000000..c309f01 --- /dev/null +++ b/PDBorderedView.h @@ -0,0 +1,34 @@ +// +// PDBorderedFill.h +// SproutedInterface +// +// Created by Philip Dow on 12/15/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDBorderedView : NSView { + + int borders[4]; + BOOL bordered; + + NSColor *fillColor; + NSColor *borderColor; + +} + +- (int*) borders; +- (void) setBorders:(int*)sides; + +- (BOOL) bordered; +- (void) setBordered:(BOOL)flag; + +- (NSColor*) fillColor; +- (void) setFillColor:(NSColor*)aColor; + +- (NSColor*) borderColor; +- (void) setBorderColor:(NSColor*)aColor; + +@end diff --git a/PDBorderedView.m b/PDBorderedView.m new file mode 100644 index 0000000..7dc2cad --- /dev/null +++ b/PDBorderedView.m @@ -0,0 +1,196 @@ +// +// PDBorderedFill.m +// SproutedInterface +// +// Created by Philip Dow on 12/15/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation PDBorderedView + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + fillColor = [[NSColor whiteColor] retain]; + borderColor = [[NSColor + colorWithCalibratedRed:157.0/255.0 green:157.0/255.0 blue:157.0/255.0 alpha:1.0] retain]; + + bordered = YES; + + borders[0] = 1; // top + borders[1] = 1; // right + borders[2] = 1; // bottom + borders[3] = 1; // left + + } + return self; +} + +- (void) dealloc { + + [fillColor release]; + fillColor = nil; + + [borderColor release]; + borderColor = nil; + + [super dealloc]; + +} + +#pragma mark - + +- (int*) borders { + return borders; +} + +- (void) setBorders:(int*)sides { + + borders[0] = sides[0]; + borders[1] = sides[1]; + borders[2] = sides[2]; + borders[3] = sides[3]; +} + +- (BOOL) bordered { + return bordered; +} + +- (void) setBordered:(BOOL)flag { + bordered = flag; +} + +- (NSColor*) fillColor { + return fillColor; +} + +- (void) setFillColor:(NSColor*)aColor { + + if ( fillColor != aColor ) { + [fillColor release]; + fillColor = [aColor copyWithZone:[self zone]]; + } +} + +- (NSColor*) borderColor { + return borderColor; +} + +- (void) setBorderColor:(NSColor*)aColor { + + if ( borderColor != aColor ) { + [borderColor release]; + borderColor = [aColor copyWithZone:[self zone]]; + } +} + +- (void)drawRect:(NSRect)rect { + + // Draw a frame and fill in white + NSRect bds = [self bounds]; + + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + + //then fills it the requested color + if ( fillColor != nil ) + { + [[self fillColor] set]; + NSRectFillUsingOperation(bds, NSCompositeSourceOver); + } + + if ( [self bordered] ) + { + + NSPoint topLeft, topRight, bottomRight, bottomLeft; + + topLeft = NSMakePoint(0.5, bds.size.height-0.5); + topRight = NSMakePoint(bds.size.width, bds.size.height-0.5); + + bottomRight = NSMakePoint(bds.size.width-0.5, 0.5); + bottomLeft = NSMakePoint(0.5, 0.5); + + + float scaleFactor = [[NSScreen mainScreen] userSpaceScaleFactor]; + if ( scaleFactor != 1.0 ) { + + // apply the scale factor + topLeft.x *= scaleFactor; + topLeft.y *= scaleFactor; + + topRight.x *= scaleFactor; + topRight.y *= scaleFactor; + + bottomRight.x *= scaleFactor; + bottomRight.y *= scaleFactor; + + bottomLeft.x *= scaleFactor; + bottomLeft.y *= scaleFactor; + + // adjust the points to integral boundaries + topLeft.x = ceil(topLeft.x); + topLeft.y = ceil(topLeft.y); + + topRight.x = ceil(topRight.x); + topRight.y = ceil(topRight.y); + + bottomRight.x = ceil(bottomRight.x); + bottomRight.y = ceil(bottomRight.y); + + bottomLeft.x = ceil(bottomLeft.x); + bottomLeft.y = ceil(bottomLeft.y); + + // convert back to user space + topLeft.x /= scaleFactor; + topLeft.y /= scaleFactor; + + topRight.x /= scaleFactor; + topRight.y /= scaleFactor; + + bottomRight.x /= scaleFactor; + bottomRight.y /= scaleFactor; + + bottomLeft.x /= scaleFactor; + bottomLeft.y /= scaleFactor; + + + } + + // + //draws an outline around the guy, just like with other views + if ( [self bordered] ) { + NSBezierPath *borderPath = [NSBezierPath bezierPath]; + if ( borders[0] ) { + [borderPath moveToPoint:topLeft]; + [borderPath lineToPoint:topRight]; + } + if ( borders[1] ) { + [borderPath moveToPoint:topRight]; + [borderPath lineToPoint:bottomRight]; + } + if ( borders[2] ) { + [borderPath moveToPoint:bottomRight]; + [borderPath lineToPoint:bottomLeft]; + } + if ( borders[3] ) { + [borderPath moveToPoint:bottomLeft]; + [borderPath lineToPoint:topLeft]; + } + + [[self borderColor] set ]; + + [currentContext saveGraphicsState]; + [currentContext setShouldAntialias:NO]; + + [borderPath setLineWidth:1.0]; + [borderPath stroke]; + + [currentContext restoreGraphicsState]; + } + } +} + +@end diff --git a/PDButton.h b/PDButton.h new file mode 100644 index 0000000..1232f8f --- /dev/null +++ b/PDButton.h @@ -0,0 +1,18 @@ +// +// PDButton.h +// SproutedInterface +// +// Created by Philip Dow on 12/4/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDButtonCell; + +@interface PDButton : NSButton { + +} + +@end diff --git a/PDButton.m b/PDButton.m new file mode 100644 index 0000000..6e1e25f --- /dev/null +++ b/PDButton.m @@ -0,0 +1,37 @@ +// +// PDButton.m +// SproutedInterface +// +// Created by Philip Dow on 12/4/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDButton + + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] + initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + [anArchiver encodeClassName:@"NSButtonCell" intoClassName:@"PDButtonCell"]; + [anArchiver encodeRootObject:[self cell]]; + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + + } + + return self; +} + + ++ (Class) cellClass +{ + return [PDButtonCell class]; +} + +@end diff --git a/PDButtonCell.h b/PDButtonCell.h new file mode 100644 index 0000000..735e0b5 --- /dev/null +++ b/PDButtonCell.h @@ -0,0 +1,16 @@ +// +// PDButtonCell.h +// SproutedInterface +// +// Created by Philip Dow on 12/4/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDButtonCell : NSButtonCell { + +} + +@end diff --git a/PDButtonCell.m b/PDButtonCell.m new file mode 100644 index 0000000..14ea5d9 --- /dev/null +++ b/PDButtonCell.m @@ -0,0 +1,131 @@ +// +// PDButtonCell.m +// SproutedInterface +// +// Created by Philip Dow on 12/4/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +#define PDButtonCellFillLeft 0 +#define PDButtonCellFillCenter 1 +#define PDButtonCellFillRight 2 + +#define PDButtonCellDarkLeft 3 +#define PDButtonCellDarkCenter 4 +#define PDButtonCellDarkRight 5 + +@implementation PDButtonCell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (id)initTextCell:(NSString *)aString { + + if ( self = [super initTextCell:aString] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (void) dealloc { + + [super dealloc]; + +} + +#pragma mark - + + + +- (NSRect)drawingRectForBounds:(NSRect)theRect { + return theRect; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect { + return theRect; +} + + + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // + // bypass the frame + // + + [self drawInteriorWithFrame:cellFrame inView:controlView]; + +} + + + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + float alpha = ( [self isEnabled] ? 1.0 : 0.70 ); + NSColor *gradientStart, *gradientEnd, *borderColor; + + if ( [self isHighlighted] ) { + + borderColor = [NSColor colorWithCalibratedRed:149.0/255.0 green:149.0/255.0 blue:149.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:186.0/255.0 green:186.0/255.0 blue:186.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:alpha]; + + } + + else { + + borderColor = [NSColor colorWithCalibratedRed:173.0/255.0 green:173.0/255.0 blue:173.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:224.0/255.0 green:224.0/255.0 blue:224.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:253.0/255.0 green:253.0/255.0 blue:253.0/255.0 alpha:alpha]; + + } + + int height = cellFrame.size.height; + int width = cellFrame.size.width; + + + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11.0], NSFontAttributeName, + ( [self isEnabled] ? [NSColor blackColor] : [NSColor lightGrayColor] ), NSForegroundColorAttributeName, nil]; + + NSSize stringSize = [[self title] sizeWithAttributes:attributes]; + + NSRect stringRect = NSMakeRect(width/2.0-stringSize.width/2.0, height/2.0-stringSize.height/2.0, + stringSize.width, stringSize.height); + + NSRect targetRect = NSMakeRect( stringRect.origin.x-16.0, stringRect.origin.y-2.0, + stringRect.size.width+32.0, stringRect.size.height+4.0 ); + + targetRect = NSInsetRect(targetRect,0.5,0.5); + + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:8.5] + linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + [borderColor set]; + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:8.5] stroke]; + + [[self title] drawInRect:stringRect withAttributes:attributes]; + + +} + +@end diff --git a/PDButtonColorWell.h b/PDButtonColorWell.h new file mode 100644 index 0000000..748d02d --- /dev/null +++ b/PDButtonColorWell.h @@ -0,0 +1,22 @@ +// +// PDButtonColorWell.h +// SproutedInterface +// +// Created by Philip Dow on 1/7/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDButtonColorWell : NSButton { + NSString *defaultsKey; +} + +- (NSColor*) color; +- (void) setColor:(NSColor*)color; + +- (NSString*) defaultsKey; +- (void) setDefaultsKey:(NSString*)aKey; + +@end diff --git a/PDButtonColorWell.m b/PDButtonColorWell.m new file mode 100644 index 0000000..0b6c0bf --- /dev/null +++ b/PDButtonColorWell.m @@ -0,0 +1,84 @@ +// +// PDButtonColorWell.m +// SproutedInterface +// +// Created by Philip Dow on 1/7/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + + +@implementation PDButtonColorWell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + [anArchiver encodeClassName:@"NSButtonCell" intoClassName:@"PDButtonColorWellCell"]; + [anArchiver encodeRootObject:[self cell]]; + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + } + + return self; +} + +- (void) dealloc +{ + [defaultsKey release]; + [super dealloc]; +} + + ++ (Class) cellClass +{ + return [PDButtonColorWellCell class]; +} + +- (BOOL) isFlipped +{ + return YES; +} + +- (NSColor*) color +{ + return [[self cell] color]; +} + +- (void) setColor:(NSColor*)color +{ + [self willChangeValueForKey:@"color"]; + [[self cell] setColor:color]; + [self didChangeValueForKey:@"color"]; + + [self setNeedsDisplay:YES]; +} + +- (NSString*) defaultsKey +{ + return defaultsKey; +} + +- (void) setDefaultsKey:(NSString*)aKey +{ + if ( defaultsKey != aKey ) + { + [self willChangeValueForKey:@"defaultsKey"]; + [defaultsKey release]; + defaultsKey = [aKey copyWithZone:[self zone]]; + [self didChangeValueForKey:@"defaultsKey"]; + + [self unbind:@"color"]; + [self bind:@"color" + toObject:[NSUserDefaultsController sharedUserDefaultsController] + withKeyPath:[NSString stringWithFormat:@"values.%@",defaultsKey] + options:[NSDictionary dictionaryWithObjectsAndKeys: + NSUnarchiveFromDataTransformerName, NSValueTransformerNameBindingOption, nil]]; + } +} + + +@end diff --git a/PDButtonColorWellCell.h b/PDButtonColorWellCell.h new file mode 100644 index 0000000..b424815 --- /dev/null +++ b/PDButtonColorWellCell.h @@ -0,0 +1,21 @@ +// +// PDButtonColorWellCell.h +// SproutedInterface +// +// Created by Philip Dow on 1/7/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDButtonColorWellCell : NSButtonCell { + + NSColor *_color; + +} + +- (NSColor*) color; +- (void) setColor:(NSColor*)color; + +@end diff --git a/PDButtonColorWellCell.m b/PDButtonColorWellCell.m new file mode 100644 index 0000000..9027f19 --- /dev/null +++ b/PDButtonColorWellCell.m @@ -0,0 +1,196 @@ +// +// PDButtonColorWellCell.m +// SproutedInterface +// +// Created by Philip Dow on 1/7/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +#define kLabelOffset 20 + +@implementation PDButtonColorWellCell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + [self setBordered:NO]; + } + + return self; +} + +- (id)initTextCell:(NSString *)aString { + + if ( self = [super initTextCell:aString] ) { + [self setBordered:NO]; + } + + return self; +} + +- (void) dealloc { + + [_color release]; + [super dealloc]; +} + +#pragma mark - + + +- (NSColor*) color +{ + return _color; +} + +- (void) setColor:(NSColor*)color +{ + if ( _color != color ) + { + [self willChangeValueForKey:@"color"]; + [_color release]; + _color = [color copyWithZone:[self zone]]; + [self didChangeValueForKey:@"color"]; + } +} + +#pragma mark - + +- (NSRect)drawingRectForBounds:(NSRect)theRect { + return theRect; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect { + return theRect; +} + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // + // bypass the frame + // + + [self drawInteriorWithFrame:cellFrame inView:controlView]; + +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + float alpha = ( [self isEnabled] ? 1.0 : 0.70 ); + NSColor *gradientStart, *gradientEnd, *borderColor; + + if ( [self isHighlighted] /*|| [self state] == NSOnState*/ ) { + + borderColor = [NSColor colorWithCalibratedRed:119.0/255.0 green:119.0/255.0 blue:119.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:186.0/255.0 green:186.0/255.0 blue:186.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:alpha]; + } + else { + + borderColor = [NSColor colorWithCalibratedRed:143.0/255.0 green:143.0/255.0 blue:143.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:224.0/255.0 green:224.0/255.0 blue:224.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:253.0/255.0 green:253.0/255.0 blue:253.0/255.0 alpha:alpha]; + } + + NSRect targetRect = cellFrame; + targetRect = NSInsetRect(targetRect,0.5,0.5); + //targetRect.origin.x += 0.5; targetRect.origin.y += 0.5; targetRect.size.width = 21; targetRect.size.height -= 1; + + NSBezierPath *thePath = [NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:2.0]; + [thePath linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + [borderColor set]; + [thePath stroke]; + + // + // draw the color inside the cell image + NSColor *cellColor = [self color]; + if ( !cellColor ) cellColor = [NSColor blackColor]; + + [cellColor set]; + //NSRect colorRect = NSInsetRect(targetRect, 5.0, 4.0); + NSRect colorRect = targetRect; + colorRect.origin.x += 5; colorRect.origin.y += 4; colorRect.size.width = 10; colorRect.size.height -= 8; + NSBezierPath *insetPath = [NSBezierPath bezierPathWithRoundedRect:colorRect cornerRadius:2.0]; + + [insetPath fill]; + + [[borderColor colorWithAlphaComponent:0.7] set]; + + //[borderColor set]; + [insetPath stroke]; + + if ( [[self title] length] != 0 ) + { + cellFrame.origin.x += kLabelOffset; + cellFrame.size.width -= kLabelOffset; + + [super drawInteriorWithFrame:cellFrame inView:controlView]; + } + +} + +/* +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + float alpha = ( [self isEnabled] ? 1.0 : 0.70 ); + NSColor *gradientStart, *gradientEnd, *borderColor; + + if ( [self isHighlighted] ) { + + borderColor = [NSColor colorWithCalibratedRed:149.0/255.0 green:149.0/255.0 blue:149.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:186.0/255.0 green:186.0/255.0 blue:186.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:alpha]; + + } + + else { + + borderColor = [NSColor colorWithCalibratedRed:173.0/255.0 green:173.0/255.0 blue:173.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:224.0/255.0 green:224.0/255.0 blue:224.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:253.0/255.0 green:253.0/255.0 blue:253.0/255.0 alpha:alpha]; + + } + + NSRect targetRect = cellFrame; + + targetRect = NSInsetRect(targetRect,0.5,0.5); + + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:2.0] + linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + [borderColor set]; + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:2.0] stroke]; + + // draw the color inside the cell image + NSColor *cellColor = [self color]; + if ( !cellColor ) cellColor = [NSColor blackColor]; + + [cellColor set]; + NSRect colorRect = NSInsetRect(targetRect, 6.0, 4.0); + [[NSBezierPath bezierPathWithRoundedRect:colorRect cornerRadius:2.0] fill]; + + // draw the label if there is one + NSString *title = [self title]; + if ( title != nil && [title length] > 0 ) + { + // really basic stuff happening here - no alignment, no elision, etc + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [self font], NSFontAttributeName, nil]; + + NSSize stringSize = [title sizeWithAttributes:attributes]; + NSRect titleRect = NSMakeRect( cellFrame.origin.x + cellFrame.size.width - stringSize.width, + cellFrame.origin.y + cellFrame.size.height - stringSize.height, + stringSize.width, stringSize.height ); + + [title drawInRect:titleRect withAttributes:attributes]; + } +} +*/ + +@end diff --git a/PDButtonTextOnImage.h b/PDButtonTextOnImage.h new file mode 100644 index 0000000..2920d20 --- /dev/null +++ b/PDButtonTextOnImage.h @@ -0,0 +1,18 @@ +// +// PDButtonTextOnImage.h +// SproutedInterface +// +// Created by Philip Dow on 1/3/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDButtonTextOnImageCell; + +@interface PDButtonTextOnImage : NSButton { + +} + +@end diff --git a/PDButtonTextOnImage.m b/PDButtonTextOnImage.m new file mode 100644 index 0000000..cb0b67e --- /dev/null +++ b/PDButtonTextOnImage.m @@ -0,0 +1,44 @@ +// +// PDButtonTextOnImage.m +// SproutedInterface +// +// Created by Philip Dow on 1/3/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDButtonTextOnImage + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + + NSFont *font = [[self cell] font]; + NSString *title = [self title]; + + [self setCell:[[[PDButtonTextOnImageCell alloc] initImageCell:[self image]] autorelease]]; + + [(PDButtonTextOnImageCell*)[self cell] setTitle:title]; + [(PDButtonTextOnImageCell*)[self cell] setFont:font]; + + } + + return self; +} + + ++ (Class) cellClass +{ + return [PDButtonTextOnImageCell class]; +} + +- (BOOL) isFlipped { return YES; } + +- (BOOL)isOpaque { return NO; } + + +@end diff --git a/PDButtonTextOnImageCell.h b/PDButtonTextOnImageCell.h new file mode 100644 index 0000000..78e03a9 --- /dev/null +++ b/PDButtonTextOnImageCell.h @@ -0,0 +1,16 @@ +// +// PDButtonTextOnImageCell.h +// SproutedInterface +// +// Created by Philip Dow on 1/3/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDButtonTextOnImageCell : NSButtonCell { + +} + +@end diff --git a/PDButtonTextOnImageCell.m b/PDButtonTextOnImageCell.m new file mode 100644 index 0000000..6f22d40 --- /dev/null +++ b/PDButtonTextOnImageCell.m @@ -0,0 +1,127 @@ +// +// PDButtonTextOnImageCell.m +// SproutedInterface +// +// Created by Philip Dow on 1/3/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation PDButtonTextOnImageCell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + [self setBordered:NO]; + } + + return self; + +} + +- (id)initTextCell:(NSString *)aString { + + if ( self = [super initTextCell:aString] ) { + [self setBordered:NO]; + } + + return self; + +} + +- (void) dealloc { + + [super dealloc]; + +} + +#pragma mark - + + + +- (NSRect)drawingRectForBounds:(NSRect)theRect { + return theRect; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect { + return theRect; +} + + + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // + // bypass the frame + // + + [self drawInteriorWithFrame:cellFrame inView:controlView]; + +} + + + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + + float height = cellFrame.size.height; + float width = cellFrame.size.width; + + float alpha = 1.0; + + NSSize centerSize; + NSSize textSize; + NSRect targetRect; + NSRect textRect; + + NSImage *cellImage = [self image]; + + NSMutableAttributedString *attrValue = [[[NSMutableAttributedString alloc] initWithString:[self title]attributes: + [NSDictionary dictionaryWithObjectsAndKeys:[self font], NSFontAttributeName, nil]] autorelease]; + + centerSize = [cellImage size]; + targetRect = NSMakeRect(width/2-centerSize.width/2, height/2-centerSize.height/2, centerSize.width, centerSize.height); + + if ( [self isHighlighted] ) { + [[NSColor blackColor] set]; + NSRectFillUsingOperation(cellFrame, NSCompositeSourceOver); + alpha = 0.6; + } + + [cellImage setFlipped:YES]; + [cellImage drawInRect:targetRect + fromRect:NSMakeRect(0,0,centerSize.width,centerSize.height) + operation:NSCompositeSourceOver fraction:alpha]; + + textSize = [attrValue size]; + + // a maximum rectangle for the text + NSRect maxTextRect = NSMakeRect(4,height/2-textSize.height/2 + 1, width-8, textSize.height); + + // the required size + textRect = NSMakeRect(width/2-textSize.width/2, height/2-textSize.height/2 + 1, textSize.width, textSize.height); + + if ( textRect.size.width > maxTextRect.size.width ) { + + NSMutableParagraphStyle *parStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopyWithZone:[self zone]]; + [parStyle setLineBreakMode:NSLineBreakByTruncatingTail]; + + [attrValue addAttribute:NSParagraphStyleAttributeName value:parStyle range:NSMakeRange(0,[attrValue length])]; + [attrValue drawInRect:maxTextRect]; + + [parStyle release]; + + } + else { + + [attrValue drawInRect:textRect]; + + } + +} + + +@end diff --git a/PDCaseInsensitiveComboBoxCell.h b/PDCaseInsensitiveComboBoxCell.h new file mode 100644 index 0000000..add7b15 --- /dev/null +++ b/PDCaseInsensitiveComboBoxCell.h @@ -0,0 +1,17 @@ +// +// PDCaseInsensitiveComboBox.h +// SproutedInterface +// +// Created by Philip Dow on 3/17/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDCaseInsensitiveComboBoxCell : NSComboBoxCell { + +} + +@end diff --git a/PDCaseInsensitiveComboBoxCell.m b/PDCaseInsensitiveComboBoxCell.m new file mode 100644 index 0000000..f8ce25e --- /dev/null +++ b/PDCaseInsensitiveComboBoxCell.m @@ -0,0 +1,45 @@ +// +// PDCaseInsensitiveComboBox.m +// SproutedInterface +// +// Created by Philip Dow on 3/17/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation PDCaseInsensitiveComboBoxCell + +-(NSString*)completedString:(NSString*)substring +{ + if([self usesDataSource]) + { + return[super completedString:substring]; + } + else //basicallydowhatcompleteshoulddo--becaseinsensitive. + { + NSArray* currentList=[self objectValues]; + NSEnumerator* theEnum=[currentList objectEnumerator]; + id eachString; + int maxLength=0; + NSString* bestMatch=@""; + + while(nil!=(eachString=[theEnum nextObject])) + { + NSString* commonPrefix= [eachString commonPrefixWithString:substring options:NSCaseInsensitiveSearch]; + if([commonPrefix length]>=[substring length]&&[commonPrefix length]>maxLength) + { + maxLength=[commonPrefix length]; + bestMatch=eachString; + break; + + //Build match string based on what user has typed so far, to show changes in capitalization. + //bestMatch=[NSString stringWithFormat:@"%@%@",substring, [eachString substringFromIndex:[substringlength]]]; + } + } + return bestMatch; + } +} + +@end \ No newline at end of file diff --git a/PDCircleButton.h b/PDCircleButton.h new file mode 100644 index 0000000..2778a49 --- /dev/null +++ b/PDCircleButton.h @@ -0,0 +1,18 @@ +// +// PDCircleButton.h +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDCircleButtonCell; + +@interface PDCircleButton : NSButton { + +} + +@end diff --git a/PDCircleButton.m b/PDCircleButton.m new file mode 100644 index 0000000..5cd2b85 --- /dev/null +++ b/PDCircleButton.m @@ -0,0 +1,37 @@ +// +// PDCircleButton.m +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDCircleButton + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] + initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + [anArchiver encodeClassName:@"NSButtonCell" intoClassName:@"PDCircleButtonCell"]; + [anArchiver encodeRootObject:[self cell]]; + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + + } + + return self; +} + + ++ (Class) cellClass +{ + return [PDCircleButtonCell class]; +} + + +@end diff --git a/PDCircleButtonCell.h b/PDCircleButtonCell.h new file mode 100644 index 0000000..9d8688d --- /dev/null +++ b/PDCircleButtonCell.h @@ -0,0 +1,16 @@ +// +// PDCircleButtonCell.h +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDCircleButtonCell : NSButtonCell { + +} + +@end diff --git a/PDCircleButtonCell.m b/PDCircleButtonCell.m new file mode 100644 index 0000000..b9c87ff --- /dev/null +++ b/PDCircleButtonCell.m @@ -0,0 +1,129 @@ +// +// PDCircleButtonCell.m +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +#define PDButtonCellFillLeft 0 +#define PDButtonCellFillCenter 1 +#define PDButtonCellFillRight 2 + +#define PDButtonCellDarkLeft 3 +#define PDButtonCellDarkCenter 4 +#define PDButtonCellDarkRight 5 + +@implementation PDCircleButtonCell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + [self setBordered:NO]; + } + + return self; + +} + +- (id)initTextCell:(NSString *)aString { + + if ( self = [super initTextCell:aString] ) { + + [self setBordered:NO]; + } + + return self; + +} + +- (void) dealloc { + + [super dealloc]; + +} + +#pragma mark - + + + +- (NSRect)drawingRectForBounds:(NSRect)theRect { + return theRect; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect { + return theRect; +} + + + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // + // bypass the frame + // + + [self drawInteriorWithFrame:cellFrame inView:controlView]; + +} + + + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + float alpha = ( [self isEnabled] ? 1.0 : 0.70 ); + NSColor *gradientStart, *gradientEnd, *borderColor; + + if ( [self isHighlighted] ) { + + borderColor = [NSColor colorWithCalibratedRed:149.0/255.0 green:149.0/255.0 blue:149.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:186.0/255.0 green:186.0/255.0 blue:186.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:alpha]; + + } + + else { + + borderColor = [NSColor colorWithCalibratedRed:173.0/255.0 green:173.0/255.0 blue:173.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:224.0/255.0 green:224.0/255.0 blue:224.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:253.0/255.0 green:253.0/255.0 blue:253.0/255.0 alpha:alpha]; + + } + + static float fontSize = 12.0; + int height = cellFrame.size.height; + int width = cellFrame.size.width; + + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:fontSize], NSFontAttributeName, + ( [self isEnabled] ? [NSColor blackColor] : [NSColor lightGrayColor] ), NSForegroundColorAttributeName, nil]; + + NSSize stringSize = [[self title] sizeWithAttributes:attributes]; + + NSRect stringRect = NSMakeRect(width/2.0-stringSize.width/2.0, height/2.0-stringSize.height/2.0, + stringSize.width, stringSize.height); + + float offset = fontSize * 1.5; + + NSRect targetRect = NSMakeRect( width/2.0 - offset/2, height/2.0 - offset/2, offset, offset ); + targetRect = NSInsetRect(targetRect,0.5,0.5); + + [[NSBezierPath bezierPathWithOvalInRect:targetRect] + linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + [borderColor set]; + [[NSBezierPath bezierPathWithOvalInRect:targetRect] stroke]; + + [[self title] drawInRect:stringRect withAttributes:attributes]; + + +} + +@end \ No newline at end of file diff --git a/PDColorWell.h b/PDColorWell.h new file mode 100644 index 0000000..d54a971 --- /dev/null +++ b/PDColorWell.h @@ -0,0 +1,17 @@ +// +// PDColorWell.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDColorWell : NSColorWell { + +} + +@end diff --git a/PDColorWell.m b/PDColorWell.m new file mode 100644 index 0000000..677f910 --- /dev/null +++ b/PDColorWell.m @@ -0,0 +1,19 @@ +// +// PDColorWell.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation PDColorWell + +- (void)activate:(BOOL)exclusive { + [[self window] makeFirstResponder:self]; + [super activate:YES]; +} + +@end diff --git a/PDDateDisplayCell.h b/PDDateDisplayCell.h new file mode 100644 index 0000000..e9afc70 --- /dev/null +++ b/PDDateDisplayCell.h @@ -0,0 +1,30 @@ +// +// PDDateDisplayCell.h +// SproutedInterface +// +// Created by Philip Dow on 5/31/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDDateDisplayCell : NSTextFieldCell { + NSDateFormatter *_dayFormatter; + NSDateFormatter *_timeFormatter; + + float widthWas; + BOOL alreadyJumpedOnNoTime; + + BOOL selected; + BOOL boldsWhenSelected; +} + +- (BOOL) isSelected; +- (void) setSelected:(BOOL)isSelected; + +- (BOOL) boldsWhenSelected; +- (void) setBoldsWhenSelected:(BOOL)doesBold; + +@end diff --git a/PDDateDisplayCell.m b/PDDateDisplayCell.m new file mode 100644 index 0000000..11edc12 --- /dev/null +++ b/PDDateDisplayCell.m @@ -0,0 +1,266 @@ +// +// PDDateDisplayCell.m +// SproutedInterface +// +// Created by Philip Dow on 5/31/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#define kEdgeInset 2 +static NSString *shorter_time = @"HH:mm"; + +@implementation PDDateDisplayCell + +// +// cells are copied, which means a formatter can be shared across the cells +// as the cells are resized, the smallest cell can determine the formatter's behavior across the board + +- (BOOL) isSelected +{ + return selected; +} + +- (void) setSelected:(BOOL)isSelected +{ + selected = isSelected; +} + +- (BOOL) boldsWhenSelected +{ + return boldsWhenSelected; +} + +- (void) setBoldsWhenSelected:(BOOL)doesBold +{ + boldsWhenSelected = doesBold; +} + +#pragma mark - + +- copyWithZone:(NSZone *)zone { + PDDateDisplayCell *cell = (PDDateDisplayCell *)[super copyWithZone:zone]; + //cell->_formatter = [_formatter retain]; + cell->_dayFormatter = [_dayFormatter retain]; + cell->_timeFormatter = [_timeFormatter retain]; + + cell->widthWas = widthWas; + cell->alreadyJumpedOnNoTime = alreadyJumpedOnNoTime; + + cell->selected = selected; + cell->boldsWhenSelected = boldsWhenSelected; + + return cell; +} + +- (void ) dealoc { + if ( _dayFormatter ) [_dayFormatter release]; + if ( _timeFormatter ) [_timeFormatter release]; +} + +- (NSColor*) textColor { + + //if ( [self isHighlighted] && ([[[self controlView] window] firstResponder] == [self controlView]) && + // [[[self controlView] window] isMainWindow] && [[[self controlView] window] isKeyWindow] ) + // return [NSColor whiteColor]; + if ( [self isHighlighted] && [[[self controlView] window] firstResponder] == [self controlView] ) + { + if ( [[[self controlView] window] isKindOfClass:[NSPanel class]] && [[[self controlView] window] isKeyWindow] ) + return [NSColor whiteColor]; + + else if ( [[[self controlView] window] isMainWindow] && [[[self controlView] window] isKeyWindow] ) + return [NSColor whiteColor]; + + else + return [super textColor]; + } + + else if ( [self isHighlighted] && [(NSTableView*)[self controlView] editedRow] != -1 && [(NSTableView*)[self controlView] editedColumn] != -1 ) + return [NSColor whiteColor]; + else + return [super textColor]; + +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + + NSString *time_string = nil, *day_string = nil; + NSSize time_size, day_size; + NSPoint time_origin, day_origin; + + NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [self font], NSFontAttributeName, [self textColor], NSForegroundColorAttributeName, nil]; + + if ([self isSelected]) + { + // prepare the text in white. + [attrs setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName]; + + // bold the text if that option has been requested + if ( [self boldsWhenSelected] ) + { + NSFont *originalFont = [attrs objectForKey:NSFontAttributeName]; + if ( originalFont ) { + NSFont *boldedFont = [[NSFontManager sharedFontManager] convertFont:originalFont toHaveTrait:NSBoldFontMask]; + if ( boldedFont ) + [attrs setObject:boldedFont forKey:NSFontAttributeName]; + } + } + } + + NSDate *date = [self objectValue]; + if ( _dayFormatter == nil ) { + + //_formatter = [[NSDateFormatter alloc] init]; + _dayFormatter = [[NSDateFormatter alloc] init]; + _timeFormatter = [[NSDateFormatter alloc] init]; + + [_dayFormatter setDateStyle:NSDateFormatterLongStyle]; + [_dayFormatter setTimeStyle:NSDateFormatterNoStyle]; + + [_timeFormatter setDateStyle:NSDateFormatterNoStyle]; + [_timeFormatter setTimeStyle:NSDateFormatterShortStyle]; + } + + if ( cellFrame.size.width > widthWas && [_dayFormatter dateStyle] != NSDateFormatterLongStyle ) { + #ifdef __DEBUG__ + NSLog(@"%@ %s - growing", [self className], _cmd); + #endif + + // we're growing, give the original date and time format another chance + //[_timeFormatter setDateFormat:nil]; + [_dayFormatter setDateStyle:NSDateFormatterLongStyle]; + [_timeFormatter setTimeStyle:NSDateFormatterShortStyle]; + } + + widthWas = cellFrame.size.width; + + // + // draw the day on the far left in medium style + //[_formatter setDateStyle:NSDateFormatterMediumStyle]; + //[_formatter setTimeStyle:NSDateFormatterNoStyle]; + +CalculateDateSize: + + + //day_string = [_formatter stringFromDate:date]; + day_string = [_dayFormatter stringFromDate:date]; + day_size = [day_string sizeWithAttributes:attrs]; + + day_origin.x = cellFrame.origin.x + kEdgeInset; + day_origin.y = cellFrame.origin.y + cellFrame.size.height/2 - day_size.height/2; + + + +CalculateTimeSize: + + // + // draw the time on the far right in short style + //[_formatter setDateStyle:NSDateFormatterNoStyle]; + //[_formatter setTimeStyle:NSDateFormatterShortStyle]; + + //time_string = [_formatter stringFromDate:date]; + time_string = [_timeFormatter stringFromDate:date]; + time_size = [time_string sizeWithAttributes:attrs]; + + time_origin.x = cellFrame.origin.x + cellFrame.size.width - time_size.width - kEdgeInset; + time_origin.y = cellFrame.origin.y + cellFrame.size.height/2 - time_size.height/2; + + + if ( time_origin.x < day_origin.x + day_size.width + kEdgeInset*3 ) { + + // + // the time doesn't fit - first try making the date smaller + if ( /* alreadyJumpedOnNoTime == NO && */ [_dayFormatter dateStyle] == NSDateFormatterLongStyle ) { + #ifdef __DEBUG__ + NSLog(@"%@ %s - recalculating date style to NSDateFormatterMediumStyle", [self className], _cmd); + #endif + + [_dayFormatter setDateStyle:NSDateFormatterMediumStyle]; + goto CalculateDateSize; + } + else if ( alreadyJumpedOnNoTime == NO && [_dayFormatter dateStyle] == NSDateFormatterMediumStyle ) { + #ifdef __DEBUG__ + NSLog(@"%@ %s - recalculating date style to NSDateFormatterShortStyle", [self className], _cmd); + #endif + + [_dayFormatter setDateStyle:NSDateFormatterShortStyle]; + goto CalculateDateSize; + } + + // + // the time doesn't fit, make it even shorter + //[_formatter setDateFormat:@"h:mm"]; + if ( ![shorter_time isEqualToString:[_timeFormatter dateFormat]] ) + [_timeFormatter setDateFormat:shorter_time]; + + //time_string = [_formatter stringFromDate:date]; + time_string = [_timeFormatter stringFromDate:date]; + time_size = [time_string sizeWithAttributes:attrs]; + + time_origin.x = cellFrame.origin.x + cellFrame.size.width - time_size.width - kEdgeInset; + time_origin.y = cellFrame.origin.y + cellFrame.size.height/2 - time_size.height/2; + + // + // if it still doesn't fit! + if ( time_origin.x < day_origin.x + day_size.width + kEdgeInset*3 ) { + + // + // time won't fit, reset date back to medium and recalculate the size + if ( alreadyJumpedOnNoTime == NO && [_dayFormatter dateStyle] == NSDateFormatterShortStyle ) + { + [_dayFormatter setDateStyle:NSDateFormatterMediumStyle]; + alreadyJumpedOnNoTime = YES; + goto CalculateDateSize; + } + else if ( alreadyJumpedOnNoTime == NO && [_dayFormatter dateStyle] == NSDateFormatterMediumStyle ) + { + [_dayFormatter setDateStyle:NSDateFormatterLongStyle]; + alreadyJumpedOnNoTime = YES; + goto CalculateDateSize; + } + else + { + day_string = [_dayFormatter stringFromDate:date]; + day_size = [day_string sizeWithAttributes:attrs]; + + day_origin.x = cellFrame.origin.x + kEdgeInset; + day_origin.y = cellFrame.origin.y + cellFrame.size.height/2 - day_size.height/2; + + // + // zero time string + [_timeFormatter setDateFormat:[NSString string]]; + time_string = [_timeFormatter stringFromDate:date]; + + alreadyJumpedOnNoTime = NO; + } + + } + + //[_formatter setDateFormat:nil]; + + } + + // + // only draw the time string if it fits at all, even after being shortened + if ( time_string != nil && [time_string length] != 0 ) + //if ( time_origin.x >= day_origin.x + day_size.width + kEdgeInset*3 ) + [time_string drawAtPoint:time_origin withAttributes:attrs]; + + // + // actually draw the day + if ( day_string != nil ) + [day_string drawAtPoint:day_origin withAttributes:attrs]; + + alreadyJumpedOnNoTime = NO; +} + +- (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + return nil; +} + +@end diff --git a/PDFavorite.h b/PDFavorite.h new file mode 100644 index 0000000..e7e5168 --- /dev/null +++ b/PDFavorite.h @@ -0,0 +1,57 @@ +// +// PDFavorite.h +// SproutedInterface +// +// Created by Philip Dow on 3/17/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#define PDFavoriteNoHover 0 +#define PDFavoriteHover 1 +#define PDFavoriteMouseDown 2 + +@interface PDFavorite : NSView { + + NSString *_title; + //NSAttributedString *_attributedTitle; + id _identifier; + //NSArray *_subElements; + + NSSize _idealSize; + int _state; + + int label; + BOOL drawsLabel; +} + +- (id) initWithFrame:(NSRect)frame title:(NSString*)title identifier:(id)identifier; + +- (NSString*) title; +- (void) setTitle:(NSString*)title; + +//- (NSAttributedString*) attributedTitle; +//- (void) setAttributedTitle:(NSAttributedString*)title; + +- (id) identifier; +- (void) setIdentifier:(id)identifier; + +- (int) state; +- (void) setState:(int)state; + +- (int) label; +- (void) setLabel:(int)aLabel; + +- (BOOL) drawsLabel; +- (void) setDrawsLabel:(BOOL)draws; + +- (NSAttributedString*) generateAttributedTitle:(NSString*)title; +- (NSAttributedString*) generateHoverAttributedTitle:(NSString*)title; +- (NSSize) idealSize; +- (NSImage*) image; + +- (void) _drawLabel:(NSRect)rect; + +@end diff --git a/PDFavorite.m b/PDFavorite.m new file mode 100644 index 0000000..6ed709d --- /dev/null +++ b/PDFavorite.m @@ -0,0 +1,304 @@ +// +// PDFavorite.m +// SproutedInterface +// +// Created by Philip Dow on 3/17/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import +#import + +static NSDictionary* TitleAttributes() +{ + static NSDictionary *textAttributes = nil; + if ( textAttributes == nil ) + { + NSMutableParagraphStyle *paragraphStyle; + + paragraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; + [paragraphStyle setAlignment:NSLeftTextAlignment]; + [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail]; + + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:11], NSFontAttributeName, + [NSColor colorWithCalibratedRed:0.20 green:0.20 blue:0.20 alpha:1.0], NSForegroundColorAttributeName, + paragraphStyle, NSParagraphStyleAttributeName, nil]; + } + return textAttributes; +} + +static NSDictionary* HoverTitleAttributes() +{ + static NSDictionary *textAttributes = nil; + if ( textAttributes == nil ) + { + NSMutableParagraphStyle *paragraphStyle; + + paragraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; + [paragraphStyle setAlignment:NSLeftTextAlignment]; + [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail]; + + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:11], NSFontAttributeName, + [NSColor colorWithCalibratedRed:0.98 green:0.98 blue:0.98 alpha:1.0], NSForegroundColorAttributeName, + paragraphStyle, NSParagraphStyleAttributeName, nil]; + } + return textAttributes; +} + +@implementation PDFavorite + +- (id) initWithFrame:(NSRect)frame title:(NSString*)title identifier:(id)identifier { + + if ( self = [self initWithFrame:frame] ) { + + _state = PDFavoriteNoHover; + _idealSize.width = -1; + + label = 0; + + if ( title ) + _title = [title copyWithZone:[self zone]]; + else + _title = [[NSString alloc] initWithString:@""]; + + if ( identifier ) + _identifier = [identifier copyWithZone:[self zone]]; + else + _identifier = [[NSString alloc] initWithString:@""]; + + } + + return self; + +} + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + } + return self; +} + +- (void) dealloc { + + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + [_title release]; + [_identifier release]; + + [super dealloc]; +} + +#pragma mark - + +- (void)drawRect:(NSRect)rect +{ + // Drawing code here. + + NSRect bds = [self bounds]; + NSAttributedString *textStr; + + NSRect stateRect = bds; + NSBezierPath *roundedRect; + + switch ( _state ) { + + case PDFavoriteHover: + + stateRect.origin.x+=1; stateRect.origin.y+=2; stateRect.size.width-=1; stateRect.size.height-=5; + + roundedRect = [NSBezierPath bezierPathWithRoundedRect:stateRect cornerRadius:8.0]; + [[NSColor colorWithCalibratedRed:0.6 green:0.6 blue:0.6 alpha:1.0] set]; + [roundedRect fill]; + + textStr = [self generateHoverAttributedTitle:[self title]]; + break; + + case PDFavoriteMouseDown: + + stateRect.origin.x+=1; stateRect.origin.y+=2; stateRect.size.width-=1; stateRect.size.height-=5; + + roundedRect = [NSBezierPath bezierPathWithRoundedRect:stateRect cornerRadius:8.0]; + [[NSColor colorWithCalibratedRed:0.45 green:0.45 blue:0.45 alpha:1.0] set]; + [roundedRect fill]; + + textStr = [self generateHoverAttributedTitle:[self title]]; + break; + + default: + + if ( [self drawsLabel] && [self label] != 0 ) + { + stateRect.origin.x+=1; stateRect.origin.y+=2; stateRect.size.width-=1; stateRect.size.height-=5; + [self _drawLabel:stateRect]; + } + + textStr = [self generateAttributedTitle:[self title]]; + break; + + } + + NSSize strSize = [textStr size]; + NSRect strBds = NSMakeRect( bds.size.width/2-strSize.width/2, + bds.size.height/2-strSize.height/2, + strSize.width, strSize.height ); + + + [textStr drawInRect:strBds]; + +} + +- (void) _drawLabel:(NSRect)rect +{ + NSRect inset = rect; + NSColor *gradientStart = [NSColor colorForLabel:[self label] gradientEnd:YES]; + NSColor *gradientEnd = [NSColor colorForLabel:[self label] gradientEnd:NO]; + NSBezierPath *aPath = [NSBezierPath bezierPathWithRoundedRect:inset cornerRadius:8.0]; // 7.3 + + [[NSColor colorWithCalibratedWhite:0.6 alpha:1.0] set]; + [aPath linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; +} + +#pragma mark - + +- (NSString*) title { return _title; } + +- (void) setTitle:(NSString*)title { + if ( _title != title ) { + [_title release]; + _title = [title copyWithZone:[self zone]]; + } +} + + +- (id) identifier { return _identifier; } + +- (void) setIdentifier:(id)identifier { + if ( _identifier != identifier ) { + [_identifier release]; + _identifier = [identifier copyWithZone:[self zone]]; + } +} + +- (int) state { return _state; } + +- (void) setState:(int)state { + + _state = state; + +} + +- (int) label +{ + return label; +} + +- (void) setLabel:(int)aLabel +{ + label = aLabel; + [self setNeedsDisplay:YES]; +} + +- (BOOL) drawsLabel +{ + return drawsLabel; +} + +- (void) setDrawsLabel:(BOOL)draws +{ + drawsLabel = draws; + [self setNeedsDisplay:YES]; +} + +#pragma mark - + +- (NSAttributedString*) generateAttributedTitle:(NSString*)title +{ + NSAttributedString *attrString = [[NSAttributedString alloc] + initWithString:( title != nil ? title : @"" ) attributes:TitleAttributes()]; + + return [attrString autorelease]; +} + +- (NSAttributedString*) generateHoverAttributedTitle:(NSString*)title +{ + NSAttributedString *attrString = [[NSAttributedString alloc] + initWithString:( title != nil ? title : @"" ) attributes:HoverTitleAttributes()]; + + return [attrString autorelease]; +} + +- (NSSize) idealSize { + + if ( _idealSize.width != -1 ) + + return _idealSize; + + else { + + NSAttributedString *theAttributedTitle = [self generateAttributedTitle:[self title]]; + + _idealSize = [theAttributedTitle size]; + _idealSize.width+=16; + + // for extra label color + _idealSize.width+=4; + + return _idealSize; + + } +} + +- (NSImage*) image { + + NSRect bds = [self bounds]; + NSAttributedString *textStr; + + NSRect stateRect = bds; + NSBezierPath *roundedRect; + + NSSize idealSize = [self idealSize]; + idealSize.height = 22; + + NSImage *returnImage = [[NSImage alloc] initWithSize:idealSize]; + + [returnImage lockFocus]; + + stateRect.origin.x+=1; stateRect.origin.y+=2; stateRect.size.width-=1; stateRect.size.height-=5; + + roundedRect = [NSBezierPath bezierPathWithRoundedRect:stateRect cornerRadius:10.0]; + [[NSColor colorWithCalibratedRed:0.45 green:0.45 blue:0.45 alpha:1.0] set]; + [roundedRect fill]; + + textStr = [self generateHoverAttributedTitle:[self title]]; + + NSSize strSize = [textStr size]; + NSRect strBds = NSMakeRect( bds.size.width/2-strSize.width/2, + bds.size.height/2-strSize.height/2, + strSize.width, strSize.height ); + + [textStr drawInRect:strBds]; + + [returnImage unlockFocus]; + + return [returnImage autorelease]; + +} + +#pragma mark - + +- (BOOL)mouseDownCanMoveWindow +{ + return NO; +} + +@end diff --git a/PDFavoritesBar.h b/PDFavoritesBar.h new file mode 100644 index 0000000..a7d9555 --- /dev/null +++ b/PDFavoritesBar.h @@ -0,0 +1,92 @@ +// +// PDFavoritesBar.h +// SproutedInterface +// +// Created by Philip Dow on 3/17/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#define PDFavoritePboardType @"PDFavoritePboardType" +#define PDFavoritesDidChangeNotification @"PDFavoritesDidChangeNotification" + +#define PDFavoriteName @"name" +#define PDFavoriteID @"id" + +#import + +@class PDFavorite; + +@interface PDFavoritesBar : NSView { + + NSColor *_backgroundColor; + NSMutableArray *_favorites; + NSMutableArray *_vFavorites; + NSMutableArray *_trackingRects; + + NSPopUpButton *_morePop; + NSMenuItem *_popTitle; + + unsigned _eventFavorite; + BOOL _titleSheet; + + id delegate; + id _target; + SEL _action; + + NSMenu *contextMenu; + + BOOL drawsLabels; +} + +- (NSColor*) backgroundColor; +- (void) setBackgroundColor:(NSColor*)color; + +- (NSMutableArray*) favorites; +- (void) setFavorites:(NSArray*)favorites; + +- (id) delegate; +- (void) setDelegate:(id)anObject; + +- (id) target; +- (void) setTarget:(id)target; + +- (SEL) action; +- (void) setAction:(SEL)action; + +- (BOOL) drawsLabels; +- (void) setDrawsLabels:(BOOL)draws; + +- (IBAction) toggleDrawsLabels:(id)sender; + +- (void) sendEvent:(unsigned)sender; +- (NSDictionary*) eventFavorite; + +- (BOOL) addFavorite:(NSDictionary*)aFavorite atIndex:(unsigned)loc requestTitle:(BOOL)showSheet; +- (void) removeFavoriteAtIndex:(unsigned)loc; + +- (void) _generateFavoriteViews:(id)object; +- (void) _positionFavoriteViews:(id)object; + +- (void) _initiateDragOperation:(unsigned)favoriteIndex location:(NSPoint)dragStart event:(NSEvent*)theEvent; + +- (void) favoritesDidChange:(NSNotification*)aNotification; +- (void) _toolbarDidChangeVisible:(NSNotification*)aNotification; + +- (NSString*) _titleFromTitleSheet:(NSString*)defaultTitle; +- (void) _okaySheet:(id)sender; +- (void) _cancelSheet:(id)sender; + +- (PDFavorite*) favoriteWithIdentifier:(id)anIdentifier; +- (void) setLabel:(int)label forFavorite:(PDFavorite*)aFavorite; +- (void) rescanLabels; + +- (NSRect) frameOfFavoriteAtIndex:(int)theIndex; + +@end + +@interface NSObject (FavoritesBarDelegate) + +- (int) favoritesBar:(PDFavoritesBar*)aFavoritesBar labelOfItemWithIdentifier:(NSString*)anIdentifier; + +@end diff --git a/PDFavoritesBar.m b/PDFavoritesBar.m new file mode 100644 index 0000000..98f4a98 --- /dev/null +++ b/PDFavoritesBar.m @@ -0,0 +1,963 @@ +// +// PDFavoritesBar.m +// SproutedInterface +// +// Created by Philip Dow on 3/17/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +#import + +#import +#import + +@implementation PDFavoritesBar + +- (id)initWithFrame:(NSRect)frame { + if ( self = [super initWithFrame:frame] ) + { + // Initialization code here. + + _titleSheet = YES; + + _backgroundColor = [[NSColor windowBackgroundColor] retain]; + _favorites = [[NSMutableArray alloc] init]; + _trackingRects = [[NSMutableArray alloc] init]; + + // CHANGES + _vFavorites = [[NSMutableArray alloc] init]; + + NSImage *moreImage = [NSImage imageNamed:@"more.tif"]; + _popTitle = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + + [_popTitle setImage:moreImage]; + + _morePop = [[NSPopUpButton alloc] initWithFrame:NSMakeRect( frame.size.width - 28, 6, 28, 10 ) pullsDown:YES]; + + [_morePop setBordered:NO]; + [_morePop setTarget:self]; + [_morePop setAction:@selector(_favoriteFromPop:)]; + [_morePop setAutoresizingMask:NSViewMinXMargin]; + [_morePop setHidden:YES]; + [[_morePop menu] addItem:_popTitle]; + [[_morePop cell] setArrowPosition:NSPopUpNoArrow]; + + [self addSubview:_morePop]; + + [self setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin]; + [self registerForDraggedTypes:[NSArray arrayWithObjects:PDFavoritePboardType, nil]]; + + [self setPostsBoundsChangedNotifications:YES]; + [self setPostsFrameChangedNotifications:YES]; + + //[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_generateFavoriteViews:) + //name:NSViewBoundsDidChangeNotification object:self]; + //[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_generateFavoriteViews:) + //name:NSViewFrameDidChangeNotification object:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_positionFavoriteViews:) + name:NSViewBoundsDidChangeNotification object:self]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_positionFavoriteViews:) + name:NSViewFrameDidChangeNotification object:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(favoritesDidChange:) + name:PDFavoritesDidChangeNotification object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_toolbarDidChangeVisible:) + name:PDToolbarDidHideNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_toolbarDidChangeVisible:) + name:PDToolbarDidShowNotification object:nil]; + + // label bindings + [self bind:@"drawsLabels" toObject:[NSUserDefaultsController sharedUserDefaultsController] + withKeyPath:@"values.FavoritesBarDrawsLabels" options:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], NSNullPlaceholderBindingOption, nil]]; + + // set the initial favorites + //NSArray *theFavorites = [[NSUserDefaults standardUserDefaults] arrayForKey:@"PDFavoritesBar"]; + //if ( theFavorites == nil ) theFavorites = [NSArray array]; + //[self setFavorites:theFavorites]; + + // build the contextual menu + contextMenu = [[NSMenu alloc] initWithTitle:@"Context"]; + + NSString *aTitle = NSLocalizedStringFromTableInBundle(@"draw labels", @"PDFavoritesBar", [NSBundle bundleWithIdentifier:@"com.sprouted.interface"], @""); + NSMenuItem *labelsToggle = [[[NSMenuItem alloc] initWithTitle:aTitle action:@selector(toggleDrawsLabels:) keyEquivalent:@""] autorelease]; + + [labelsToggle setTarget:self]; + [contextMenu addItem:labelsToggle]; + [self setMenu:contextMenu]; + + } + return self; +} + +- (void) dealloc { + + #ifdef __DEBUG__ + NSLog(@"%@ %s - beginning",[self className],_cmd); + #endif + + int i; + for ( i = 0; i < [_trackingRects count]; i++ ) + [self removeTrackingRect:[[_trackingRects objectAtIndex:i] intValue]]; + + [_trackingRects release]; + [_vFavorites release]; + + [_backgroundColor release]; + [_favorites release]; + [_morePop release]; + [_popTitle release]; + + [contextMenu release]; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + //[self unbind:@"favorites"]; + [self unregisterDraggedTypes]; + + #ifdef __DEBUG__ + NSLog(@"%@ %s - ending",[self className],_cmd); + #endif + + [super dealloc]; +} + +#pragma mark - + +- (void)drawRect:(NSRect)rect { + // Drawing code here. + + NSRect bds = [self bounds]; + + // fill with the label bar color + [_backgroundColor set]; + NSRectFillUsingOperation(bds, NSCompositeSourceOver); + + // draw a gradient over that + NSColor *gradientStart = [NSColor colorWithCalibratedWhite:0.86 alpha:0.8]; // 0.6 // 0.82 + NSColor *gradientEnd = [NSColor colorWithCalibratedWhite:0.88 alpha:0.8]; // 0.92 + [[NSBezierPath bezierPathWithRect:bds] linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + NSGraphicsContext *context = [NSGraphicsContext currentContext]; + [context saveGraphicsState]; + [context setShouldAntialias:NO]; + + [[NSColor darkGrayColor] set]; + [[NSBezierPath bezierPathWithLineFrom:NSMakePoint(0,bds.size.height-1) to:NSMakePoint(bds.size.width,bds.size.height-1) lineWidth:1] stroke]; + + [[NSColor colorWithCalibratedWhite:1.0 alpha:0.82] set]; + [[NSBezierPath bezierPathWithLineFrom:NSMakePoint(0,bds.size.height-2) to:NSMakePoint(bds.size.width,bds.size.height-2) lineWidth:1] stroke]; + + [context restoreGraphicsState]; +} + +#pragma mark - + +- (NSColor*) backgroundColor +{ + return _backgroundColor; + } + +- (void) setBackgroundColor:(NSColor*)color +{ + if ( _backgroundColor != color ) + { + [_backgroundColor release]; + _backgroundColor = [color copyWithZone:[self zone]]; + } +} + +- (NSMutableArray*) favorites +{ + return _favorites; +} + +- (void) setFavorites:(NSArray*)favorites +{ + if ( _favorites != favorites ) + { + [_favorites release]; + _favorites = [favorites retain]; + + // regenerate the favorites view + [self _generateFavoriteViews:nil]; + + // and re-position + //[self _positionFavoriteViews:nil]; + } +} + +#pragma mark - + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate:(id)anObject +{ + delegate = anObject; +} + +- (id) target +{ + return _target; +} + +- (void) setTarget:(id)target +{ + _target = target; +} + +- (SEL) action +{ + return _action; +} + +- (void) setAction:(SEL)action +{ + _action = action; +} + +- (BOOL) drawsLabels +{ + return drawsLabels; +} + +- (void) setDrawsLabels:(BOOL)draws +{ + drawsLabels = draws; + [_vFavorites setValue:[NSNumber numberWithBool:drawsLabels] forKey:@"drawsLabel"]; +} + +- (IBAction) toggleDrawsLabels:(id)sender +{ + [[NSUserDefaults standardUserDefaults] + setBool:![[NSUserDefaults standardUserDefaults] boolForKey:@"FavoritesBarDrawsLabels"] + forKey:@"FavoritesBarDrawsLabels"]; + + //[self setDrawsLabels:![self drawsLabels]]; + //[_vFavorites setValue:[NSNumber numberWithBool:[self drawsLabels]] forKey:@"drawsLabel"]; + //[_vFavorites setValue:[NSNumber numberWithBool:YES] forKey:@"needsDisplay"]; +} + +#pragma mark - + +- (void) sendEvent:(unsigned)sender { + + _eventFavorite = sender; + if ( [[self target] respondsToSelector:[self action]] ) + [[self target] performSelector:[self action] withObject:self]; + +} + +- (NSDictionary*) eventFavorite { + + return [[self favorites] objectAtIndex:_eventFavorite]; + +} + +#pragma mark - + +- (BOOL) addFavorite:(NSDictionary*)aFavorite atIndex:(unsigned)loc requestTitle:(BOOL)showSheet { + + NSMutableArray *tempFavs = [[[self favorites] mutableCopyWithZone:[self zone]] autorelease]; + if ( tempFavs == nil ) tempFavs = [NSMutableArray array]; + + if ( loc > [tempFavs count] ) + loc = [tempFavs count]; + else if ( loc < 0 ) + loc = 0; + + // + // show a sheet if requested + if ( showSheet ) { + + NSString *newTitle = [self _titleFromTitleSheet:[aFavorite objectForKey:PDFavoriteName]]; + if ( !newTitle ) return NO; + + NSMutableDictionary *modAddition = [aFavorite mutableCopyWithZone:[self zone]]; + [modAddition setObject:newTitle forKey:PDFavoriteName]; + + [tempFavs insertObject:modAddition atIndex:loc]; + + [modAddition release]; + + } + else { + + [tempFavs insertObject:aFavorite atIndex:loc]; + + } + + [self setFavorites:tempFavs]; + [[NSUserDefaults standardUserDefaults] setObject:tempFavs forKey:@"PDFavoritesBar"]; + [[NSNotificationCenter defaultCenter] postNotificationName:PDFavoritesDidChangeNotification + object:self userInfo:[NSDictionary dictionaryWithObject:tempFavs forKey:@"favorites"]]; + + return YES; + +} + +- (void) removeFavoriteAtIndex:(unsigned)loc +{ + NSMutableArray *tempFavs = [[[self favorites] mutableCopyWithZone:[self zone]] autorelease]; + if ( tempFavs == nil ) tempFavs = [NSMutableArray array]; + + if ( loc > [tempFavs count] ) + loc = [tempFavs count]; + else if ( loc < 0 ) + loc = 0; + + [tempFavs removeObjectAtIndex:loc]; + + [self setFavorites:tempFavs]; + [self _generateFavoriteViews:self]; + [self _positionFavoriteViews:self]; + + [[NSUserDefaults standardUserDefaults] setObject:tempFavs forKey:@"PDFavoritesBar"]; + [[NSNotificationCenter defaultCenter] postNotificationName:PDFavoritesDidChangeNotification + object:self userInfo:[NSDictionary dictionaryWithObject:tempFavs forKey:@"favorites"]]; +} + +#pragma mark - + +- (void) favoritesDidChange:(NSNotification*)aNotification +{ + if ( [aNotification object] != self ) + { + NSArray *theFavorites = [[aNotification userInfo] objectForKey:@"favorites"]; + [self setFavorites:theFavorites]; + } +} + +- (PDFavorite*) favoriteWithIdentifier:(id)anIdentifier +{ + int anIndex = [[_vFavorites valueForKey:@"identifier"] indexOfObject:anIdentifier]; + if ( anIndex == NSNotFound ) return nil; + else return [_vFavorites objectAtIndex:anIndex]; +} + +- (void) setLabel:(int)label forFavorite:(PDFavorite*)aFavorite +{ + [aFavorite setLabel:label]; + [aFavorite setNeedsDisplay:YES]; +} + +- (void) rescanLabels +{ + if ( ![[self delegate] respondsToSelector:@selector(favoritesBar:labelOfItemWithIdentifier:)] ) + return; + + PDFavorite *aFavorite; + NSEnumerator *enumerator = [_vFavorites objectEnumerator]; + + while ( aFavorite = [enumerator nextObject] ) + [aFavorite setLabel:[[self delegate] favoritesBar:self labelOfItemWithIdentifier:[aFavorite identifier]]]; +} + +#pragma mark - + + +- (void) _generateFavoriteViews:(id)object { + + #ifdef __DEBUG__ + NSLog(@"%@ %s - beginning",[self className],_cmd); + #endif + + int i; + + [_vFavorites makeObjectsPerformSelector:@selector(removeFromSuperview)]; + [_vFavorites removeAllObjects]; + + for ( i = 0; i < [_favorites count]; i++ ) { + + NSDictionary *aFavDict = [_favorites objectAtIndex:i]; + PDFavorite *aFavorite = [[[PDFavorite alloc] initWithFrame:NSMakeRect(0,0,40,22) + title:[aFavDict objectForKey:PDFavoriteName] identifier:[aFavDict objectForKey:PDFavoriteID]] autorelease]; + + if ( [[self delegate] respondsToSelector:@selector(favoritesBar:labelOfItemWithIdentifier:)] ) + [aFavorite setLabel:[[self delegate] favoritesBar:self labelOfItemWithIdentifier:[aFavorite identifier]]]; + + [_vFavorites addObject:aFavorite]; + + } + + [_vFavorites setValue:[NSNumber numberWithBool:[self drawsLabels]] forKey:@"drawsLabel"]; + + #ifdef __DEBUG__ + NSLog(@"%@ %s - ending",[self className],_cmd); + #endif +} + +- (void) _positionFavoriteViews:(id)object +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s - beginning",[self className],_cmd); + #endif + + int i; + int totalWidth = 10; + NSRect bds = [self bounds]; + + for ( i = 0; i < [_trackingRects count]; i++ ) + [self removeTrackingRect:[[_trackingRects objectAtIndex:i] intValue]]; + + [_trackingRects removeAllObjects]; + //[_vFavorites makeObjectsPerformSelector:@selector(removeFromSuperview)]; + + [_morePop removeAllItems]; + [_morePop setHidden:YES]; + + for ( i = 0; i < [_vFavorites count]; i++ ) { + + PDFavorite *aFavorite = [_vFavorites objectAtIndex:i]; + + NSSize idealSize = [aFavorite idealSize]; + + if ( totalWidth + idealSize.width < bds.size.width - 10 ) { + + NSRect thisRect = NSMakeRect(totalWidth, 0, idealSize.width, 22); + NSTrackingRectTag thisTrack = [self addTrackingRect:thisRect owner:self userData:nil assumeInside:NO]; + + [aFavorite setFrame:thisRect]; + [_trackingRects addObject:[NSNumber numberWithInt:thisTrack]]; + + if ( [aFavorite superview] == nil ) + [self addSubview:aFavorite]; + + } + else { + + if ( [aFavorite superview] != nil ) + [aFavorite removeFromSuperview]; + + if ( [_morePop isHidden] ) + [_morePop setHidden:NO]; + if ( [_morePop numberOfItems] == 0 ) + [[_morePop menu] addItem:_popTitle]; + + NSMenuItem *thisItem = [[NSMenuItem alloc] initWithTitle:[aFavorite title] action:@selector(_favoriteFromPop:) keyEquivalent:@""]; + [thisItem setTag:i]; + [thisItem setTarget:self]; + [thisItem setRepresentedObject:aFavorite]; + + [[_morePop menu] addItem:thisItem]; + [thisItem release]; + } + + totalWidth+=idealSize.width+=4; + } + + #ifdef __DEBUG__ + NSLog(@"%@ %s - ending",[self className],_cmd); + #endif + +} + +/* +- (void) _generateFavoriteViews:(id)object { + + #ifdef __DEBUG__ + NSLog(@"%@ %s - beginning",[self className],_cmd); + #endif + + int i; + int totalWidth = 10; + NSRect bds = [self bounds]; + + if ( _trackingRects != nil ) + { + for ( i = 0; i < [_trackingRects count]; i++ ) + [self removeTrackingRect:[[_trackingRects objectAtIndex:i] intValue]]; + + [_trackingRects release]; + _trackingRects = nil; + } + + _trackingRects = [[NSMutableArray alloc] initWithCapacity:[_favorites count]]; + + if ( _vFavorites != nil ) + { + [_vFavorites makeObjectsPerformSelector:@selector(removeFromSuperview)]; + + [_vFavorites release]; + _vFavorites = nil; + } + + _vFavorites = [[NSMutableArray alloc] initWithCapacity:[_favorites count]]; + + for ( i = 0; i < [_favorites count]; i++ ) { + + NSDictionary *aFavDict = [_favorites objectAtIndex:i]; + PDFavorite *aFavorite = [[PDFavorite alloc] initWithFrame:NSMakeRect(0,0,40,22) + title:[aFavDict objectForKey:PDFavoriteName] identifier:[aFavDict objectForKey:PDFavoriteID]]; + + if ( [[self delegate] respondsToSelector:@selector(favoritesBar:labelOfItemWithIdentifier:)] ) + [aFavorite setLabel:[[self delegate] favoritesBar:self labelOfItemWithIdentifier:[aFavorite identifier]]]; + + [_vFavorites setValue:[NSNumber numberWithBool:[self drawsLabels]] forKey:@"drawsLabel"]; + [_vFavorites addObject:aFavorite]; + + } + + [_morePop removeAllItems]; + [_morePop setHidden:YES]; + + for ( i = 0; i < [_vFavorites count]; i++ ) { + + PDFavorite *aFavorite = [_vFavorites objectAtIndex:i]; + + NSSize idealSize = [aFavorite idealSize]; + + if ( totalWidth + idealSize.width < bds.size.width - 10 ) { + + NSRect thisRect = NSMakeRect(totalWidth, 0, idealSize.width, 22); + NSTrackingRectTag thisTrack = [self addTrackingRect:thisRect owner:self userData:nil assumeInside:NO]; + + [aFavorite setFrame:thisRect]; + [_trackingRects addObject:[NSNumber numberWithInt:thisTrack]]; + [self addSubview:aFavorite]; + + } + else { + + if ( [_morePop isHidden] ) + [_morePop setHidden:NO]; + if ( [_morePop numberOfItems] == 0 ) + [[_morePop menu] addItem:_popTitle]; + + NSMenuItem *thisItem = [[NSMenuItem alloc] initWithTitle:[aFavorite title] action:@selector(_favoriteFromPop:) keyEquivalent:@""]; + [thisItem setTag:i]; + [thisItem setTarget:self]; + [thisItem setRepresentedObject:aFavorite]; + + [[_morePop menu] addItem:thisItem]; + [thisItem release]; + } + + totalWidth+=idealSize.width+=4; + } + + #ifdef __DEBUG__ + NSLog(@"%@ %s - ending",[self className],_cmd); + #endif +} +*/ + +- (void) _favoriteFromPop:(id) sender { + + [self sendEvent:[sender tag]]; + +} + +#pragma mark - + +/* +- (void)viewDidMoveToSuperview { + + if ( [self superview] != nil ) + // [self _generateFavoriteViews:nil]; + [self _positionFavoriteViews:nil]; +} +*/ + +- (void)viewDidMoveToWindow { + + if ( [self window] != nil ) + // [self _generateFavoriteViews:nil]; + [self _positionFavoriteViews:nil]; +} + +#pragma mark - + +- (void)mouseEntered:(NSEvent *)theEvent { + + int i; + NSTrackingRectTag trackTag = [theEvent trackingNumber]; + + for ( i = 0; i < [_trackingRects count]; i++ ) { + if ( [[_trackingRects objectAtIndex:i] intValue] == trackTag ) { + [[_vFavorites objectAtIndex:i] setState:PDFavoriteHover]; + [[_vFavorites objectAtIndex:i] setNeedsDisplay:YES]; + break; + } + } +} + +- (void)mouseExited:(NSEvent *)theEvent { + + int i; + NSTrackingRectTag trackTag = [theEvent trackingNumber]; + + for ( i = 0; i < [_trackingRects count]; i++ ) { + if ( [[_trackingRects objectAtIndex:i] intValue] == trackTag ) { + [[_vFavorites objectAtIndex:i] setState:PDFavoriteNoHover]; + [[_vFavorites objectAtIndex:i] setNeedsDisplay:YES]; + break; + } + } +} + +#pragma mark - + + +- (void)mouseDown:(NSEvent *)theEvent { + + // + // converts the event into a tab selection or close and + // sends ourself the message + // + + // enter my own even loop until we have some kind of result + BOOL keepOn = YES; + BOOL isInside = YES; + NSPoint localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + NSPoint originalPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + NSRect bds = [self bounds]; + NSRect innenRect; + + int i; + int totalWidth = 10; + + for ( i = 0; i < [_vFavorites count]; i++ ) { + + PDFavorite *aFavorite = [_vFavorites objectAtIndex:i]; + NSSize idealSize = [aFavorite idealSize]; + + if ( totalWidth + idealSize.width < bds.size.width - 10 ) { + + NSRect thisRect = NSMakeRect(totalWidth, 0, idealSize.width, 22); + if ( NSPointInRect(localPoint,thisRect) ) { + innenRect = thisRect; + break; + } + } + + totalWidth+=idealSize.width+=4; + + } + + if ( i < [_vFavorites count] ) { + + [[_vFavorites objectAtIndex:i] setState:PDFavoriteMouseDown]; + [(PDFavorite*)[_vFavorites objectAtIndex:i] display]; + + while (keepOn) { + + theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | + NSLeftMouseDraggedMask]; + + localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + isInside = [self mouse:localPoint inRect:innenRect]; + + switch ([theEvent type]) { + case NSLeftMouseDragged: + + if ( originalPoint.x - localPoint.x <= -10 || originalPoint.x - localPoint.x >= 10 || + originalPoint.y - localPoint.y <= -6 || originalPoint.y - localPoint.y >= 6 ) { + + [self _initiateDragOperation:i location:localPoint event:theEvent]; + keepOn = NO; + + } + + break; + + case NSLeftMouseUp: + + if (isInside) { + [self sendEvent:i]; + [[_vFavorites objectAtIndex:i] setState:PDFavoriteHover]; + [(PDFavorite*)[_vFavorites objectAtIndex:i] display]; + } + else { + [[_vFavorites objectAtIndex:i] setState:PDFavoriteNoHover]; + [(PDFavorite*)[_vFavorites objectAtIndex:i] display]; + } + + keepOn = NO; + break; + + default: + /* Ignore any other kind of event. */ + break; + } + }; + + } +} + +#pragma mark - + +- (BOOL)performDragOperation:(id )sender { + + NSPasteboard *pboard = [sender draggingPasteboard]; + //gets the dragging-specific pasteboard from the sender + + NSArray *types = [NSArray arrayWithObjects:PDFavoritePboardType, nil]; + //a list of types that we can accept + + NSString *desiredType = [pboard availableTypeFromArray:types]; + // one desired type + + if ( [desiredType isEqualToString:PDFavoritePboardType] ) { + + // + // determine where the drop is to occur and make it + + NSPoint localPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; + NSDictionary *favDict = [pboard propertyListForType:PDFavoritePboardType]; + + int i; + int totalWidth = 10; + BOOL requireTitle; + + for ( i = 0; i < [_vFavorites count]; i++ ) { + + PDFavorite *aFavorite = [_vFavorites objectAtIndex:i]; + NSSize idealSize = [aFavorite idealSize]; + + if ( localPoint.x < totalWidth + idealSize.width ) + break; + + totalWidth+=idealSize.width+=4; + + } + + requireTitle = ( [sender draggingSource] != self ); + + if ( [self addFavorite:favDict atIndex:i requestTitle:requireTitle] ) + { + // regenerate and reposition favorite views + [self _generateFavoriteViews:nil]; + [self _positionFavoriteViews:nil]; + return YES; + } + else { + return NO; + } + + } + else { + + return NO; + + } + +} + +- (NSDragOperation)draggingEntered:(id )sender { + + if ( [sender draggingSource] == self ) + return NSDragOperationMove; + else + return NSDragOperationCopy; + +} + +- (NSDragOperation)draggingUpdated:(id )sender { + + if ( [sender draggingSource] == self ) + return NSDragOperationMove; + else + return NSDragOperationCopy; + +} + +- (BOOL)prepareForDragOperation:(id )sender { + + return YES; + +} + +- (void) _initiateDragOperation:(unsigned)favoriteIndex location:(NSPoint)dragStart event:(NSEvent*)theEvent { + + NSSize dragOffset = NSMakeSize(0.0, 0.0); + NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; + NSImage *dragImage = [(PDFavorite*)[_vFavorites objectAtIndex:favoriteIndex] image]; + + //NSPoint diff; + //dragOffset.width = [[_vFavorites objectAtIndex:favoriteIndex] idealSize].width/2; + + dragStart.x = dragStart.x - [dragImage size].width/2; + dragStart.y = dragStart.y - [dragImage size].height/2; + + [pboard declareTypes:[NSArray arrayWithObjects:PDFavoritePboardType, nil] owner:self]; + [pboard setPropertyList:[_favorites objectAtIndex:favoriteIndex] forType:PDFavoritePboardType]; + + [self removeFavoriteAtIndex:favoriteIndex]; + + [self dragImage:dragImage at:dragStart offset:dragOffset event:theEvent pasteboard:pboard source:self slideBack:NO]; + +} + +#pragma mark - + +- (NSString*) _titleFromTitleSheet:(NSString*)defaultTitle { + + int result; + NSString *returnValue; + NSBundle *interfaceBundle = [NSBundle bundleWithIdentifier:@"com.sprouted.interface"]; + + // + // build the window and components + NSRect contentRect = NSMakeRect(0,0,259,127); + NSWindow *sheetWin = [[NSWindow alloc] initWithContentRect:contentRect styleMask:(NSTitledWindowMask|NSResizableWindowMask) + backing:NSBackingStoreBuffered defer:YES]; + + NSRect frameRect = [sheetWin frameRectForContentRect:contentRect]; + [sheetWin setMaxSize:NSMakeSize(600, frameRect.size.height)]; + [sheetWin setMinSize:NSMakeSize(259, frameRect.size.height)]; + + NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(17,90,225,17)]; + [label setStringValue:NSLocalizedStringFromTableInBundle(@"Favorites Name",@"PDFavoritesBar",interfaceBundle,@"")]; // localize + [label setSelectable:NO]; + [label setEditable:NO]; + [label setBezeled:NO]; + [label setBordered:NO]; + [label setDrawsBackground:NO]; + [[label cell] setControlSize:NSRegularControlSize]; + [label setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + + NSTextField *title = [[NSTextField alloc] initWithFrame:NSMakeRect(20,60,219,22)]; + [title setStringValue:defaultTitle]; + [title setSelectable:YES]; + [title setEditable:YES]; + [title setBezeled:YES]; + [title setDrawsBackground:YES]; + [title setBezelStyle:NSTextFieldSquareBezel]; + [title setAutoresizingMask:NSViewWidthSizable]; + [[title cell] setScrollable:YES]; + [[title cell] setControlSize:NSRegularControlSize]; + [title setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + + NSButton *cancel = [[NSButton alloc] initWithFrame:NSMakeRect(81,12,82,32)]; + [cancel setTarget:self]; + [cancel setAction:@selector(_cancelSheet:)]; + [cancel setTitle:NSLocalizedStringFromTableInBundle(@"Cancel",@"PDFavoritesBar",interfaceBundle,@"")]; // localize + [cancel setBezelStyle:NSRoundedBezelStyle]; + [cancel setButtonType:NSMomentaryPushInButton]; + [cancel setKeyEquivalent:@"\E"]; + [cancel setAutoresizingMask:NSViewMinXMargin]; + [[cancel cell] setControlSize:NSRegularControlSize]; + [cancel setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + + NSButton *okay = [[NSButton alloc] initWithFrame:NSMakeRect(163,12,82,32)]; + [okay setTarget:self]; + [okay setAction:@selector(_okaySheet:)]; + [okay setTitle:NSLocalizedStringFromTableInBundle(@"OK",@"PDFavoritesBar",interfaceBundle,@"")]; // localize + [okay setBezelStyle:NSRoundedBezelStyle]; + [okay setButtonType:NSMomentaryPushInButton]; + [okay setAutoresizingMask:NSViewMinXMargin]; + [[okay cell] setControlSize:NSRegularControlSize]; + [okay setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + + [[sheetWin contentView] addSubview:label]; + [[sheetWin contentView] addSubview:title]; + [[sheetWin contentView] addSubview:cancel]; + [[sheetWin contentView] addSubview:okay]; + + [sheetWin setDefaultButtonCell:[okay cell]]; + + // + // set the frame + NSString *winFrameStr = [[NSUserDefaults standardUserDefaults] stringForKey:@"PDFavoritesBarWindowFrame"]; + if ( winFrameStr ) [sheetWin setFrameFromString:winFrameStr]; + + // + // run the sheet + [NSApp beginSheet:sheetWin modalForWindow:[self window] modalDelegate: nil didEndSelector: nil contextInfo: nil]; + result = [NSApp runModalForWindow:sheetWin]; + + [title validateEditing]; + + [NSApp endSheet:sheetWin]; + [sheetWin close]; + + // + // grab the value + if ( result != NSRunStoppedResponse ) + returnValue = nil; + else + returnValue = [title stringValue]; + + // + // save the frame + winFrameStr = [sheetWin stringWithSavedFrame]; + [[NSUserDefaults standardUserDefaults] setObject:winFrameStr forKey:@"PDFavoritesBarWindowFrame"]; + + // + // clean up + [cancel release]; + [okay release]; + [label release]; + [title release]; + //[sheetWin release]; + + // + // return the value + return returnValue; + +} + +- (void) _okaySheet:(id)sender +{ + [NSApp stopModal]; +} + +- (void) _cancelSheet:(id)sender +{ + [NSApp abortModal]; +} + +#pragma mark - + +- (BOOL)mouseDownCanMoveWindow +{ + return NO; +} + +- (NSRect) frameOfFavoriteAtIndex:(int)theIndex +{ + if ( theIndex >= [_vFavorites count] ) + { + //NSLog(@"%@ %s - index %i is greater than avaible count %i", [self className], _cmd, theIndex, [_vFavorites count] - 1 ); + return NSZeroRect; + } + else + { + return [[_vFavorites objectAtIndex:theIndex] bounds]; + } +} + +#pragma mark - + +- (void) _toolbarDidChangeVisible:(NSNotification*)aNotification +{ + if ( [[self window] toolbar] == [aNotification object] ) + //[self _generateFavoriteViews:nil]; + [self _positionFavoriteViews:nil]; +} + +#pragma mark - + +- (BOOL)validateMenuItem:(NSMenuItem*)menuItem +{ + BOOL enabled = YES; + SEL action = [menuItem action]; + + if ( action == @selector(toggleDrawsLabels:) ) + [menuItem setState:( [self drawsLabels] ? NSOnState : NSOffState) ]; + + return enabled; +} + +@end diff --git a/PDFileInfoView.h b/PDFileInfoView.h new file mode 100644 index 0000000..dfcf528 --- /dev/null +++ b/PDFileInfoView.h @@ -0,0 +1,30 @@ +// +// PDFileInfoView.h +// SproutedInterface +// +// Created by Philip Dow on 6/17/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +typedef UInt32 PDFileInfoAlignment; +enum PDInfoAlignment { + PDFileInfoAlignLeft = 0, + PDFileInfoAlignRight = 1 +}; + + +@interface PDFileInfoView : NSView { + NSURL *url; + NSImageCell *cell; + PDFileInfoAlignment viewAlignment; +} + +- (NSURL*) url; +- (void) setURL:(NSURL*)aURL; + +- (void) _drawInfoForFile; + +@end diff --git a/PDFileInfoView.m b/PDFileInfoView.m new file mode 100644 index 0000000..0fdcc13 --- /dev/null +++ b/PDFileInfoView.m @@ -0,0 +1,842 @@ +// +// PDFileInfoView.m +// SproutedInterface +// +// Created by Philip Dow on 6/17/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import +#import + +#import + +#define kFilterOffset 14 + +@implementation PDFileInfoView + +static NSString* NoNullString( NSString *aString ) +{ + return ( aString == nil ? [NSString string] : aString ); +} + +- (id)initWithFrame:(NSRect)frameRect +{ + if ( self = [super initWithFrame:frameRect] ) + { + cell = [[NSImageCell alloc] initImageCell:nil]; + [cell setImageFrameStyle:NSImageFrameNone]; + + viewAlignment = PDFileInfoAlignLeft; + } + return self; +} + +- (void) dealloc +{ + [url release]; + [cell release]; + [super dealloc]; +} + +#pragma mark - + +- (BOOL) isFlipped +{ + return YES; +} + +- (void)drawRect:(NSRect)rect { + // Drawing code here. + + [super drawRect:rect]; + + if ( [self url] == nil ) + return; + + NSRect bds = [self bounds]; + NSSize cellSize = NSMakeSize(128,128); + + NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[[self url] path]]; + [icon setSize:NSMakeSize(128,128)]; + + /* + CIFilter *perspectiveTransform = [CIFilter filterWithName:@"CIPerspectiveTransform"]; + [perspectiveTransform setDefaults]; + + CIVector *trVector = [CIVector vectorWithX:128-kFilterOffset Y:128-kFilterOffset]; + CIVector *brVector = [CIVector vectorWithX:128-kFilterOffset Y:kFilterOffset]; + CIVector *tlVector = [CIVector vectorWithX:0 Y:128]; + CIVector *blVector = [CIVector vectorWithX:0 Y:0]; + + [perspectiveTransform setValue:[NSImage CIImageFromImage:icon] forKey:@"inputImage"]; + [perspectiveTransform setValue:trVector forKey:@"inputTopRight"]; + [perspectiveTransform setValue:brVector forKey:@"inputBottomRight"]; + [perspectiveTransform setValue:tlVector forKey:@"inputTopLeft"]; + [perspectiveTransform setValue:blVector forKey:@"inputBottomLeft"]; + + icon = [NSImage imageFromCIImage: [perspectiveTransform valueForKey:@"outputImage"] ]; + + NSImage *reflectedIcon = [icon reflectedImage:0.33]; + */ + + [[NSColor colorWithCalibratedWhite:1.0 alpha:1.0] set]; + NSRectFillUsingOperation(rect, NSCompositeSourceOver); + + /* + NSRect iconBoundary; + + if ( viewAlignment == PDFileInfoAlignLeft ) + iconBoundary = NSMakeRect( 5, bds.size.height - 5, cellSize.width+10, cellSize.height+10 ); + else if ( viewAlignment == PDFileInfoAlignRight ) + iconBoundary = NSMakeRect( bds.size.width - cellSize.width - 15, bds.size.height - 5, cellSize.width+10, cellSize.height+10 ); + */ + + NSRect iconRect; + //NSRect reflectedRect; + + if ( viewAlignment == PDFileInfoAlignLeft ) + iconRect = NSMakeRect( 10, /*bds.size.height - cellSize.height - 30*/ 30, cellSize.width, cellSize.height ); + else if ( viewAlignment == PDFileInfoAlignRight ) + iconRect = NSMakeRect( bds.size.width - cellSize.width - 10, /*bds.size.height - cellSize.height - 30*/ 30, cellSize.width, cellSize.height ); + + //reflectedRect = iconRect; + //reflectedRect.origin.y -= 128; + + [cell setObjectValue:icon]; + [cell setImageAlignment:NSImageAlignBottom]; + [cell drawWithFrame:iconRect inView:self]; + + //[cell setObjectValue:reflectedIcon]; + //[cell setImageAlignment:NSImageAlignTop]; + //[cell drawWithFrame:reflectedRect inView:self]; + + // draw the information for the resource + [self _drawInfoForFile]; + + // set up a cursor rect +} + +- (void) _drawInfoForFile +{ + unsigned long long kBytesInKilobyte = 1024; + unsigned long long kBytesInMegabyte = 1048576; + unsigned long long kBytesInGigabyte = 1073741824; + + NSString *path = [[self url] path]; + NSFileManager *fm = [NSFileManager defaultManager]; + + FSRef fsRef; + FSCatalogInfo catInfo; + // #warning returns 0 when the file is a package/directory + + if ( ![fm fileExistsAtPath:path] ) + { + return; + } + + NSSize cellSize = NSMakeSize(128,128); + NSSize stringSize; + NSRect stringRect; + unsigned int originaXOffset= ( viewAlignment == PDFileInfoAlignLeft ? 5*2 + cellSize.width + 10 : 5*2 ); + + NSBundle *myBundle = [NSBundle bundleWithIdentifier:@"com.sprouted.interface"]; + + NSMutableParagraphStyle *titleParagraph = [[[NSParagraphStyle defaultParagraphStyle] mutableCopyWithZone:[self zone]] autorelease]; + [titleParagraph setLineBreakMode:NSLineBreakByTruncatingMiddle]; + + NSShadow *textShadow = [[[NSShadow alloc] init] autorelease]; + + [textShadow setShadowColor:[NSColor colorWithCalibratedWhite:0.96 alpha:0.96]]; + [textShadow setShadowOffset:NSMakeSize(0,-1)]; + + NSDictionary *titleAttrs = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, + textShadow, NSShadowAttributeName, + titleParagraph, NSParagraphStyleAttributeName, nil]; + + NSDictionary *labelAttrs = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:11], NSFontAttributeName, + [NSColor colorWithCalibratedWhite:0.4 alpha:1.0], NSForegroundColorAttributeName, + textShadow, NSShadowAttributeName, nil]; + + NSDictionary *propertyAttrs = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, + titleParagraph, NSParagraphStyleAttributeName, + textShadow, NSShadowAttributeName, nil]; + + MDItemRef mdItem = MDItemCreate(NULL,(CFStringRef)path); + + // derive an icon for the file based on the unique file system file number + NSDictionary *fileAttributes = [fm fileAttributesAtPath:path traverseLink:NO]; + + + NSRect bds = [self bounds]; + NSRect iconRect; + NSRect infoRect; + NSRect infoReflectionRect; + + NSSize lwTitle, lwKind, lwSize, lwCreated, lwModified, lwLastOpened, lwMax; + + lwTitle = NSMakeSize(0, 0); + lwKind = NSMakeSize(0, 0); + lwSize = NSMakeSize(0, 0); + lwCreated = NSMakeSize(0, 0); + lwModified = NSMakeSize(0, 0); + lwLastOpened = NSMakeSize(0, 0); + lwMax = NSMakeSize(0, 0); + + if ( viewAlignment == PDFileInfoAlignLeft ) + iconRect = NSMakeRect( 10, bds.size.height - cellSize.height - 30, cellSize.width, cellSize.height + 20 ); + else if ( viewAlignment == PDFileInfoAlignRight ) + iconRect = NSMakeRect( bds.size.width - cellSize.width - 10, bds.size.height - cellSize.height - 30, cellSize.width, cellSize.height + 20 ); + + infoRect = iconRect; + infoRect.origin.x = originaXOffset; + infoRect.size.width = bds.size.width - originaXOffset; + + infoReflectionRect = infoRect; + infoReflectionRect.origin.y -= 152; + + //unsigned int xOffset = 0, yOffset = infoRect.size.height - 20; + unsigned int xOffset = originaXOffset, yOffset = 50; // 30 for the image cell, 50 is a bit arbitrary + + NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; + [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; + [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; + + // only draw if there is room for the information + if ( infoRect.size.width > 1 ) + { + + // first calculate all the label widths + + // labels + NSString *titleLabel = NSLocalizedStringFromTableInBundle(@"mditem title name", @"FileInfo", myBundle, @""); + NSString *typeLabel = NSLocalizedStringFromTableInBundle(@"mditem kind name", @"FileInfo", myBundle, @""); + NSString *sizeLabel = NSLocalizedStringFromTableInBundle(@"mditem size name", @"FileInfo", myBundle, @""); + NSString *dateLabel = NSLocalizedStringFromTableInBundle(@"mditem created name", @"FileInfo", myBundle, @""); + NSString *dateModifiedLabel = NSLocalizedStringFromTableInBundle(@"mditem modified name", @"FileInfo", myBundle, @""); + NSString *lastOpenedLabel = NSLocalizedStringFromTableInBundle(@"mditem lastopened name", @"FileInfo", myBundle, @""); + + // values + NSString *displayName = /*[fm displayNameAtPath:path];*/ [[path lastPathComponent] stringByDeletingPathExtension]; + + NSDate *dateCreated = [fileAttributes valueForKey:NSFileCreationDate]; + NSString *dateCreatedAsString = nil; + if ( dateCreated != nil ) dateCreatedAsString = NoNullString([dateFormatter stringFromDate:dateCreated]); + + NSDate *dateModified = [fileAttributes valueForKey:NSFileModificationDate]; + NSString *dateModifiedAsString = nil; + if ( dateModified != nil )dateModifiedAsString = NoNullString([dateFormatter stringFromDate:dateModified]); + + NSString *typeDescription = nil; + NSString *lastOpenedAsString = nil; + + if ( mdItem != NULL ) + { + typeDescription = [(NSString*)MDItemCopyAttribute(mdItem,(CFStringRef)kMDItemKind) autorelease]; + + NSDate *lastOpened = (NSDate*)[(NSString*)MDItemCopyAttribute(mdItem,(CFStringRef)kMDItemLastUsedDate) autorelease]; + if ( lastOpened != nil ) lastOpenedAsString = NoNullString([dateFormatter stringFromDate:lastOpened]); + + CFRelease(mdItem); + mdItem = NULL; + } + + // actually calculate the sizes + + if ( titleLabel != nil ) lwTitle = [titleLabel sizeWithAttributes:labelAttrs]; + if ( lwTitle.width > lwMax.width ) lwMax.width = lwTitle.width; + + if ( typeLabel != nil ) lwKind = [typeLabel sizeWithAttributes:labelAttrs]; + if ( lwKind.width > lwMax.width ) lwMax.width = lwKind.width; + + if ( sizeLabel != nil ) lwSize = [sizeLabel sizeWithAttributes:labelAttrs]; + if ( lwSize.width > lwMax.width ) lwMax.width = lwSize.width; + + if ( dateLabel != nil ) lwCreated = [dateLabel sizeWithAttributes:labelAttrs]; + if ( lwCreated.width > lwMax.width ) lwMax.width = lwCreated.width; + + if ( dateModifiedLabel != nil ) lwModified = [dateModifiedLabel sizeWithAttributes:labelAttrs]; + if ( lwModified.width > lwMax.width ) lwMax.width = lwModified.width; + + if ( lastOpenedLabel != nil ) lwLastOpened = [lastOpenedLabel sizeWithAttributes:labelAttrs]; + if ( lwLastOpened.width > lwMax.width ) lwMax.width = lwLastOpened.width; + + // max widths + unsigned int maxWidth = [self bounds].size.width - ( xOffset + lwMax.width + 8 + 20 ); + float greatestWidth = 0; + + // do the drawing + + // display name + if ( displayName != nil ) + { + // label + stringRect = NSMakeRect( xOffset + ( lwMax.width - lwTitle.width), yOffset, lwTitle.width, lwTitle.height ); + [titleLabel drawInRect:stringRect withAttributes:labelAttrs]; + + if ( xOffset + lwTitle.width > greatestWidth ) + greatestWidth = xOffset + lwTitle.width; + + xOffset += lwMax.width; + xOffset += 8; + + // value + stringSize = [displayName sizeWithAttributes:titleAttrs]; + [displayName drawInRect:NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ) withAttributes:titleAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + yOffset += stringSize.height; + xOffset = originaXOffset; + } + + // file type + if ( typeLabel != nil && typeDescription != nil ) + { + // label + stringRect = NSMakeRect( xOffset + ( lwMax.width - lwKind.width), yOffset, lwKind.width, lwKind.height ); + [typeLabel drawInRect:stringRect withAttributes:labelAttrs]; + + if ( xOffset + lwKind.width > greatestWidth ) + greatestWidth = xOffset + lwKind.width; + + xOffset += lwMax.width; + xOffset += 8; + + // value + stringSize = [typeDescription sizeWithAttributes:propertyAttrs]; + stringRect = NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ); + [typeDescription drawInRect:stringRect withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + yOffset += stringSize.height; + xOffset = originaXOffset; + } // file type + + // file size + if ( FSPathMakeRef((const UInt8 *)[path UTF8String] ,&fsRef,NULL) == noErr && + FSGetCatalogInfo(&fsRef,kFSCatInfoGettableInfo,&catInfo,NULL,NULL,NULL) == noErr ) + { + // is it necessary to add the resource size? + UInt64 file_size = catInfo.dataPhysicalSize + catInfo.rsrcPhysicalSize; + NSString *fileSizeAsString; + + if ( file_size != 0 ) + { + if ( file_size / kBytesInGigabyte > 1 ) + { + NSNumber *gigabytes = [NSNumber numberWithInt:(file_size / kBytesInGigabyte)]; + fileSizeAsString = NoNullString( [[gigabytes stringValue] stringByAppendingString: + NSLocalizedStringFromTableInBundle(@"mditem size gb", @"FileInfo", myBundle, @"")] ); + } + else + { + if ( file_size / kBytesInMegabyte > 1 ) + { + NSNumber *megabytes = [NSNumber numberWithInt:(file_size / kBytesInMegabyte)]; + fileSizeAsString = NoNullString( [[megabytes stringValue] stringByAppendingString: + NSLocalizedStringFromTableInBundle(@"mditem size mb", @"FileInfo", myBundle, @"")] ); + } + else + { + NSNumber *kilobytes = [NSNumber numberWithInt:(file_size / kBytesInKilobyte)]; + fileSizeAsString = NoNullString( [[kilobytes stringValue] stringByAppendingString: + NSLocalizedStringFromTableInBundle(@"mditem size kb", @"FileInfo", myBundle, @"")] ); + } + } + + + + if ( sizeLabel != nil && fileSizeAsString != nil ) + { + // label + stringRect = NSMakeRect( xOffset + ( lwMax.width - lwSize.width), yOffset, lwSize.width, lwSize.height ); + [sizeLabel drawInRect:stringRect withAttributes:labelAttrs]; + + if ( xOffset + lwSize.width > greatestWidth ) + greatestWidth = xOffset + lwSize.width; + + xOffset += lwMax.width; + xOffset += 8; + + // value + stringSize = [fileSizeAsString sizeWithAttributes:propertyAttrs]; + stringRect = NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ); + [fileSizeAsString drawInRect:stringRect withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + yOffset += stringSize.height; + xOffset = originaXOffset; + } + } + } // file size + + + // date created + if ( dateLabel != nil && dateCreatedAsString != nil ) + { + // label + stringRect = NSMakeRect( xOffset + ( lwMax.width - lwCreated.width), yOffset, lwCreated.width, lwCreated.height ); + [dateLabel drawInRect:stringRect withAttributes:labelAttrs]; + + if ( xOffset + lwCreated.width > greatestWidth ) + greatestWidth = xOffset + lwCreated.width; + + xOffset += lwMax.width; + xOffset += 8; + + // value + stringSize = [dateCreatedAsString sizeWithAttributes:propertyAttrs]; + stringRect = NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ); + [dateCreatedAsString drawInRect:stringRect withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + yOffset += stringSize.height; + xOffset = originaXOffset; + } // date created + + + // date modified + if ( dateModifiedLabel != nil && dateModifiedAsString != nil ) + { + // label + stringRect = NSMakeRect( xOffset + ( lwMax.width - lwModified.width), yOffset, lwModified.width, lwModified.height ); + [dateModifiedLabel drawInRect:stringRect withAttributes:labelAttrs]; + + if ( xOffset + lwModified.width > greatestWidth ) + greatestWidth = xOffset + lwModified.width; + + xOffset += lwMax.width; + xOffset += 8; + + // value + stringSize = [dateModifiedAsString sizeWithAttributes:propertyAttrs]; + stringRect = NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ); + [dateModifiedAsString drawInRect:stringRect withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + yOffset += stringSize.height; + xOffset = originaXOffset; + } // date modified + + + // last opened + if ( lastOpenedLabel != nil && lastOpenedAsString != nil ) + { + // label + stringRect = NSMakeRect( xOffset + ( lwMax.width - lwLastOpened.width), yOffset, lwLastOpened.width, lwLastOpened.height ); + [lastOpenedLabel drawInRect:stringRect withAttributes:labelAttrs]; + + if ( xOffset + lwLastOpened.width > greatestWidth ) + greatestWidth = xOffset + lwLastOpened.width; + + xOffset += lwMax.width; + xOffset += 8; + + // value + stringSize = [lastOpenedAsString sizeWithAttributes:propertyAttrs]; + stringRect = NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ); + [lastOpenedAsString drawInRect:stringRect withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + yOffset += stringSize.height; + xOffset = originaXOffset; + } // last opened + } +} + + +/* + +- (void) _drawInfoForFile +{ + unsigned long long kBytesInKilobyte = 1024; + unsigned long long kBytesInMegabyte = 1048576; + unsigned long long kBytesInGigabyte = 1073741824; + + NSString *path = [[self url] path]; + NSFileManager *fm = [NSFileManager defaultManager]; + + FSRef fsRef; + FSCatalogInfo catInfo; + // #warning returns 0 when the file is a package/directory + + if ( ![fm fileExistsAtPath:path] ) + { + return; + } + + NSSize cellSize = NSMakeSize(128,128); + NSSize stringSize; + //NSRect stringRect; + unsigned int originaXOffset= ( viewAlignment == PDFileInfoAlignLeft ? 5*2 + cellSize.width + 10 : 5*2 ); + + //unsigned int xOffset = originaXOffset, yOffset = 20; + //unsigned int xOffset = 0, yOffset = [self bounds].size.height - 20; + unsigned int maxWidth = [self bounds].size.width - ( 5*2 + cellSize.width + 10*2 ); + float greatestWidth = 0; + + NSMutableParagraphStyle *titleParagraph = [[[NSParagraphStyle defaultParagraphStyle] mutableCopyWithZone:[self zone]] autorelease]; + [titleParagraph setLineBreakMode:NSLineBreakByTruncatingTail]; + + NSShadow *textShadow = [[[NSShadow alloc] init] autorelease]; + + [textShadow setShadowColor:[NSColor colorWithCalibratedWhite:0.96 alpha:0.96]]; + [textShadow setShadowOffset:NSMakeSize(0,-1)]; + + NSDictionary *titleAttrs = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:13], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, + textShadow, NSShadowAttributeName, + titleParagraph, NSParagraphStyleAttributeName, nil]; + + NSDictionary *labelAttrs = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:13], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, + textShadow, NSShadowAttributeName, nil]; + + NSDictionary *propertyAttrs = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:13], NSFontAttributeName, + [NSColor blackColor], NSForegroundColorAttributeName, + titleParagraph, NSParagraphStyleAttributeName, + textShadow, NSShadowAttributeName, nil]; + + MDItemRef mdItem = MDItemCreate(NULL,(CFStringRef)path); + + // derive an icon for the file based on the unique file system file number + NSDictionary *fileAttributes = [fm fileAttributesAtPath:path traverseLink:NO]; + + + NSRect bds = [self bounds]; + NSRect iconRect; + NSRect infoRect; + NSRect infoReflectionRect; + + if ( viewAlignment == PDFileInfoAlignLeft ) + iconRect = NSMakeRect( 10, bds.size.height - cellSize.height -10, cellSize.width, cellSize.height ); + else if ( viewAlignment == PDFileInfoAlignRight ) + iconRect = NSMakeRect( bds.size.width - cellSize.width - 10, bds.size.height - cellSize.height -10, cellSize.width, cellSize.height ); + + infoRect = iconRect; + infoRect.origin.x = originaXOffset; + infoRect.size.width = bds.size.width - originaXOffset; + + infoReflectionRect = infoRect; + infoReflectionRect.origin.y -= 132; + + unsigned int xOffset = 0, yOffset = infoRect.size.height - 20; + + // only draw if there is room for the information + if ( infoRect.size.width > 1 ) + { + + NSImage *textImage = [[[NSImage alloc] initWithSize:infoRect.size] autorelease]; + + [textImage lockFocus]; + + // draw the header + NSString *displayName = [fm displayNameAtPath:path]; + if ( displayName != nil ) + { + stringSize = [displayName sizeWithAttributes:titleAttrs]; + [displayName drawInRect:NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ) withAttributes:titleAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + //yOffset += stringSize.height; + //yOffset += 14; + yOffset -= stringSize.height; + yOffset -= 14; + } + + // file type + if ( mdItem != NULL ) + { + NSString *typeLabel = NSLocalizedString(@"mditem kind name",@""); + NSString *typeDescription = [(NSString*)MDItemCopyAttribute(mdItem,(CFStringRef)kMDItemKind) autorelease]; + + if ( typeLabel != nil && typeDescription != nil ) + { + stringSize = [typeLabel sizeWithAttributes:labelAttrs]; + [typeLabel drawInRect:NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ) withAttributes:labelAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + xOffset += ( stringSize.width < maxWidth ? stringSize.width : maxWidth ); + xOffset += 8; + + stringSize = [typeDescription sizeWithAttributes:propertyAttrs]; + [typeDescription drawInRect:NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ) withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + //yOffset += stringSize.height; + //xOffset = originaXOffset; + yOffset -= stringSize.height; + xOffset = 0; + } + } + + // file size + if ( FSPathMakeRef((const UInt8 *)[path UTF8String] ,&fsRef,NULL) == noErr && + FSGetCatalogInfo(&fsRef,kFSCatInfoGettableInfo,&catInfo,NULL,NULL,NULL) == noErr ) + { + + // is it necessary to add the resource size? + UInt64 file_size = catInfo.dataPhysicalSize + catInfo.rsrcPhysicalSize; + NSString *fileSizeAsString; + + if ( file_size != 0 ) + { + if ( file_size / kBytesInGigabyte > 1 ) + { + NSNumber *gigabytes = [NSNumber numberWithInt:(file_size / kBytesInGigabyte)]; + fileSizeAsString = NoNullString([[gigabytes stringValue] stringByAppendingString:NSLocalizedString(@"mditem size gb",@"")]); + } + else + { + if ( file_size / kBytesInMegabyte > 1 ) + { + NSNumber *megabytes = [NSNumber numberWithInt:(file_size / kBytesInMegabyte)]; + fileSizeAsString = NoNullString([[megabytes stringValue] stringByAppendingString:NSLocalizedString(@"mditem size mb",@"")]); + } + else + { + NSNumber *kilobytes = [NSNumber numberWithInt:(file_size / kBytesInKilobyte)]; + fileSizeAsString = NoNullString([[kilobytes stringValue] stringByAppendingString:NSLocalizedString(@"mditem size kb",@"")]); + } + } + + NSString *sizeLabel = NSLocalizedString(@"mditem size name",@""); + + if ( sizeLabel != nil && fileSizeAsString != nil ) + { + stringSize = [sizeLabel sizeWithAttributes:labelAttrs]; + [sizeLabel drawInRect:NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ) withAttributes:labelAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + xOffset += ( stringSize.width < maxWidth ? stringSize.width : maxWidth ); + xOffset += 8; + + stringSize = [fileSizeAsString sizeWithAttributes:propertyAttrs]; + [fileSizeAsString drawInRect:NSMakeRect( xOffset, yOffset, ( stringSize.width < maxWidth ? stringSize.width : maxWidth ), stringSize.height ) withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + //yOffset += stringSize.height; + //xOffset = originaXOffset; + yOffset -= stringSize.height; + xOffset = 0; + } + } + } + + // date created and modified; + NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; + [dateFormatter setDateStyle:NSDateFormatterLongStyle]; + [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; + + // date created + NSDate *dateCreated = [fileAttributes valueForKey:NSFileCreationDate]; + if ( dateCreated != nil ) + { + NSString *dateLabel = NSLocalizedString(@"mditem created name",@""); + NSString *dateCreatedAsString = NoNullString([dateFormatter stringFromDate:dateCreated]); + + if ( dateLabel != nil && dateCreatedAsString != nil ) + { + stringSize = [dateLabel sizeWithAttributes:labelAttrs]; + [dateLabel drawInRect:NSMakeRect( xOffset, yOffset, stringSize.width, stringSize.height ) withAttributes:labelAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + xOffset += stringSize.width; + xOffset += 8; + + stringSize = [dateCreatedAsString sizeWithAttributes:propertyAttrs]; + [dateCreatedAsString drawInRect:NSMakeRect( xOffset, yOffset, stringSize.width, stringSize.height ) withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + //yOffset += stringSize.height; + //xOffset = originaXOffset; + yOffset -= stringSize.height; + xOffset = 0; + } + } + + // date modified + NSDate *dateModified = [fileAttributes valueForKey:NSFileModificationDate]; + if ( dateModified != nil ) + { + NSString *dateModifiedLabel = NSLocalizedString(@"mditem modified name",@""); + NSString *dateModifiedAsString = NoNullString([dateFormatter stringFromDate:dateModified]); + + if ( dateModifiedLabel != nil && dateModifiedAsString != nil ) + { + stringSize = [dateModifiedLabel sizeWithAttributes:labelAttrs]; + [dateModifiedLabel drawInRect:NSMakeRect( xOffset, yOffset, stringSize.width, stringSize.height ) withAttributes:labelAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + xOffset += stringSize.width; + xOffset += 8; + + stringSize = [dateModifiedAsString sizeWithAttributes:propertyAttrs]; + [dateModifiedAsString drawInRect:NSMakeRect( xOffset, yOffset, stringSize.width, stringSize.height ) withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + //yOffset += stringSize.height; + //xOffset = originaXOffset; + yOffset -= stringSize.height; + xOffset = 0; + } + } + + // last opened + if ( mdItem != NULL ) + { + NSDate *lastOpened = (NSDate*)[(NSString*)MDItemCopyAttribute(mdItem,(CFStringRef)kMDItemLastUsedDate) autorelease]; + if ( lastOpened != nil ) + { + NSString *lastOpenedLabel = NSLocalizedString(@"mditem lastopened name",@""); + NSString *lastOpenedAsString = NoNullString([dateFormatter stringFromDate:lastOpened]); + + if ( lastOpenedLabel != nil && lastOpenedAsString != nil ) + { + stringSize = [lastOpenedLabel sizeWithAttributes:labelAttrs]; + [lastOpenedLabel drawInRect:NSMakeRect( xOffset, yOffset, stringSize.width, stringSize.height ) withAttributes:labelAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + xOffset += stringSize.width; + xOffset += 8; + + stringSize = [lastOpenedAsString sizeWithAttributes:propertyAttrs]; + [lastOpenedAsString drawInRect:NSMakeRect( xOffset, yOffset, stringSize.width, stringSize.height ) withAttributes:propertyAttrs]; + + if ( xOffset + stringSize.width > greatestWidth ) + greatestWidth = xOffset + stringSize.width; + + //yOffset += stringSize.height; + //xOffset = originaXOffset; + yOffset -= stringSize.height; + xOffset = 0; + } + } + + // clean up + CFRelease(mdItem); + + } + + [textImage unlockFocus]; + [textImage drawInRect:infoRect fromRect:NSMakeRect(0,0,infoRect.size.width,infoRect.size.height) + operation:NSCompositeSourceOver fraction:1.0]; + + // draw the reflection + + NSImage *reflectedText = [NSImage reflectedImage:textImage amountReflected:0.33]; + + [cell setImageAlignment:NSImageAlignTop]; + [cell setObjectValue:reflectedText]; + [cell drawInteriorWithFrame:infoReflectionRect inView:self]; + } +} + +*/ + +#pragma mark - + +- (void)resetCursorRects +{ + NSRect iconRect; + + NSRect bds = [self bounds]; + NSSize cellSize = NSMakeSize(128,128); + + if ( viewAlignment == PDFileInfoAlignLeft ) + iconRect = NSMakeRect( 10, bds.size.height - cellSize.height - 30, cellSize.width, cellSize.height ); + else if ( viewAlignment == PDFileInfoAlignRight ) + iconRect = NSMakeRect( bds.size.width - cellSize.width - 10, bds.size.height - cellSize.height - 30, cellSize.width, cellSize.height ); + + [self addCursorRect:iconRect cursor:[NSCursor pointingHandCursor]]; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + NSPoint curPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + NSRect iconRect; + + NSRect bds = [self bounds]; + NSSize cellSize = NSMakeSize(128,128); + + if ( viewAlignment == PDFileInfoAlignLeft ) + iconRect = NSMakeRect( 10, bds.size.height - cellSize.height - 30, cellSize.width, cellSize.height ); + else if ( viewAlignment == PDFileInfoAlignRight ) + iconRect = NSMakeRect( bds.size.width - cellSize.width - 10, bds.size.height - cellSize.height - 30, cellSize.width, cellSize.height ); + + if ( NSMouseInRect(curPoint,iconRect,[self isFlipped]) ) + { + if ( ![[NSWorkspace sharedWorkspace] openFile:[[self url] path]] ) + { + if ( ![[NSWorkspace sharedWorkspace] selectFile:[[self url] path] inFileViewerRootedAtPath: + [[[self url] path] stringByDeletingLastPathComponent]] ) + NSBeep(); + } + } + else + [super mouseDown:theEvent]; +} + +#pragma mark - + +- (NSURL*) url +{ + return url; +} + +- (void) setURL:(NSURL*)aURL +{ + if ( aURL != url ) + { + [url release]; + url = [aURL copyWithZone:[self zone]]; + + [[self window] invalidateCursorRectsForView:self]; + } +} + + +@end diff --git a/PDFontDisplay.h b/PDFontDisplay.h new file mode 100644 index 0000000..7875f77 --- /dev/null +++ b/PDFontDisplay.h @@ -0,0 +1,18 @@ +// +// PDFontDisplay.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDFontPreview; + +@interface PDFontDisplay : NSView +{ +} + +@end diff --git a/PDFontDisplay.m b/PDFontDisplay.m new file mode 100644 index 0000000..cd26c02 --- /dev/null +++ b/PDFontDisplay.m @@ -0,0 +1,63 @@ +// +// PDFontDisplay.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDFontDisplay + +- (id)initWithFrame:(NSRect)frameRect +{ + if ((self = [super initWithFrame:frameRect]) != nil) { + // Add initialization code here + } + return self; +} + +- (void)drawRect:(NSRect)rect { + + /* + This view whites out the background, draws a control rect around itself, + and then displays the font according to its superview's font and color preferences. + The result is similar to that found in Safari's or Mail's preferences. + */ + + NSRect bds = [self bounds]; + + //fills it white + [[NSColor whiteColor] set]; + NSRectFillUsingOperation(NSMakeRect( 1, 1, bds.size.width-2, bds.size.height-2), NSCompositeSourceOver); + + //get a string description of the font + NSString *drawString = [NSString stringWithFormat:@"%@ %i", [[(PDFontPreview*)[self superview] font] displayName], + [[NSNumber numberWithFloat:[[(PDFontPreview*)[self superview] font] pointSize]] intValue]]; + + //set up our attributes so we know how to draw the string + NSMutableDictionary *tempAttributes = [NSMutableDictionary dictionary]; + [tempAttributes setObject:[(PDFontPreview*)[self superview] font] forKey:NSFontAttributeName]; + + //make sure our color is correct + [tempAttributes setObject:[(PDFontPreview*)[self superview] color] forKey:NSForegroundColorAttributeName]; + + //grab the fonts size + NSSize stringSize = [drawString sizeWithAttributes:tempAttributes]; + //center a rect + NSRect stringRect = NSMakeRect( bds.size.width/2 - stringSize.width/2, bds.size.height/2 - stringSize.height/2, + stringSize.width, stringSize.height ); + + //and draw the string in the rect + [drawString drawInRect:stringRect withAttributes:tempAttributes]; + + //draws an outline around the guy, just like with other views + //last to cover up any text that might overflow into this single pixel line + [[NSColor controlShadowColor] set ]; + NSFrameRect(bds); +} + +@end diff --git a/PDFontPreview.h b/PDFontPreview.h new file mode 100644 index 0000000..220ebc4 --- /dev/null +++ b/PDFontPreview.h @@ -0,0 +1,96 @@ +// +// PDFontPreview.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDButtonColorWell; +@class PDFontDisplay; + +@interface PDFontPreview : NSView +{ + PDFontDisplay *fontDisplay; + NSButton *changeFont; + /*NSColorWell *fontColor;*/ + PDButtonColorWell *fontColor; + + NSFont *font; + NSColor *color; + + NSString *_defaultsKey; + NSString *colorDefaultsKey; + + id target; + SEL selector; +} + +// ------------------------------------------------- +// +// Key-Value methods for setting the three user defaults keys. +// It is important to set these values. +// +// ------------------------------------------------- + +// +// used for encoding the font object as archived data +- (void) setDefaultsKey:(NSString*)aKey; +- (NSString*) defaultsKey; + +- (void) setColorDefaultsKey:(NSString*)newObject; +- (NSString*) colorDefaultsKey; + + +// ------------------------------------------------- +// +// Key-Value methods for setting the initial font and color methods. +// It is not necessary to use these methods. Setting the user defaults +// keys using the above methods will initialize the color and font values. +// +// ------------------------------------------------- + +- (void) setFont:(NSFont*)newObject; +- (NSFont*) font; +- (void) setColor:(NSColor*)newObject; +- (NSColor*) color; + +// ------------------------------------------------- +// +// If you do not want the user to be able to change the font color, +// call setColorHidden and pass YES. +// +// ------------------------------------------------- + +- (void) setColorHidden:(BOOL)hideColor; +- (BOOL) colorHidden; + +// ------------------------------------------------- +// +// Target-Action behavior. PDFontPreview can behave like a control if +// you set a target and action. Whenever the font or color is changed, +// the action will be triggered. PDFontPreview passes itself as the only +// object to the selector. You can retrieve the new font and color settings +// by calling the above font and color accessors. +// +// ------------------------------------------------- + +- (void) setTarget:(id) newTarget; +- (void) setAction:(SEL) newSelector; + +// ------------------------------------------------- +// +// Internal methods, actions called by the Set Font button +// and the color well. You should not call these methods. +// +// ------------------------------------------------- + +- (void) selectFont:(id) sender; +- (void) selectColor:(id) sender; + +- (void) changeColor:(id) sender; + +@end diff --git a/PDFontPreview.m b/PDFontPreview.m new file mode 100644 index 0000000..293ec70 --- /dev/null +++ b/PDFontPreview.m @@ -0,0 +1,262 @@ +// +// PDFontPreview.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import +#import +#import + +@implementation PDFontPreview + +- (id)initWithFrame:(NSRect)frameRect +{ + if ((self = [super initWithFrame:frameRect]) != nil) { + + NSRect oldFrame; + NSPoint buttonOrigin; + + //building the Set Font button from scratch + changeFont = [[NSButton alloc] initWithFrame:NSMakeRect( frameRect.size.width-104, frameRect.size.height/2-16, 104, 32 )]; + [changeFont setButtonType:NSMomentaryPushInButton]; + [changeFont setBezelStyle:NSRoundedBezelStyle]; + [changeFont setBordered:YES]; + [changeFont setTarget:self]; + [changeFont setAction:@selector(selectFont:)]; + [changeFont setTitle:NSLocalizedStringFromTableInBundle(@"set font title", @"PDFontPreview", [NSBundle bundleWithIdentifier:@"com.sprouted.interface"], @"")]; + [changeFont sizeToFit]; + + oldFrame = [changeFont frame]; + oldFrame.size.width+=10; + [changeFont setFrame:oldFrame]; + + buttonOrigin = NSMakePoint(frameRect.size.width - [changeFont frame].size.width, frameRect.size.height/2-16 - 1); + + [changeFont setFrameOrigin:buttonOrigin]; + [self addSubview:changeFont]; + + //building the PDFontDisplay view from scratch + fontDisplay = [[PDFontDisplay alloc] initWithFrame: + NSMakeRect( 0, frameRect.size.height/2-13, buttonOrigin.x-25, 26 )]; + + [self addSubview:fontDisplay]; + + //building the Color button from scratch + /*fontColor = [[NSColorWell alloc] initWithFrame: + NSMakeRect( buttonOrigin.x-26, frameRect.size.height/2-13, 20, 26 )];*/ + + fontColor = [[PDButtonColorWell alloc] initWithFrame: + NSMakeRect( buttonOrigin.x-26, frameRect.size.height/2-13, 20, 26 )]; + [fontColor setColor:[NSColor blackColor]]; + //[fontColor setTarget:self]; + //[fontColor setAction:@selector(changeColor:)]; + + [fontColor setTarget:self]; + [fontColor setAction:@selector(selectColor:)]; + + [self addSubview:fontColor]; + + //default font and color + [self setFont:[NSFont systemFontOfSize:12.0]]; + [self setColor:[NSColor blackColor]]; + + //default keys - you must change these values in your object's init or awakeFromNib code. + + _defaultsKey = [[NSString alloc] initWithString:@"FontDefault"]; + colorDefaultsKey = [[NSString alloc] initWithString:@"Color Preview Default"]; + + //no target or selector + target = nil; + selector = nil; + + } + return self; +} + +- (void) dealloc { + + [_defaultsKey release]; + _defaultsKey = nil; + + [changeFont release]; + changeFont = nil; + [fontDisplay release]; + fontDisplay = nil; + + [colorDefaultsKey release]; + colorDefaultsKey = nil; + + [fontColor release]; + fontColor = nil; + [color release]; + color = nil; + [font release]; + font = nil; + + [super dealloc]; +} + +#pragma mark - + +- (NSString*) defaultsKey { return _defaultsKey; } + +- (void) setDefaultsKey:(NSString*)aKey { + + if ( _defaultsKey != aKey ) { + [_defaultsKey release]; + _defaultsKey = [aKey copyWithZone:[self zone]]; + } + + NSFont *aFont = [[NSUserDefaults standardUserDefaults] fontForKey:aKey]; + if ( aFont != nil ) [self setFont:aFont]; + +} + +- (void) setColorDefaultsKey:(NSString*)newObject { + + if ( colorDefaultsKey != newObject ) { + [colorDefaultsKey release]; + colorDefaultsKey = [newObject copyWithZone:[self zone]]; + } + + //and once we have our color, set the color and image well + NSData *theData=[[NSUserDefaults standardUserDefaults] dataForKey:newObject]; + if (theData) { + [self setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:theData]]; + [fontColor setColor:[self color]]; + } +} + +- (NSString*) colorDefaultsKey { + return colorDefaultsKey; +} + +#pragma mark - + +- (void) setFont:(NSFont*)newObject { + + //copy our font object + if ( font != newObject ) { + [font release]; + font = [newObject copyWithZone:[self zone]]; + } + + // + //set the defaults for size and name + if ( [self defaultsKey] ) + [[NSUserDefaults standardUserDefaults] setFont:font forKey:[self defaultsKey]]; + + //anytime we change our font, we must change the display font + [fontDisplay setNeedsDisplay:YES]; + +} + +- (NSFont*) font { + return font; +} + +- (void) setColor:(NSColor*)newObject { + + //copy the color into our object + if ( color != newObject ) { + [color release]; + color = [newObject copyWithZone:[self zone]]; + } + + //set the user default + if ( [self colorDefaultsKey] ) + [[NSUserDefaults standardUserDefaults] setObject:[NSArchiver archivedDataWithRootObject:color] forKey:colorDefaultsKey]; + + //anytime we change our color, we must change the display color + [fontDisplay setNeedsDisplay:YES]; + +} + +- (NSColor*) color { + return color; +} + + +- (void) setColorHidden:(BOOL)hideColor { + + int dif = 0; + + //resize our font display accordingly + if ( hideColor && ![fontColor isHidden] ) + dif += ([fontColor frame].size.width-1); + else if ( !hideColor && [fontColor isHidden] ) + dif -= ([fontColor frame].size.width+-1); + + if ( dif ) { + NSRect displayRect = [fontDisplay frame]; + displayRect.size.width += dif; + [fontDisplay setFrame:displayRect]; + } + + //and pass the hiddenness on + [fontColor setHidden:hideColor]; +} + +- (BOOL) colorHidden { + return [fontColor isHidden]; +} + + +- (void) setTarget:(id) newTarget { + target = newTarget; +} + +- (void) setAction:(SEL) newSelector { + selector = newSelector; +} + +#pragma mark - + +- (void) selectFont:(id) sender { + //set me to first responder so I catch font changes + [[self window] makeFirstResponder:self]; + //set the font panel's current font + [[NSFontPanel sharedFontPanel] setPanelFont:[self font] isMultiple:NO]; + //show the font panel + [[NSFontPanel sharedFontPanel] makeKeyAndOrderFront:self]; +} + +- (void) selectColor:(id) sender { + [[self window] makeFirstResponder:fontColor]; + [[NSColorPanel sharedColorPanel] setColor:[self color]]; + [NSApp orderFrontColorPanel:fontColor]; +} + +- (void) changeColor:(id) sender { + //send it off to our display + [self setColor:[sender color]]; + [fontColor setColor:[sender color]]; + //and send our action + if ( target && selector ) [target performSelector:selector withObject:self]; +} + +- (void)changeFont:(id)sender { + + NSFont *oldFont = [self font]; + NSFont *newFont = [sender convertFont:oldFont]; + + [self setFont:newFont]; + + //and send our action + if ( target && selector ) [target performSelector:selector withObject:self]; +} + +- (void)drawRect:(NSRect)rect +{ + /* + This view does not draw anything. It's simply a superview which abstractly manages + its component parts, making it simpler for the programmer and user to interact with them. + */ +} + +@end diff --git a/PDGradientView.h b/PDGradientView.h new file mode 100644 index 0000000..d99fac5 --- /dev/null +++ b/PDGradientView.h @@ -0,0 +1,48 @@ +// +// PDGradientView.h +// SproutedInterface +// +// Created by Philip Dow on 10/20/06. +// Copyright 2006 Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import // needed for Core Image + +@interface PDGradientView : NSView { + + NSColor *gradientStartColor; + NSColor *gradientEndColor; + NSColor *backgroundColor; + + int borders[4]; + BOOL bordered; + + NSColor *fillColor; + NSColor *borderColor; + +} + +- (int*) borders; +- (void) setBorders:(int*)sides; + +- (BOOL) bordered; +- (void) setBordered:(BOOL)flag; + +- (NSColor*) fillColor; +- (void) setFillColor:(NSColor*)aColor; + +- (NSColor*) borderColor; +- (void) setBorderColor:(NSColor*)aColor; + +- (NSColor *)gradientStartColor; +- (void)setGradientStartColor:(NSColor *)newGradientStartColor; +- (NSColor *)gradientEndColor; +- (void)setGradientEndColor:(NSColor *)newGradientEndColor; +- (NSColor *)backgroundColor; +- (void)setBackgroundColor:(NSColor *)newBackgroundColor; + +- (void) resetGradient; + +@end diff --git a/PDGradientView.m b/PDGradientView.m new file mode 100644 index 0000000..38bf01b --- /dev/null +++ b/PDGradientView.m @@ -0,0 +1,310 @@ +// +// PDGradientView.m +// SproutedInterface +// +// Created by Philip Dow on 10/20/06. +// Copyright 2006 Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation PDGradientView + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + + if ( [self respondsToSelector:@selector(addTrackingArea:)] ) + { + //10.5 + [self setGradientStartColor:[NSColor colorWithCalibratedWhite:0.82 alpha:0.8]]; // 1.0, 0.6 + [self setGradientEndColor:[NSColor colorWithCalibratedWhite:0.92 alpha:0.8]]; // 1.0 + } + else + { + //10.4 + [self setGradientStartColor:[NSColor colorWithCalibratedWhite:0.92 alpha:0.8]]; // 1.0, 0.6 + [self setGradientEndColor:[NSColor colorWithCalibratedWhite:0.82 alpha:0.8]]; // 1.0 + } + [self setBackgroundColor:[NSColor windowBackgroundColor]]; // 1.0 + + fillColor = [[NSColor whiteColor] retain]; + borderColor = [[NSColor colorWithCalibratedRed:157.0/255.0 green:157.0/255.0 blue:157.0/255.0 alpha:1.0] retain]; + + bordered = NO; + + borders[0] = 0; // top + borders[1] = 0; // right + borders[2] = 0; // bottom + borders[3] = 0; // left + + } + return self; +} + +- (void) dealloc { + [gradientStartColor release]; + [gradientEndColor release]; + [backgroundColor release]; + [fillColor release]; + [borderColor release]; + + [super dealloc]; +} + +#pragma mark - + +- (void) resetGradient +{ + [self setGradientStartColor:[NSColor colorWithCalibratedWhite:0.92 alpha:0.6]]; // 1.0 + [self setGradientEndColor:[NSColor colorWithCalibratedWhite:0.82 alpha:0.6]]; // 1.0 + [self setBackgroundColor:[NSColor windowBackgroundColor]]; // 1.0 +} + +#pragma mark - + +- (int*) borders { + return borders; +} + +- (void) setBorders:(int*)sides { + + borders[0] = sides[0]; + borders[1] = sides[1]; + borders[2] = sides[2]; + borders[3] = sides[3]; +} + +- (BOOL) bordered { + return bordered; +} + +- (void) setBordered:(BOOL)flag { + bordered = flag; +} + +- (NSColor*) fillColor { + return fillColor; +} + +- (void) setFillColor:(NSColor*)aColor { + + if ( fillColor != aColor ) { + [fillColor release]; + fillColor = [aColor copyWithZone:[self zone]]; + } +} + +- (NSColor*) borderColor { + return borderColor; +} + +- (void) setBorderColor:(NSColor*)aColor { + + if ( borderColor != aColor ) { + [borderColor release]; + borderColor = [aColor copyWithZone:[self zone]]; + } +} + +- (NSColor *)gradientStartColor +{ + return gradientStartColor; +} + + +- (void)setGradientStartColor:(NSColor *)newGradientStartColor +{ + // Must ensure gradient colors are in NSCalibratedRGBColorSpace, or Core Image gets angry. + + NSColor *newCalibratedGradientStartColor = [newGradientStartColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + + [newCalibratedGradientStartColor retain]; + [gradientStartColor release]; + gradientStartColor = newCalibratedGradientStartColor; + +} + + +- (NSColor *)gradientEndColor +{ + return gradientEndColor; +} + + +- (void)setGradientEndColor:(NSColor *)newGradientEndColor +{ + // Must ensure gradient colors are in NSCalibratedRGBColorSpace, or Core Image gets angry. + NSColor *newCalibratedGradientEndColor = [newGradientEndColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + + [newCalibratedGradientEndColor retain]; + [gradientEndColor release]; + gradientEndColor = newCalibratedGradientEndColor; +} + + +- (NSColor *)backgroundColor +{ + return backgroundColor; +} + + +- (void)setBackgroundColor:(NSColor *)newBackgroundColor +{ + [newBackgroundColor retain]; + [backgroundColor release]; + backgroundColor = newBackgroundColor; +} + +#pragma mark - + +- (void)drawRect:(NSRect)rect { + + // Construct rounded rect path + NSRect boxRect = [self bounds]; + NSRect bgRect = boxRect; + + int minX = NSMinX(bgRect); + int maxX = NSMaxX(bgRect); + int minY = NSMinY(bgRect); + int maxY = NSMaxY(bgRect); + + NSBezierPath *bgPath = [NSBezierPath bezierPathWithRect:rect]; + + // Draw solid color background + [backgroundColor set]; + [bgPath fill]; + + // Draw gradient background using Core Image + + // Wonder if there's a nicer way to get a CIColor from an NSColor... + CIColor* startColor = [CIColor colorWithRed:[gradientStartColor redComponent] + green:[gradientStartColor greenComponent] + blue:[gradientStartColor blueComponent] + alpha:[gradientStartColor alphaComponent]]; + CIColor* endColor = [CIColor colorWithRed:[gradientEndColor redComponent] + green:[gradientEndColor greenComponent] + blue:[gradientEndColor blueComponent] + alpha:[gradientEndColor alphaComponent]]; + + CIFilter *myFilter = [CIFilter filterWithName:@"CILinearGradient"]; + [myFilter setDefaults]; + [myFilter setValue:[CIVector vectorWithX:(minX) + Y:(minY)] + forKey:@"inputPoint0"]; + [myFilter setValue:[CIVector vectorWithX:(minX) + Y:(maxY)] + forKey:@"inputPoint1"]; + [myFilter setValue:startColor + forKey:@"inputColor0"]; + [myFilter setValue:endColor + forKey:@"inputColor1"]; + CIImage *theImage = [myFilter valueForKey:@"outputImage"]; + + + // Get a CIContext from the NSGraphicsContext, and use it to draw the CIImage + CGRect dest = CGRectMake(minX, minY, maxX - minX, maxY - minY); + + CGPoint pt = CGPointMake(bgRect.origin.x, bgRect.origin.y); + + NSGraphicsContext *nsContext = [NSGraphicsContext currentContext]; + [nsContext saveGraphicsState]; + + [bgPath addClip]; + + [[nsContext CIContext] drawImage:theImage + atPoint:pt + fromRect:dest]; + + [nsContext restoreGraphicsState]; + + if ( [self bordered] ) { + + NSPoint topLeft, topRight, bottomRight, bottomLeft; + + topLeft = NSMakePoint(0.5, boxRect.size.height-0.5); + topRight = NSMakePoint(boxRect.size.width, boxRect.size.height-0.5); + + bottomRight = NSMakePoint(boxRect.size.width-0.5, 0.5); + bottomLeft = NSMakePoint(0.5, 0.5); + + // does this actually work? + float scaleFactor = [[NSScreen mainScreen] userSpaceScaleFactor]; + if ( scaleFactor != 1.0 ) { + + // apply the scale factor + topLeft.x *= scaleFactor; + topLeft.y *= scaleFactor; + + topRight.x *= scaleFactor; + topRight.y *= scaleFactor; + + bottomRight.x *= scaleFactor; + bottomRight.y *= scaleFactor; + + bottomLeft.x *= scaleFactor; + bottomLeft.y *= scaleFactor; + + // adjust the points to integral boundaries + topLeft.x = ceil(topLeft.x); + topLeft.y = ceil(topLeft.y); + + topRight.x = ceil(topRight.x); + topRight.y = ceil(topRight.y); + + bottomRight.x = ceil(bottomRight.x); + bottomRight.y = ceil(bottomRight.y); + + bottomLeft.x = ceil(bottomLeft.x); + bottomLeft.y = ceil(bottomLeft.y); + + // convert back to user space + topLeft.x /= scaleFactor; + topLeft.y /= scaleFactor; + + topRight.x /= scaleFactor; + topRight.y /= scaleFactor; + + bottomRight.x /= scaleFactor; + bottomRight.y /= scaleFactor; + + bottomLeft.x /= scaleFactor; + bottomLeft.y /= scaleFactor; + } + + // + //draws an outline around the guy, just like with other views + NSBezierPath *borderPath = [NSBezierPath bezierPath]; + if ( borders[0] ) { + [borderPath moveToPoint:topLeft]; + [borderPath lineToPoint:topRight]; + } + if ( borders[1] ) { + [borderPath moveToPoint:topRight]; + [borderPath lineToPoint:bottomRight]; + } + if ( borders[2] ) { + [borderPath moveToPoint:bottomRight]; + [borderPath lineToPoint:bottomLeft]; + } + if ( borders[3] ) { + [borderPath moveToPoint:bottomLeft]; + [borderPath lineToPoint:topLeft]; + } + + [[self borderColor] set ]; + + [nsContext saveGraphicsState]; + [nsContext setShouldAntialias:NO]; + + [borderPath setLineWidth:1.0]; + [borderPath stroke]; + + [nsContext restoreGraphicsState]; + } +} + +@end diff --git a/PDHorizontallyCenteredText.h b/PDHorizontallyCenteredText.h new file mode 100644 index 0000000..50f91e9 --- /dev/null +++ b/PDHorizontallyCenteredText.h @@ -0,0 +1,25 @@ +// +// PDHorizontallyCenteredText.h +// SproutedInterface +// +// Created by Philip Dow on 6/19/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDHorizontallyCenteredText : NSTextFieldCell { + + BOOL selected; + BOOL boldsWhenSelected; +} + +- (BOOL) isSelected; +- (void) setSelected:(BOOL)isSelected; + +- (BOOL) boldsWhenSelected; +- (void) setBoldsWhenSelected:(BOOL)doesBold; + +@end diff --git a/PDHorizontallyCenteredText.m b/PDHorizontallyCenteredText.m new file mode 100644 index 0000000..3872ad6 --- /dev/null +++ b/PDHorizontallyCenteredText.m @@ -0,0 +1,97 @@ +// +// PDHorizontallyCenteredText.m +// SproutedInterface +// +// Created by Philip Dow on 6/19/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDHorizontallyCenteredText + +- (BOOL) isSelected +{ + return selected; +} + +- (void) setSelected:(BOOL)isSelected +{ + selected = isSelected; +} + +- (BOOL) boldsWhenSelected +{ + return boldsWhenSelected; +} + +- (void) setBoldsWhenSelected:(BOOL)doesBold +{ + boldsWhenSelected = doesBold; +} + +#pragma mark - + +- (NSColor*) textColor { + + //if ( [self isHighlighted] && ([[[self controlView] window] firstResponder] == [self controlView]) && + // [[[self controlView] window] isMainWindow] && [[[self controlView] window] isKeyWindow] ) + // return [NSColor whiteColor]; + if ( [self isHighlighted] && [[[self controlView] window] firstResponder] == [self controlView] ) + { + if ( [[[self controlView] window] isKindOfClass:[NSPanel class]] && [[[self controlView] window] isKeyWindow] ) + return [NSColor whiteColor]; + + else if ( [[[self controlView] window] isMainWindow] && [[[self controlView] window] isKeyWindow] ) + return [NSColor whiteColor]; + + else + return [super textColor]; + } + + else if ( [self isHighlighted] && [(NSTableView*)[self controlView] editedRow] != -1 && [(NSTableView*)[self controlView] editedColumn] != -1 ) + return [NSColor whiteColor]; + else + return [super textColor]; + +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + NSMutableAttributedString *attrString = [[[self attributedStringValue] mutableCopyWithZone:[self zone]] autorelease]; + [attrString addAttribute:NSParagraphStyleAttributeName value: + [NSParagraphStyle defaultParagraphStyleWithLineBreakMode:NSLineBreakByTruncatingTail] + range:NSMakeRange(0,[attrString length])]; + + NSSize stringSize = [attrString size]; + cellFrame.origin.y += ( cellFrame.size.height/2 - stringSize.height/2 ); + + if ([self isSelected]) + { + // prepare the text in white. + [attrString addAttribute:NSForegroundColorAttributeName value:[NSColor whiteColor] range:NSMakeRange(0,[attrString length])]; + + // bold the text if that option has been requested + if ( [attrString length] > 0 && [self boldsWhenSelected] ) + { + NSFont *originalFont = [attrString attribute:NSFontAttributeName atIndex:0 effectiveRange:nil]; + if ( originalFont ) { + NSFont *boldedFont = [[NSFontManager sharedFontManager] convertFont:originalFont toHaveTrait:NSBoldFontMask]; + if ( boldedFont ) + [attrString addAttribute:NSFontAttributeName value:boldedFont range:NSMakeRange(0,[attrString length])]; + } + } + } + + [attrString drawInRect:cellFrame]; + //[super drawInteriorWithFrame:cellFrame inView:controlView]; +} + +- (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + return nil; +} + +@end diff --git a/PDInvisibleButton.h b/PDInvisibleButton.h new file mode 100644 index 0000000..c64a9d4 --- /dev/null +++ b/PDInvisibleButton.h @@ -0,0 +1,17 @@ +// +// PDInvisibleButton.h +// SproutedInterface +// +// Created by Philip Dow on 1/9/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDInvisibleButton : NSButton { + +} + +@end diff --git a/PDInvisibleButton.m b/PDInvisibleButton.m new file mode 100644 index 0000000..5298b45 --- /dev/null +++ b/PDInvisibleButton.m @@ -0,0 +1,45 @@ +// +// PDInvisibleButton.m +// SproutedInterface +// +// Created by Philip Dow on 1/9/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +// +// USE: hidden escape button to, say, close a window? + +@implementation PDInvisibleButton + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] + initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + [anArchiver encodeClassName:@"NSButtonCell" intoClassName:@"PDInvisibleButtonCell"]; + [anArchiver encodeRootObject:[self cell]]; + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + + } + + return self; +} + + ++ (Class) cellClass +{ + return [PDInvisibleButtonCell class]; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + // don't do a damn thing + return; +} + +@end diff --git a/PDInvisibleButtonCell.h b/PDInvisibleButtonCell.h new file mode 100644 index 0000000..1b6f1db --- /dev/null +++ b/PDInvisibleButtonCell.h @@ -0,0 +1,17 @@ +// +// PDInvisibleButtonCell.h +// SproutedInterface +// +// Created by Philip Dow on 1/9/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDInvisibleButtonCell : NSButtonCell { + +} + +@end diff --git a/PDInvisibleButtonCell.m b/PDInvisibleButtonCell.m new file mode 100644 index 0000000..2487839 --- /dev/null +++ b/PDInvisibleButtonCell.m @@ -0,0 +1,63 @@ +// +// PDInvisibleButtonCell.m +// SproutedInterface +// +// Created by Philip Dow on 1/9/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation PDInvisibleButtonCell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (id)initTextCell:(NSString *)aString { + + if ( self = [super initTextCell:aString] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (void) dealloc { + + [super dealloc]; + +} + +#pragma mark - + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // + // bypass the frame + // + + [self drawInteriorWithFrame:cellFrame inView:controlView]; + +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // perform no drawing + return; +} + +@end diff --git a/PDMatrixButton.h b/PDMatrixButton.h new file mode 100644 index 0000000..31675a4 --- /dev/null +++ b/PDMatrixButton.h @@ -0,0 +1,18 @@ +// +// PDButton.h +// SproutedInterface +// +// Created by Philip Dow on 12/4/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDMatrixButtonCell; + +@interface PDMatrixButton : NSButton { + +} + +@end diff --git a/PDMatrixButton.m b/PDMatrixButton.m new file mode 100644 index 0000000..4cc4bd7 --- /dev/null +++ b/PDMatrixButton.m @@ -0,0 +1,36 @@ +// +// PDMatrixButton.m +// SproutedInterface +// +// Created by Philip Dow on 5/10/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDMatrixButton + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] + initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + [anArchiver encodeClassName:@"NSButtonCell" intoClassName:@"PDMatrixButtonCell"]; + [anArchiver encodeRootObject:[self cell]]; + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + + } + + return self; +} + + ++ (Class) cellClass +{ + return [PDMatrixButtonCell class]; +} + +@end diff --git a/PDMatrixButtonCell.h b/PDMatrixButtonCell.h new file mode 100644 index 0000000..7929027 --- /dev/null +++ b/PDMatrixButtonCell.h @@ -0,0 +1,16 @@ +// +// PDMatrixButtonCell.h +// SproutedInterface +// +// Created by Philip Dow on 5/10/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDMatrixButtonCell : NSButtonCell { + +} + +@end diff --git a/PDMatrixButtonCell.m b/PDMatrixButtonCell.m new file mode 100644 index 0000000..1ab9274 --- /dev/null +++ b/PDMatrixButtonCell.m @@ -0,0 +1,186 @@ +// +// PDMatrixButtonCell.m +// SproutedInterface +// +// Created by Philip Dow on 5/10/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +@implementation PDMatrixButtonCell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (id)initTextCell:(NSString *)aString { + + if ( self = [super initTextCell:aString] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (id)initImageCell:(NSImage *)anImage { + + if ( self = [super initImageCell:anImage] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (void) dealloc { + + [super dealloc]; + +} + +#pragma mark - + + + +- (NSRect)drawingRectForBounds:(NSRect)theRect { + return theRect; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect { + return theRect; +} + + + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // + // bypass the frame + // + + [self drawInteriorWithFrame:cellFrame inView:controlView]; + +} + + + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + float alpha = ( [self isEnabled] ? 1.0 : 0.70 ); + NSColor *gradientStart, *gradientEnd, *borderColor; + + int x = cellFrame.origin.x; + int y = cellFrame.origin.y; + + int height = cellFrame.size.height; + + if ( [self isHighlighted] || [self state] == NSOnState ) { + + if ( [self isHighlighted] ) { + + borderColor = [NSColor colorWithCalibratedRed:149.0/255.0 green:149.0/255.0 blue:149.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:186.0/255.0 green:186.0/255.0 blue:186.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:alpha]; + + } + + else if ( [self state] == NSOnState ) { + + borderColor = [NSColor colorWithCalibratedRed:173.0/255.0 green:173.0/255.0 blue:173.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:224.0/255.0 green:224.0/255.0 blue:224.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:253.0/255.0 green:253.0/255.0 blue:253.0/255.0 alpha:alpha]; + + } + + NSRect targetRect = NSInsetRect(cellFrame,0.5,2.5); + targetRect.size.width-=4.0; + + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:8.5] + linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + [borderColor set]; + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:8.5] stroke]; + + } + + static int kImageOffset = 2; + + NSImage *image; + + if ( [self isHighlighted] ) { + + NSImage *original = [self image]; + NSImage *dark = [[NSImage alloc] initWithSize:NSMakeSize([original size].width,[original size].height)]; + + [dark lockFocus]; + [[NSColor blackColor] set]; + NSRectFillUsingOperation(NSMakeRect(0,0,[original size].width,[original size].height), NSCompositeSourceOver); + [dark unlockFocus]; + + //image = [[original copyWithZone:[self zone]] autorelease]; + image = [[NSImage alloc] initWithSize:NSMakeSize([original size].width,[original size].height)]; + [image setFlipped:[[self controlView] isFlipped]]; + + [image lockFocus]; + [original drawInRect:NSMakeRect(0,0,[original size].width,[original size].height) + fromRect:NSMakeRect(0,0,[original size].width,[original size].height) + operation:NSCompositeSourceOver fraction:1.0]; + [dark drawInRect:NSMakeRect(0,0,[original size].width,[original size].height) + fromRect:NSMakeRect(0,0,[original size].width,[original size].height) + operation:NSCompositePlusDarker fraction:0.20]; + [original drawInRect:NSMakeRect(0,0,[original size].width,[original size].height) + fromRect:NSMakeRect(0,0,[original size].width,[original size].height) + operation:NSCompositeDestinationIn fraction:1.0]; + [image unlockFocus]; + + [dark release]; + + } + else { + // + // pass it along + image = [[self image] copyWithZone:[self zone]]; + [image setFlipped:[[self controlView] isFlipped]]; + } + + NSSize imageSize = [image size]; + NSRect imageTarget = NSMakeRect(x + kImageOffset, y + height/2-imageSize.height/2, imageSize.width, imageSize.height); + + [image drawInRect:imageTarget fromRect:NSMakeRect(0,0,imageSize.width,imageSize.height) + operation:NSCompositeSourceOver fraction:1.0]; + + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11.0], NSFontAttributeName, + ( [self isEnabled] ? [NSColor blackColor] : [NSColor lightGrayColor] ), NSForegroundColorAttributeName, nil]; + + NSSize stringSize = [[self title] sizeWithAttributes:attributes]; + + NSRect stringRect = NSMakeRect( x + kImageOffset*2 + imageSize.width, y + height/2.0-stringSize.height/2.0, + stringSize.width, stringSize.height); + + [[self title] drawInRect:stringRect withAttributes:attributes]; + + [image release]; + +} + + +@end diff --git a/PDMediaBar.h b/PDMediaBar.h new file mode 100644 index 0000000..cd06da9 --- /dev/null +++ b/PDMediaBar.h @@ -0,0 +1,95 @@ +// +// PDMediaBar.h +// SproutedInterface +// +// Created by Philip Dow on 2/21/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +typedef enum { + kMediaBarDefaultItem = 100, + kMediaBarShowInFinder = 101, + kMediaBarOpenWithFinder = 102, + kMediaBarGetInfo = 103 +} MediaBarDefaultActions; + + +@class PDMediabarItem; + +@interface PDMediaBar : JournlerGradientView { + + id delegate; + + NSString *prefsIdentifier; + NSDictionary *barDictionary; + NSArray *itemIdentifiers; + NSArray *customItemDictionaries; + + NSArray *itemArray; +} + +- (id) delegate; +- (void) setDelegate:(id)anObject; + +// the following accessors should not be called - think of them as private + +- (NSString*)prefsIdentifier; +- (void) setPrefsIdentifier:(NSString*)aString; + +- (NSDictionary*)barDictionary; +- (void) setBarDictionary:(NSDictionary*)aDictionary; + +- (NSArray*)itemIdentifiers; +- (void) setItemIdentifiers:(NSArray*)anArray; + +- (NSArray*)customItemDictionaries; +- (void) setCustomItemDictionaries:(NSArray*)anArray; + +- (NSArray*) itemArray; +- (void) setItemArray:(NSArray*)anArray; + +- (void) loadItems; +- (void) displayItems; + +- (IBAction) addCustomMediabarItem:(id)sender; +- (IBAction) editCustomMediabarItem:(id)sender; +- (IBAction) removeCustomMediabarItem:(id)sender; + +- (void) _removeCustomMediabarItem:(PDMediabarItem*)anItem; +- (void) _editCustomMediabarItem:(PDMediabarItem*)anItem; + +- (void) _didChangeFrame:(NSNotification*)aNotification; + +@end + +@interface NSObject (PDMediaBarDelegate) + +// +// for now the media bar only allows buttons +// it begins from the right with the first item returned by preferences + +- (void) setupMediabar:(PDMediaBar*)aMediabar url:(NSURL*)aURL; + +- (NSImage*) defaultOpenWithFinderImage:(PDMediaBar*)aMediabar; +- (float) mediabarMinimumWidthForUnmanagedControls:(PDMediaBar*)aMediabar; + +- (BOOL) canCustomizeMediabar:(PDMediaBar*)aMediabar; +- (NSString*) mediabarIdentifier:(PDMediaBar*)aMediabar; + +// the action method for user-defined mediabar items. +// to find out which mediabar the item is associated with, call -mediabar on it +- (IBAction) perfomCustomMediabarItemAction:(PDMediabarItem*)anItem; + +// subclasses may override when offering their own default items, +// call super to get support for any standard items you also include +- (PDMediabarItem*) mediabar:(PDMediaBar *)mediabar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoMediabar:(BOOL)flag; + +// subclasses should override to provide the default item identifiers +// listing is displayed from the right to the left in the media bar +- (NSArray*) mediabarDefaultItemIdentifiers:(PDMediaBar *)mediabar; + +@end diff --git a/PDMediaBar.m b/PDMediaBar.m new file mode 100644 index 0000000..0a6d7a3 --- /dev/null +++ b/PDMediaBar.m @@ -0,0 +1,591 @@ +// +// PDMediaBar.m +// SproutedInterface +// +// Created by Philip Dow on 2/21/07. +// Copyright Sprouted. All rights reserved. +// + +#import +#import +#import + +@implementation PDMediaBar + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + + [self setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_didChangeFrame:) name:NSViewFrameDidChangeNotification object:self]; + } + return self; +} + +- (void) dealloc +{ + [prefsIdentifier release]; + [barDictionary release]; + [itemIdentifiers release]; + [customItemDictionaries release]; + [itemArray release]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:self]; + + [super dealloc]; +} + +- (void)drawRect:(NSRect)rect { + // Drawing code here. + + [super drawRect:rect]; +} + +- (BOOL)mouseDownCanMoveWindow +{ + return NO; +} + +#pragma mark - + +- (NSMenu *)menuForEvent:(NSEvent *)theEvent +{ + if ( ![[self delegate] canCustomizeMediabar:self] ) + return nil; + + float xMargin = [self bounds].size.width; + float barHeight = [self bounds].size.height; + + NSPoint local_point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + PDMediabarItem *anItem, *representedItem = nil; + NSEnumerator* enumerator = [itemArray objectEnumerator]; + + while ( anItem = [enumerator nextObject] ) + { + xMargin -= 4; + xMargin -= [anItem frame].size.width; + + NSRect itemFrame = NSMakeRect( xMargin, barHeight/2 - [anItem frame].size.height/2, + [anItem frame].size.width, [anItem frame].size.height); + + if ( [self mouse:local_point inRect:itemFrame] ) + { + representedItem = anItem; + break; + } + } + + NSBundle *myBundle = [NSBundle bundleWithIdentifier:@"com.sprouted.interface"]; + + NSMenu *contextMenu = [[[NSMenu alloc] initWithTitle:[NSString string]] autorelease]; + + NSMenuItem *addItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"add item title", @"Mediabar", myBundle, @"") + action:@selector(addCustomMediabarItem:) keyEquivalent:[NSString string]] autorelease]; + + [addItem setTarget:self]; + [addItem setRepresentedObject:representedItem]; + + [contextMenu addItem:addItem]; + + NSMenuItem *editItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"edit item title", @"Mediabar", myBundle, @"") + action:@selector(editCustomMediabarItem:) keyEquivalent:[NSString string]] autorelease]; + + [editItem setTarget:self]; + [editItem setRepresentedObject:representedItem]; + + [contextMenu addItem:editItem]; + + NSMenuItem *removeItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"remove item title", @"Mediabar", myBundle, @"") + action:@selector(removeCustomMediabarItem:) keyEquivalent:[NSString string]] autorelease]; + + [removeItem setTarget:self]; + [removeItem setRepresentedObject:representedItem]; + + [contextMenu addItem:removeItem]; + + return contextMenu; + +} + +#pragma mark - + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate:(id)anObject +{ + delegate = anObject; +} + +#pragma mark - + +- (NSString*)prefsIdentifier +{ + return prefsIdentifier; +} + +- (void) setPrefsIdentifier:(NSString*)aString +{ + if ( prefsIdentifier != aString ) + { + [prefsIdentifier release]; + prefsIdentifier = [aString copyWithZone:[self zone]]; + } +} + +- (NSDictionary*)barDictionary +{ + return barDictionary; +} + +- (void) setBarDictionary:(NSDictionary*)aDictionary +{ + if ( barDictionary != aDictionary ) + { + [barDictionary release]; + barDictionary = [aDictionary copyWithZone:[self zone]]; + } +} + +- (NSArray*)itemIdentifiers +{ + return itemIdentifiers; +} + +- (void) setItemIdentifiers:(NSArray*)anArray +{ + if ( itemIdentifiers != anArray ) + { + [itemIdentifiers release]; + itemIdentifiers = [anArray copyWithZone:[self zone]]; + } +} + +- (NSArray*)customItemDictionaries +{ + return customItemDictionaries; +} + +- (void) setCustomItemDictionaries:(NSArray*)anArray +{ + if ( customItemDictionaries != anArray ) + { + [customItemDictionaries release]; + customItemDictionaries = [anArray copyWithZone:[self zone]]; + } +} + +- (NSArray*) itemArray +{ + return itemArray; +} + +- (void) setItemArray:(NSArray*)anArray +{ + if ( itemArray != anArray ) + { + [itemArray release]; + itemArray = [anArray copyWithZone:[self zone]]; + } +} + +#pragma mark - + +- (void) loadItems +{ + if ( ![[self delegate] canCustomizeMediabar:self] ) + return; + + // clear out the old items first + NSView *aView; + NSEnumerator *enumerator = [[self itemArray] objectEnumerator]; + + while ( aView = [enumerator nextObject] ) + [aView removeFromSuperview]; + + // load in the new ones + NSMutableArray *myItems = [NSMutableArray array]; + + // build the bar from our defaults and from preferences + NSString *myPrefsIdentifier = [NSString stringWithFormat:@"PDMediaBar Configuration %@", [[self delegate] mediabarIdentifier:self]]; + NSDictionary *myBarDictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:myPrefsIdentifier]; + + NSArray *myItemIdentifiers = nil; + NSArray *myCustomItemDictionaries = nil; + + if ( myBarDictionary == nil || ( myItemIdentifiers = [myBarDictionary objectForKey:@"ItemIdentifiers"] ) == nil ) + { + myItemIdentifiers = [[self delegate] mediabarDefaultItemIdentifiers:self]; + myBarDictionary = [NSDictionary dictionaryWithObject:myItemIdentifiers forKey:@"ItemIdentifiers"]; + [[NSUserDefaults standardUserDefaults] setObject:myBarDictionary forKey:myPrefsIdentifier]; + } + else + myCustomItemDictionaries = [myBarDictionary objectForKey:@"CustomItemDictionaries"]; + + [self setPrefsIdentifier:myPrefsIdentifier]; + [self setBarDictionary:myBarDictionary]; + [self setItemIdentifiers:myItemIdentifiers]; + [self setCustomItemDictionaries:myCustomItemDictionaries]; + + NSString *anIdentifier; + enumerator = [itemIdentifiers objectEnumerator]; + + while ( anIdentifier = [enumerator nextObject] ) + { + PDMediabarItem *anItem = [[self delegate] mediabar:self itemForItemIdentifier:anIdentifier willBeInsertedIntoMediabar:YES]; + if ( anItem != nil ) + { + [anItem setMediabar:self]; + [myItems addObject:anItem]; + } + else + { + // try building a custom item from it + if ( customItemDictionaries == nil ) + { + NSLog(@"%@ %s - no custom properties in prefs, unabe to build a custom item for media bar %@, item identifier %@", + [[self delegate] className], _cmd, [[self delegate] mediabarIdentifier:self], anIdentifier ); + continue; + } + + // find the item in our customs list + NSDictionary *aDictionary; + NSEnumerator *dictionariesEnumerator = [customItemDictionaries objectEnumerator]; + + while ( aDictionary = [dictionariesEnumerator nextObject] ) + { + if ( [anIdentifier isEqualToString:[aDictionary objectForKey:@"identifier"]] ) + { + anItem = [[[PDMediabarItem alloc] initWithDictionaryRepresentation:aDictionary] autorelease]; + if ( anItem == nil ) + NSLog(@"%@ %s - unable to initalize media bar item from dictionary %@", [[self delegate] className], _cmd, aDictionary); + else + { + [anItem setTarget:[self delegate]]; + [anItem setAction:@selector(perfomCustomMediabarItemAction:)]; + + [anItem setMediabar:self]; + [myItems addObject:anItem]; + } + + break; + } + } + } + } + + [self setItemArray:myItems]; +} + +- (void) displayItems +{ + if ( ![[self delegate] canCustomizeMediabar:self] ) + return; + + /* + NSView *aView; + NSEnumerator *enumerator = [[self itemArray] objectEnumerator]; + + while ( aView = [enumerator nextObject] ) + [aView removeFromSuperview]; + */ + + float xMargin = [self bounds].size.width; + float barHeight = [self bounds].size.height; + float minWidth = [[self delegate] mediabarMinimumWidthForUnmanagedControls:self]; + + PDMediabarItem *anItem; + NSEnumerator *enumerator = [itemArray objectEnumerator]; + + while ( anItem = [enumerator nextObject] ) + { + xMargin -= 4; + xMargin -= [anItem frame].size.width; + + NSRect itemFrame = NSMakeRect( xMargin, barHeight/2 - [anItem frame].size.height/2, + [anItem frame].size.width, [anItem frame].size.height); + [anItem setFrame:itemFrame]; + [self addSubview:anItem]; + + if ( xMargin < minWidth ) + [anItem setHidden:YES]; + else + [anItem setHidden:NO]; + } +} + +#pragma mark - + +- (IBAction) removeCustomMediabarItem:(id)sender +{ + if ( ![[self delegate] canCustomizeMediabar:self] ) + return; + + [self _removeCustomMediabarItem:[sender representedObject]]; +} + +- (IBAction) editCustomMediabarItem:(id)sender +{ + if ( ![[self delegate] canCustomizeMediabar:self] ) + return; + + [self _editCustomMediabarItem:[sender representedObject]]; +} + +#pragma mark - + +- (void) _removeCustomMediabarItem:(PDMediabarItem*)anItem +{ + // reset the preferences + NSMutableDictionary *myBarDictionary = [[[self barDictionary] mutableCopyWithZone:[self zone]] autorelease]; + if ( myBarDictionary == nil ) myBarDictionary = [NSMutableDictionary dictionary]; + + NSMutableArray *myItemIdentifiers = nil; + NSMutableArray *myCustomItemDictionaries = nil; + + if ( myBarDictionary == nil || ( myItemIdentifiers = [[[myBarDictionary objectForKey:@"ItemIdentifiers"] mutableCopyWithZone:[self zone]] autorelease] ) == nil ) + { + myItemIdentifiers = [[[[self delegate] mediabarDefaultItemIdentifiers:self] mutableCopyWithZone:[self zone]] autorelease]; + myCustomItemDictionaries = [NSMutableArray array]; + + myBarDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys: + myItemIdentifiers, @"ItemIdentifiers", + myCustomItemDictionaries, @"CustomItemDictionaries", nil]; + } + else + { + myCustomItemDictionaries = [[[myBarDictionary objectForKey:@"CustomItemDictionaries"] mutableCopyWithZone:[self zone]] autorelease]; + if ( myCustomItemDictionaries == nil ) + myCustomItemDictionaries = [NSMutableArray array]; + } + + int itemIdentifierIndex = [myItemIdentifiers indexOfObject:[anItem identifier]]; + if ( itemIdentifierIndex != NSNotFound ) + [myItemIdentifiers removeObjectAtIndex:itemIdentifierIndex]; + else + NSLog(@"%@ %s - unable to remove item with identifier %@", [self className], _cmd, [anItem identifier]); + + int dictionaryIndex = [[myCustomItemDictionaries valueForKey:@"identifier"] indexOfObject:[anItem identifier]]; + if ( dictionaryIndex != NSNotFound ) + [myCustomItemDictionaries removeObjectAtIndex:dictionaryIndex]; + else + NSLog(@"%@ %s - unable to remove item with identifier %@", [self className], _cmd, [anItem identifier]); + + // reset the items in the dictinoary + [myBarDictionary setObject:myItemIdentifiers forKey:@"ItemIdentifiers"]; + [myBarDictionary setObject:myCustomItemDictionaries forKey:@"CustomItemDictionaries"]; + + // reset the preferences + [[NSUserDefaults standardUserDefaults] setObject:myBarDictionary forKey:prefsIdentifier]; + + // reset our local representation + [self setCustomItemDictionaries:myCustomItemDictionaries]; + [self setItemIdentifiers:myItemIdentifiers]; + [self setBarDictionary:myBarDictionary]; + + // remove the item from the display + [anItem removeFromSuperview]; + + // rebuild the bar + [self loadItems]; + [self displayItems]; + + [self setNeedsDisplay:YES]; +} + +- (void) _editCustomMediabarItem:(PDMediabarItem*)anItem +{ + NewMediabarItemController *itemCreator = [[[NewMediabarItemController alloc] + initWithDictionaryRepresentation:[anItem dictionaryRepresentation]] autorelease]; + + [itemCreator setDelegate:self]; + [itemCreator setRepresentedObject:anItem]; + [itemCreator runAsSheetForWindow:[self window] attached:NO location:NSMakeRect(0,0,0,0)]; +} + +#pragma mark - + +- (IBAction) addCustomMediabarItem:(id)sender +{ + // subclasses may override to provide custom behavior + if ( [[self delegate] canCustomizeMediabar:self] ) + { + NewMediabarItemController *itemCreator = [[[NewMediabarItemController alloc] init] autorelease]; + + [itemCreator setDelegate:self]; + [itemCreator runAsSheetForWindow:[self window] attached:NO location:NSMakeRect(0,0,0,0)]; + } + + else + { + NSBeep(); + } +} + +- (void) mediabarItemCreateDidSaveAction:(NewMediabarItemController*)mediabarItemCreator +{ + PDMediabarItem *theRepresentedObject = [mediabarItemCreator representedObject]; + + // create a new item if no item is being edited + if ( theRepresentedObject == nil ) + { + + // the delegate method from the media bar item creator - subclasses may override if they wish + NSDictionary *dictionaryRepresentation = [mediabarItemCreator dictionaryRepresentation]; + + // reset the preferences + NSMutableDictionary *myBarDictionary = [[[self barDictionary] mutableCopyWithZone:[self zone]] autorelease]; + if ( myBarDictionary == nil ) myBarDictionary = [NSMutableDictionary dictionary]; + + NSMutableArray *myItemIdentifiers = nil; + NSMutableArray *myCustomItemDictionaries = nil; + + if ( myBarDictionary == nil || ( myItemIdentifiers = [[[myBarDictionary objectForKey:@"ItemIdentifiers"] mutableCopyWithZone:[self zone]] autorelease] ) == nil ) + { + myItemIdentifiers = [[[[self delegate] mediabarDefaultItemIdentifiers:self] mutableCopyWithZone:[self zone]] autorelease]; + myCustomItemDictionaries = [NSMutableArray array]; + + myBarDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys: + myItemIdentifiers, @"ItemIdentifiers", + myCustomItemDictionaries, @"CustomItemDictionaries", nil]; + } + else + { + myCustomItemDictionaries = [[[myBarDictionary objectForKey:@"CustomItemDictionaries"] mutableCopyWithZone:[self zone]] autorelease]; + if ( myCustomItemDictionaries == nil ) + myCustomItemDictionaries = [NSMutableArray array]; + } + + [myItemIdentifiers addObject:[dictionaryRepresentation objectForKey:@"identifier"]]; + [myCustomItemDictionaries addObject:dictionaryRepresentation]; + + [myBarDictionary setObject:myItemIdentifiers forKey:@"ItemIdentifiers"]; + [myBarDictionary setObject:myCustomItemDictionaries forKey:@"CustomItemDictionaries"]; + + [[NSUserDefaults standardUserDefaults] setObject:myBarDictionary forKey:prefsIdentifier]; + + [self setCustomItemDictionaries:myCustomItemDictionaries]; + [self setItemIdentifiers:myItemIdentifiers]; + [self setBarDictionary:myBarDictionary]; + + // rebuild the bar + [self loadItems]; + [self displayItems]; + + } + + // modify the represented item if one was being edited + else + { + // the delegate method from the media bar item creator - subclasses may override if they wish + + NSDictionary *dictionaryRepresentation = [mediabarItemCreator dictionaryRepresentation]; + + // save the preferences + NSMutableDictionary *myBarDictionary = [[[self barDictionary] mutableCopyWithZone:[self zone]] autorelease]; + if ( myBarDictionary == nil ) myBarDictionary = [NSMutableDictionary dictionary]; + + NSMutableArray *myItemIdentifiers = nil; + NSMutableArray *myCustomItemDictionaries = nil; + + if ( myBarDictionary == nil || ( myItemIdentifiers = [[[myBarDictionary objectForKey:@"ItemIdentifiers"] mutableCopyWithZone:[self zone]] autorelease] ) == nil ) + { + myItemIdentifiers = [[[[self delegate] mediabarDefaultItemIdentifiers:self] mutableCopyWithZone:[self zone]] autorelease]; + myCustomItemDictionaries = [NSMutableArray array]; + + myBarDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys: + myItemIdentifiers, @"ItemIdentifiers", + myCustomItemDictionaries, @"CustomItemDictionaries", nil]; + } + else + { + myCustomItemDictionaries = [[[myBarDictionary objectForKey:@"CustomItemDictionaries"] mutableCopyWithZone:[self zone]] autorelease]; + if ( myCustomItemDictionaries == nil ) + myCustomItemDictionaries = [NSMutableArray array]; + } + + /* + int itemIdentifierIndex = [myItemIdentifiers indexOfObject:[theRepresentedObject identifier]]; + if ( itemIdentifierIndex != NSNotFound ) + [myItemIdentifiers removeObjectAtIndex:itemIdentifierIndex]; + else + NSLog(@"%@ %s - unable to remove item with identifier %@", [self className], _cmd, [theRepresentedObject identifier]); + */ + + int dictionaryIndex = [[myCustomItemDictionaries valueForKey:@"identifier"] indexOfObject:[theRepresentedObject identifier]]; + if ( dictionaryIndex != NSNotFound ) + [myCustomItemDictionaries replaceObjectAtIndex:dictionaryIndex withObject:dictionaryRepresentation]; + else + NSLog(@"%@ %s - unable to edit item with identifier %@", [self className], _cmd, [theRepresentedObject identifier]); + + // reset the items in the dictinoary + [myBarDictionary setObject:myItemIdentifiers forKey:@"ItemIdentifiers"]; + [myBarDictionary setObject:myCustomItemDictionaries forKey:@"CustomItemDictionaries"]; + + // reset the preferences + [[NSUserDefaults standardUserDefaults] setObject:myBarDictionary forKey:prefsIdentifier]; + + // reset our local representation + [self setCustomItemDictionaries:myCustomItemDictionaries]; + [self setItemIdentifiers:myItemIdentifiers]; + [self setBarDictionary:myBarDictionary]; + + // re-attribute the item + [theRepresentedObject setAttributesFromDictionaryRepresentation:dictionaryRepresentation]; + + } + + [self setNeedsDisplay:YES]; +} + +#pragma mark - + +- (BOOL)validateMenuItem:(NSMenuItem*)menuItem +{ + BOOL enabled = YES; + SEL action = [menuItem action]; + + if ( ![[self delegate] canCustomizeMediabar:self] ) + return NO; + + + if ( action == @selector(addCustomMediabarItem:) ) + enabled = YES; + + else if ( action == @selector(editCustomMediabarItem:) ) + enabled = ( [[menuItem representedObject] isKindOfClass:[PDMediabarItem class]] && ( [[[menuItem representedObject] typeIdentifier] intValue] != kMenubarItemDefault ) ); + + else if ( action == @selector(removeCustomMediabarItem:) ) + enabled = ( [[menuItem representedObject] isKindOfClass:[PDMediabarItem class]] && ( [[[menuItem representedObject] typeIdentifier] intValue] != kMenubarItemDefault ) ); + + return enabled; +} + +#pragma mark - + +- (void) _didChangeFrame:(NSNotification*)aNotification +{ + float minWidth = [[self delegate] mediabarMinimumWidthForUnmanagedControls:self]; + float xMargin = [self bounds].size.width; + + PDMediabarItem *anItem; + NSEnumerator *enumerator = [itemArray objectEnumerator]; + + while ( anItem = [enumerator nextObject] ) + { + xMargin -= 4; + xMargin -= [anItem frame].size.width; + + if ( xMargin < minWidth ) + [anItem setHidden:YES]; + else + [anItem setHidden:NO]; + } +} + +@end diff --git a/PDMediabarItem.h b/PDMediabarItem.h new file mode 100644 index 0000000..ca2be9b --- /dev/null +++ b/PDMediabarItem.h @@ -0,0 +1,59 @@ +// +// PDMediabarItem.h +// SproutedInterface +// +// Created by Philip Dow on 2/20/07. +// Copyright Sprouted. All rights reserved. +// + +#import + +typedef enum { + kMenubarItemDefault = 0, + kMenubarItemURI = 1, + kMenubarItemAppleScript = 2, +} MenubarItemType; + +@class PDMediaBar; + +@interface PDMediabarItem : NSButton { + + NSString *identifier; + NSNumber *typeIdentifier; + + NSURL *targetURI; + NSAttributedString *targetScript; + + PDMediaBar *mediabar; +} + +- (id) initWithItemIdentifier:(NSString*)aString; +- (id) initWithDictionaryRepresentation:(NSDictionary*)aDictionary; + +- (NSDictionary*) dictionaryRepresentation; +- (void) setAttributesFromDictionaryRepresentation:(NSDictionary*)aDictionary; + +- (NSString*)identifier; +- (void) setIdentifier:(NSString*)aString; + +- (NSSize) size; +- (void) setSize:(NSSize)aSize; + +- (PDMediaBar*) mediabar; +- (void) setMediabar:(PDMediaBar*)aMediabar; + +// a custom button should target the media controller itself +// a special function that grabs the target information and takes the appropriate action +// a default method will handle this, but it may be overridden + +- (NSNumber*) typeIdentifier; +- (void) setTypeIdentifier:(NSNumber*)aNumber; + +- (NSURL*) targetURI; +- (void) setTargetURI:(NSURL*)aURL; + +- (NSAttributedString*) targetScript; +- (void) setTargetScript:(NSAttributedString*)aString; + + +@end diff --git a/PDMediabarItem.m b/PDMediabarItem.m new file mode 100644 index 0000000..91e8b9f --- /dev/null +++ b/PDMediabarItem.m @@ -0,0 +1,229 @@ +// +// PDMediabarItem.m +// SproutedInterface +// +// Created by Philip Dow on 2/20/07. +// Copyright Sprouted. All rights reserved. +// + +#import +#import + +@implementation PDMediabarItem + +- (id) initWithItemIdentifier:(NSString*)aString +{ + if ( self = [self init] ) + { + [self setIdentifier:aString]; + } + + return self; +} + +- (id) initWithDictionaryRepresentation:(NSDictionary*)aDictionary +{ + if ( self = [self init] ) + { + [self setIdentifier:[aDictionary objectForKey:@"identifier"]]; + [self setTypeIdentifier:[aDictionary objectForKey:@"typeIdentifier"]]; + + NSData *scriptData = [aDictionary objectForKey:@"targetScript"]; + if ( scriptData != nil ) [self setTargetScript:[NSKeyedUnarchiver unarchiveObjectWithData:scriptData]]; + + NSData *imageData = [aDictionary objectForKey:@"image"]; + if ( imageData != nil ) [self setImage:[NSKeyedUnarchiver unarchiveObjectWithData:imageData]]; + [self setTitle:[aDictionary objectForKey:@"title"]]; + [self setToolTip:[aDictionary objectForKey:@"tooltip"]]; + + NSString *uriString = [aDictionary objectForKey:@"targetURI"]; + if ( uriString != nil ) [self setTargetURI:[NSURL URLWithString:uriString]]; + + } + + return self; +} + +- (id) init +{ + if ( self = [super initWithFrame:NSMakeRect(0,0,32,32)] ) + { + [(NSButtonCell*)[self cell] setImagePosition:NSImageOnly]; + [(NSButtonCell*)[self cell] setControlSize:NSRegularControlSize]; + [(NSButtonCell*)[self cell] setButtonType:NSMomentaryChangeButton]; + [(NSButtonCell*)[self cell] setLineBreakMode:NSLineBreakByWordWrapping]; + [(NSButtonCell*)[self cell] setBaseWritingDirection:NSWritingDirectionNatural]; + //[(NSButtonCell*)[self cell] setType:NSImageCellType]; + + [self setBordered:NO]; + [self setBezelStyle:NSRegularSquareBezelStyle]; + [(NSButtonCell*)[self cell] setBezeled:NO]; + [self setTransparent:NO]; + [self setImagePosition:NSImageOnly]; + [self setShowsBorderOnlyWhileMouseInside:NO]; + [self setAutoresizingMask:(NSViewMinXMargin)]; + [self setFont:[NSFont controlContentFontOfSize:11]]; + + + [self setAllowsMixedState:NO]; + [self setState:NSOffState]; + + [self setContinuous:NO]; + //[self sendActionOn:NSLeftMouseUpMask]; + + } + + return self; +} + +- (void) dealloc +{ + [identifier release]; + [typeIdentifier release]; + [targetURI release]; + [targetScript release]; + //[mediabar release]; + + [super dealloc]; +} + +#pragma mark - + +- (NSDictionary*) dictionaryRepresentation +{ + NSMutableDictionary *representation = [NSMutableDictionary dictionaryWithCapacity:4]; + + if ( identifier != nil ) [representation setObject:[self identifier] forKey:@"identifier"]; + if ( typeIdentifier != nil ) [representation setObject:[self typeIdentifier] forKey:@"typeIdentifier"]; + if ( targetURI != nil ) [representation setObject:[[self targetURI] absoluteString] forKey:@"targetURI"]; + if ( targetScript != nil ) [representation setObject:[NSKeyedArchiver archivedDataWithRootObject:[self targetScript]] forKey:@"targetScript"]; + + if ( [self image] != nil ) [representation setObject:[NSKeyedArchiver archivedDataWithRootObject:[self image]] forKey:@"image"]; + if ( [self title] != nil ) [representation setObject:[self title] forKey:@"title"]; + if ( [self toolTip] != nil ) [representation setObject:[self toolTip] forKey:@"tooltip"]; + + return representation; +} + +- (void) setAttributesFromDictionaryRepresentation:(NSDictionary*)aDictionary +{ + [self setIdentifier:[aDictionary objectForKey:@"identifier"]]; + [self setTypeIdentifier:[aDictionary objectForKey:@"typeIdentifier"]]; + + NSData *scriptData = [aDictionary objectForKey:@"targetScript"]; + if ( scriptData != nil ) [self setTargetScript:[NSKeyedUnarchiver unarchiveObjectWithData:scriptData]]; + + NSData *imageData = [aDictionary objectForKey:@"image"]; + if ( imageData != nil ) [self setImage:[NSKeyedUnarchiver unarchiveObjectWithData:imageData]]; + [self setTitle:[aDictionary objectForKey:@"title"]]; + [self setToolTip:[aDictionary objectForKey:@"tooltip"]]; + + NSString *uriString = [aDictionary objectForKey:@"targetURI"]; + if ( uriString != nil ) [self setTargetURI:[NSURL URLWithString:uriString]]; + +} + +#pragma mark - + +- (NSString *)identifier +{ + return identifier; +} + +- (void) setIdentifier:(NSString*)aString +{ + if ( identifier != aString ) + { + [identifier release]; + identifier = [aString copyWithZone:[self zone]]; + } +} + +- (NSSize) size +{ + return [self frame].size; +} + +- (void) setSize:(NSSize)aSize +{ + NSRect aFrame = [self frame]; + aFrame.size = aSize; + [self setFrame:aFrame]; +} + +- (PDMediaBar*) mediabar +{ + return mediabar; +} + +- (void) setMediabar:(PDMediaBar*)aMediabar +{ + if ( mediabar != aMediabar ) + { + //[mediabar release]; + //mediabar = [aMediabar retain]; + + // do not retain otherwise there is a retain loop between the mediabar and the mediabar items + mediabar = aMediabar; + } +} + +#pragma mark - + +- (NSNumber*) typeIdentifier +{ + return typeIdentifier; +} + +- (void) setTypeIdentifier:(NSNumber*)aNumber +{ + if ( typeIdentifier != aNumber ) + { + [typeIdentifier release]; + typeIdentifier = [aNumber copyWithZone:[self zone]]; + } +} + +- (NSURL*) targetURI +{ + return targetURI; +} + +- (void) setTargetURI:(NSURL*)aURL +{ + if ( targetURI != aURL ) + { + [targetURI release]; + targetURI = [aURL copyWithZone:[self zone]]; + } +} + +- (NSAttributedString*) targetScript +{ + return targetScript; +} + +- (void) setTargetScript:(NSAttributedString*)aString +{ + if ( targetScript != aString ) + { + [targetScript release]; + targetScript = [aString copyWithZone:[self zone]]; + } +} + +#pragma mark - + +- (void) setTitle:(NSString*)aString +{ + [super setTitle:aString]; + [self setImagePosition:NSImageOnly]; + //[(NSButtonCell*)[self cell] setType:NSImageCellType]; +} + +- (NSMenu *)menuForEvent:(NSEvent *)theEvent +{ + return [[self mediabar] menuForEvent:theEvent]; +} + +@end diff --git a/PDOutlineView.h b/PDOutlineView.h new file mode 100644 index 0000000..d32f682 --- /dev/null +++ b/PDOutlineView.h @@ -0,0 +1,24 @@ +// +// PDOutlineView.h +// SproutedInterface +// +// Created by Philip Dow on 2/1/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDOutlineView : NSOutlineView { + +} + +@end + +@interface NSObject (PDOutlineViewDelegate) + +- (void) outlineView:(NSOutlineView*)anOutlineView leftNavigationEvent:(NSEvent*)anEvent; +- (void) outlineView:(NSOutlineView*)anOutlineView rightNavigationEvent:(NSEvent*)anEvent; + +@end \ No newline at end of file diff --git a/PDOutlineView.m b/PDOutlineView.m new file mode 100644 index 0000000..d2d0409 --- /dev/null +++ b/PDOutlineView.m @@ -0,0 +1,48 @@ +// +// PDOutlineView.m +// SproutedInterface +// +// Created by Philip Dow on 2/1/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation PDOutlineView + +- (void)keyDown:(NSEvent *)event +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s - beginning", [self className], _cmd) + #endif + + unsigned int modifierFlags = [event modifierFlags]; + unichar key = [[event charactersIgnoringModifiers] characterAtIndex:0]; + + if ( key == NSLeftArrowFunctionKey && [[self delegate] respondsToSelector:@selector(outlineView:leftNavigationEvent:)] && ( modifierFlags & NSCommandKeyMask ) ) + [[self delegate] outlineView:self leftNavigationEvent:event]; + else if ( key == NSRightArrowFunctionKey && [[self delegate] respondsToSelector:@selector(outlineView:rightNavigationEvent:)] && ( modifierFlags & NSCommandKeyMask ) ) + [[self delegate] outlineView:self rightNavigationEvent:event]; + else + { + [super keyDown:event]; + } + + #ifdef __DEBUG__ + NSLog(@"%@ %s - ending", [self className], _cmd) + #endif +} + +- (void)cancelOperation:(id)sender +{ + if ([self currentEditor] != nil) + { + [self abortEditing]; + + // We lose focus so re-establish + [[self window] makeFirstResponder:self]; + } +} + +@end diff --git a/PDPhotoView.h b/PDPhotoView.h new file mode 100644 index 0000000..c50a115 --- /dev/null +++ b/PDPhotoView.h @@ -0,0 +1,75 @@ + +// I believe I override a number of methods in MUPhotoView +// so as to use a cell with the class, MUPhotoCell. + +// +// +// MUPhotoView +// +// Copyright (c) 2006 Blake Seely +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// * You include a link to http://www.blakeseely.com in your final product. +// +// Version History: +// +// Version 1.0 - April 17, 2006 - Initial Release +// Version 1.1 - April 29, 2006 - Photo removal support, Added support for reduced-size drawing during live resize +// Version 1.2 - September 24, 2006 - Updated selection behavior, Changed to MIT license, Fixed issue where no images would show, fixed autoscroll + +#import +#import + +//#import +//#import "MUPhotoView.h" + +//! MUPhotoView displays a grid of photos similar to iPhoto's main photo view. The class gives developers several options for providing images - via bindings or delegation. + +//! MUPhotoView displays a resizeable grid of photos, similar to iPhoto's photo view functionality. MUPhotoView provides developers with two different options for passing photo information to the view +//! Most importantly, MUPhotoView currently only deals with an array of photos. It does not yet know how to display titles or any other metadata. It also does not know how to find NSImage objects +//! that are inside another object - it expects NSImage objects. The first method for providing those objects it by binding an array of NSImage objects to the "photosArray" key of the view. +//! If this key has been bound, MUPhotoView will fetch all the images it displays from that binding. The second method is to have a delegate object provide the photos. MUPhotoView will only +//! call the delegate's photo methods if the photosArray key has not been bound. Please see the MUPhotoViewDelegate category documentation for descriptions of the methods. +@interface PDPhotoView : MUPhotoView { + // Please do not access ivars directly - use the accessor methods documented below + + BOOL drawsBackground; + unsigned indexForMenuEvent; + + NSCell *cell; + NSCursor *hoverCursor; + + BOOL amPrinting; +} + +- (NSCell*) cell; +- (void) setCell:(NSCell*)aCell; + +- (NSCursor*) hoverCursor; +- (void) setHoverCursor:(NSCursor*)aCursor; + +- (unsigned) indexForMenuEvent; +- (void) setIndexForMenuEvent:(unsigned)anIndex; + +- (BOOL) drawsBackground; +- (void) setDrawsBackground:(BOOL)draws; + +- (void) ownerWillClose:(NSNotification*)aNotification; + +//- (float)calculatePrintHeight; + +@end + +@interface NSObject (MUPhotoViewMoreDelegateMethods) + +- (NSString*) photoView:(MUPhotoView*)photoView titleForObjectAtIndex:(unsigned int)index; +- (NSString*) photoView:(MUPhotoView*)photoView tooltipForObjectAtIndex:(unsigned int)index; + +@end diff --git a/PDPhotoView.m b/PDPhotoView.m new file mode 100644 index 0000000..61c3035 --- /dev/null +++ b/PDPhotoView.m @@ -0,0 +1,786 @@ + +// I believe I override a number of methods in MUPhotoView +// so as to use a cell with the class, MUPhotoCell. + +// +// MUPhotoView +// +// Copyright (c) 2006 Blake Seely +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// * You include a link to http://www.blakeseely.com in your final product. +// +// Version History: +// +// Version 1.0 - April 17, 2006 - Initial Release +// Version 1.1 - April 29, 2006 - Photo removal support, Added support for reduced-size drawing during live resize +// Version 1.2 - September 24, 2006 - Updated selection behavior, Changed to MIT license, Fixed issue where no images would show, fixed autoscroll + +#import +#import + +@implementation PDPhotoView + +#pragma mark - +// Initializers and Dealloc +#pragma mark Initializers and Dealloc + +- (id)initWithFrame:(NSRect)frameRect +{ + if ((self = [super initWithFrame:frameRect]) != nil) { + + drawsBackground = YES; + hoverCursor = nil; + amPrinting = NO; + + MUPhotoCell *defaultCell = [[[MUPhotoCell alloc] initImageCell:nil] autorelease]; + [self setCell:defaultCell]; + + } + + return self; +} + +#pragma mark - + +- (void) dealloc +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + [cell release], cell = nil; + [hoverCursor release], hoverCursor = nil; + + [super dealloc]; +} + +- (void) ownerWillClose:(NSNotification*)aNotification +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + if ( autoscrollTimer != nil ) + { + [autoscrollTimer invalidate]; + autoscrollTimer = nil; + } + + if ( photoResizeTimer != nil ) + { + [photoResizeTimer invalidate]; + photoResizeTimer = nil; + } + +} + +#pragma mark - + +- (NSCell*) cell +{ + return cell; +} + +- (void) setCell:(NSCell*)aCell +{ + if ( cell != aCell ) + { + [cell release]; + cell = [aCell copyWithZone:[self zone]]; + } +} + +- (NSCursor*) hoverCursor +{ + return hoverCursor; +} + +- (void) setHoverCursor:(NSCursor*)aCursor +{ + if ( hoverCursor != aCursor ) + { + [hoverCursor release]; + hoverCursor = [aCursor retain]; + } +} + +- (unsigned) indexForMenuEvent +{ + return indexForMenuEvent; +} + +- (void) setIndexForMenuEvent:(unsigned)anIndex +{ + indexForMenuEvent = anIndex; +} + +- (NSMenu *)menuForEvent:(NSEvent *)theEvent +{ + [self setIndexForMenuEvent:[self photoIndexForPoint:[self convertPoint:[theEvent locationInWindow] fromView:nil]]]; + return [super menuForEvent:theEvent]; +} + +- (BOOL) drawsBackground +{ + return drawsBackground; +} + +- (void) setDrawsBackground:(BOOL)draws +{ + drawsBackground = draws; +} + +#pragma mark - + +- (void)setSelectionIndexes:(NSIndexSet *)indexes +{ + // CHANGE: allow the delegate to modify the selection even if the selected photo index is available + + NSMutableIndexSet *oldSelection = nil; + + // Set the new selection, but save the old selection so we know exactly what to redraw + if (nil != [self selectedPhotoIndexes]) + { + oldSelection = [[self selectedPhotoIndexes] retain]; + if ( [delegate respondsToSelector:@selector(photoView:willSetSelectionIndexes:)] ) + indexes = [delegate photoView:self willSetSelectionIndexes:indexes]; + [self setSelectedPhotoIndexes:indexes]; + } + else if (nil != delegate) + { + // We have to iterate through the photos to figure out which ones the delegate thinks are selected - that's the only way to know the old selection when in delegate mode + oldSelection = [[NSMutableIndexSet alloc] init]; + int i, count = [self photoCount]; + for( i = 0; i < count; i += 1 ) + { + if ([self isPhotoSelectedAtIndex:i]) + { + [oldSelection addIndex:i]; + } + } + + // Now update the selection + indexes = [delegate photoView:self willSetSelectionIndexes:indexes]; + [delegate photoView:self didSetSelectionIndexes:indexes]; + } + + [self dirtyDisplayRectsForNewSelection:indexes oldSelection:oldSelection]; + [oldSelection release]; +} + +- (void)setPhotosArray:(NSArray *)aPhotosArray +{ + [super setPhotosArray:aPhotosArray]; + [[self window] invalidateCursorRectsForView:self]; + // call super's implementation and invalidate our cursor rects +} + +- (void)setPhotoSize:(float)aPhotoSize +{ + [super setPhotoSize:aPhotoSize]; + [[self window] invalidateCursorRectsForView:self]; + // call super's implementation and invalidate our cursor rects +} + +- (void)setPhotoVerticalSpacing:(float)aPhotoVerticalSpacing +{ + [super setPhotoVerticalSpacing:aPhotoVerticalSpacing]; + [[self window] invalidateCursorRectsForView:self]; + // call super's implementation and invalidate our cursor rects +} + +- (void)setPhotoHorizontalSpacing:(float)aPhotoHorizontalSpacing +{ + [super setPhotoHorizontalSpacing:aPhotoHorizontalSpacing]; + [[self window] invalidateCursorRectsForView:self]; + // call super's implementation and invalidate our cursor rects +} + +#pragma mark - + +- (void)drawRect:(NSRect)rect +{ + if ( [NSGraphicsContext currentContextDrawingToScreen] ) + [self removeAllToolTips]; // djw: are these really working? + + if ( [self drawsBackground] ) + { + // draw the background color + [[self backgroundColor] set]; + [NSBezierPath fillRect:rect]; + } + + // update internal grid size, adjust height based on the new grid size + // because I may not find out that the photos array has changed until I draw and read the photos from the delegate, this call has to stay here + [self updateGridAndFrame]; + + // get the number of photos + unsigned photoCount = [self photoCount]; + if (0 == photoCount) + return; + + // any other setup + if (useHighQualityResize) { + [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; + } + + NSCell *drawingCell = [self cell]; + + /**** BEGIN Drawing Photos ****/ + NSRange rangeToDraw = [self photoIndexRangeForRect:rect]; // adjusts for photoCount if the rect goes outside my range + unsigned index; + unsigned lastIndex = rangeToDraw.location + rangeToDraw.length; + // Our version of photoIndexRangeForRect: returns one item more in the range than the MUPhotoView 1.2 version. Hence we also + // must do one less iteration so here we do < instead of <= + for (index = rangeToDraw.location; index < lastIndex; index++) { + + // Get the image at the current index - a gray bezier anywhere in the view means it asked for an image, but got nil for that index + NSImage *photo = nil; + if ([self inLiveResize]) { + photo = [self fastPhotoAtIndex:index]; + } + + if (nil == photo) { + photo = [self photoAtIndex:index]; + } + + // scale it to the appropriate size, this method should automatically set high quality if necessary + photo = [self scalePhoto:photo]; + + // get all the appropriate positioning information + NSRect gridRect = [self centerScanRect:[self gridRectForIndex:index]]; + NSSize scaledSize = [self scaledPhotoSizeForSize:[photo size]]; + NSRect photoRect = [self rectCenteredInRect:gridRect withSize:scaledSize]; + photoRect = [self centerScanRect:photoRect]; + + //**** BEGIN Background Drawing - any drawing that technically goes under the image ****/ + // kSelectionStyleShadowBox draws a semi-transparent rounded rect behind/around the image + if ([self isPhotoSelectedAtIndex:index] && [self useShadowSelection]) { + + NSBezierPath *shadowBoxPath = [self shadowBoxPathForRect:gridRect]; + [shadowBoxColor set]; + [shadowBoxPath fill]; + } + + //**** END Background Drawing ****/ + + // draw the current photo + + NSString *photoTitle = nil; + if ( [[self delegate] respondsToSelector:@selector(photoView:titleForObjectAtIndex:)] ) + photoTitle = [[self delegate] photoView:self titleForObjectAtIndex:index]; + + [drawingCell setTitle:photoTitle]; + [drawingCell setObjectValue:photo]; + + // kBorderStyleShadow - set the appropriate shadow + if ([self useShadowBorder]) { + [borderShadow set]; + } + + [drawingCell drawWithFrame:photoRect inView:self]; + + [noShadow set]; + + // register the tooltip area - photoRect + if ( [NSGraphicsContext currentContextDrawingToScreen] ) + [self addToolTipRect:photoRect owner:self userData:nil]; + + // restore the photo's flipped status + + // BEGIN Foreground Drawing - includes outline borders, selection rectangles + if ([self isPhotoSelectedAtIndex:index] && [self useBorderSelection]) { + NSBezierPath *selectionBorder = [NSBezierPath bezierPathWithRect:NSInsetRect(photoRect,-3.0,-3.0)]; + [selectionBorder setLineWidth:[self selectionBorderWidth]]; + [[self selectionBorderColor] set]; + [selectionBorder stroke]; + } else if ([self useOutlineBorder]) { + photoRect = NSInsetRect(photoRect,0.5,0.5); // line up the 1px border so it completely fills a single row of pixels + NSBezierPath *outline = [NSBezierPath bezierPathWithRect:photoRect]; + [outline setLineWidth:1.0]; + [borderOutlineColor set]; + [outline stroke]; + } + + + //**** END Foreground Drawing ****// + + + } + + //**** END Drawing Photos ****// +} + +- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)userData +{ + unsigned idx = [self photoIndexForPoint:point]; + if (idx < [self photoCount] && [[self delegate] respondsToSelector:@selector(photoView:tooltipForObjectAtIndex:)] ) + { + return [delegate photoView:self tooltipForObjectAtIndex:[self photoIndexForPoint:point]]; + } + return nil; +} + +- (void)resetCursorRects +{ + if ( [self hoverCursor] == nil ) + return; + + int i; + for ( i = 0; i < [[self photosArray] count]; i++ ) + [self addCursorRect:[self photoRectForIndex:i] cursor:[self hoverCursor]]; +} + +#pragma mark - + +- (void) mouseDown:(NSEvent *) event +{ + if ( [event clickCount] == 2) + { + // There could be more than one selected photo. In that case, call the delegates doubleClickOnPhotoAtIndex routine for + // each selected photo. + unsigned int selectedIndex = [[self selectionIndexes] firstIndex]; + while (selectedIndex != NSNotFound) { + [delegate photoView:self doubleClickOnPhotoAtIndex:selectedIndex withFrame:[self photoRectForIndex:selectedIndex]]; + selectedIndex = [[self selectionIndexes] indexGreaterThanIndex:selectedIndex]; + } + } + else + { + mouseDown = YES; + mouseDownPoint = [self convertPoint:[event locationInWindow] fromView:nil]; + mouseCurrentPoint = mouseDownPoint; + + unsigned clickedIndex = [self photoIndexForPoint:mouseDownPoint]; + NSRect photoRect = [self photoRectForIndex:clickedIndex]; + unsigned int flags = [event modifierFlags]; + NSMutableIndexSet* indexes = [[self selectionIndexes] mutableCopy]; + BOOL imageHit = NSPointInRect(mouseDownPoint, photoRect); + + if (imageHit) { + if (flags & NSCommandKeyMask) { + // Flip current image selection state. + if ([indexes containsIndex:clickedIndex]) { + [indexes removeIndex:clickedIndex]; + } else { + [indexes addIndex:clickedIndex]; + } + } else { + if (flags & NSShiftKeyMask) { + // Add range to selection. + if ([indexes count] == 0) { + [indexes addIndex:clickedIndex]; + } else { + unsigned int origin = (clickedIndex < [indexes lastIndex]) ? clickedIndex :[indexes lastIndex]; + unsigned int length = (clickedIndex < [indexes lastIndex]) ? [indexes lastIndex] - clickedIndex : clickedIndex - [indexes lastIndex]; + + length++; + [indexes addIndexesInRange:NSMakeRange(origin, length)]; + } + } else { + if (![self isPhotoSelectedAtIndex:clickedIndex]) { + // Photo selection without modifiers. + [indexes removeAllIndexes]; + [indexes addIndex:clickedIndex]; + } + } + } + + potentialDragDrop = YES; + } else { + if ((flags & NSShiftKeyMask) == 0) { + [indexes removeAllIndexes]; + } + potentialDragDrop = NO; + } + + [self setSelectionIndexes:indexes]; + [indexes release]; + } +} + +- (void)mouseUp:(NSEvent *)event +{ + return; + // don't do a damn thing + // bug: calling set needs display results in background drawing over image parts without the images being asked to draw again +} + +- (void)mouseDragged:(NSEvent *)event +{ + + + if (0 == columns) return; + mouseCurrentPoint = [self convertPoint:[event locationInWindow] fromView:nil]; + + // if the mouse has moved less than 5px in either direction, don't register the drag yet + float xFromStart = fabs((mouseDownPoint.x - mouseCurrentPoint.x)); + float yFromStart = fabs((mouseDownPoint.y - mouseCurrentPoint.y)); + if ((xFromStart < 5) && (yFromStart < 5)) { + return; + + } else if (potentialDragDrop && (nil != delegate)) { + // create a drag image + unsigned clickedIndex = [self photoIndexForPoint:mouseDownPoint]; + NSImage *clickedImage = [self photoAtIndex:clickedIndex]; + //BOOL flipped = [clickedImage isFlipped]; + //[clickedImage setFlipped:NO]; + NSSize scaledSize = [self scaledPhotoSizeForSize:[clickedImage size]]; + if (nil == clickedImage) { // creates a red image, which should let the user/developer know something is wrong + clickedImage = [[[NSImage alloc] initWithSize:NSMakeSize(photoSize,photoSize)] autorelease]; + [clickedImage lockFocus]; + [[NSColor redColor] set]; + [NSBezierPath fillRect:NSMakeRect(0,0,photoSize,photoSize)]; + [clickedImage unlockFocus]; + } + NSImage *dragImage = [[NSImage alloc] initWithSize:scaledSize]; + + // draw the drag image as a semi-transparent copy of the image the user dragged, and optionally a red badge indicating the number of photos + [dragImage lockFocus]; + [clickedImage drawInRect:NSMakeRect(0,0,scaledSize.width,scaledSize.height) fromRect:NSMakeRect(0,0,[clickedImage size].width,[clickedImage size].height) operation:NSCompositeCopy fraction:0.5]; + [dragImage unlockFocus]; + + //[clickedImage setFlipped:flipped]; + + // if there's more than one image, put a badge on the photo + if ([[self selectionIndexes] count] > 1) { + NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init]; + [attributes setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName]; + [attributes setObject:[NSFont fontWithName:@"Helvetica" size:14] forKey:NSFontAttributeName]; + NSAttributedString *badgeString = [[NSAttributedString alloc] initWithString:[[NSNumber numberWithInt:[[self selectionIndexes] count]] stringValue] attributes:attributes]; + NSSize stringSize = [badgeString size]; + int diameter = stringSize.width; + if (stringSize.height > diameter) diameter = stringSize.height; + diameter += 5; + + // calculate the badge circle + int minY = 5; + int maxX = [dragImage size].width - 5; + int maxY = minY + diameter; + int minX = maxX - diameter; + NSBezierPath *circle = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(minX,minY,maxX-minX,maxY-minY)]; + // draw the circle + [dragImage lockFocus]; + [[NSColor colorWithDeviceRed:1 green:0.1 blue:0.1 alpha:0.7] set]; + [circle fill]; + [dragImage unlockFocus]; + + // draw the string + NSPoint point; + point.x = maxX - ((maxX - minX) / 2) - 1; + point.y = (maxY - minY) / 2; + point.x = point.x - (stringSize.width / 2); + point.y = point.y - (stringSize.height / 2) + 7; + + [dragImage lockFocus]; + [badgeString drawAtPoint:point]; + [dragImage unlockFocus]; + + [badgeString release]; + [attributes release]; + } + + // get the pasteboard and register the returned types with delegate as the owner + NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSDragPboard]; + [pb declareTypes:[NSArray array] owner:nil]; // clear the pasteboard + [delegate photoView:self fillPasteboardForDrag:pb]; + + // place the cursor in the center of the drag image + NSPoint p = [self convertPoint:[event locationInWindow] fromView:nil]; + NSSize imageSize = [dragImage size]; + p.x = p.x - imageSize.width / 2; + p.y = p.y + imageSize.height / 2; + + [self dragImage:dragImage at:p offset:NSZeroSize event:event pasteboard:pb source:self slideBack:YES]; + + [dragImage release]; + + } else { + // adjust the mouse current point so that it's not outside the frame + NSRect frameRect = [self frame]; + if (mouseCurrentPoint.x < NSMinX(frameRect)) + mouseCurrentPoint.x = NSMinX(frameRect); + if (mouseCurrentPoint.x > NSMaxX(frameRect)) + mouseCurrentPoint.x = NSMaxX(frameRect); + if (mouseCurrentPoint.y < NSMinY(frameRect)) + mouseCurrentPoint.y = NSMinY(frameRect); + if (mouseCurrentPoint.y > NSMaxY(frameRect)) + mouseCurrentPoint.y = NSMaxY(frameRect); + + // determine the rect for the current drag area + float minX, maxX, minY, maxY; + minX = (mouseCurrentPoint.x < mouseDownPoint.x) ? mouseCurrentPoint.x : mouseDownPoint.x; + minY = (mouseCurrentPoint.y < mouseDownPoint.y) ? mouseCurrentPoint.y : mouseDownPoint.y; + maxX = (mouseCurrentPoint.x > mouseDownPoint.x) ? mouseCurrentPoint.x : mouseDownPoint.x; + maxY = (mouseCurrentPoint.y > mouseDownPoint.y) ? mouseCurrentPoint.y : mouseDownPoint.y; + if (maxY > NSMaxY(frameRect)) + maxY = NSMaxY(frameRect); + if (maxX > NSMaxX(frameRect)) + maxX = NSMaxX(frameRect); + + NSRect selectionRect = NSMakeRect(minX,minY,maxX-minX,maxY-minY); + + unsigned minIndex = [self photoIndexForPoint:NSMakePoint(minX, minY)]; + unsigned xRun = [self photoIndexForPoint:NSMakePoint(maxX, minY)] - minIndex + 1; + unsigned yRun = [self photoIndexForPoint:NSMakePoint(minX, maxY)] - minIndex + 1; + unsigned selectedRows = (yRun / columns); + + // Save the current selection (if any), then populate the drag indexes + // this allows us to shift band select to add to the current selection. + [dragSelectedPhotoIndexes removeAllIndexes]; + [dragSelectedPhotoIndexes addIndexes:[self selectionIndexes]]; + + // add indexes in the drag rectangle + int i; + for (i = 0; i <= selectedRows; i++) { + unsigned rowStartIndex = (i * columns) + minIndex; + int j; + for (j = rowStartIndex; j < (rowStartIndex + xRun); j++) { + if (NSIntersectsRect([self photoRectForIndex:j],selectionRect)) + [dragSelectedPhotoIndexes addIndex:j]; + } + } + + // if requested, set the selection. this could cause a rapid series of KVO notifications, so if this is false, the view tracks + // the selection internally, but doesn't pass it to the bindings or the delegates until the drag is over. + // This will cause an appropriate redraw. + if (sendsLiveSelectionUpdates) + { + [self setSelectionIndexes:dragSelectedPhotoIndexes]; + } + + [[self superview] autoscroll:event]; + [self setNeedsDisplayInRect:[self visibleRect]]; + } +} + + +- (NSImage *)fastPhotoAtIndex:(unsigned)index +{ + NSImage *fastPhoto = nil; + if ((nil != [self photosArray]) && (index < [[self photosArray] count])) + { + fastPhoto = [photosFastArray objectAtIndex:index]; + if ((NSNull *)fastPhoto == [NSNull null]) + { + // Change this if you want higher/lower quality fast photos + float fastPhotoSize = 100.0; + + NSImageRep *fullSizePhotoRep = [[self scalePhoto:[self photoAtIndex:index]] bestRepresentationForDevice:nil]; + + // Figure out what the scaled size is + float longSide = [fullSizePhotoRep pixelsWide]; + if (longSide < [fullSizePhotoRep pixelsHigh]) + longSide = [fullSizePhotoRep pixelsHigh]; + + float scale = fastPhotoSize / longSide; + + NSSize scaledSize; + scaledSize.width = [fullSizePhotoRep pixelsWide] * scale; + scaledSize.height = [fullSizePhotoRep pixelsHigh] * scale; + + // Draw the full-size image into our fast, small image. + fastPhoto = [[NSImage alloc] initWithSize:scaledSize]; + //[fastPhoto setFlipped:YES]; + [fastPhoto lockFocus]; + [fullSizePhotoRep drawInRect:NSMakeRect(0.0, 0.0, scaledSize.width, scaledSize.height)]; + [fastPhoto unlockFocus]; + + // Save it off + [photosFastArray replaceObjectAtIndex:index withObject:fastPhoto]; + + [fastPhoto autorelease]; + } + } else if ((nil != delegate) && ([delegate respondsToSelector:@selector(photoView:fastPhotoAtIndex:)])) { + fastPhoto = [delegate photoView:self fastPhotoAtIndex:index]; + } + + // if the above calls failed, try to just fetch the full size image + if (![fastPhoto isValid]) { + fastPhoto = [self photoAtIndex:index]; + } + + return fastPhoto; +} + +- (NSImage *)scaleImage:(NSImage *)image toSize:(float)size +{ + NSImageRep *fullSizePhotoRep = [[self scalePhoto:image] bestRepresentationForDevice:nil]; + + float longSide = [fullSizePhotoRep pixelsWide]; + if (longSide < [fullSizePhotoRep pixelsHigh]) + longSide = [fullSizePhotoRep pixelsHigh]; + + float scale = size / longSide; + + NSSize scaledSize; + scaledSize.width = [fullSizePhotoRep pixelsWide] * scale; + scaledSize.height = [fullSizePhotoRep pixelsHigh] * scale; + + NSImage *fastPhoto = [[NSImage alloc] initWithSize:scaledSize]; + //[fastPhoto setFlipped:YES]; + [fastPhoto lockFocus]; + [fullSizePhotoRep drawInRect:NSMakeRect(0.0, 0.0, scaledSize.width, scaledSize.height)]; + [fastPhoto unlockFocus]; + + return [fastPhoto autorelease]; +} + +#pragma mark - + +- (void)updateGridAndFrame +{ + /**** BEGIN Dimension calculations and adjustments ****/ + + + // get the number of photos + unsigned photoCount = [self photoCount]; + + // calculate the base grid size + gridSize.height = [self photoSize] + [self photoVerticalSpacing]; + gridSize.width = [self photoSize] + [self photoHorizontalSpacing]; + + // if there are no photos, return + if (0 == photoCount) { + columns = 0; + rows = 0; + float width = [self frame].size.width; + float height = [[[self enclosingScrollView] contentView] frame].size.height; + [self setFrameSize:NSMakeSize(width, height)]; + return; + } + + // calculate the number of columns (ivar) + float width = [self frame].size.width; + columns = width / gridSize.width; + + // minimum 1 column + if (1 > columns) + columns = 1; + + // if we have fewer photos than columns, adjust downward + if (photoCount < columns) + columns = photoCount; + + // adjust the grid size width for extra space + gridSize.width += (width - (columns * gridSize.width)) / columns; + + // calculate the number of rows of photos based on the total count and the number of columns (ivar) + rows = photoCount / columns; + if (0 < (photoCount % columns)) + rows++; + // adjust my frame height to contain all the photos + float height = rows * gridSize.height; + NSScrollView *scroll = [self enclosingScrollView]; + if ((nil != scroll) && (height < [[scroll contentView] frame].size.height)) + height = [[scroll contentView] frame].size.height; + + // set my new frame size + [self setFrameSize:NSMakeSize(width, height)]; + + + + /**** END Dimension calculations and adjustments ****/ + return; +} + + +- (void)updatePhotoResizing +{ + NSTimeInterval timeSinceResize = [[NSDate date] timeIntervalSinceReferenceDate] - [photoResizeTime timeIntervalSinceReferenceDate]; + if (timeSinceResize > 1) { + isDonePhotoResizing = YES; + //[photoResizeTimer invalidate]; + + // **** PhilDow change - release timer **** + //[photoResizeTimer release]; + //photoResizeTimer = nil; + } + + if ( [self inLiveResize] ) + [self viewDidEndLiveResize]; +} + +#pragma mark - + +- (NSAttributedString *)pageHeader +{ + return nil; +} + +- (NSAttributedString *)pageFooter +{ + return nil; +} + +/* +// Return the number of pages available for printing +- (BOOL)knowsPageRange:(NSRangePointer)range { + + [self updateGridAndFrame]; + + NSRect bounds = NSMakeRect(0, 0, gridSize.width, gridSize.height); + float printHeight = [self calculatePrintHeight]; + + range->location = 1; + range->length = NSHeight(bounds) / printHeight + 1; + return YES; +} + +// Return the drawing rectangle for a particular page number +- (NSRect)rectForPage:(int)page { + NSRect bounds = NSMakeRect(0, 0, gridSize.width, gridSize.height); + float pageHeight = [self calculatePrintHeight]; + return NSMakeRect( NSMinX(bounds), NSMaxY(bounds) - page * pageHeight, + NSWidth(bounds), pageHeight ); +} + +// Calculate the vertical size of the view that fits on a single page +- (float)calculatePrintHeight { + // Obtain the print info object for the current operation + NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo]; + + // Calculate the page height in points + NSSize paperSize = [pi paperSize]; + float pageHeight = paperSize.height - [pi topMargin] - [pi bottomMargin]; + + // Convert height to the scaled view + float scale = [[[pi dictionary] objectForKey:NSPrintScalingFactor] + floatValue]; + return pageHeight / scale; +} +*/ + +/* +- (void)beginDocument +{ + amPrinting = YES; + [super beginDocument]; +} + +- (void)endDocument +{ + [super endDocument]; + amPrinting = NO; +} +*/ + +/* +- (void)print:(id)sender +{ + amPrinting = YES; + [super print:sender]; + amPrinting = NO; +} +*/ + +@end + diff --git a/PDPopUpButton.h b/PDPopUpButton.h new file mode 100644 index 0000000..30b95be --- /dev/null +++ b/PDPopUpButton.h @@ -0,0 +1,18 @@ +// +// PDPopupButton.h +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDPopUpButtonCell; + +@interface PDPopUpButton : NSPopUpButton { + +} + +@end diff --git a/PDPopUpButton.m b/PDPopUpButton.m new file mode 100644 index 0000000..f245df0 --- /dev/null +++ b/PDPopUpButton.m @@ -0,0 +1,86 @@ +// +// PDPopupButton.m +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDPopUpButton + +- (id)initWithCoder:(NSCoder *)decoder { + + /* + NSKeyedUnarchiver *coder = (id)decoder; + + // gather info about the superclass's cell and save the archiver's old mapping + Class superCell = [[self superclass] cellClass]; + NSString *oldClassName = NSStringFromClass( superCell ); + + NSLog(@"%@ -> %@", oldClassName, NSStringFromClass([[self class] cellClass])); + + Class oldClass = [coder classForClassName: oldClassName]; + if( !oldClass ) oldClass = superCell; + + // override what comes out of the unarchiver + [coder setClass: [[self class] cellClass] forClassName: oldClassName]; + + // unarchive + self = [super initWithCoder: coder]; + + // set it back + //[coder setClass: oldClass forClassName: oldClassName]; + + NSLog([[self cell] className]); + */ + + /* + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] + initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + + [anArchiver encodeClassName:@"NSPopUpButtonCell" intoClassName:@"PDPopUpButtonCell"]; + [anArchiver encodeRootObject:[self cell]]; + + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + + } + */ + + if ( self = [super initWithCoder:decoder] ) { + NSMenu *theMenu = [[self menu] copyWithZone:[self zone]]; + [self setCell:[[[PDPopUpButtonCell alloc] + initTextCell:[self title] + pullsDown:[self pullsDown]] autorelease]]; + [self setMenu:theMenu]; + [theMenu release]; + } + + return self; +} + + +- (void) awakeFromNib { + + [[self cell] setMenu:[self menu]]; + +} + + ++ (Class) cellClass +{ + return [PDPopUpButtonCell class]; +} + + +//- (BOOL) isFlipped { return YES; } + +- (BOOL)isOpaque { return NO; } + + +@end diff --git a/PDPopUpButtonCell.h b/PDPopUpButtonCell.h new file mode 100644 index 0000000..3743058 --- /dev/null +++ b/PDPopUpButtonCell.h @@ -0,0 +1,16 @@ +// +// PDPopupButtonCell.h +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDPopUpButtonCell : NSPopUpButtonCell { + +} + +@end diff --git a/PDPopUpButtonCell.m b/PDPopUpButtonCell.m new file mode 100644 index 0000000..a71f30b --- /dev/null +++ b/PDPopUpButtonCell.m @@ -0,0 +1,133 @@ +// +// PDPopupButtonCell.m +// SproutedInterface +// +// Created by Philip Dow on 12/10/05. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +@implementation PDPopUpButtonCell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + [self setBordered:NO]; + } + + return self; + +} + +- (id)initTextCell:(NSString *)stringValue pullsDown:(BOOL)pullDown { + + if ( self = [super initTextCell:stringValue pullsDown:pullDown] ) { + + [self setBordered:NO]; + } + + return self; +} + +- (void) dealloc { + + [super dealloc]; + +} + +#pragma mark - + +- (NSRect)drawingRectForBounds:(NSRect)theRect { + return theRect; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect { + return theRect; +} + + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // + // bypass the frame + // + + [self drawInteriorWithFrame:cellFrame inView:controlView]; + +} + + + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + float alpha = ( [self isEnabled] ? 1.0 : 0.70 ); + NSColor *gradientStart, *gradientEnd, *borderColor, *arrowColor; + + if ( [self isHighlighted] ) { + + arrowColor = [NSColor colorWithCalibratedRed:40.0/255.0 green:40.0/255.0 blue:40.0/255.0 alpha:0.9]; + borderColor = [NSColor colorWithCalibratedRed:149.0/255.0 green:149.0/255.0 blue:149.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:186.0/255.0 green:186.0/255.0 blue:186.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:alpha]; + + } + + else { + + arrowColor = [NSColor colorWithCalibratedRed:40.0/255.0 green:40.0/255.0 blue:40.0/255.0 alpha:0.8]; + borderColor = [NSColor colorWithCalibratedRed:173.0/255.0 green:173.0/255.0 blue:173.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:224.0/255.0 green:224.0/255.0 blue:224.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:253.0/255.0 green:253.0/255.0 blue:253.0/255.0 alpha:alpha]; + + } + + int height = cellFrame.size.height; + int width = cellFrame.size.width; + + NSMutableParagraphStyle *pStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopyWithZone:[self zone]]; + [pStyle setLineBreakMode:NSLineBreakByTruncatingTail]; + + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11.0], NSFontAttributeName, + ( [self isEnabled] ? [NSColor blackColor] : [NSColor lightGrayColor] ), NSForegroundColorAttributeName, + pStyle, NSParagraphStyleAttributeName, nil]; + + NSSize stringSize = [[self title] sizeWithAttributes:attributes]; + + NSRect stringRect = NSMakeRect(width/2.0-stringSize.width/2.0, height/2.0-stringSize.height/2.0, + stringSize.width, stringSize.height); + + NSRect targetRect = NSMakeRect( cellFrame.origin.x, stringRect.origin.y-2.0, + cellFrame.size.width, stringRect.size.height+4.0 ); + + targetRect = NSInsetRect(targetRect,3.5,0.5); + + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:8.5] + linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + [borderColor set]; + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:8.5] stroke]; + + NSBezierPath *arrowsPath = [NSBezierPath bezierPath]; + [arrowsPath appendBezierPathWithTriangleInRect:NSMakeRect(0,1,5,4) orientation:AMTriangleUp]; + [arrowsPath appendBezierPathWithTriangleInRect:NSMakeRect(0,-5,5,4) orientation:AMTriangleDown]; + + NSAffineTransform *transform = [NSAffineTransform transform]; + [transform translateXBy:width-17.0 yBy:height/2.0]; + + [arrowsPath transformUsingAffineTransform:transform]; + [arrowColor set]; + [arrowsPath fill]; + + [[self titleOfSelectedItem] drawInRect:NSMakeRect(12, height/2-stringSize.height/2, width-30, stringSize.height) + withAttributes:attributes]; + +} + +@end diff --git a/PDPopUpButtonToolbarItem.h b/PDPopUpButtonToolbarItem.h new file mode 100644 index 0000000..2f6dafc --- /dev/null +++ b/PDPopUpButtonToolbarItem.h @@ -0,0 +1,21 @@ +// +// PDPopUpButtonToolbarItem.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDPopUpButtonToolbarItem : NSPopUpButton +{ + +} + +- (NSSize) iconSize; +- (void) setIconSize:(NSSize)aSize; + +@end diff --git a/PDPopUpButtonToolbarItem.m b/PDPopUpButtonToolbarItem.m new file mode 100644 index 0000000..e6daac0 --- /dev/null +++ b/PDPopUpButtonToolbarItem.m @@ -0,0 +1,77 @@ +// +// PDPopUpButtonToolbarItem.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDPopUpButtonToolbarItem + + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) + { + NSMenu *theMenu = [[self menu] copyWithZone:[self zone]]; + + [self setCell:[[[PDPopUpButtonToolbarItemCell alloc] + initTextCell:[self title] + pullsDown:[self pullsDown]] autorelease]]; + [self setMenu:theMenu]; + + [self setIconSize:NSMakeSize(32,32)]; + [[self cell] setFont:[NSFont systemFontOfSize:11]]; + + [theMenu release]; + } + + /* + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] + initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + + [anArchiver encodeClassName:@"NSPopUpButtonCell" intoClassName:@"PDPopUpButtonToolbarItemCell"]; + [anArchiver encodeRootObject:[self cell]]; + + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + [self setIconSize:NSMakeSize(32,32)]; + [[self cell] setFont:[NSFont systemFontOfSize:11]]; + + } + */ + + return self; +} + + +- (void) awakeFromNib { + + [[self cell] setMenu:[self menu]]; + +} + + ++ (Class) cellClass +{ + return [PDPopUpButtonToolbarItemCell class]; +} + +- (NSSize) iconSize +{ + return [(PDPopUpButtonToolbarItemCell*)[self cell] iconSize]; +} + +-(void) setIconSize:(NSSize)aSize +{ + [(PDPopUpButtonToolbarItemCell*)[self cell] setIconSize:aSize]; + [self setNeedsDisplay:YES]; +} + + +@end diff --git a/PDPopUpButtonToolbarItemCell.h b/PDPopUpButtonToolbarItemCell.h new file mode 100644 index 0000000..bda059f --- /dev/null +++ b/PDPopUpButtonToolbarItemCell.h @@ -0,0 +1,22 @@ + +// +// PDPopUpButtonToolbarItemCell.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDPopUpButtonToolbarItemCell : NSPopUpButtonCell +{ + + NSSize size; +} + +- (NSSize) iconSize; +- (void) setIconSize:(NSSize)aSize; + +@end diff --git a/PDPopUpButtonToolbarItemCell.m b/PDPopUpButtonToolbarItemCell.m new file mode 100644 index 0000000..34b92c3 --- /dev/null +++ b/PDPopUpButtonToolbarItemCell.m @@ -0,0 +1,63 @@ + +// +// PDPopUpButtonToolbarItemCell.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation PDPopUpButtonToolbarItemCell + + +- (NSSize) iconSize +{ + return size; +} + +- (void) setIconSize:(NSSize)aSize +{ + size = aSize; +} + +#pragma mark Drawing + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + [ self drawInteriorWithFrame:cellFrame inView:controlView ]; +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + NSImage *image = [self image]; + + NSRect sourceRect = NSMakeRect( 0.0, 0.0, [ image size ].width ,[ image size ].height); + NSRect destRect = sourceRect; + NSRect controlBounds = [ controlView bounds ]; + + NSGraphicsContext* context = [ NSGraphicsContext currentContext ]; + [ context saveGraphicsState ]; + [context setImageInterpolation:NSImageInterpolationHigh]; + + if ( [ controlView isFlipped ] ) + { + NSAffineTransform* flipTransform = [ NSAffineTransform transform ]; + [ flipTransform translateXBy:0.0 yBy:NSMaxY(controlBounds) ]; + [ flipTransform scaleXBy:1.0 yBy:-1.0 ]; + [ flipTransform concat ]; + } + + destRect.origin.x = ( controlBounds.size.width - [self iconSize].width ) / 2.0; + destRect.origin.y = ( controlBounds.size.height - [self iconSize].height ) / 2.0; + destRect.size.width = [self iconSize].width; + destRect.size.height = [self iconSize].height; + + [ image drawInRect:destRect fromRect:sourceRect operation:NSCompositeSourceOver fraction:( [(NSControl*)[self controlView] isEnabled] ? 1.0 : 0.55 ) ]; + + [ context restoreGraphicsState ]; +} + +@end diff --git a/PDPredicateBuilder.h b/PDPredicateBuilder.h new file mode 100644 index 0000000..1a36a4c --- /dev/null +++ b/PDPredicateBuilder.h @@ -0,0 +1,36 @@ +// +// PDPredicateBuilder.h +// SproutedInterface +// +// Created by Philip Dow on 4/10/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// +// And the .m? + +@protocol PDPredicateBuilder + +- (NSCompoundPredicateType) compoundPredicateType; +- (void) setCompoundPredicateType:(NSCompoundPredicateType)predicateType; + +- (NSPredicate*) predicate; +- (void) setPredicate:(NSPredicate*)aPredicate; + +- (id) delegate; +- (void) setDelegate:(id)anObject; + +- (NSView*) contentView; + +- (NSSize) requiredSize; +- (void) setMinWidth:(float)width; + +- (BOOL) validatePredicate; + +@end + +@interface NSObject (PDPredicateBuilderDelegate) + +- (void) predicateBuilder:(id)aPredicateBuilder predicateDidChange:(NSPredicate*)newPredicate; +- (void) predicateBuilder:(id)aPredicateBuilder sizeDidChange:(NSSize)newSize; + +@end \ No newline at end of file diff --git a/PDPrintTextView.h b/PDPrintTextView.h new file mode 100644 index 0000000..26a6f24 --- /dev/null +++ b/PDPrintTextView.h @@ -0,0 +1,41 @@ +// +// PDPrintTextView.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +// +// This class overrides two methods to provide custom header +// and footers during printing: +// - (NSAttributedString *)pageHeader; +// - (NSAttributedString *)pageFooter; +// +// + +#import + +@interface PDPrintTextView : NSTextView +{ + BOOL printHeader; + BOOL printFooter; + + NSString *printTitle; + NSString *printDate; +} + +- (BOOL) printHeader; +- (void) setPrintHeader:(BOOL)print; + +- (BOOL) printFooter; +- (void) setPrintFooter:(BOOL)print; + +- (NSString*) printTitle; +- (void) setPrintTitle:(NSString*)title; + +- (NSString*) printDate; +- (void) setPrintDate:(NSString*)date; + +@end diff --git a/PDPrintTextView.m b/PDPrintTextView.m new file mode 100644 index 0000000..3f24fa2 --- /dev/null +++ b/PDPrintTextView.m @@ -0,0 +1,136 @@ +// +// PDPrintTextView.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation PDPrintTextView + +- (id)initWithFrame:(NSRect)frameRect textContainer:(NSTextContainer *)aTextContainer { + if ( self = [super initWithFrame:frameRect textContainer:aTextContainer] ) { + printTitle = [[NSString alloc] init]; + printDate = [[NSString alloc] init]; + + } + return self; +} + +- (void) dealloc { + [printTitle release]; + printTitle = nil; + [printDate release]; + printDate = nil; + [super dealloc]; +} + +#pragma mark - + +- (BOOL) printHeader { return printHeader; } + +- (void) setPrintHeader:(BOOL)print { + printHeader = print; +} + +- (BOOL) printFooter { return printFooter; } + +- (void) setPrintFooter:(BOOL)print { + printFooter = print; +} + +- (NSString*) printTitle { return ( printTitle ? printTitle : [NSString string] ); } + +- (void) setPrintTitle:(NSString*)title { + if ( printTitle != title ) { + [printTitle release]; + printTitle = [title copyWithZone:[self zone]]; + } +} + +- (NSString*) printDate { return ( printDate ? printDate : [NSString string] ); } + +- (void) setPrintDate:(NSString*)date { + if ( printDate != date ) { + [printDate release]; + printDate = [date copyWithZone:[self zone]]; + } +} + +#pragma mark - + +- (NSString *)printJobTitle { + + return [self printTitle]; + +} + +- (NSAttributedString *)pageHeader { + + // + // For now returns a string including the journal title and the date + // Will very likely allow for customization later + // + + NSAttributedString *theHeader = nil; + + if ( [self printHeader] ) { + + // format the title and date appropriately + /* + NSMutableDictionary *attributes; + NSMutableParagraphStyle *paragraph; + NSAttributedString *supersReturn; + NSAttributedString *attributedHeader; + NSString *plainText; + + supersReturn = [super pageHeader]; + paragraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + attributes = [[supersReturn attributesAtIndex:0 effectiveRange:nil] mutableCopy]; + plainText = [NSString stringWithFormat:@"%@\n%@", [self printJobTitle], [self printDate]]; + + [paragraph setAlignment:NSRightTextAlignment]; + [attributes setObject:paragraph forKey:NSParagraphStyleAttributeName]; + + attributedHeader = [[NSAttributedString alloc] initWithString:plainText attributes:attributes]; + + // clean up those mutable copies + [attributes release]; + [paragraph release]; + + return [attributedHeader autorelease]; + */ + + theHeader = [super pageHeader]; + + } + else { + + theHeader = [[[NSAttributedString alloc] initWithString:[NSString string]] autorelease]; + + } + + return theHeader; +} + +- (NSAttributedString *)pageFooter { + + // + // Use super's implementation + // - I'm not sure how to figure out which page is being queried + // + + NSAttributedString *theFooter = nil; + + if ( [self printFooter] ) + theFooter = [super pageFooter]; + else + theFooter = [[[NSAttributedString alloc] initWithString:[NSString string]] autorelease]; + + return theFooter; +} + +@end diff --git a/PDRankCell.h b/PDRankCell.h new file mode 100644 index 0000000..e606e05 --- /dev/null +++ b/PDRankCell.h @@ -0,0 +1,27 @@ +// +// PDRankCell.h +// SproutedInterface +// +// Created by Philip Dow on 9/13/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDRankCell : NSTextFieldCell { + float minRank, maxRank, rank; +} + +- (float) minRank; +- (void) setMinRank:(float)value; + +- (float) maxRank; +- (void) setMaxRank:(float)value; + +- (float) rank; +- (void) setRank:(float)value; + + +@end diff --git a/PDRankCell.m b/PDRankCell.m new file mode 100644 index 0000000..d5bb1f3 --- /dev/null +++ b/PDRankCell.m @@ -0,0 +1,120 @@ +// +// PDRankCell.m +// SproutedInterface +// +// Created by Philip Dow on 9/13/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import +#import + +@implementation PDRankCell + +- (id)initWithCoder:(NSCoder *)decoder { + if ( self = [super initWithCoder:decoder] ) { + minRank = 0.0; + maxRank = 1.0; + } + return self; +} + +- (id)initTextCell:(NSString *)aString { + if ( self = [super initTextCell:aString] ) { + minRank = 0.0; + maxRank = 1.0; + } + return self; +} + +#pragma mark - + +- (float) minRank { + return minRank; +} + +- (void) setMinRank:(float)value { + minRank = value; +} + +- (float) maxRank { + return maxRank; +} + +- (void) setMaxRank:(float)value { + maxRank = value; +} + +- (float) rank { + return rank; +} + +- (void) setRank:(float)value { + rank = value; +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + NSNumber *rankObject = [self objectValue]; + float aRank = [rankObject floatValue]; + + // draw the rank + if ( aRank != 0 ) { + + NSRect inset = cellFrame; + + inset.origin.x += 2; + inset.size.width -= 4; + inset.origin.y += 1; + inset.size.height -= 2; + + float delta = ( maxRank - minRank ); + if ( delta == 0 ) delta = 0.000001; + + float rankPercent = ( aRank - minRank ) / delta; + float distance = ( inset.size.width * rankPercent ); + + NSRect rankRect = NSMakeRect( inset.origin.x, + inset.origin.y + ( inset.size.height / 2 ) - 5, + distance, 10); + + NSColor *gradientStart, *gradientEnd; + + gradientStart = [NSColor colorWithCalibratedRed:195.0/255.0 green:195.0/255.0 blue:195.0/255.0 alpha:0.8]; + gradientEnd = [NSColor colorWithCalibratedRed:161.0/255.0 green:161.0/255.0 blue:161.0/255.0 alpha:0.8]; + + NSRect top, bottom; + NSDivideRect(rankRect, &top, &bottom, rankRect.size.height/2, NSMinYEdge); + top.size.height++; + + NSBezierPath *contentPath = [NSBezierPath bezierPath]; + NSBezierPath *clipPath = [NSBezierPath bezierPathWithRoundedRect:rankRect cornerRadius:5.0]; + + int i; + for ( i = 1; i < distance; i+=2 ) + { + [contentPath moveToPoint:NSMakePoint(rankRect.origin.x+i, rankRect.origin.y)]; + [contentPath lineToPoint:NSMakePoint(rankRect.origin.x+i, rankRect.origin.y+rankRect.size.height)]; + } + + NSGraphicsContext *context = [NSGraphicsContext currentContext]; + [context saveGraphicsState]; + [clipPath setClip]; + + [[NSBezierPath bezierPathWithRect:top] linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + [[NSBezierPath bezierPathWithRect:bottom] linearGradientFillWithStartColor:gradientEnd endColor:gradientStart]; + + //[context setShouldAntialias:NO]; + + //[[NSColor colorWithCalibratedWhite:1.0 alpha:0.3] set]; + //[contentPath setLineWidth:1.0]; + //[contentPath stroke]; + + [context restoreGraphicsState]; + } +} + + +@end diff --git a/PDSelfValidatingToolbarItem.h b/PDSelfValidatingToolbarItem.h new file mode 100644 index 0000000..b495a16 --- /dev/null +++ b/PDSelfValidatingToolbarItem.h @@ -0,0 +1,25 @@ +// +// PDSelfEnablingToolbarItem.h +// SproutedInterface +// +// Created by Philip Dow on 12/1/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDSelfValidatingToolbarItem : NSToolbarItem { + + BOOL forcedEnabled; + BOOL forcedEnabling; +} + +- (BOOL) forcedEnabled; +- (void) setForcedEnabled:(BOOL)doEnable; + +- (BOOL) forcedEnabling; +- (void) setForcedEnabling:(BOOL)doForce; + +@end diff --git a/PDSelfValidatingToolbarItem.m b/PDSelfValidatingToolbarItem.m new file mode 100644 index 0000000..9a50db0 --- /dev/null +++ b/PDSelfValidatingToolbarItem.m @@ -0,0 +1,43 @@ +// +// PDSelfEnablingToolbarItem.m +// SproutedInterface +// +// Created by Philip Dow on 12/1/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation PDSelfValidatingToolbarItem + +- (BOOL) forcedEnabled +{ + return forcedEnabled; +} + +- (void) setForcedEnabled:(BOOL)doEnable +{ + forcedEnabled = doEnable; + //[self validate]; +} + +- (BOOL) forcedEnabling +{ + return forcedEnabling; +} + +- (void) setForcedEnabling:(BOOL)doForce +{ + forcedEnabling = doForce; +} + +#pragma mark - + +- (void)validate +{ + [self setEnabled:( [self forcedEnabling] ? [self forcedEnabled] : ( [NSApp targetForAction:[self action]] != nil ) )]; +} + +@end diff --git a/PDStylesButton.h b/PDStylesButton.h new file mode 100644 index 0000000..1efa933 --- /dev/null +++ b/PDStylesButton.h @@ -0,0 +1,21 @@ +// +// PDStylesButton.h +// SproutedInterface +// +// Created by Philip Dow on 5/1/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDStylesButtonCell; + +@interface PDStylesButton : NSButton { + +} + +- (int) superscriptValue; +- (void) setSuperscriptValue:(int)offset; + +@end diff --git a/PDStylesButton.m b/PDStylesButton.m new file mode 100644 index 0000000..ee17f05 --- /dev/null +++ b/PDStylesButton.m @@ -0,0 +1,43 @@ +// +// PDStylesButton.m +// SproutedInterface +// +// Created by Philip Dow on 5/1/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +@implementation PDStylesButton + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] + initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + [anArchiver encodeClassName:@"NSButtonCell" intoClassName:@"PDStylesButtonCell"]; + [anArchiver encodeRootObject:[self cell]]; + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + } + + return self; +} + + ++ (Class) cellClass +{ + return [PDStylesButtonCell class]; +} + +- (int) superscriptValue { + return [[self cell] superscriptValue]; +} + +- (void) setSuperscriptValue:(int)offset { + [[self cell] setSuperscriptValue:offset]; +} + +@end diff --git a/PDStylesButtonCell.h b/PDStylesButtonCell.h new file mode 100644 index 0000000..c41c78b --- /dev/null +++ b/PDStylesButtonCell.h @@ -0,0 +1,20 @@ +// +// PDStylesButtonCell.h +// SproutedInterface +// +// Created by Philip Dow on 5/1/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@interface PDStylesButtonCell : NSButtonCell { + + int _superscriptValue; +} + +- (int) superscriptValue; +- (void) setSuperscriptValue:(int)offset; + +@end diff --git a/PDStylesButtonCell.m b/PDStylesButtonCell.m new file mode 100644 index 0000000..ef2bf6f --- /dev/null +++ b/PDStylesButtonCell.m @@ -0,0 +1,161 @@ +// +// PDStylesButtonCell.m +// SproutedInterface +// +// Created by Philip Dow on 5/1/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +@implementation PDStylesButtonCell + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (id)initTextCell:(NSString *)aString { + + if ( self = [super initTextCell:aString] ) { + + [self setBordered:NO]; + + } + + return self; + +} + +- (int) superscriptValue { return _superscriptValue; } + +- (void) setSuperscriptValue:(int)offset { + _superscriptValue = offset; +} + +- (NSRect)drawingRectForBounds:(NSRect)theRect { + return theRect; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect { + return theRect; +} + + + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + // + // bypass the frame + // + + [self drawInteriorWithFrame:cellFrame inView:controlView]; + +} + + + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + float alpha = ( [self isEnabled] ? 1.0 : 0.70 ); + NSColor *gradientStart, *gradientEnd, *borderColor; + + if ( [self isHighlighted] || [self state] == NSOnState ) { + + borderColor = [NSColor colorWithCalibratedRed:149.0/255.0 green:149.0/255.0 blue:149.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:186.0/255.0 green:186.0/255.0 blue:186.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:218.0/255.0 green:218.0/255.0 blue:218.0/255.0 alpha:alpha]; + + } + + else { + + borderColor = [NSColor colorWithCalibratedRed:173.0/255.0 green:173.0/255.0 blue:173.0/255.0 alpha:alpha]; + gradientEnd = [NSColor colorWithCalibratedRed:224.0/255.0 green:224.0/255.0 blue:224.0/255.0 alpha:alpha]; + gradientStart = [NSColor colorWithCalibratedRed:253.0/255.0 green:253.0/255.0 blue:253.0/255.0 alpha:alpha]; + + } + + int height = cellFrame.size.height; + int width = cellFrame.size.width; + + + // + // draw the gradient + + NSRect targetRect = NSInsetRect(cellFrame,0.5,0.5); + + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:2.0] + linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + [borderColor set]; + [[NSBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:2.0] stroke]; + + // + // draw the string value + + NSAttributedString *attr_string = [self attributedTitle]; + if ( attr_string != nil ) { + + NSSize stringSize = [attr_string size]; + NSRect stringRect = NSMakeRect(width/2.0-stringSize.width/2.0, height/2.0-stringSize.height/2.0, + stringSize.width, stringSize.height); + + if ( _superscriptValue != 0 ) { + + int widthOffset = stringSize.width/3/2; + + NSAttributedString *partNorm = [attr_string attributedSubstringFromRange:NSMakeRange(0,1)]; + NSMutableAttributedString *partScript = + [[attr_string attributedSubstringFromRange:NSMakeRange(1,[attr_string length]-1)] mutableCopy]; + + NSFont *aFont = [partScript attribute:NSFontAttributeName atIndex:0 effectiveRange:nil]; + NSFont *sizedFont = [[NSFontManager sharedFontManager] convertFont:aFont toSize:[aFont pointSize]-3]; + + [partScript addAttribute:NSFontAttributeName value:sizedFont range:NSMakeRange(0,[partScript length])]; + + [partNorm drawAtPoint:NSMakePoint(stringRect.origin.x+widthOffset, stringRect.origin.y)]; + [partScript drawAtPoint: + NSMakePoint(stringRect.origin.x+widthOffset+(stringRect.size.width/[attr_string length]), + stringRect.origin.y - (_superscriptValue*3))]; + + } + else { + + [attr_string drawInRect:stringRect]; + + } + + } + else { + + NSString *title = [self title]; + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:11.0], NSFontAttributeName, + ( [self isEnabled] ? [NSColor blackColor] : [NSColor lightGrayColor] ), NSForegroundColorAttributeName, nil]; + + NSSize stringSize = [title sizeWithAttributes:attributes]; + NSRect stringRect = NSMakeRect(width/2.0-stringSize.width/2.0, height/2.0-stringSize.height/2.0, + stringSize.width, stringSize.height); + + [title drawInRect:stringRect withAttributes:attributes]; + + } + + + +} + + +@end diff --git a/PDTableView.h b/PDTableView.h new file mode 100644 index 0000000..d14c9fa --- /dev/null +++ b/PDTableView.h @@ -0,0 +1,25 @@ +// +// PDTableView.h +// SproutedInterface +// +// Created by Philip Dow on 2/1/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDTableView : NSTableView { + +} + +@end + + +@interface NSObject (PDTableViewDelegate) + +- (void) tableView:(NSTableView*)aTableView leftNavigationEvent:(NSEvent*)anEvent; +- (void) tableView:(NSTableView*)aTableView rightNavigationEvent:(NSEvent*)anEvent; + +@end \ No newline at end of file diff --git a/PDTableView.m b/PDTableView.m new file mode 100644 index 0000000..eda3c1a --- /dev/null +++ b/PDTableView.m @@ -0,0 +1,49 @@ +// +// PDTableView.m +// SproutedInterface +// +// Created by Philip Dow on 2/1/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@implementation PDTableView + +- (void)keyDown:(NSEvent *)event +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s - beginning", [self className], _cmd) + #endif + + unsigned int modifierFlags = [event modifierFlags]; + unichar key = [[event charactersIgnoringModifiers] characterAtIndex:0]; + + if ( key == NSLeftArrowFunctionKey && [[self delegate] respondsToSelector:@selector(tableView:leftNavigationEvent:)] && ( modifierFlags & NSCommandKeyMask ) ) + [[self delegate] tableView:self leftNavigationEvent:event]; + else if ( key == NSRightArrowFunctionKey && [[self delegate] respondsToSelector:@selector(tableView:rightNavigationEvent:)] && ( modifierFlags & NSCommandKeyMask ) ) + [[self delegate] tableView:self rightNavigationEvent:event]; + else + { + [super keyDown:event]; + } + + #ifdef __DEBUG__ + NSLog(@"%@ %s - ending", [self className], _cmd) + #endif + +} + +- (void)cancelOperation:(id)sender +{ + if ([self currentEditor] != nil) + { + [self abortEditing]; + + // We lose focus so re-establish + [[self window] makeFirstResponder:self]; + } +} + +@end diff --git a/PDTabsView.h b/PDTabsView.h new file mode 100644 index 0000000..d02613f --- /dev/null +++ b/PDTabsView.h @@ -0,0 +1,128 @@ +// +// PDTabsView.h +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +@class PDTabs; + +@interface PDTabsView : NSView +{ + int availableWidth; + int closeDown; + + int _flashingTab; + int _malFlash; + BOOL _flashing; + + int _tabToSelect; + int _targetTabForContext; + BOOL _amSwitching; + + BOOL _tabFromTop; + BOOL drawsShadow; + + NSPoint _lastViewLoc; + NSDate *_lastStillMoment; + + NSPopUpButton *morePop; + NSMenuItem *popTitle; + + NSColor *backgroundColor; + + int lastTabCount; + int hoverIndex; + int closeHoverIndex; + int selectingIndex; + NSMutableArray *titleTrackingRects; + NSMutableArray *closeButtonTrackingRects; + + NSImage *tabCloseFront; + NSImage *tabCloseFrontDown; + + NSImage *tabCloseBack; + NSImage *tabCloseBackDown; + + NSImage *backRollover; + NSImage *frontRollover; + + int borders[4]; + + // Jourlner Additions -------------------------- + + NSImage *totalImage; + + IBOutlet id delegate; + IBOutlet id dataSource; + + NSMenu *contextMenu; +} + +- (id) delegate; +- (void) setDelegate:(id)anObject; + +- (id) dataSource; +- (void) setDataSource:(id)anObject; + +- (int) selectedTab; + +#pragma mark - + +- (BOOL) tabFromTop; +- (void) setTabFromTop:(BOOL)direction; + +- (BOOL) drawsShadow; +- (void) setDrawsShadow:(BOOL)shadow; + +- (int*) borders; +- (void) setBorders:(int*)theBorders; + +- (int) availableWidth; +- (void) setAvailableWidth:(int)tabWidth; + +- (NSColor*) backgroundColor; +- (void) setBackgroundColor:(NSColor*)color; + +- (void) handleRegisterDragTypes; +- (void) handleDeregisterDragTypes; + +- (void) flashTab:(int)tab; +- (void) flash:(NSTimer*)timer; + +- (void) closeTab:(int)tab; +- (void) selectTab:(int)newSelection; + +- (IBAction) selectTabByPop:(id)sender; + +- (IBAction) newTab:(id)sender; +- (IBAction) closeTargetedTab:(id)sender; +- (IBAction) closeOtherTabs:(id)sender; + +- (void) updateTrackingRects; +- (void) _updateTrackingRects:(NSNotification*)aNotification; +- (void) _toolbarDidChangeVisible:(NSNotification*)aNotification; + +- (NSRect) frameOfTabAtIndex:(int)theIndex; +- (NSRect) frameOfCloseButtonAtIndex:(int)theIndex; + +@end + +@interface NSObject (PDTabsDataSource) + +- (unsigned int) numberOfTabsInTabView:(PDTabsView*)aTabView; +- (unsigned int) selectedTabIndexInTabView:(PDTabsView*)aTabView; +- (NSString*) tabsView:(PDTabsView*)aTabView titleForTabAtIndex:(unsigned int)index; + +@end + +@interface NSObject (PDTabsDelegate) + +- (void) tabsView:(PDTabsView*)aTabView removedTabAtIndex:(int)index; +- (void) tabsView:(PDTabsView*)aTabView selectedTabAtIndex:(int)index; + +@end diff --git a/PDTabsView.m b/PDTabsView.m new file mode 100644 index 0000000..affea23 --- /dev/null +++ b/PDTabsView.m @@ -0,0 +1,1316 @@ + +// +// PDTabsView.m +// SproutedInterface +// +// Created by Philip Dow on xx. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import + +#import + +#import +#import +#import + +#define kMaxTabWidth 180 +#define kMinTabWidth 84 +#define kTabOffset 12 +#define kTabHeight 21 +#define kLabelOffset 3 +#define kCloseOffset 4 + +//static NSString *kABPeopleUIDsPboardType = @"ABPeopleUIDsPboardType"; +//static NSString *kMailMessagePboardType = @"MV Super-secret message transfer pasteboard type"; + +static NSDictionary* TitleAttributes() +{ + static NSDictionary *textAttributes = nil; + if ( textAttributes == nil ) + { + NSMutableParagraphStyle *paragraphStyle; + + paragraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; + [paragraphStyle setAlignment:NSLeftTextAlignment]; + [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail]; + + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: + [NSFont boldSystemFontOfSize:11], NSFontAttributeName, + [NSColor colorWithCalibratedWhite:0.55 alpha:1.0], NSForegroundColorAttributeName, + paragraphStyle, NSParagraphStyleAttributeName, nil]; + } + return textAttributes; +} + +@implementation PDTabsView + +- (id)initWithFrame:(NSRect)frameRect +{ + if ((self = [super initWithFrame:frameRect]) != nil) + { + // Add initialization code here + + NSMutableParagraphStyle *tempStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopyWithZone:[self zone]]; + [tempStyle setLineBreakMode:NSLineBreakByTruncatingTail]; + + tabCloseFront = [BundledImageWithName(@"tabclosefront.tif", @"com.sprouted.interface") retain]; + tabCloseBack = [BundledImageWithName(@"tabcloseback.tif", @"com.sprouted.interface") retain]; + + tabCloseFrontDown = [BundledImageWithName(@"tabclosefrontdown.tif", @"com.sprouted.interface") retain]; + tabCloseBackDown = [BundledImageWithName(@"tabclosebackdown.tif", @"com.sprouted.interface") retain]; + + backRollover = [BundledImageWithName(@"tabclosebackroll.tif", @"com.sprouted.interface") retain]; + frontRollover = [BundledImageWithName(@"tabclosefrontroll.tif", @"com.sprouted.interface") retain]; + + // build a popup button that indicates more + NSImage *moreImage = BundledImageWithName(@"more.tif", @"com.sprouted.interface"); + popTitle = [[NSMenuItem alloc] initWithTitle:@"" + action:nil + keyEquivalent:@""]; + + [popTitle setImage:moreImage]; + + morePop = [[NSPopUpButton alloc] initWithFrame:NSMakeRect( frameRect.size.width - 28, 6, 28, 10 ) pullsDown:YES]; + + [morePop setBordered:NO]; + [morePop setTarget:self]; + [morePop setAction:@selector(selectTabByPop:)]; + [morePop setAutoresizingMask:NSViewMinXMargin]; + [morePop setHidden:YES]; + [[morePop menu] addItem:popTitle]; + [[morePop cell] setArrowPosition:NSPopUpNoArrow]; + + [self addSubview:morePop]; + + _flashingTab = -1; + closeDown = -1; + _targetTabForContext = -1; + _tabToSelect = -1; + + hoverIndex = -1; + selectingIndex = -1; + closeHoverIndex = -1; + + borders[0] = 0; borders[1] = 0; borders[2] = 0; borders[3] = 0; + + drawsShadow = YES; + _amSwitching = NO; + _lastViewLoc = NSZeroPoint; + _lastStillMoment = [[NSDate date] retain]; + + titleTrackingRects = [[NSMutableArray alloc] init]; + closeButtonTrackingRects = [[NSMutableArray alloc] init]; + + [self setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin]; + + //totalImage = [[NSImage alloc] initWithSize:frameRect.size]; + + //backgroundColor = [[NSColor windowBackgroundColor] retain]; + backgroundColor = [[NSColor colorWithCalibratedWhite:230.0/255.0 alpha:1.0] retain]; + + // build the contextual menu + contextMenu = [[NSMenu alloc] initWithTitle:@"Context"]; + + NSMenuItem *newTabItem = [[[NSMenuItem alloc] + initWithTitle:NSLocalizedStringFromTableInBundle( + @"new tab", + @"PDTabsView", + [NSBundle bundleWithIdentifier:@"com.sprouted.interface"], + @"") + action:@selector(newTab:) + keyEquivalent:@""] autorelease]; + + NSMenuItem *closeTabItem = [[[NSMenuItem alloc] + initWithTitle:NSLocalizedStringFromTableInBundle( + @"close tab", + @"PDTabsView", + [NSBundle bundleWithIdentifier:@"com.sprouted.interface"], + @"") + action:@selector(closeTargetedTab:) + keyEquivalent:@""] autorelease]; + + NSMenuItem *closeOtherTabsItem = [[[NSMenuItem alloc] + initWithTitle:NSLocalizedStringFromTableInBundle( + @"close other tabs", + @"PDTabsView", + [NSBundle bundleWithIdentifier:@"com.sprouted.interface"], + @"") + action:@selector(closeOtherTabs:) + keyEquivalent:@""] autorelease]; + + [newTabItem setTarget:self]; + [closeTabItem setTarget:self]; + [closeOtherTabsItem setTarget:self]; + + [contextMenu addItem:newTabItem]; + [contextMenu addItem:closeTabItem]; + [contextMenu addItem:[NSMenuItem separatorItem]]; + [contextMenu addItem:closeOtherTabsItem]; + + [self setMenu:contextMenu]; + + // a default _tabFromTop of no means the tabs will draw from the bottom without programmatic change + _tabFromTop = YES; + + [self handleRegisterDragTypes]; + + [self setPostsBoundsChangedNotifications:YES]; + [self setPostsFrameChangedNotifications:YES]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_updateTrackingRects:) + name:NSViewBoundsDidChangeNotification + object:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_updateTrackingRects:) + name:NSViewFrameDidChangeNotification + object:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_toolbarDidChangeVisible:) + name:PDToolbarDidHideNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_toolbarDidChangeVisible:) + name:PDToolbarDidShowNotification + object:nil]; + + // clean up + [tempStyle release]; + } + return self; +} + +- (void) dealloc +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s", [self className], _cmd); + #endif + + [backgroundColor release]; + [_lastStillMoment release]; + + [morePop release]; + [popTitle release]; + + [tabCloseFront release]; + [tabCloseBack release]; + + [tabCloseFrontDown release]; + [tabCloseBackDown release]; + + [backRollover release]; + [frontRollover release]; + + //[totalImage release]; + + [contextMenu release]; + [titleTrackingRects release]; + [closeButtonTrackingRects release]; + + [self handleDeregisterDragTypes]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [super dealloc]; +} + +#pragma mark - + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate:(id)anObject +{ + delegate = anObject; +} + +- (id) dataSource +{ + return dataSource; +} + +- (void) setDataSource:(id)anObject +{ + dataSource = anObject; +} + +- (int) selectedTab +{ + return [[self dataSource] selectedTabIndexInTabView:self]; +} + +#pragma mark - + +- (int*) borders +{ + return borders; +} + +- (void) setBorders:(int*)theBorders +{ + borders[0] = theBorders[0]; + borders[1] = theBorders[1]; + borders[2] = theBorders[2]; + borders[3] = theBorders[3]; +} + +- (BOOL) tabFromTop +{ + return _tabFromTop; +} + +- (void) setTabFromTop:(BOOL)direction +{ + _tabFromTop = direction; +} + +- (BOOL) drawsShadow +{ + return drawsShadow; +} + +- (void) setDrawsShadow:(BOOL)shadow +{ + drawsShadow = shadow; +} + +- (int) availableWidth +{ + return availableWidth; +} + +- (void) setAvailableWidth:(int)tabWidth +{ + availableWidth = tabWidth; +} + +- (NSColor*) backgroundColor +{ + return backgroundColor; +} + +- (void) setBackgroundColor:(NSColor*)color +{ + if ( backgroundColor != color ) + { + [backgroundColor release]; + backgroundColor = [color copyWithZone:[self zone]]; + } +} + +#pragma mark - +#pragma mark Dragging & Autotabselecting + +- (void) handleRegisterDragTypes +{ + // how can I just register for everything under the sun? + [self registerForDraggedTypes:[NSArray arrayWithObjects: + kABPeopleUIDsPboardType, kMailMessagePboardType, NSFilenamesPboardType, kWebURLsWithTitlesPboardType, NSURLPboardType, + NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSTIFFPboardType, NSPICTPboardType, nil]]; + /* PDFolderIDPboardType, PDEntryIDPboardType, PDResourceIDPboardType, nil]]; */ +} + +- (void) handleDeregisterDragTypes +{ + [self unregisterDraggedTypes]; +} + +- (BOOL)performDragOperation:(id )sender +{ + return NO; +} + +- (unsigned int)dragOperationForDraggingInfo:(id )dragInfo type:(NSString *)type { + return NSDragOperationGeneric; +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + if ( [self isHidden] ) + return NSDragOperationNone; + + return NSDragOperationGeneric; +} + +- (void)draggingExited:(id )sender +{ + //we aren't particularily interested in this so we will do nothing + //this is one of the methods that we do not have to implement +} + +- (NSDragOperation)draggingUpdated:(id )sender +{ + + // + // - record the last point, set a time, invalidate the timer if it's already going + // + + NSPoint mouseLoc = [sender draggingLocation]; + NSPoint viewLoc = [self convertPoint:mouseLoc fromView:nil]; + + int i; + int overTab = 0; + + // calculate positional variables + int myX = viewLoc.x - kTabOffset; // 12 (kTabOffset) takes into account our self imposed left offset + //int myY = viewLoc.y; + + // calculate the tab visisble count + int tabsAvailable = ([self bounds].size.width - kTabOffset*2) / [self availableWidth]; + //int actualTabs = [[self displayText] count]; + int actualTabs = [dataSource numberOfTabsInTabView:self]; + int limit = ( tabsAvailable <= actualTabs ? tabsAvailable : actualTabs ); + + // do nothing if there is nothing to display + //if ( [self availableWidth] == 0 || [displayText count] == 1 ) + if ( [self availableWidth] == 0 || actualTabs == 1 ) + return NSDragOperationNone; + + // otherwise find out which tab the mouse is over + for ( i = 0; i < limit; i++ ) { + if ( myX > i * [self availableWidth] && myX < (i+1) * [self availableWidth] ) { + overTab = i; + break; + } + } + + // if the viewLoc is within the selected tab, do nothing + if ( overTab == [self selectedTab] || _amSwitching) + return NSDragOperationNone; + + // check the current viewLoc against the last viewLoc + if ( viewLoc.x == _lastViewLoc.x && viewLoc.y == _lastViewLoc.y ) { + + // how long has it been? + if ( [[NSDate date] timeIntervalSinceDate:_lastStillMoment] >= 0.5 ) { + + // if over 1 second, flash and select this tab + _amSwitching = YES; + _tabToSelect = overTab; + [self flashTab:overTab]; + + return NSDragOperationNone; + + } + else { + + return NSDragOperationGeneric; + + } + + } + else { + + // reset the last view loc + _lastViewLoc.x = viewLoc.x; + _lastViewLoc.y = viewLoc.y; + + [_lastStillMoment release]; + _lastStillMoment = [[NSDate date] retain]; + + return NSDragOperationGeneric; + + } + + return NSDragOperationGeneric; + +} + +- (BOOL)prepareForDragOperation:(id )sender +{ + return ( ![self isHidden] ); +} + +- (void)concludeDragOperation:(id )sender +{ + +} + +- (BOOL)ignoreModifierKeysWhileDragging +{ + return YES; +} + +#pragma mark - + +- (IBAction) newTab:(id)sender +{ + if ( [[self delegate] respondsToSelector:@selector(newTab:)] ) + [[self delegate] performSelector:@selector(newTab:) withObject:self]; + else + NSBeep(); +} + +- (void) closeTab:(int)tab +{ + NSRect invalidatedRect = [self frameOfTabAtIndex:tab]; + invalidatedRect.size.width = [self bounds].size.width - invalidatedRect.origin.x; + + // let the interested folks know what's up + if ( delegate != nil && [delegate respondsToSelector:@selector(tabsView:removedTabAtIndex:)] ) + [delegate tabsView:self removedTabAtIndex:tab]; + + hoverIndex = -1; + closeHoverIndex = -1; + + [self setNeedsDisplayInRect:invalidatedRect]; +} + +- (void) selectTab:(int)newSelection +{ + if ( [delegate respondsToSelector:@selector(selectedTabIndexInTabView:)] ) + { + NSRect previousRect = [self frameOfTabAtIndex:[[self delegate] selectedTabIndexInTabView:self]]; + [self setNeedsDisplayInRect:previousRect]; + } + + NSRect invalidatedRect = [self frameOfTabAtIndex:newSelection]; + + // let the interested folks know what's up + if ( delegate != nil && [delegate respondsToSelector:@selector(tabsView:selectedTabAtIndex:)] ) + [delegate tabsView:self selectedTabAtIndex:newSelection]; + + // invalidate these guys just for good measure + closeHoverIndex = -1; + [self setNeedsDisplayInRect:invalidatedRect]; +} + +#pragma mark - + +- (void) flashTab:(int)tab +{ + // + // uses a timer to flash a tab, thus drawing attention to it + // make sure there is any point to doing this + + // if the tab being flashed is the tab currently selected, beep + if ( tab == [self selectedTab] ) + { + NSBeep(); + return; + } + + _flashing = NO; + _malFlash = 0; + _flashingTab = tab; + + // fire off the timer + [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(flash:) userInfo:nil repeats:YES]; +} + +- (void) flash:(NSTimer*)timer +{ + + // invert the flash + _flashing = !_flashing; + + // invalidate the timer if this is the last time through + if ( _malFlash == 4 ) + { + _flashing = NO; + _flashingTab = -1; + + if ( _tabToSelect != -1 ) + [self selectTab:_tabToSelect]; + + _amSwitching = NO; + + [timer invalidate]; + return; + } + else + { + _malFlash++; + } + + [self setNeedsDisplayInRect:[self frameOfTabAtIndex:_tabToSelect]]; +} + + +#pragma mark - +#pragma mark Perform the drawing + +- (void)drawRect:(NSRect)rect +{ + + // + // responsible for producing the the appearance of a tabbed display + // - check the available width + // - correctly position the text and close buttons + // - properly render the selected tab + // + + int i; + int tabsAvailable; + int availableCellWidth; + + NSRect bds = [self bounds]; + + int tabCount = [dataSource numberOfTabsInTabView:self]; + if ( tabCount != 0 ) + { + availableCellWidth = ( bds.size.width - kTabOffset*2 ) / tabCount; + + if ( availableCellWidth > kMaxTabWidth ) availableCellWidth = kMaxTabWidth; + if ( availableCellWidth < kMinTabWidth ) availableCellWidth = kMinTabWidth; + + tabsAvailable = (bds.size.width - kTabOffset*2) / availableCellWidth; + + if ( tabsAvailable < tabCount ) + availableCellWidth = ( bds.size.width - 10 - kTabOffset*2 ) / tabsAvailable; + // an additional 10 pixels for the more indicator + + [self setAvailableWidth:availableCellWidth]; + } + else + { + [self setAvailableWidth:0]; + availableCellWidth = 0; + tabsAvailable = 0; + } + + //[totalImage setSize:bds.size]; + //[totalImage lockFocus]; + + //[[NSColor clearColor] set]; + //NSRectFillUsingOperation(bds, NSCompositeSourceOver); + + // fill with the label bar color + //[[NSColor windowBackgroundColor] set]; + [[NSColor colorWithCalibratedWhite:230.0/255.0 alpha:1.0] set]; + NSRectFillUsingOperation(bds, NSCompositeSourceOver); + + // draw a gradient over that + NSColor *gradientEnd = [NSColor colorWithCalibratedWhite:0.86 alpha:0.8]; //0.6 0.82 + NSColor *gradientStart = [NSColor colorWithCalibratedWhite:0.88 alpha:0.8]; // 0.92 + [[NSBezierPath bezierPathWithRect:bds] linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + NSColor *darkBorder = ( borders[0] == YES ? [NSColor darkGrayColor] : [[NSColor darkGrayColor] colorWithAlphaComponent:0.5] ); + NSColor *lightBorder = ( borders[0] == YES ? [NSColor colorWithCalibratedWhite:1.0 alpha:0.82] : [NSColor colorWithCalibratedWhite:1.0 alpha:0.4] ); + + if ( YES ) + { + NSGraphicsContext *context = [NSGraphicsContext currentContext]; + [context saveGraphicsState]; + [context setShouldAntialias:NO]; + + //[[NSColor darkGrayColor] set]; + [darkBorder set]; + [[NSBezierPath bezierPathWithLineFrom:NSMakePoint(0,bds.size.height-1) to:NSMakePoint(bds.size.width,bds.size.height-1) lineWidth:1] stroke]; + + //[[NSColor colorWithCalibratedWhite:1.0 alpha:0.9] set]; + [lightBorder set]; + [[NSBezierPath bezierPathWithLineFrom:NSMakePoint(0,bds.size.height-2) to:NSMakePoint(bds.size.width,bds.size.height-2) lineWidth:1] stroke]; + + [context restoreGraphicsState]; + } + + // forget what this is for + /* + NSImage *shadowBackground = [[[NSImage alloc] initWithSize:NSMakeSize(bds.size.width,bds.size.height)] autorelease]; + [shadowBackground lockFocus]; + + //[[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:1.0] set]; + [[NSColor colorWithCalibratedWhite:230.0/255.0 alpha:1.0] set]; + NSRectFillUsingOperation(bds, NSCompositeSourceOver); + + [shadowBackground unlockFocus]; + [shadowBackground drawInRect:bds fromRect:bds operation:NSCompositeSourceOver fraction:0.2]; + */ + + // frame ourselves with a shadow + + //NSShadow *shadow = nil; + + //[totalImage unlockFocus]; + + //[backgroundColor set]; + //NSRectFillUsingOperation(bds, NSCompositeSourceOver); + + // draw a gradient over that + //[[NSBezierPath bezierPathWithRect:bds] linearGradientFillWithStartColor:gradientStart endColor:gradientEnd]; + + // composite the tab image on top if it all - seems wasteful + //[totalImage compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver]; + + // run through the available titles until we can go no further,drawing the titles and the associated close buttons + for ( i = 0; i < tabCount; i++ ) + { + if ( (i+1) * availableCellWidth <= bds.size.width - kTabOffset*2 ) + { + if ( _flashing && _flashingTab == i ) + { + + // redraw the background if we're flashing + [[NSColor colorWithCalibratedRed:0.6 green:0.6 blue:0.6 alpha:0.5] set]; + NSRectFillUsingOperation(NSMakeRect(i * availableCellWidth + kTabOffset, 1, availableCellWidth - 1, bds.size.height - 2 ), NSCompositeSourceOver); + + /* + NSRect hoverRect = NSMakeRect(i * availableCellWidth + kTabOffset + 1, 2, availableCellWidth - 1 - 2, 17 ); + NSBezierPath *aPath = [NSBezierPath bezierPathWithRoundedRect:hoverRect cornerRadius:8.0]; + + [[NSColor colorWithCalibratedRed:0.45 green:0.45 blue:0.45 alpha:1.0] set]; + [aPath fill]; + */ + } + + else if ( i == hoverIndex ) + { + // height = 22 + // stateRect.origin.x+=1; stateRect.origin.y+=2; stateRect.size.width-=1; stateRect.size.height-=5; + + [[NSColor colorWithCalibratedWhite:0.9 alpha:0.9] set]; + NSRectFillUsingOperation( NSMakeRect( i * availableCellWidth + ( hoverIndex == 0 ? 0 : kTabOffset ), + 0, + availableCellWidth - 1 + ( hoverIndex == 0 ? kTabOffset : 0 ), + bds.size.height -1 ) + , NSCompositeSourceOver); + + /* + NSRect hoverRect = NSMakeRect(i * availableCellWidth + kTabOffset + 1, 2, availableCellWidth - 1 - 2, 17 ); + NSBezierPath *aPath = [NSBezierPath bezierPathWithRoundedRect:hoverRect cornerRadius:8.0]; + + if ( i != selectingIndex ) + [[NSColor colorWithCalibratedRed:0.6 green:0.6 blue:0.6 alpha:1.0] set]; + else + [[NSColor colorWithCalibratedRed:0.45 green:0.45 blue:0.45 alpha:1.0] set]; + + [aPath fill]; + */ + } + + // draw the title identifying this tab + NSString *theTitle = [dataSource tabsView:self titleForTabAtIndex:i]; + theTitle = ( theTitle != nil ? theTitle : [NSString string] ); + + NSMutableAttributedString *drawString = [[[[NSAttributedString alloc] + initWithString:theTitle attributes:TitleAttributes()] mutableCopyWithZone:[self zone]] autorelease]; + + if ( /* i == hoverIndex || */ ( _flashing == YES && _flashingTab == i ) ) + [drawString addAttribute:NSForegroundColorAttributeName value:[NSColor whiteColor] range:NSMakeRange(0,[drawString length])]; + else if ( i == [self selectedTab] ) + [drawString addAttribute:NSForegroundColorAttributeName value:[NSColor blackColor] range:NSMakeRange(0,[drawString length])]; + + int left; + if ( tabCount == 1 ) + left = 22; + else + left = i * availableCellWidth + 36; + + float heightOffset = kLabelOffset; + NSRect drawRect = NSMakeRect( left - 2, heightOffset, availableCellWidth - 36, [NSFont smallSystemFontSize]+4 ); + // + 30 : space for an initial tab, space for the close button + // initial space is 12 (kTabOffset) + + [drawString drawInRect:drawRect]; + + //if ( !(i == hoverIndex || i == hoverIndex-1) ) + { + // draw the separator + [[NSGraphicsContext currentContext] saveGraphicsState]; + [[NSGraphicsContext currentContext] setShouldAntialias:NO]; + + [[NSColor colorWithCalibratedWhite:0.0 alpha:0.15] set]; + [[NSBezierPath bezierPathWithLineFrom:NSMakePoint((i+1) * availableCellWidth + kTabOffset - 1, 1) /*2*/ + to:NSMakePoint((i+1) * availableCellWidth + kTabOffset - 1,[self bounds].size.height - 3) lineWidth:1.0] stroke]; /*4*/ + + [[NSGraphicsContext currentContext] restoreGraphicsState]; + } + + // draw the close button for this tab, but only if there is more than one tab + if ( tabCount != 1 ) + { + NSImage *closeButton; + + if ( i == closeDown ) + closeButton = ( i == [self selectedTab] ? tabCloseFrontDown : tabCloseBackDown ); + else + { + if ( i == /*hoverIndex*/ closeHoverIndex ) + //closeButton = ( i == [self selectedTab] ? frontRollover : backRollover ); + closeButton = backRollover; + else + closeButton = ( i == [self selectedTab] ? tabCloseFront : tabCloseBack ); + //closeButton = tabCloseBack; + } + + float closeHeightOffset = kCloseOffset; + NSRect closeRect = NSMakeRect( i * availableCellWidth + 17 - 1, closeHeightOffset, [closeButton size].width, [closeButton size].height ); + + [closeButton drawInRect:closeRect fromRect:NSMakeRect(0,0,[closeButton size].width, [closeButton size].height) + operation:NSCompositeSourceOver fraction:1.0]; + } + + if ( i == tabCount - 1 && ![morePop isHidden] ) + [morePop setHidden:YES]; + } + else + { + // once we hit this point, we need to indicate that more tabs are available + // and then break out of the loop so that we don't repeat this code + + if ( _flashing && _flashingTab >= i ) + { + // redraw the background if we're flashing + NSRect popFrame = [morePop frame]; + + popFrame.origin.x+=6; + popFrame.origin.y-=3; + popFrame.size.width-=8; + popFrame.size.height+=8; + + [[NSColor colorWithCalibratedRed:0.6 green:0.6 blue:0.6 alpha:0.5] set]; + + NSRectFillUsingOperation(popFrame, NSCompositeSourceOver); + } + + // clean our the menu and rebuild it + [morePop removeAllItems]; + [[morePop menu] addItem:popTitle]; + + int rt; + for ( rt = i; rt < tabCount - 1; rt++ ) + { + NSString *theTitle = [dataSource tabsView:self titleForTabAtIndex:rt]; + theTitle = ( theTitle != nil ? theTitle : [NSString string] ); + + NSMenuItem *anItem = [[[NSMenuItem alloc] initWithTitle:theTitle + action:@selector(selectTabByPop:) + keyEquivalent:@""] autorelease]; + + [[morePop menu] addItem:anItem]; + } + + [morePop setHidden:NO]; + + break; + } + } + + // add the tracking rects if the tab count differs + if ( lastTabCount != tabCount ) + { + [self updateTrackingRects]; + lastTabCount = tabCount; + } +} + +- (NSRect) frameOfTabAtIndex:(int)theIndex +{ + + if ( [[self delegate] respondsToSelector:@selector(numberOfTabsInTabView:)] && theIndex != -1 ) + { + if ( theIndex >= [[self delegate] numberOfTabsInTabView:self] ) + { + //NSLog(@"%@ %s - index %i is greater than avaible count %i", [self className], _cmd, theIndex, [[self delegate] numberOfTabsInTabView:self] - 1 ); + return NSZeroRect; + } + else + { + return NSMakeRect(theIndex * [self availableWidth] + (theIndex == 0 ? 0 : kTabOffset ), 0, + [self availableWidth] + ( theIndex == 0 ? kTabOffset : 0 ), [self bounds].size.height ); + } + } + else + { + return NSZeroRect; + } +} + +- (NSRect) frameOfCloseButtonAtIndex:(int)theIndex +{ + if ( [[self delegate] respondsToSelector:@selector(numberOfTabsInTabView:)] && theIndex != -1 ) + { + if ( theIndex >= [[self delegate] numberOfTabsInTabView:self] ) + { + //NSLog(@"%@ %s - index %i is greater than avaible count %i", [self className], _cmd, theIndex, [[self delegate] numberOfTabsInTabView:self] - 1 ); + return NSZeroRect; + } + else + { + return NSMakeRect( theIndex * [self availableWidth] + 17 - 1, + kCloseOffset, + [backRollover size].width, + [backRollover size].height ); + } + } + else + { + return NSZeroRect; + } +} + +#pragma mark - +#pragma mark Tracking + +- (void) updateTrackingRects +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + int i; + int tabsAvailable; + int availableCellWidth; + + NSRect bds = [self bounds]; + int tabCount = [dataSource numberOfTabsInTabView:self]; + + // remove the previous tooltips + [self removeAllToolTips]; + + // remove the previous tracking rectangles + for ( i = 0; i < [titleTrackingRects count]; i++ ) + [self removeTrackingRect:[[titleTrackingRects objectAtIndex:i] intValue]]; + for ( i = 0; i < [closeButtonTrackingRects count]; i++ ) + [self removeTrackingRect:[[closeButtonTrackingRects objectAtIndex:i] intValue]]; + + [titleTrackingRects removeAllObjects]; + [closeButtonTrackingRects removeAllObjects]; + + if ( tabCount != 0 ) + { + availableCellWidth = ( bds.size.width - kTabOffset*2 ) / tabCount; + + if ( availableCellWidth > kMaxTabWidth ) availableCellWidth = kMaxTabWidth; + if ( availableCellWidth < kMinTabWidth ) availableCellWidth = kMinTabWidth; + + tabsAvailable = (bds.size.width - kTabOffset*2) / availableCellWidth; + + if ( tabsAvailable < tabCount ) + availableCellWidth = ( bds.size.width - 10 - kTabOffset*2 ) / tabsAvailable; + // an additional 10 pixels for the more indicator + } + else + { + availableCellWidth = 0; + tabsAvailable = 0; + } + + + // run through the available titles until we can go no further,drawing the titles and the associated close buttons + for ( i = 0; i < tabCount; i++ ) + { + if ( (i+1) * availableCellWidth <= bds.size.width - kTabOffset*2 ) + { + + int left; + if ( tabCount == 1 ) + left = 18; + else + left = i * availableCellWidth + 36; + + float heightOffset = kLabelOffset; + + NSRect tooltipRect = NSMakeRect( left, heightOffset, availableCellWidth - 36, 17 ); + NSRect drawRect = NSMakeRect(i * availableCellWidth + kTabOffset + 4, 2, availableCellWidth - 1 - 8, 17 ); + + NSTrackingRectTag aTrackingRect = [self addTrackingRect:drawRect owner:self userData:nil assumeInside:NO]; + [titleTrackingRects addObject:[NSNumber numberWithInt:aTrackingRect]]; + + if ( [[self delegate] respondsToSelector:@selector(tabsView:titleForTabAtIndex:)] ) + [self addToolTipRect:tooltipRect owner:[[[self delegate] tabsView:self titleForTabAtIndex:i] retain] userData:nil]; + + // + 30 : space for an initial tab, space for the close button + // initial space is 12 (kTabOffset) + + // draw the close button for this tab, but only if there is more than one tab + if ( tabCount != 1 ) + { + float closeHeightOffset = kCloseOffset; + NSRect closeRect = NSMakeRect( i * availableCellWidth + 17, closeHeightOffset, [tabCloseFrontDown size].width, [tabCloseFrontDown size].height ); + + [self addToolTipRect:closeRect owner: + [NSLocalizedStringFromTableInBundle(@"close tab tip", @"PDTabsView", [NSBundle bundleWithIdentifier:@"com.sprouted.interface"], @"") retain] userData:nil]; + + NSTrackingRectTag aTrackingRect = [self addTrackingRect:closeRect owner:self userData:nil assumeInside:NO]; + [closeButtonTrackingRects addObject:[NSNumber numberWithInt:aTrackingRect]]; + } + } + else + { + // once we hit this point, we need to indicate that more tabs are available + // and then break out of the loop so that we don't repeat this code + + if ( _flashing && _flashingTab >= i ) + { + // redraw the background if we're flashing + NSRect popFrame = [morePop frame]; + + popFrame.origin.x+=6; + popFrame.origin.y-=3; + popFrame.size.width-=8; + popFrame.size.height+=8; + } + + break; + } + } + +} + +- (void) _updateTrackingRects:(NSNotification*)aNotification +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + [self updateTrackingRects]; +} + +- (void) _toolbarDidChangeVisible:(NSNotification*)aNotification +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + if ( [[self window] toolbar] == [aNotification object] ) + [self updateTrackingRects]; +} + +#pragma mark - + +- (void)mouseEntered:(NSEvent *)theEvent +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + int i; + BOOL forceHoverUpdate = NO; + NSTrackingRectTag trackTag = [theEvent trackingNumber]; + + // first check for a match with the close button + + for ( i = 0; i < [closeButtonTrackingRects count]; i++ ) + { + if ( trackTag == [[closeButtonTrackingRects objectAtIndex:i] intValue] ) + { + closeHoverIndex = i; + if ( hoverIndex != closeHoverIndex ) + { + forceHoverUpdate = YES; + hoverIndex = i; + } + } + } + + // then check for a match with the whole thing + if ( hoverIndex == -1 ) + { + for ( i = 0; i < [titleTrackingRects count]; i++ ) + { + if ( trackTag == [[titleTrackingRects objectAtIndex:i] intValue] ) + { + hoverIndex = i; + forceHoverUpdate = YES; + } + } + } + + if ( closeHoverIndex != -1 ) + { + #ifdef __DEBUG__ + NSLog(@"%@ %s - hovering at %i", [self className], _cmd, closeHoverIndex); + #endif + + NSRect invalidatedRect = [self frameOfCloseButtonAtIndex:closeHoverIndex]; + invalidatedRect.origin.x-=1; invalidatedRect.size.width+=2; + [self setNeedsDisplayInRect:invalidatedRect]; + } + + if ( hoverIndex != -1 && forceHoverUpdate ) + { + #ifdef __DEBUG__ + NSLog(@"%@ %s - hovering at %i", [self className], _cmd, hoverIndex); + #endif + + NSRect invalidatedRect = [self frameOfTabAtIndex:hoverIndex]; + invalidatedRect.origin.x-=1; invalidatedRect.size.width+=2; + [self setNeedsDisplayInRect:invalidatedRect]; + } +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + #ifdef __DEBUG__ + NSLog(@"%@ %s",[self className],_cmd); + #endif + + if ( [[self delegate] respondsToSelector:@selector(numberOfTabsInTabView:)] + && closeHoverIndex != -1 && closeHoverIndex < [[self delegate] numberOfTabsInTabView:self] ) + { + NSRect invalidatedRect = [self frameOfCloseButtonAtIndex:closeHoverIndex]; + invalidatedRect.origin.x-=1; invalidatedRect.size.width+=2; + [self setNeedsDisplayInRect:invalidatedRect]; + + closeHoverIndex = -1; + } + + else if ( [[self delegate] respondsToSelector:@selector(numberOfTabsInTabView:)] + && hoverIndex != -1 && hoverIndex < [[self delegate] numberOfTabsInTabView:self] ) + { + NSRect invalidatedRect = [self frameOfTabAtIndex:hoverIndex]; + invalidatedRect.origin.x-=1; invalidatedRect.size.width+=2; + [self setNeedsDisplayInRect:invalidatedRect]; + + hoverIndex = -1; + } + +} + + +#pragma mark - + +- (BOOL)mouseDownCanMoveWindow +{ + return NO; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + // converts the event into a tab selection or close and + // sends ourself the message + + BOOL clickedInEmptySpace = YES; + BOOL closing = NO; + int i; + + // mouse location and x coordinates + NSPoint mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + int myX = mouseLoc.x - kTabOffset; // 12 (kTabOffset) takes into account our self imposed left offset + int myY = mouseLoc.y; + + int actualTabs = [dataSource numberOfTabsInTabView:self]; + + // do nothing if there is nothing to display + if ( [self availableWidth] == 0 ) + { + return; + } + else if ( actualTabs == 1 ) + { + // bail unless the user double-clicked outside of the tab's area + if ( myX > 1 * [self availableWidth] + && myX < [self bounds].size.width - 30 + && [theEvent clickCount] > 1 + && [[self delegate] respondsToSelector:@selector(newTab:)] ) + { + // create and select the new tab + [[self delegate] performSelector:@selector(newTab:) withObject:self]; + [self selectTab:[dataSource numberOfTabsInTabView:self]-1]; + } + else + { + return; + } + } + else + { + int tabsAvailable = ([self bounds].size.width - kTabOffset*2) / [self availableWidth]; + int limit = ( tabsAvailable <= actualTabs ? tabsAvailable : actualTabs ); + + // selecting within a close box + + for ( i = 0; i < limit; i++ ) + { + if ( myX >= i * [self availableWidth] + 5 && myX <= i * [self availableWidth] + 5 + [tabCloseFront size].width + && myY >= kCloseOffset && myY <= kCloseOffset + [tabCloseFront size].height ) + { + // enter my own even loop until we have some kind of result + BOOL keepOn = YES; + BOOL isInside = YES; + NSPoint mouseLoc; + + NSRect invalidatedRect; + NSRect innenRect = NSMakeRect(i * [self availableWidth] + 17, kCloseOffset, [tabCloseFront size].width, [tabCloseFront size].height); + + closeDown = i; + clickedInEmptySpace = NO; + + [self displayRect:[self frameOfTabAtIndex:i]]; + + while (keepOn) + { + theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask]; + + mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + isInside = [self mouse:mouseLoc inRect:innenRect]; + + switch ([theEvent type]) + { + case NSLeftMouseDragged: + invalidatedRect = [self frameOfTabAtIndex:i]; + closeDown = ( isInside ? i : -1 ); + [self displayRect:invalidatedRect]; + break; + + case NSLeftMouseUp: + invalidatedRect = [self frameOfTabAtIndex:i]; + if (isInside) [self closeTab:i]; + closeDown = -1; + [self displayRect:invalidatedRect]; + keepOn = NO; + + break; + default: + /* Ignore any other kind of event. */ + break; + } + }; + + //[self setNeedsDisplay:YES]; + closing = YES; + } + } + + // basic tab selection + if ( !closing ) + { + for ( i = 0; i < limit; i++ ) + { + if ( myX > i * [self availableWidth] && myX < (i+1) * [self availableWidth] ) + { + // do not process this if this tab is already selected + if ( i != [self selectedTab] ) + { + selectingIndex = i; + NSRect hoverRect = NSMakeRect(selectingIndex * [self availableWidth] + kTabOffset, 2, [self availableWidth] - 1, 17 ); + [self setNeedsDisplayInRect:hoverRect]; + [self selectTab:i]; + } + + clickedInEmptySpace = NO; + break; + } + } + + closeDown = -1; + } + + // clicked in empty space, clicked count > 1, new delegate wants new tabs + if ( clickedInEmptySpace == YES + && myX < [self bounds].size.width - 30 + && [theEvent clickCount] > 1 + && [[self delegate] respondsToSelector:@selector(newTab:)] ) + { + // create and select the new tab + [[self delegate] performSelector:@selector(newTab:) withObject:self]; + [self selectTab:[dataSource numberOfTabsInTabView:self]-1]; + } + } +} + +#pragma mark - + +- (void)mouseUp:(NSEvent *)theEvent +{ + NSRect hoverRect = NSMakeRect(selectingIndex * [self availableWidth] + kTabOffset, 2, [self availableWidth] - 1, 17 ); + selectingIndex = -1; + [self setNeedsDisplayInRect:hoverRect]; +} + +- (IBAction) selectTabByPop:(id)sender +{ + int tabsAvailable = ([self bounds].size.width - kTabOffset*2) / [self availableWidth]; + int selectedItem = [morePop indexOfSelectedItem]; + + int tabToSelect = tabsAvailable + selectedItem - 1; + + [self selectTab:tabToSelect]; +} + +#pragma mark - + +- (NSMenu *)menuForEvent:(NSEvent *)theEvent +{ + _targetTabForContext = -1; + + NSPoint local_point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + int i, limit, tabsAvailable, actualTabs; + int myX = local_point.x - kTabOffset; + + actualTabs = [[self dataSource] numberOfTabsInTabView:self]; + + // do nothing if there is nothing to display + if ( [self availableWidth] == 0 || actualTabs == 1 ) + return [super menuForEvent:theEvent]; + + tabsAvailable = ([self bounds].size.width - kTabOffset*2) / [self availableWidth]; + limit = ( tabsAvailable <= actualTabs ? tabsAvailable : actualTabs ); + + for ( i = 0; i < limit; i++ ) + { + if ( myX > i * [self availableWidth] && myX < (i+1) * [self availableWidth] ) + { + _targetTabForContext = i; + break; + } + } + + return [super menuForEvent:theEvent]; +} + +- (BOOL)validateMenuItem:(NSMenuItem*)menuItem +{ + BOOL enabled = YES; + SEL action = [menuItem action]; + + if ( action == @selector(newTab:) ) + enabled = [[self delegate] respondsToSelector:@selector(newTab:)]; + else if ( action == @selector(closeTargetedTab:) ) + enabled = ( [[self dataSource] numberOfTabsInTabView:self] != 1 && _targetTabForContext != -1 ); + else if ( action == @selector(closeOtherTabs:) ) + enabled = ( [[self dataSource] numberOfTabsInTabView:self] != 1 && _targetTabForContext != -1 ); + + return enabled; +} + +- (IBAction) closeTargetedTab:(id)sender +{ + if ( _targetTabForContext == - 1 ) + { + NSBeep(); return; + } + + [self closeTab:_targetTabForContext]; + _targetTabForContext = -1; +} + +- (IBAction) closeOtherTabs:(id)sender +{ + if ( _targetTabForContext == - 1 ) + { + NSBeep(); return; + } + + int i; + int numTabs = [[self dataSource] numberOfTabsInTabView:self]; + + // close the tabs behind + for ( i = numTabs - 1; i > _targetTabForContext; i-- ) + [self closeTab:i]; + + // close the tabs in front + for ( i = 0; i < _targetTabForContext; i++ ) + [self closeTab:0]; + + _targetTabForContext = -1; +} + +- (void)viewDidMoveToWindow +{ + if ( [self window] != nil ) + [self updateTrackingRects]; +} + +@end diff --git a/PDTokenField.h b/PDTokenField.h new file mode 100644 index 0000000..b21e52f --- /dev/null +++ b/PDTokenField.h @@ -0,0 +1,25 @@ +// +// PDTokenField.h +// SproutedInterface +// +// Created by Philip Dow on 8/20/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// +// Note that PDTokenField does not require PDTokenFieldCell +// + +#import + + +@interface PDTokenField : NSTokenField { + +} + +@end + +@interface NSObject (PDTokenFieldDelegate) + +- (void)tokenField:(PDTokenField *)tokenField didReadTokens:(NSArray*)theTokens fromPasteboard:(NSPasteboard *)pboard; + +@end \ No newline at end of file diff --git a/PDTokenField.m b/PDTokenField.m new file mode 100644 index 0000000..f0b6b3f --- /dev/null +++ b/PDTokenField.m @@ -0,0 +1,45 @@ +// +// PDTokenField.m +// SproutedInterface +// +// Created by Philip Dow on 8/20/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// +// Note that PDTokenField does not require PDTokenFieldCell +// + +#import + + +@implementation PDTokenField + +- (BOOL)performDragOperation:(id )sender +{ + if ([super performDragOperation:sender]) + { + //NSLog(@"perform custom drag operation"); + + if ( [self delegate] != nil && [[self delegate] respondsToSelector:@selector(tokenField:didReadTokens:fromPasteboard:)] ) + [[self delegate] tokenField:self didReadTokens:[self objectValue] fromPasteboard:[sender draggingPasteboard]]; + + return YES; + } + else + { + return NO; + } +} + +-(NSArray *)tokenFieldCell:(NSTokenFieldCell *)tokenFieldCell readFromPasteboard:(NSPasteboard *)pboard +{ + id tokens = [super tokenFieldCell:tokenFieldCell readFromPasteboard:pboard]; + + if ( [self delegate] != nil && [[self delegate] respondsToSelector:@selector(tokenField:didReadTokens:fromPasteboard:)] ) + [[self delegate] tokenField:self didReadTokens:tokens fromPasteboard:pboard]; + + return tokens; +} + + +@end diff --git a/PDTokenFieldCell.h b/PDTokenFieldCell.h new file mode 100644 index 0000000..e1bd353 --- /dev/null +++ b/PDTokenFieldCell.h @@ -0,0 +1,31 @@ +// +// PDTokenFieldCell.h +// SproutedInterface +// +// Created by Philip Dow on 7/16/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// +// Note that PDTokenFieldCell does not require PDTokenField +// + +#import + + +@interface PDTokenFieldCell : NSTokenFieldCell { + +} + +@end + +@interface NSTokenFieldCell (SuperImplemented) + +- (id) _tokensFromPasteboard:(id)fp8; + +@end + +@interface NSObject (PDTokenFieldCellDelegate) + +- (void)tokenFieldCell:(PDTokenFieldCell *)tokenFieldCell didReadTokens:(NSArray*)theTokens fromPasteboard:(NSPasteboard *)pboard; + +@end \ No newline at end of file diff --git a/PDTokenFieldCell.m b/PDTokenFieldCell.m new file mode 100644 index 0000000..496c197 --- /dev/null +++ b/PDTokenFieldCell.m @@ -0,0 +1,67 @@ +// +// PDTokenFieldCell.m +// SproutedInterface +// +// Created by Philip Dow on 7/16/07. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// +// Note that PDTokenFieldCell does not require PDTokenField +// + +#import + + +@implementation PDTokenFieldCell + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + //NSLog(@"%@ %s view is a %@",[self className],_cmd,[controlView className]); + + [self setControlSize:NSSmallControlSize]; + [super drawWithFrame:cellFrame inView:controlView]; +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + //NSLog(@"%@ %s view is a %@",[self className],_cmd,[controlView className]); + + // a small adjustment if we're running 10.4 but not 10.5 + if ( ![self respondsToSelector:@selector(scriptingValueForSpecifier:)] ) + cellFrame.origin.y -= 1; + + [self setControlSize:NSSmallControlSize]; + [super drawInteriorWithFrame:cellFrame inView:controlView]; +} + +#pragma mark - +#pragma mark Fixes for Bindings Problem with Dragging & Copy/Paste + +- (BOOL)performDragOperation:(id )sender +{ + if ([super performDragOperation:sender]) + { + //NSLog(@"perform custom drag operation"); + + if ( [self delegate] != nil && [[self delegate] respondsToSelector:@selector(tokenFieldCell:didReadTokens:fromPasteboard:)] ) + [[self delegate] tokenFieldCell:self didReadTokens:[self objectValue] fromPasteboard:[sender draggingPasteboard]]; + + return YES; + } + else + { + return NO; + } +} + +- (id) _tokensFromPasteboard:(id)fp8 +{ + id tokens = [super _tokensFromPasteboard:fp8]; + + if ( [self delegate] != nil && [[self delegate] respondsToSelector:@selector(tokenFieldCell:didReadTokens:fromPasteboard:)] ) + [[self delegate] tokenFieldCell:self didReadTokens:tokens fromPasteboard:fp8]; + + return tokens; +} + +@end diff --git a/PDToolbar.h b/PDToolbar.h new file mode 100644 index 0000000..f2a80f7 --- /dev/null +++ b/PDToolbar.h @@ -0,0 +1,32 @@ +// +// PDDelegatedToolbar.h +// SproutedInterface +// +// Created by Philip Dow on 12/1/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#define PDToolbarDidShowNotification @"PDToolbarDidShowNotification" +#define PDToolbarDidHideNotification @"PDToolbarDidHideNotification" + +@interface PDToolbar : NSToolbar { + +} + +- (NSToolbarItem*) itemWithTag:(int)aTag; +- (NSToolbarItem*) itemWithIdentifier:(NSString*)identifier; + +@end + +@interface NSObject (PDToolbarDelegate) + +- (void) toolbarDidChangeSizeMode:(PDToolbar*)aToolbar; +- (void) toolbarDidChangeDisplayMode:(PDToolbar*)aToolbar; + +- (void) toolbarDidShow:(PDToolbar*)aToolbar; +- (void) toolbarDidHide:(PDToolbar*)aToolbar; + +@end \ No newline at end of file diff --git a/PDToolbar.m b/PDToolbar.m new file mode 100644 index 0000000..766febd --- /dev/null +++ b/PDToolbar.m @@ -0,0 +1,85 @@ +// +// PDDelegatedToolbar.m +// SproutedInterface +// +// Created by Philip Dow on 12/1/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@implementation PDToolbar + +- (NSToolbarItem*) itemWithTag:(int)aTag +{ + NSToolbarItem *anItem, *theItem = nil; + NSEnumerator *enumerator = [[self items] objectEnumerator]; + + while ( anItem = [enumerator nextObject] ) + { + if ( [anItem tag] == aTag ) + { + theItem = anItem; + break; + } + } + + return theItem; +} + +- (NSToolbarItem*) itemWithIdentifier:(NSString*)identifier +{ + NSToolbarItem *anItem, *theItem = nil; + NSEnumerator *enumerator = [[self items] objectEnumerator]; + + while ( anItem = [enumerator nextObject] ) + { + if ( [[anItem itemIdentifier] isEqualToString:identifier] ) + { + theItem = anItem; + break; + } + } + + return theItem; +} + +#pragma mark - + +- (void)setDisplayMode:(NSToolbarDisplayMode)displayMode +{ + [super setDisplayMode:displayMode]; + if ( [[self delegate] respondsToSelector:@selector(toolbarDidChangeDisplayMode:)] ) + [[self delegate] toolbarDidChangeDisplayMode:self]; +} + +- (void)setSizeMode:(NSToolbarSizeMode)sizeMode +{ + [super setSizeMode:sizeMode]; + if ( [[self delegate] respondsToSelector:@selector(toolbarDidChangeSizeMode:)] ) + [[self delegate] toolbarDidChangeSizeMode:self]; + +} + +- (void)setVisible:(BOOL)shown +{ + [super setVisible:shown]; + + if ( shown ) + [[NSNotificationCenter defaultCenter] postNotificationName:PDToolbarDidShowNotification + object:self + userInfo:nil]; + else + [[NSNotificationCenter defaultCenter] postNotificationName:PDToolbarDidHideNotification + object:self + userInfo:nil]; + + if (shown && [[self delegate] respondsToSelector:@selector(toolbarDidShow:)] ) + [[self delegate] toolbarDidShow:self]; + else if ( !shown && [[self delegate] respondsToSelector:@selector(toolbarDidHide:)] ) + [[self delegate] toolbarDidHide:self]; +} + +@end diff --git a/PDURLTextField.h b/PDURLTextField.h new file mode 100644 index 0000000..52e3082 --- /dev/null +++ b/PDURLTextField.h @@ -0,0 +1,29 @@ +// +// PDURLTextField.h +// SproutedInterface +// +// Created by Philip Dow on 5/28/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + + +@interface PDURLTextField : NSTextField { + + NSString *_url_title; +} + ++ (NSImage*) defaultImage; + +- (NSString*)URLTitle; +- (void) setURLTitle:(NSString*)aTitle; + +- (NSImage*) image; +- (void) setImage:(NSImage*)anImage; + +- (double) estimatedProgress; +- (void) setEstimatedProgress:(double)estimate; + +@end diff --git a/PDURLTextField.m b/PDURLTextField.m new file mode 100644 index 0000000..848269c --- /dev/null +++ b/PDURLTextField.m @@ -0,0 +1,163 @@ +// +// PDURLTextField.m +// SproutedInterface +// +// Created by Philip Dow on 5/28/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import +#import +#import + +#define kWebURLsWithTitlesPboardType @"WebURLsWithTitlesPboardType" + +static NSString *kFileURLIdentifier = @"file://"; + +@implementation PDURLTextField + + +- (id)initWithCoder:(NSCoder *)decoder { + + if ( self = [super initWithCoder:decoder] ) { + + NSArchiver * anArchiver = [[[NSArchiver alloc] + initForWritingWithMutableData:[NSMutableData dataWithCapacity: 256]] autorelease]; + [anArchiver encodeClassName:@"NSTextFieldCell" intoClassName:@"PDURLTextFieldCell"]; + [anArchiver encodeRootObject:[self cell]]; + [self setCell:[NSUnarchiver unarchiveObjectWithData:[anArchiver archiverData]]]; + + _url_title = [[NSString alloc] init]; + [self setImage:[PDURLTextField defaultImage]]; + + } + + return self; +} + + ++ (Class) cellClass +{ + return [PDURLTextFieldCell class]; +} + +- (void) dealloc { + [_url_title release]; + _url_title = nil; + [super dealloc]; +} + +#pragma mark - + ++ (NSImage*) defaultImage +{ + return BundledImageWithName(@"PDURLTextFieldWebDefault.tiff", @"com.sprouted.interface"); +} + + +- (void)setStringValue:(NSString *)aString { + [super setStringValue:aString]; + if ( [[self stringValue] rangeOfString:kFileURLIdentifier options:NSCaseInsensitiveSearch].location == 0 ) + [self setImage:BundledImageWithName(@"PDURLTextFieldFileDefault.png", @"com.sprouted.interface")]; + [self setURLTitle:nil]; +} + +- (void)setObjectValue:(id )object +{ + [super setObjectValue:object]; + if ( [[self stringValue] rangeOfString:kFileURLIdentifier options:NSCaseInsensitiveSearch].location == 0 ) + [self setImage:BundledImageWithName(@"PDURLTextFieldFileDefault.png", @"com.sprouted.interface")]; + [self setURLTitle:nil]; +} + +- (NSString*)URLTitle { + if (_url_title != nil && [_url_title length] != 0 ) + return _url_title; + else + return [self stringValue]; +} + +- (void) setURLTitle:(NSString*)aTitle { + if ( _url_title != aTitle ) { + [_url_title release]; + _url_title = [aTitle copyWithZone:[self zone]]; + } +} + +- (NSImage*) image { return [[self cell] image]; } + +- (void) setImage:(NSImage*)anImage { + [[self cell] setImage:anImage]; +} + +- (double) estimatedProgress { return [[self cell] estimatedProgress]; } + +- (void) setEstimatedProgress:(double)estimate { + [[self cell] setEstimatedProgress:estimate]; + [self setNeedsDisplay:YES]; +} + +#pragma mark - + +- (void)mouseDragged:(NSEvent *)theEvent +{ + static int kTextOffest = 4.0; + + // do not execute a drag if there is nothing to drag + if ( [self stringValue] == nil || [[self stringValue] length] == 0 ) return; + + NSPoint image_location = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; + + NSImage *my_image = [[self image] copyWithZone:[self zone]];; + NSString *drag_title = [self URLTitle]; + if ( !drag_title || [drag_title length] == 0 ) drag_title = [self stringValue]; + + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [self font], NSFontAttributeName, + [NSColor colorWithCalibratedWhite:0.2 alpha:1.0], NSForegroundColorAttributeName, nil]; + + NSSize image_size = [my_image size]; + NSSize title_size = [drag_title sizeWithAttributes:attributes]; + NSSize total_size = NSMakeSize( (image_size.width + kTextOffest + title_size.width), + (image_size.height > title_size.height ? image_size.height : title_size.height) ); + + NSSize drag_offset = NSMakeSize(image_size.width/2, image_size.height/2); + image_location.x -= drag_offset.width; + image_location.y += drag_offset.height; + + NSImage *drag_image = [[NSImage alloc] initWithSize:total_size]; + [my_image setFlipped:[drag_image isFlipped]]; + + [drag_image lockFocus]; + + [my_image compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver]; + [drag_title drawAtPoint:NSMakePoint(image_size.width+kTextOffest,0) withAttributes:attributes]; + + [drag_image unlockFocus]; + + NSArray *types = [NSArray arrayWithObjects: + kWebURLsWithTitlesPboardType, NSURLPboardType, nil]; + + NSURL *url = [NSURL URLWithString:[self stringValue]]; + + NSArray *url_array = [NSArray arrayWithObjects:[self stringValue],nil]; + NSArray *title_array = [NSArray arrayWithObjects:drag_title, nil]; + NSArray *web_urls_array = [NSArray arrayWithObjects:url_array,title_array,nil]; + + [pboard declareTypes:types owner:self]; + + [pboard setPropertyList:web_urls_array forType:kWebURLsWithTitlesPboardType]; + [url writeToPasteboard:pboard]; + + [self dragImage:drag_image at:image_location offset:NSMakeSize(0.0,0.0) + event:theEvent pasteboard:pboard source:self slideBack:YES]; + + [my_image release]; + [drag_image release]; + + return; +} + +@end diff --git a/PDURLTextFieldCell.h b/PDURLTextFieldCell.h new file mode 100644 index 0000000..441c92b --- /dev/null +++ b/PDURLTextFieldCell.h @@ -0,0 +1,43 @@ +// +// PDURLTextFieldCell.h +// SproutedInterface +// +// Created by Philip Dow on 5/28/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +typedef enum { + kPDAppearanceStyleBlue = 1, + kPDAppearanceStyleGraphite = 2 +} PDAppearanceStyle; + +@interface PDURLTextFieldCell : NSTextFieldCell { + + double _estimated_progress; + PDAppearanceStyle _appearanceStyle; + + NSImage *_image; + NSColor *_progress_gradient_start, *_progress_gradient_end; + +} + +- (void) _initializeColors; + +- (NSRect) textRectForFrame:(NSRect)frame; +- (NSRect) imageRectForFrame:(NSRect)frame; +- (NSRect) progressRectForFrame:(NSRect)frame; + +- (NSImage*) image; +- (void) setImage:(NSImage*)anImage; + +- (double) estimatedProgress; +- (void) setEstimatedProgress:(double)estimate; + +- (void) drawFocusRingWithFrame:(NSRect)cellFrame inView:(NSView *)controlView; +- (void) drawProgressIndicatorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView; +- (void) drawImageWithFrame:(NSRect)cellFrame inView:(NSView *)controlView; + +@end diff --git a/PDURLTextFieldCell.m b/PDURLTextFieldCell.m new file mode 100644 index 0000000..4e843cf --- /dev/null +++ b/PDURLTextFieldCell.m @@ -0,0 +1,229 @@ +// +// PDURLTextFieldCell.m +// SproutedInterface +// +// Created by Philip Dow on 5/28/06. +// Copyright Sprouted. All rights reserved. +// Inquiries should be directed to developer@journler.com +// + +#import + +#import +#import + +@implementation PDURLTextFieldCell + + +- (id)initWithCoder:(NSCoder *)decoder { + if ( self = [super initWithCoder:decoder] ) { + _appearanceStyle = kPDAppearanceStyleBlue; + [self _initializeColors]; + } + return self; +} + + +- (id)initImageCell:(NSImage *)anImage { + if ( self = [super initImageCell:anImage] ) { + _appearanceStyle = kPDAppearanceStyleBlue; + [self _initializeColors]; + } + return self; +} + +- (id)initTextCell:(NSString *)aString { + if ( self = [super initTextCell:aString] ) { + _appearanceStyle = kPDAppearanceStyleBlue; + [self _initializeColors]; + } + return self; +} + +- (void) dealloc { + [_image release]; + [_progress_gradient_start release]; + [_progress_gradient_end release]; + [super dealloc]; +} + +#pragma mark - + +- (void) _initializeColors { + if ( _appearanceStyle == kPDAppearanceStyleGraphite ) { + _progress_gradient_start = [[NSColor + colorWithCalibratedRed:187.0/255.0 green:196.0/255.0 blue:209.0/255.0 alpha:1.0] retain]; + _progress_gradient_end = [[NSColor + colorWithCalibratedRed:174.0/255.0 green:185.0/255.0 blue:201.0/255.0 alpha:1.0] retain]; + } + else { + _progress_gradient_start = [[NSColor + colorWithCalibratedRed:167.0/255.0 green:209.0/255.0 blue:255.0/255.0 alpha:1.0] retain]; + _progress_gradient_end = [[NSColor + colorWithCalibratedRed:126.0/255.0 green:186.0/255.0 blue:255.0/255.0 alpha:1.0] retain]; + } +} + +#pragma mark - + +- (NSRect)textRectForFrame:(NSRect)frame +{ + frame.origin.x += 20; + frame.size.width -= 20; + return frame; +} + +- (NSRect) imageRectForFrame:(NSRect)frame { + frame.origin.x+=2; + frame.size.width = 18; + return frame; +} + +- (NSRect) progressRectForFrame:(NSRect)frame { + if ( [self estimatedProgress] == 0 || [self estimatedProgress] > 1 ) return NSZeroRect; + + frame.origin.y+=2; + frame.size.height-=3; + frame.origin.x+=1; + frame.size.width=( (frame.size.width-=2) * [self estimatedProgress] ); + return frame; +} + +#pragma mark - + +- (NSImage*) image { return _image; } + +- (void) setImage:(NSImage*)anImage { + if ( _image != anImage ) { + [_image release]; + _image = [anImage copyWithZone:[self zone]]; + } +} + +- (double) estimatedProgress { return _estimated_progress; } + +- (void) setEstimatedProgress:(double)estimate { + _estimated_progress = estimate; +} + +#pragma mark - + +- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj + delegate:(id)anObject event:(NSEvent *)theEvent +{ + [super editWithFrame:[self textRectForFrame:aRect] inView:controlView editor:textObj + delegate:anObject event:theEvent]; +} + +- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj + delegate:(id)anObject start:(int)selStart length:(int)selLength +{ + [super selectWithFrame:[self textRectForFrame:aRect] inView:controlView editor:textObj + delegate:anObject start:selStart length:selLength]; +} + + +- (void)resetCursorRect:(NSRect)cellFrame inView:(NSView *)controlView +{ + [super resetCursorRect:[self textRectForFrame:cellFrame] inView:controlView]; +} + +#pragma mark - + + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + // disable focus ring so I can draw my own + [self setFocusRingType:NSFocusRingTypeNone]; + + [super drawWithFrame:cellFrame inView:controlView]; + + // re-enable focus ring so that mine is properly erased + [self setFocusRingType:NSFocusRingTypeDefault]; +} + +- (void)highlight:(BOOL)flag withFrame:(NSRect)cellFrame inView:(NSView *)controlView { + // disable focus ring so I can draw my own + [self setFocusRingType:NSFocusRingTypeNone]; + + [super highlight:flag withFrame:cellFrame inView:controlView]; + + // re-enable focus ring so that mine is properly erased + [self setFocusRingType:NSFocusRingTypeDefault]; +} + + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + + BOOL does_draw_background = [self drawsBackground]; + + // Draw the focus ring + [self drawFocusRingWithFrame:cellFrame inView:controlView]; + + // Draw Progress Bar + [self drawProgressIndicatorWithFrame:[self progressRectForFrame:cellFrame] inView:controlView]; + + // Draw the FavIcon + [self drawImageWithFrame:[self imageRectForFrame:cellFrame] inView:controlView]; + + // Draw Remaining Things + + // Disable background drawing so that the interior draw does not erase the progress bar + [self setDrawsBackground:NO]; + + [super drawInteriorWithFrame:[self textRectForFrame:cellFrame] inView:controlView]; + + // Renable background drawing so that the view otherwise properly draws itself + [self setDrawsBackground:does_draw_background]; + +} + +- (void)drawFocusRingWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + if ([super showsFirstResponder] && [[controlView window] isKeyWindow] ) { + + [NSGraphicsContext saveGraphicsState]; + NSSetFocusRingStyle(NSFocusRingOnly); + NSBezierPath *bezier = [NSBezierPath bezierPathWithRect:cellFrame]; + [bezier fill]; + [NSGraphicsContext restoreGraphicsState]; + + } +} + +- (void) drawProgressIndicatorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { + + NSRect top, bottom; + NSDivideRect(cellFrame, &top, &bottom, cellFrame.size.height/2, NSMinYEdge); + top.size.height++; + + [[NSBezierPath bezierPathWithRect:top] linearGradientFillWithStartColor:_progress_gradient_start endColor:_progress_gradient_end]; + [[NSBezierPath bezierPathWithRect:bottom] linearGradientFillWithStartColor:_progress_gradient_end endColor:_progress_gradient_start]; +} + +- (void)drawImageWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + + NSImage *target_image = [self image]; + if ( target_image != nil ) { + + [target_image setFlipped:[controlView isFlipped]]; + + NSSize image_size = [target_image size]; + if ( image_size.width > 20 ) [target_image setSize:NSMakeSize(20, image_size.height*20/image_size.width)]; + + NSRect source_rect = NSMakeRect( 0, 0, image_size.width, image_size.height ); + NSRect target_rect = NSMakeRect( cellFrame.size.width/2 - image_size.width/2, + cellFrame.size.height/2 - image_size.height/2, + image_size.width, image_size.height ); + + target_rect.origin.x+=cellFrame.origin.x; + target_rect.origin.y+=cellFrame.origin.y; + + [target_image drawInRect:target_rect fromRect:source_rect + operation:NSCompositeSourceOver fraction:1.0]; + + } + +} + +@end \ No newline at end of file diff --git a/PDURLTextFieldFileDefault.png b/PDURLTextFieldFileDefault.png new file mode 100644 index 0000000..df17799 Binary files /dev/null and b/PDURLTextFieldFileDefault.png differ diff --git a/PDURLTextFieldWebDefault.tiff b/PDURLTextFieldWebDefault.tiff new file mode 100644 index 0000000..42020ff Binary files /dev/null and b/PDURLTextFieldWebDefault.tiff differ diff --git a/PolishedWindow.h b/PolishedWindow.h new file mode 100644 index 0000000..7e89098 --- /dev/null +++ b/PolishedWindow.h @@ -0,0 +1,42 @@ +// +// PolishedWindow.h +// TunesWindow +// +// Created by Matt Gemmell on 12/02/2006. +// Copyright 2006 Magic Aubergine. All rights reserved. +// + +#import + +// offers the 10.5 window look in 10.4 + +@interface PolishedWindow : NSWindow { + BOOL _flat; + BOOL forceDisplay; + + // PD Modifications to improve rendering during drag + NSImage *bottomLeft; + NSImage *bottomMiddle; + NSColor *bottomMiddlePattern; + NSImage *bottomRight; + NSImage *topLeft; + NSImage *topMiddle; + NSColor *topMiddlePattern; + NSImage *topRight; + NSImage *middleLeft; + NSImage *middleRight; + // End modifications +} + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(unsigned int)styleMask + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag + flat:(BOOL)flat; + +- (NSColor *)sizedPolishedBackground; + +- (BOOL)flat; +- (void)setFlat:(BOOL)newFlat; + +@end \ No newline at end of file diff --git a/PolishedWindow.m b/PolishedWindow.m new file mode 100644 index 0000000..c572fd3 --- /dev/null +++ b/PolishedWindow.m @@ -0,0 +1,250 @@ +// +// PolishedWindow.m +// TunesWindow +// +// Created by Matt Gemmell on 12/02/2006. +// Copyright 2006 Magic Aubergine. All rights reserved. +// + +#import +#import + +@implementation PolishedWindow + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag +{ + return [self initWithContentRect:contentRect styleMask:styleMask backing:bufferingType defer:flag flat:NO]; +} + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag flat:(BOOL)flat +{ + + if ( ![self respondsToSelector:@selector(contentBorderThicknessForEdge:)] ) + { + // Conditionally add textured window flag to stylemask + unsigned int newStyle; + if (styleMask & NSTexturedBackgroundWindowMask){ + newStyle = styleMask; + } else { + newStyle = (NSTexturedBackgroundWindowMask | styleMask); + } + + if (self = [super initWithContentRect:contentRect styleMask:newStyle backing:bufferingType defer:flag] ) + { + // PD Modifications + _flat = YES; + forceDisplay = NO; + + bottomLeft = [BundledImageWithName(@"flat_bottom_left.png", @"com.sprouted.interface") retain]; + bottomMiddle = [BundledImageWithName(@"flat_bottom_middle.png", @"com.sprouted.interface") retain]; + bottomMiddlePattern = [[NSColor colorWithPatternImage:bottomMiddle] retain]; + + bottomRight = [BundledImageWithName(@"flat_bottom_right.png", @"com.sprouted.interface") retain]; + topLeft = [BundledImageWithName(@"flat_top_left.png", @"com.sprouted.interface") retain]; + topMiddle = [BundledImageWithName(@"flat_top_middle.png", @"com.sprouted.interface")retain]; + topMiddlePattern = [[NSColor colorWithPatternImage:topMiddle] retain]; + + topRight = [BundledImageWithName(@"flat_top_right.png", @"com.sprouted.interface") retain]; + middleLeft = [BundledImageWithName(@"middle_left.png", @"com.sprouted.interface") retain]; + middleRight = [BundledImageWithName(@"middle_right.png", @"com.sprouted.interface") retain]; + + [self setBackgroundColor:[self sizedPolishedBackground]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResize:) + name:NSWindowDidResizeNotification object:self]; + } + } + else + { + if ( self = [super initWithContentRect:contentRect styleMask:styleMask backing:bufferingType defer:flag] ) + { + //[self setContentBorderThickness:20.0 forEdge:NSMinYEdge]; + //[self setAutorecalculatesContentBorderThickness:YES forEdge:NSMinYEdge]; + } + } + + return self; +} + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 +- (void)setToolbar:(NSToolbar *)toolbar +{ + //if ( ![self respondsToSelector:@selector(contentBorderThicknessForEdge:)] ) + //{ + // Only actually call this if we respond to it on this machine + if ([toolbar respondsToSelector:@selector(setShowsBaselineSeparator:)]) { + [toolbar setShowsBaselineSeparator:NO]; + } + //} + [super setToolbar:toolbar]; +} +#endif + +- (void)dealloc +{ + if ( ![self respondsToSelector:@selector(contentBorderThicknessForEdge:)] ) + { + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResizeNotification object:self]; + + [bottomLeft release]; + [bottomMiddle release]; + [bottomMiddlePattern release]; + [bottomRight release]; + [topLeft release]; + [topMiddle release]; + [topMiddlePattern release]; + [topRight release]; + [middleLeft release]; + [middleRight release]; + } + + [super dealloc]; +} + +- (void)windowDidResize:(NSNotification *)aNotification +{ + if ( ![self respondsToSelector:@selector(contentBorderThicknessForEdge:)] ) + { + [self setBackgroundColor:[self sizedPolishedBackground]]; + if (forceDisplay) { + [self display]; + } + } +} + +- (void)setMinSize:(NSSize)aSize +{ + if ( ![self respondsToSelector:@selector(contentBorderThicknessForEdge:)] ) + [super setMinSize:NSMakeSize(MAX(aSize.width, 150.0), MAX(aSize.height, 150.0))]; + else + [super setMinSize:aSize]; +} + +- (void)setFrame:(NSRect)frameRect display:(BOOL)displayFlag animate:(BOOL)animationFlag +{ + if ( ![self respondsToSelector:@selector(contentBorderThicknessForEdge:)] ) + { + forceDisplay = YES; + [super setFrame:frameRect display:displayFlag animate:animationFlag]; + forceDisplay = NO; + } + else + [super setFrame:frameRect display:displayFlag animate:animationFlag]; +} + +- (NSColor *)sizedPolishedBackground +{ + NSImage *bg = [[NSImage alloc] initWithSize:[self frame].size]; + + // Find background color to draw into window + [topMiddle lockFocus]; + NSColor *bgColor = NSReadPixel(NSMakePoint(0, 0)); + [topMiddle unlockFocus]; + //NSColor *bgColor = [NSColor colorWithCalibratedWhite:235.0/255.0 alpha:1.0]; + + // Set min width of temporary pattern image to prevent flickering at small widths + float minWidth = 300.0; + + // Create temporary image for top-middle pattern + NSImage *topMiddleImg = [[NSImage alloc] initWithSize:NSMakeSize(MAX(minWidth, [self frame].size.width), [topMiddle size].height)]; + [topMiddleImg lockFocus]; + [topMiddlePattern set]; + NSRectFillUsingOperation(NSMakeRect(0, 0, [topMiddleImg size].width, [topMiddleImg size].height), NSCompositeSourceOver); + [topMiddleImg unlockFocus]; + + // Create temporary image for bottom-middle pattern + NSImage *bottomMiddleImg = [[NSImage alloc] initWithSize:NSMakeSize(MAX(minWidth, [self frame].size.width), [bottomMiddle size].height)]; + [bottomMiddleImg lockFocus]; + [bottomMiddlePattern set]; + NSRectFillUsingOperation(NSMakeRect(0, 0, [bottomMiddleImg size].width, [bottomMiddleImg size].height), NSCompositeSourceOver); + [bottomMiddleImg unlockFocus]; + + // Begin drawing into our main image + [bg lockFocus]; + + // Composite current background color into bg + [bgColor set]; + NSRectFillUsingOperation(NSMakeRect(0, 0, [bg size].width, [bg size].height), NSCompositeSourceOver); + + if ([self flat]) { + // Composite middle left/right images + + //[middleLeft setFlipped:NO]; + [middleLeft drawInRect:NSMakeRect(0, 0, + [middleLeft size].width, + [self frame].size.height) + fromRect:NSMakeRect(0, 0, + [middleLeft size].width, + [middleLeft size].height) + operation:NSCompositeSourceOver + fraction:1.0]; + + //[middleRight setFlipped:NO]; + [middleRight drawInRect:NSMakeRect([self frame].size.width - [middleRight size].width + 1.0, 0, + [middleRight size].width, + [self frame].size.height) + fromRect:NSMakeRect(0, 0, + [middleRight size].width, + [middleRight size].height) + operation:NSCompositeSourceOver + fraction:1.0]; + } + + // Composite bottom-middle image + //[bottomMiddleImg setFlipped:NO]; + [bottomMiddleImg drawInRect:NSMakeRect([bottomLeft size].width, 0, + [bg size].width - [bottomLeft size].width - [bottomRight size].width, + [bottomLeft size].height) + fromRect:NSMakeRect(0, 0, + [bg size].width - [bottomLeft size].width - [bottomRight size].width, + [bottomLeft size].height) + operation:NSCompositeSourceOver + fraction:1.0]; + [bottomMiddleImg release]; + + // Composite bottom-left and bottom-right images + //[bottomLeft setFlipped:NO]; + [bottomLeft compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver]; + + //[bottomRight setFlipped:NO]; + [bottomRight compositeToPoint:NSMakePoint([bg size].width - [bottomRight size].width, 0) operation:NSCompositeSourceOver]; + + // Composite top-middle image + //[topMiddleImg setFlipped:NO]; + [topMiddleImg drawInRect:NSMakeRect([topLeft size].width, + [bg size].height - [topLeft size].height, + [bg size].width - [topLeft size].width - [topRight size].width, + [topLeft size].height) + fromRect:NSMakeRect(0, 0, + [bg size].width - [topLeft size].width - [topRight size].width, + [topLeft size].height) + operation:NSCompositeSourceOver + fraction:1.0]; + [topMiddleImg release]; + + // Composite top-left and top-right images + //[topLeft setFlipped:NO]; + [topLeft compositeToPoint:NSMakePoint(0, [bg size].height - [topLeft size].height) operation:NSCompositeSourceOver]; + + //[topRight setFlipped:NO]; + [topRight compositeToPoint:NSMakePoint([bg size].width - [topRight size].width, + [bg size].height - [topRight size].height) operation:NSCompositeSourceOver]; + + [bg unlockFocus]; + + return [NSColor colorWithPatternImage:[bg autorelease]]; +} + +- (BOOL)flat +{ + return _flat; +} + +- (void)setFlat:(BOOL)newFlat +{ + _flat = newFlat; + forceDisplay = YES; + [self windowDidResize:nil]; + forceDisplay = NO; +} + +@end diff --git a/PreviewBarSmall.png b/PreviewBarSmall.png new file mode 100644 index 0000000..945b162 Binary files /dev/null and b/PreviewBarSmall.png differ diff --git a/QTBarSmall.png b/QTBarSmall.png new file mode 100644 index 0000000..637642d Binary files /dev/null and b/QTBarSmall.png differ diff --git a/RBSplitSubview.h b/RBSplitSubview.h new file mode 100644 index 0000000..d058230 --- /dev/null +++ b/RBSplitSubview.h @@ -0,0 +1,146 @@ +// +// RBSplitSubview.h version 1.1.4 +// RBSplitView +// +// Created by Rainer Brockerhoff on 19/11/2004. +// Copyright 2004-2006 Rainer Brockerhoff. +// Some Rights Reserved under the Creative Commons Attribution License, version 2.5, and/or the MIT License. +// + +#import + +@class RBSplitView; + +// These values are used to inquire about the status of a subview. +typedef enum { + RBSSubviewExpanding=-2, + RBSSubviewCollapsing=-1, + RBSSubviewNormal=0, + RBSSubviewCollapsed=1 +} RBSSubviewStatus; + +@interface RBSplitSubview : NSView { +// Subclasses normally should use setter methods instead of changing instance variables by assignment. +// Most getter methods simply return the corresponding instance variable, so with some care, subclasses +// could reference them directly. + NSString* identifier; // An identifier string for the subview, default is @"". + int tag; // A tag integer for the subview, default is 0. + float minDimension; // The minimum dimension. Must be 1.0 or any larger integer. + float maxDimension; // The maximum dimension. Must be at least equal to the minDimension. + // Set to a large number if there's no maximum. + double fraction; // A fractional part of the dimension, used for proportional resizing. + // Normally varies between -0.999... and 0.999... + // When collapsed, holds the proportion of the RBSplitView's dimension + // the view was occupying before collapsing. + NSRect previous; // Holds the frame rect for the last delegate notification. + NSSize savedSize; // This holds the size the subview had before it was resized beyond + // its minimum or maximum limits. Valid if notInLimits is YES. + unsigned int actDivider; // This is set temporarily while an alternate drag view is being dragged. + BOOL canDragWindow; // This is set temporarily during a mouseDown on a non-opaque subview. + BOOL canCollapse; // YES if the subview can be collapsed. + BOOL notInLimits; // YES if the subview's dimensions are outside the set limits. +} + +// This class method returns YES if some RBSplitSubview is being animated. ++ (BOOL)animating; + +// This is the designated initializer for creating extra subviews programmatically. +- (id)initWithFrame:(NSRect)frame; + +// Returns the immediately containing RBSplitView, or nil if there is none. +// couplingSplitView returns nil if we're a non-coupled RBSplitView. +// outermostSplitView returns the outermost RBSplitView. +- (RBSplitView*)splitView; +- (RBSplitView*)couplingSplitView; +- (RBSplitView*)outermostSplitView; + +// Returns self if we're a RBSplitView, nil otherwise. Convenient for testing or calling methods. +// coupledSplitView returns nil if we're a non-coupled RBSplitView. +- (RBSplitView*)asSplitView; +- (RBSplitView*)coupledSplitView; + +// Sets and gets the coupling between the view and its containing RBSplitView (if any). Coupled +// RBSplitViews take some parameters, such as divider images, from the containing view. The default +// for RBSplitView is YES. However, calling setCoupled: on a RBSplitSubview will have no effect, +// and isCoupled will always return false. +- (void)setCoupled:(BOOL)flag; +- (BOOL)isCoupled; + +// Returns YES if the containing RBSplitView is horizontal. +- (BOOL)splitViewIsHorizontal; + +// Returns the number of subviews. Just a convenience method. +- (unsigned)numberOfSubviews; + +// Sets and gets the tag. +- (void)setTag:(int)theTag; +- (int)tag; + +// Sets and gets the identifier string. Will never be nil. +- (void)setIdentifier:(NSString*)aString; +- (NSString*)identifier; + +// Position means the subview's position within the RBSplitView - counts from zero left to right +// or top to bottom. Setting it will move the subview to another position without changing its size, +// status or attributes. Set position to 0 to move it to the start, or to some large number to move it +// to the end of the RBSplitView. +- (unsigned)position; +- (void)setPosition:(unsigned)newPosition; + +// Returns YES if the subview is collapsed. Collapsed subviews are squashed down to zero but never +// made smaller than the minimum dimension as far as their own subviews are concerned. If the +// subview is being animated this will return NO. +- (BOOL)isCollapsed; + +// This will return the current status of the subview. Negative values mean the subview is +// being animated. +- (RBSSubviewStatus)status; + +// Sets and gets the ability to collapse the subview. However, this can be overridden by the delegate. +- (BOOL)canCollapse; +- (void)setCanCollapse:(BOOL)flag; + +// Tests whether the subview can shrink or expand further. +- (BOOL)canShrink; +- (BOOL)canExpand; + +// Sets and gets the minimum and maximum dimensions. They're set at the same time to make sure values +// are consistent. Despite being floats, they'll always have integer values. The minimum value for the +// minimum is 1.0. Pass 0.0 for the maximum to set it to some huge number. +- (float)minDimension; +- (float)maxDimension; +- (void)setMinDimension:(float)newMinDimension andMaxDimension:(float)newMaxDimension; + +// Call this to expand a subview programmatically. It will return the subview's dimension after +// expansion. +- (float)expand; + +// Call this to collapse a subview programmatically. It will return the negative +// of the subview's dimension _before_ collapsing, or 0.0 if the subview can't be collapsed. +- (float)collapse; + +// These calls collapse and expand subviews with animation. They return YES if animation +// startup was successful. +- (BOOL)collapseWithAnimation; +- (BOOL)expandWithAnimation; + +// These methods collapse and expand subviews with animation, depending on the parameters. +// They return YES if animation startup was successful. If resize is NO, the subview is +// collapsed/expanded without resizing it during animation. +- (BOOL)collapseWithAnimation:(BOOL)animate withResize:(BOOL)resize; +- (BOOL)expandWithAnimation:(BOOL)animate withResize:(BOOL)resize; + +// Returns the current dimension of the subview. +- (float)dimension; + +// Sets the current dimension of the subview, subject to the current maximum and minimum. +// If the subview is collapsed, this has no immediate effect. +- (void)setDimension:(float)value; + +// This method is used internally when a divider is dragged. It tries to change the subview's dimension +// and returns the actual change, collapsing or expanding whenever possible. You usually won't need +// to call this directly. +- (float)changeDimensionBy:(float)increment mayCollapse:(BOOL)mayCollapse move:(BOOL)move; + +@end + diff --git a/RBSplitSubview.m b/RBSplitSubview.m new file mode 100644 index 0000000..31dc9bf --- /dev/null +++ b/RBSplitSubview.m @@ -0,0 +1,927 @@ +// +// RBSplitSubview.m version 1.1.4 +// RBSplitView +// +// Created by Rainer Brockerhoff on 19/11/2004. +// Copyright 2004-2006 Rainer Brockerhoff. +// Some Rights Reserved under the Creative Commons Attribution License, version 2.5, and/or the MIT License. +// + +#import +#import + +// This variable points to the animation data structure while an animation is in +// progress; if there's none, it will be NULL. Animating may be very CPU-intensive so +// we allow only one animation to take place at a time. +static animationData* currentAnimation = NULL; + +@implementation RBSplitSubview + +// This class method returns YES if an animation is in progress. ++ (BOOL)animating { + return currentAnimation!=NULL; +} + +// This is the designated initializer for RBSplitSubview. It sets some reasonable defaults. However, you +// can't rely on anything working until you insert it into a RBSplitView. +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + fraction = 0.0; + canCollapse = NO; + notInLimits = NO; + minDimension = 1.0; + maxDimension = WAYOUT; + identifier = @""; + previous = NSZeroRect; + savedSize = frame.size; + actDivider = NSNotFound; + canDragWindow = NO; + } + return self; +} + +// Just releases our stuff when going away. +- (void)dealloc { + [identifier release]; + [super dealloc]; +} + +// These return nil since we're not a RBSplitView (they're overridden there). +- (RBSplitView*)asSplitView { + return nil; +} + +- (RBSplitView*)coupledSplitView { + return nil; +} + +// Sets and gets the coupling between a RBSplitView and its containing RBSplitView (if any). +// For convenience, these methods are also implemented here. +- (void)setCoupled:(BOOL)flag { +} + +- (BOOL)isCoupled { + return NO; +} + +// RBSplitSubviews are never flipped, unless they're RBSplitViews. +- (BOOL)isFlipped { + return NO; +} + +// We copy the opacity of the owning split view. +- (BOOL)isOpaque { + return [[self couplingSplitView] isOpaque]; +} + +// A hidden RBSplitSubview is not redrawn and is not considered for drawing dividers. +// This won't work before 10.3, though. +- (void)setHidden:(BOOL)flag { + if ([self isHidden]!=flag) { + RBSplitView* sv = [self splitView]; + [self RB___setHidden:flag]; + if (flag) { + [sv adjustSubviews]; + } else { + [sv adjustSubviewsExcepting:self]; + } + } +} + +// RBSplitSubviews can't be in the responder chain. +- (BOOL)acceptsFirstResponder { + return NO; +} + +// Mousing down should move the window only for a completely transparent background. This might have +// unintended side effects in metal windows, so for those you might want to use a background color +// with a very low alpha (0.01 for instance). +// This is commented out as I'm still experimenting with it. +/*- (BOOL)mouseDownCanMoveWindow { + RBSplitView* sv = [self asSplitView]; + if (!sv) { + sv = [self couplingSplitView]; + } + return [sv background]==nil; + return YES; +}*/ + +// This returns the owning splitview. It's guaranteed to return a RBSplitView or nil. +// You should avoid having "orphan" RBSplitSubviews, or at least manipulating +// them while they're not inserted in a RBSplitView. +- (RBSplitView*)splitView { + id result = [self superview]; + if ([result isKindOfClass:[RBSplitView class]]) { + return (RBSplitView*)result; + } + return nil; +} + +// This also returns the owning splitview. It's overridden for nested RBSplitViews. +- (RBSplitView*)couplingSplitView { + id result = [self superview]; + if ([result isKindOfClass:[RBSplitView class]]) { + return (RBSplitView*)result; + } + return nil; +} + +// This returns the outermost directly containing RBSplitView, or nil. +- (RBSplitView*)outermostSplitView { + id result = nil; + id sv = self; + while ((sv = [sv superview])&&[sv isKindOfClass:[RBSplitView class]]) { + result = sv; + } + return result; +} + +// This convenience method returns YES if the containing RBSplitView is horizontal. +- (BOOL)splitViewIsHorizontal { + return [[self splitView] isHorizontal]; +} + +// You can use either tags (ints) or identifiers (NSStrings) to identify individual subviews. +// We take care not to have nil identifiers. +- (void)setTag:(int)theTag { + tag = theTag; +} + +- (int)tag { + return tag; +} + +- (void)setIdentifier:(NSString*)aString { + [identifier autorelease]; + identifier = aString?[aString retain]:@""; +} + +- (NSString*)identifier { + return identifier; +} + +// If we have an identifier, this will make debugging a little easier by appending it to the +// default description. +- (NSString*)description { + return [identifier length]>0?[NSString stringWithFormat:@"%@(%@)",[super description],identifier]:[super description]; +} + +// This pair of methods allows you to get and change the position of a subview (within the split view); +// this counts from zero from the left or top of the split view. +- (unsigned)position { + RBSplitView* sv = [self splitView]; + return sv?[[sv subviews] indexOfObjectIdenticalTo:self]:0; +} + +- (void)setPosition:(unsigned)newPosition { + RBSplitView* sv = [self splitView]; + if (sv) { + [self retain]; + [self removeFromSuperviewWithoutNeedingDisplay]; + NSArray* subviews = [sv subviews]; + if (newPosition>=[subviews count]) { + [sv addSubview:self positioned:NSWindowAbove relativeTo:nil]; + } else { + [sv addSubview:self positioned:NSWindowBelow relativeTo:[subviews objectAtIndex:newPosition]]; + } + [self release]; + } +} + +// Tests whether the subview is collapsed. +- (BOOL)isCollapsed { + return [self RB___visibleDimension]<=0.0; +} + +// Tests whether the subview can shrink further. +- (BOOL)canShrink { + return [self RB___visibleDimension]>([self canCollapse]?0.0:minDimension); +} + +// Tests whether the subview can expand further. +- (BOOL)canExpand { + return [self RB___visibleDimension]collapsing?RBSSubviewCollapsing:RBSSubviewExpanding; + } + return [self RB___visibleDimension]<=0.0?RBSSubviewCollapsed:RBSSubviewNormal; +} + +// Tests whether the subview can be collapsed. The local instance variable will be overridden by the +// delegate method if it's implemented. +- (BOOL)canCollapse { + BOOL result = canCollapse; + RBSplitView* sv = [self splitView]; + if ([sv RB___numberOfSubviews]<2) { + return NO; + } + id delegate = [sv delegate]; + if ([delegate respondsToSelector:@selector(splitView:canCollapse:)]) { + result = [delegate splitView:sv canCollapse:self]; + } + return result; +} + +// This sets the subview's "canCollapse" flag. Ignored if the delegate's splitView:canCollapse: +// method is implemented. +- (void)setCanCollapse:(BOOL)flag { + canCollapse = flag; +} + +// This expands a collapsed subview and calls the delegate's splitView:didExpand: method, if it exists. +// This is not called internally by other methods; call this to expand a subview programmatically. +// As a convenience to other methods, it returns the subview's dimension after expanding (this may be +// off by 1 pixel due to rounding) or 0.0 if it couldn't be expanded. +// The delegate should not change the subview's frame. +- (float)expand { + return [self RB___expandAndSetToMinimum:NO]; +} + +// This collapses an expanded subview and calls the delegate's splitView:didCollapse: method, if it exists. +// This is not called internally by other methods; call this to expand a subview programmatically. +// As a convenience to other methods, it returns the negative of the subview's dimension before +// collapsing (or 0.0 if it couldn't be collapsed). +// The delegate should not change the subview's frame. +- (float)collapse { + return [self RB___collapse]; +} + +// This tries to collapse the subview with animation, and collapses it instantly if some other +// subview is animating. Returns YES if animation was started successfully. +- (BOOL)collapseWithAnimation { + return [self collapseWithAnimation:YES withResize:YES]; +} + +// This tries to expand the subview with animation, and expands it instantly if some other +// subview is animating. Returns YES if animation was started successfully. +- (BOOL)expandWithAnimation { + return [self expandWithAnimation:YES withResize:YES]; +} + +// These methods collapse and expand subviews with animation, depending on the parameters. +// They return YES if animation startup was successful. If resize is NO, the subview is +// collapsed/expanded without resizing it during animation. +- (BOOL)collapseWithAnimation:(BOOL)animate withResize:(BOOL)resize { + if ([self status]==RBSSubviewNormal) { + if ([self canCollapse]) { + if (animate&&[self RB___animationData:YES resize:resize]) { + [self RB___clearResponder]; + [self RB___stepAnimation]; + return YES; + } else { + [self RB___collapse]; + } + } + } + return NO; +} + +- (BOOL)expandWithAnimation:(BOOL)animate withResize:(BOOL)resize { + if ([self status]==RBSSubviewCollapsed) { + if (animate&&[self RB___animationData:YES resize:resize]) { + [self RB___stepAnimation]; + return YES; + } else { + [self RB___expandAndSetToMinimum:NO]; + } + } + return NO; +} + + +// These 3 methods get and set the view's minimum and maximum dimensions. +// The minimum dimension ought to be an integer at least equal to 1.0 but we make sure. +// The maximum dimension ought to be an integer at least equal to the minimum. As a convenience, +// pass in zero to set it to some huge number. +- (float)minDimension { + return minDimension; +} + +- (float)maxDimension { + return maxDimension; +} + +- (void)setMinDimension:(float)newMinDimension andMaxDimension:(float)newMaxDimension { + minDimension = MAX(1.0,floorf(newMinDimension)); + if (newMaxDimension<1.0) { + newMaxDimension = WAYOUT; + } + maxDimension = MAX(minDimension,floorf(newMaxDimension)); + float dim = [self dimension]; + if ((dimmaxDimension)) { + [[self splitView] setMustAdjust]; + } +} + +// This returns the subview's dimension. If it's collapsed, it returns the dimension it would have +// after expanding. +- (float)dimension { + float dim = [self RB___visibleDimension]; + if (dim<=0.0) { + dim = [[self splitView] RB___dimensionWithoutDividers]*fraction; + if (dimmaxDimension) { + dim = maxDimension; + } + } + return dim; +} + +// Sets the current dimension of the subview, subject to the current maximum and minimum. +// If the subview is collapsed, this will have an effect only after reexpanding. +- (void)setDimension:(float)value { + RBSplitView* sv = [self splitView]; + NSSize size = [self frame].size; + BOOL ishor = [sv isHorizontal]; + if (DIM(size)>0.0) { +// We're not collapsed, set the size and adjust other subviews. + DIM(size) = value; + [self setFrameSize:size]; + [sv adjustSubviewsExcepting:self]; + } else { +// We're collapsed, adjust the fraction so that we'll have the (approximately) correct +// dimension after expanding. + fraction = value/[sv RB___dimensionWithoutDividers]; + } +} + +// This just draws the background of a subview, then tells the delegate, if any. +// The delegate would usually draw a frame inside the subview. +- (void)drawRect:(NSRect)rect { + RBSplitView* sv = [self splitView]; + NSColor* bg = [sv background]; + if (bg) { + [bg set]; + NSRectFillUsingOperation(rect,NSCompositeSourceOver); + } + id del = [sv delegate]; + if ([del respondsToSelector:@selector(splitView:willDrawSubview:inRect:)]) { + [del splitView:sv willDrawSubview:self inRect:rect]; + } +} + +// We check if the RBSplitView must be adjusted before redisplaying programmatically. +// if so, we adjust and display the whole RBSplitView. +- (void)display { + RBSplitView* sv = [self splitView]; + if (sv) { + if ([sv mustAdjust]) { + [sv display]; + } else { + [super display]; + } + } +} + +// RBSplitSubviews will always resize their own subviews. +- (BOOL)autoresizesSubviews { + return YES; +} + +// This is method is called automatically when the subview is resized; don't call it yourself. +- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize { + RBSplitView* sv = [self splitView]; + if (sv) { + BOOL ishor = [sv isHorizontal]; + NSRect frame = [self frame]; + float dim = DIM(frame.size); + float other = OTHER(frame.size); +// We resize subviews only when we're inside the subview's limits and the containing splitview's limits. + animationData* anim = [self RB___animationData:NO resize:NO]; + if ((dim>=(anim&&!anim->resizing?anim->dimension:minDimension))&&(dim<=maxDimension)&&(other>=[sv minDimension])&&(other<=[sv maxDimension])) { + if (notInLimits) { +// The subviews can be resized, so we restore the saved size. + oldBoundsSize = savedSize; + } +// We save the size every time the subview's subviews are resized within the limits. + notInLimits = NO; + savedSize = frame.size; + [super resizeSubviewsWithOldSize:oldBoundsSize]; + } else { + notInLimits = YES; + } + } +} + +// This method is used internally when a divider is dragged. It tries to change the subview's dimension +// and returns the actual change, collapsing or expanding whenever possible. You usually won't need +// to call this directly. +- (float)changeDimensionBy:(float)increment mayCollapse:(BOOL)mayCollapse move:(BOOL)move { + RBSplitView* sv = [self splitView]; + if (!sv||(fabsf(increment)<1.0)) { + return 0.0; + } + BOOL ishor = [sv isHorizontal]; + NSRect frame = [self frame]; + float olddim = DIM(frame.size); + float newdim = MAX(0.0,olddim+increment); + if (newdimolddim) { + if (olddim<1.0) { +// Expand if needed. + if (newdim>(minDimension*(0.5+HYSTERESIS))) { + newdim = MAX(newdim,[self RB___expandAndSetToMinimum:YES]); + } else { + return 0.0; + } + } + if (newdim>maxDimension) { + newdim = maxDimension; + } + } + if ((int)newdim!=(int)olddim) { +// The dimension has changed. + increment = newdim-olddim; + DIM(frame.size) = newdim; + if (move) { + DIM(frame.origin) -= increment; + } +// We call super instead of self here to postpone adjusting subviews for nested splitviews. +// [super setFrameSize:frame.size]; + [super setFrame:frame]; + [sv RB___setMustClearFractions]; + [sv setMustAdjust]; + } + return newdim-olddim; +} + +// This convenience method returns the number of subviews (surprise!) +- (unsigned)numberOfSubviews { + return [[self subviews] count]; +} + +// We return the deepest subview that's hit by aPoint. We also check with the delegate if aPoint is +// within an alternate drag view. +- (NSView*)hitTest:(NSPoint)aPoint { + RBSplitView* sv = [self splitView]; + if ([self mouse:aPoint inRect:[self frame]]) { + id delegate = [sv delegate]; + if ([delegate respondsToSelector:@selector(splitView:dividerForPoint:inSubview:)]) { + actDivider = [delegate splitView:sv dividerForPoint:aPoint inSubview:self]; + if ((int)actDivider<(int)([sv RB___numberOfSubviews]-1)) { + return self; + } + } + actDivider = NSNotFound; + NSView* result = [super hitTest:aPoint]; + canDragWindow = ![result isOpaque]; + return result; + } + return nil; +} + +// This method handles clicking and dragging in an empty portion of the subview, or in an alternate +// drag view as designated by the delegate. +- (void)mouseDown:(NSEvent*)theEvent { + NSWindow* window = [self window]; + NSPoint where = [theEvent locationInWindow]; + if (actDivider=WAYOUT) { +// The subview was collapsed when encoded, so we correct the origin and collapse it. + BOOL ishor = [self splitViewIsHorizontal]; + previous.origin.x -= WAYOUT; + DIM(previous.size) = 0.0; + [self setFrameOrigin:previous.origin]; + [self setFrameSize:previous.size]; + } + previous = NSZeroRect; + if ([coder allowsKeyedCoding]) { + [self setIdentifier:[coder decodeObjectForKey:@"identifier"]]; + tag = [coder decodeIntForKey:@"tag"]; + minDimension = [coder decodeFloatForKey:@"minDimension"]; + maxDimension = [coder decodeFloatForKey:@"maxDimension"]; + fraction = [coder decodeDoubleForKey:@"fraction"]; + canCollapse = [coder decodeBoolForKey:@"canCollapse"]; + } else { + [self setIdentifier:[coder decodeObject]]; + [coder decodeValueOfObjCType:@encode(typeof(tag)) at:&tag]; + [coder decodeValueOfObjCType:@encode(typeof(minDimension)) at:&minDimension]; + [coder decodeValueOfObjCType:@encode(typeof(maxDimension)) at:&maxDimension]; + [coder decodeValueOfObjCType:@encode(typeof(fraction)) at:&fraction]; + [coder decodeValueOfObjCType:@encode(typeof(canCollapse)) at:&canCollapse]; + } + } + return self; +} + +@end + +@implementation RBSplitSubview (RB___SubviewAdditions) + +// This hides/shows the subview without calling adjustSubview. +- (void)RB___setHidden:(BOOL)flag { + [super setHidden:flag]; +} + +// This internal method returns the current animationData. It will always return nil if +// the receiver isn't the current owner and some other subview is already being animated. +// Otherwise, if the parameter is YES, a new animation will be started (or the current +// one will be restarted). +- (animationData*)RB___animationData:(BOOL)start resize:(BOOL)resize { + if (currentAnimation&&(currentAnimation->owner!=self)) { +// There already is an animation in progress on some other subview. + return nil; + } + if (start) { +// We want to start (or restart) an animation. + RBSplitView* sv = [self splitView]; + if (sv) { + float dim = [self dimension]; +// First assume the default time, then ask the delegate. + NSTimeInterval total = dim*(0.2/150.0); + id delegate = [sv delegate]; + if ([delegate respondsToSelector:@selector(splitView:willAnimateSubview:withDimension:)]) { + total = [delegate splitView:sv willAnimateSubview:self withDimension:dim]; + } +// No use animating anything shorter than the frametime. + if (total>FRAMETIME) { + if (!currentAnimation) { + currentAnimation = (animationData*)malloc(sizeof(animationData)); + } + if (currentAnimation) { + currentAnimation->owner = self; + currentAnimation->stepsDone = 0; + currentAnimation->elapsedTime = 0.0; + currentAnimation->dimension = dim; + currentAnimation->collapsing = ![self isCollapsed]; + currentAnimation->totalTime = total; + currentAnimation->finishTime = [NSDate timeIntervalSinceReferenceDate]+total; + currentAnimation->resizing = resize; + [sv RB___setDragging:YES]; + } + } else if (currentAnimation) { + free(currentAnimation); + currentAnimation = NULL; + } + } + } + return currentAnimation; +} + +// This internal method steps the animation to the next frame. +- (void)RB___stepAnimation { + NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; + animationData* anim = [self RB___animationData:NO resize:NO]; + if (anim) { + RBSplitView* sv = [self splitView]; + NSTimeInterval remain = anim->finishTime-now; + NSRect frame = [self frame]; + BOOL ishor = [sv isHorizontal]; +// Continuing animation only makes sense if we still have at least FRAMETIME available. + if (remain>=FRAMETIME) { + float dim = DIM(frame.size); + float avg = anim->elapsedTime; +// We try to keep a record of how long it takes, on the average, to resize and adjust +// one animation frame. + if (anim->stepsDone) { + avg /= anim->stepsDone; + } + NSTimeInterval delay = MIN(0.0,FRAMETIME-avg); +// We adjust the new dimension proportionally to how much of the designated time has passed. + dim = floorf(anim->dimension*(remain-avg)/anim->totalTime); + if (dim>4.0) { + if (!anim->collapsing) { + dim = anim->dimension-dim; + } + DIM(frame.size) = dim; + [self RB___setFrame:frame withFraction:0.0 notify:NO]; + [sv adjustSubviews]; + [self display]; + anim->elapsedTime += [NSDate timeIntervalSinceReferenceDate]-now; + ++anim->stepsDone; +// Schedule a timer to do the next animation step. + [self performSelector:@selector(RB___stepAnimation) withObject:nil afterDelay:delay inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,NSModalPanelRunLoopMode, + NSEventTrackingRunLoopMode,nil]]; + return; + } + } +// We're finished, either collapse or expand entirely now. + if (anim->collapsing) { + DIM(frame.size) = 0.0; + [self RB___finishCollapse:frame withFraction:anim->dimension/[sv RB___dimensionWithoutDividers]]; + } else { + float savemin,savemax; + float dim = [self RB___setMinAndMaxTo:anim->dimension savingMin:&savemin andMax:&savemax]; + DIM(frame.size) = dim; + [self RB___finishExpand:frame withFraction:0.0]; + minDimension = savemin; + maxDimension = savemax; + } + } +} + +// This internal method stops the animation, if the receiver is being animated. It will +// return YES if the animation was stopped. +- (BOOL)RB___stopAnimation { + if (currentAnimation&&(currentAnimation->owner==self)) { + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(RB___stepAnimation) object:nil]; + free(currentAnimation); + currentAnimation = NULL; + [[self splitView] RB___setDragging:NO]; + return YES; + } + return NO; +} + +// This internal method returns the actual visible dimension of the subview. Differs from -dimension in +// that it returns 0.0 if the subview is collapsed. +- (float)RB___visibleDimension { + BOOL ishor = [self splitViewIsHorizontal]; + NSRect frame = [self frame]; + return MAX(0.0,DIM(frame.size)); +} + +// This pair of internal methods is used only inside -[RBSplitView adjustSubviews] to copy subview data +// from and to that method's internal cache. +- (void)RB___copyIntoCache:(subviewCache*)cache { + cache->sub = self; + cache->rect = [self frame]; + cache->size = [self RB___visibleDimension]; + cache->fraction = fraction; + cache->constrain = NO; +} + +- (void)RB___updateFromCache:(subviewCache*)cache withTotalDimension:(float)value { + float dim = [self RB___visibleDimension]; + if (cache->size>=1.0) { +// New state is not collapsed. + if (dim>=1.0) { +// Old state was not collapsed, so we just change the frame. + [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES]; + } else { +// Old state was collapsed, so we expand it. + [self RB___finishExpand:cache->rect withFraction:cache->fraction]; + } + } else { +// New state is collapsed. + if (dim>=1.0) { +// Old state was not collapsed, so we clear first responder and change the frame. + [self RB___clearResponder]; + [self RB___finishCollapse:cache->rect withFraction:dim/value]; + } else { +// It was collapsed already, but the frame may have changed, so we set it. + [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES]; + } + } +} + +// This internal method sets minimum and maximum values to the same value, saves the old values, +// and returns the new value (which will be limited to the old values). +- (float)RB___setMinAndMaxTo:(float)value savingMin:(float*)oldmin andMax:(float*)oldmax { + *oldmin = [self minDimension]; + *oldmax = [self maxDimension]; + if (value<*oldmin) { + value = *oldmin; + } + if (value>*oldmax) { + value = *oldmax; + } + minDimension = maxDimension = value; + return value; +} + +// This internal method tries to clear the first responder, if the current responder is a descendant of +// the receiving subview. If so, it will set first responder to nil, redisplay the former responder and +// return YES. Returns NO otherwise. +- (BOOL)RB___clearResponder { + NSWindow* window = [self window]; + if (window) { + NSView* responder = (NSView*)[window firstResponder]; + if (responder&&[responder respondsToSelector:@selector(isDescendantOf:)]) { + if ([responder isDescendantOf:self]) { + if ([window makeFirstResponder:nil]) { + [responder display]; + return YES; + } + } + } + } + return NO; +} + +// This internal method collapses a subview. +// It returns the negative of the size of the subview before collapsing, or 0.0 if it wasn't collapsed. +- (float)RB___collapse { + float result = 0.0; + if (![self isCollapsed]) { + RBSplitView* sv = [self splitView]; + if (sv&&[self canCollapse]) { + [self RB___clearResponder]; + NSRect frame = [self frame]; + BOOL ishor = [sv isHorizontal]; + result = DIM(frame.size); +// For collapsed views, fraction will contain the fraction of the dimension previously occupied + DIM(frame.size) = 0.0; + [self RB___finishCollapse:frame withFraction:result/[sv RB___dimensionWithoutDividers]]; + } + } + return -result; +} + +// This internal method finishes the collapse of a subview, stopping the animation if +// there is one, and calling the delegate method if there is one. +- (void)RB___finishCollapse:(NSRect)rect withFraction:(double)value { + RBSplitView* sv = [self splitView]; + BOOL finish = [self RB___stopAnimation]; + [self RB___setFrame:rect withFraction:value notify:YES]; + [sv RB___setMustClearFractions]; + if (finish) { + [self display]; + } + id delegate = [sv delegate]; + if ([delegate respondsToSelector:@selector(splitView:didCollapse:)]) { + [delegate splitView:sv didCollapse:self]; + } +} + +// This internal method expands a subview. setToMinimum will usually be YES during a divider drag. +// It returns the size of the subview after expanding, or 0.0 if it wasn't expanded. +- (float)RB___expandAndSetToMinimum:(BOOL)setToMinimum { + float result = 0.0; + RBSplitView* sv = [self splitView]; + if (sv&&[self isCollapsed]) { + NSRect frame = [super frame]; + double frac = fraction; + BOOL ishor = [sv isHorizontal]; + if (setToMinimum) { + result = DIM(frame.size) = minDimension; + } else { + result = [sv RB___dimensionWithoutDividers]*frac; +// We need to apply a compensation factor for proportional resizing in adjustSubviews. + float newdim = floorf((frac>=1.0)?result:result/(1.0-frac)); + DIM(frame.size) = newdim; + result = floorf(result); + } + [self RB___finishExpand:frame withFraction:0.0]; + } + return result; +} + +// This internal method finishes the the expansion of a subview, stopping the animation if +// there is one, and calling the delegate method if there is one. +- (void)RB___finishExpand:(NSRect)rect withFraction:(double)value { + RBSplitView* sv = [self splitView]; + BOOL finish = [self RB___stopAnimation]; + [self RB___setFrame:rect withFraction:value notify:YES]; + [sv RB___setMustClearFractions]; + if (finish) { + [self display]; + } + id delegate = [sv delegate]; + if ([delegate respondsToSelector:@selector(splitView:didExpand:)]) { + [delegate splitView:sv didExpand:self]; + } +} + +// These internal methods set the subview's frame or size, and also store a fraction value +// which is used to ensure repeatability when the whole split view is resized. +- (void)RB___setFrame:(NSRect)rect withFraction:(double)value notify:(BOOL)notify { + RBSplitView* sv = [self splitView]; + id delegate = nil; + if (notify) { + delegate = [sv delegate]; +// If the delegate method isn't implemented, we ignore the delegate altogether. + if ([delegate respondsToSelector:@selector(splitView:changedFrameOfSubview:from:to:)]) { +// If the rects are equal, the delegate isn't called. + if (NSEqualRects(previous,rect)) { + delegate = nil; + } + } else { + delegate = nil; + } + } + [sv setMustAdjust]; + [self setFrame:rect]; + fraction = value; + [delegate splitView:sv changedFrameOfSubview:self from:previous to:rect]; + previous = delegate?rect:NSZeroRect; +} + +- (void)RB___setFrameSize:(NSSize)size withFraction:(double)value { + [[self splitView] setMustAdjust]; + [self setFrameSize:size]; + fraction = value; +} + +// This internal method gets the fraction value. +- (double)RB___fraction { + return fraction; +} + +@end + diff --git a/RBSplitView.h b/RBSplitView.h new file mode 100644 index 0000000..0c786c2 --- /dev/null +++ b/RBSplitView.h @@ -0,0 +1,225 @@ +// +// RBSplitView.h version 1.1.4 +// RBSplitView +// +// Created by Rainer Brockerhoff on 24/09/2004. +// Copyright 2004-2006 Rainer Brockerhoff. +// Some Rights Reserved under the Creative Commons Attribution License, version 2.5, and/or the MIT License. +// + +#import "RBSplitSubview.h" + +// These values are used to handle the various cursor types. +typedef enum { + RBSVHorizontalCursor=0, // appears over horizontal dividers + RBSVVerticalCursor, // appears over vertical dividers + RBSV2WayCursor, // appears over two-way thumbs + RBSVDragCursor, // appears while dragging + RBSVCursorTypeCount +} RBSVCursorType; + +@interface RBSplitView : RBSplitSubview { +// Subclasses normally should use setter methods instead of changing instance variables by assignment. +// Most getter methods simply return the corresponding instance variable, so with some care, subclasses +// could reference them directly. + IBOutlet id delegate; // The delegate (may be nil). + NSString* autosaveName; // This name is used for storing subview proportions in user defaults. + NSColor* background; // The color used to paint the view's background (may be nil). + NSImage* divider; // The image used for the divider "dimple". + NSRect* dividers; // A C array of NSRects, one for each divider. + float dividerThickness; // Actual divider width; should be an integer and at least 1.0. + BOOL mustAdjust; // Set internally if the subviews need to be adjusted. + BOOL mustClearFractions; // Set internally if fractions should be cleared before adjusting. + BOOL isHorizontal; // The divider's orientation; default is vertical. + BOOL canSaveState; // Set internally to allow saving subview state. + BOOL isCoupled; // If YES, take some parameters from the containing RBSplitView, if any. + BOOL isAdjusting; // Set internally while the subviews are being adjusted. + BOOL isDragging; // Set internally while in a drag loop. + BOOL isInScrollView; // Set internally if directly contained in an NSScrollView. +} + +// These class methods get and set the cursor used for each type. +// Pass in nil to reset to the default cursor for that type. ++ (NSCursor*)cursor:(RBSVCursorType)type; ++ (void)setCursor:(RBSVCursorType)type toCursor:(NSCursor*)cursor; + +// This class method clears the saved state for a given autosave name from the defaults. ++ (void)removeStateUsingName:(NSString*)name; + +// This class method returns the actual key used to store autosave data in the defaults. ++ (NSString*)defaultsKeyForName:(NSString*)name isHorizontal:(BOOL)orientation; + +// Sets and gets the autosaveName; this will be the key used to store the subviews' proportions +// in the user defaults. Default is @"", which doesn't save anything. Set flag to YES to set +// unique names for nested subviews. You are responsible for avoiding duplicates. +- (void)setAutosaveName:(NSString*)aString recursively:(BOOL)flag; +- (NSString*)autosaveName; + +// Saves the current state of the subviews if there's a valid autosave name set. If the argument +// is YES, it's then also called recursively for nested RBSplitViews. Returns YES if successful. +- (BOOL)saveState:(BOOL)recurse; + +// Restores the saved state of the subviews if there's a valid autosave name set. If the argument +// is YES, it's first called recursively for nested RBSplitViews. Returns YES if successful. +// You need to call adjustSubviews after calling this. +- (BOOL)restoreState:(BOOL)recurse; + +// Returns a string encoding the current state of all direct subviews. Does not check for nesting. +- (NSString*)stringWithSavedState; + +// Readjusts all direct subviews according to the encoded string parameter. The number of subviews +// must match. Returns YES if successful. Does not check for nesting. +- (BOOL)setStateFromString:(NSString*)aString; + +// Returns an array with complete state information for the receiver and all subviews, taking +// nesting into account. Don't store this array in a file, as its format might change in the +// future; this is for taking a state snapshot and later restoring it with setStatesFromArray. +- (NSArray*)arrayWithStates; + +// Restores the state of the receiver and all subviews. The array must have been produced by a +// previous call to arrayWithStates. Returns YES if successful. This will fail if you have +// added or removed subviews in the meantime! +// You need to call adjustSubviews after calling this. +- (BOOL)setStatesFromArray:(NSArray*)array; + +// This is the designated initializer for creating RBSplitViews programmatically. +- (id)initWithFrame:(NSRect)frame; + +// This convenience initializer adds any number of subviews and adjusts them proportionally. +- (id)initWithFrame:(NSRect)frame andSubviews:(unsigned)count; + +// Sets and gets the delegate. (Delegates aren't retained.) See further down for delegate methods. +- (void)setDelegate:(id)anObject; +- (id)delegate; + +// Returns a subview which has a certain identifier string, or nil if there's none +- (RBSplitSubview*)subviewWithIdentifier:(NSString*)anIdentifier; + +// Returns the subview at a certain position. Returns nil if the position is invalid. +- (RBSplitSubview*)subviewAtPosition:(unsigned)position; + +// Adds a subview at a certain position. +- (void)addSubview:(NSView*)aView atPosition:(unsigned)position; + +// Sets and gets the divider thickness, which should be a positive integer or zero. +// Setting the divider image also resets this automatically, so you would call this +// only if you want the divider to be larger or smaller than the image. Zero means that +// the image dimensions will be used. +- (void)setDividerThickness:(float)thickness; +- (float)dividerThickness; + +// Sets and gets the divider image. The default image can also be set in Interface Builder, so usually +// there's no need to call this. Passing in nil means that the default divider thickness will be zero, +// and no mouse events will be processed, so that the dividers can be moved only programmatically. +- (void)setDivider:(NSImage*)image; +- (NSImage*)divider; + +// Sets and gets the view background. The default is nil, meaning no background is +// drawn and the view and its subviews are considered transparent. +- (void)setBackground:(NSColor*)color; +- (NSColor*)background; + +// Sets and gets the orientation. This uses the same convention as NSSplitView: vertical means the +// dividers are vertical, but the subviews are in a horizontal row. Sort of counter-intuitive, yes. +- (void)setVertical:(BOOL)flag; +- (BOOL)isVertical; +- (BOOL)isHorizontal; + +// Call this to force adjusting the subviews before display. Called automatically if anything +// relevant is changed. +- (void)setMustAdjust; + +// Returns YES if there's a pending adjustment. +- (BOOL)mustAdjust; + +// Returns YES if we're in a dragging loop. +- (BOOL)isDragging; + +// Returns YES if the view is directly contained in an NSScrollView. +- (BOOL)isInScrollView; + +// Call this to recalculate all subview dimensions. Normally this is done automatically whenever +// something relevant is changed, so you rarely will need to call this explicitly. +- (void)adjustSubviews; + +// This method should be called only from within the splitView:wasResizedFrom:to: delegate method +// to keep some specific subview the same size. +- (void)adjustSubviewsExcepting:(RBSplitSubview*)excepting; + +// This method draws dividers. You should never call it directly but you can override it when +// subclassing, if you need custom dividers. +- (void)drawDivider:(NSImage*)anImage inRect:(NSRect)rect betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing; + +@end + +// The following methods are optionally implemented by the delegate. + +@interface NSObject(RBSplitViewDelegate) + +// The delegate can override a subview's ability to collapse by implementing this method. +// Return YES to allow collapsing. If this is implemented, the subviews' built-in +// 'collapsed' flags are ignored. +- (BOOL)splitView:(RBSplitView*)sender canCollapse:(RBSplitSubview*)subview; + +// The delegate can alter the divider's appearance by implementing this method. +// Before calling this, the divider is filled with the background, and afterwards +// the divider image is drawn into the returned rect. If imageRect is empty, no +// divider image will be drawn, because there are nested RBSplitViews. Return +// NSZeroRect to suppress the divider image. Return imageRect to use the default +// location for the image, or change its origin to place the image elsewhere. +// You could also draw the divider yourself at this point and return NSZeroRect. +- (NSRect)splitView:(RBSplitView*)sender willDrawDividerInRect:(NSRect)dividerRect betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing withProposedRect:(NSRect)imageRect; + +// These methods are called after a subview is completely collapsed or expanded. adjustSubviews may or may not +// have been called, however. +- (void)splitView:(RBSplitView*)sender didCollapse:(RBSplitSubview*)subview; +- (void)splitView:(RBSplitView*)sender didExpand:(RBSplitSubview*)subview; + +// These methods are called just before and after adjusting subviews. +- (void)willAdjustSubviews:(RBSplitView*)sender; +- (void)didAdjustSubviews:(RBSplitView*)sender; + +// This method will be called after a RBSplitView is resized with setFrameSize: but before +// adjustSubviews is called on it. +- (void)splitView:(RBSplitView*)sender wasResizedFrom:(float)oldDimension to:(float)newDimension; + +// This method will be called when a divider is double-clicked and both leading and trailing +// subviews can be collapsed. Return either of the parameters to collapse that subview, or nil +// to collapse neither. If not implemented, the smaller subview will be collapsed. +- (RBSplitSubview*)splitView:(RBSplitView*)sender collapseLeading:(RBSplitSubview*)leading orTrailing:(RBSplitSubview*)trailing; + +// This method will be called when a cursor rect is being set (inside resetCursorRects). The +// proposed rect is passed in. Return the actual rect, or NSZeroRect to suppress cursor setting +// for this divider. This won't be called for two-axis thumbs, however. The rects are in +// sender's local coordinates. +- (NSRect)splitView:(RBSplitView*)sender cursorRect:(NSRect)rect forDivider:(unsigned int)divider; + +// This method will be called whenever a mouse-down event is received in a divider. Return YES to have +// the event handled by the split view, NO if you wish to ignore it or handle it in the delegate. +- (BOOL)splitView:(RBSplitView*)sender shouldHandleEvent:(NSEvent*)theEvent inDivider:(unsigned int)divider betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing; + +// This method will be called just before a subview will be collapsed or expanded with animation. +// Return the approximate time the animation should take, or 0.0 to disallow animation. +// If not implemented, it will use the default of 0.2 seconds per 150 pixels. +- (NSTimeInterval)splitView:(RBSplitView*)sender willAnimateSubview:(RBSplitSubview*)subview withDimension:(float)dimension; + +// This method will be called whenever a subview's frame is changed, usually from inside adjustSubviews' final loop. +// You'd normally use this to move some auxiliary view to keep it aligned with the subview. +- (void)splitView:(RBSplitView*)sender changedFrameOfSubview:(RBSplitSubview*)subview from:(NSRect)fromRect to:(NSRect)toRect; + +// This method is called whenever the event handlers want to check if some point within the RBSplitSubview +// should act as an alternate drag view. Usually, the delegate will check the point (which is in sender's +// local coordinates) against the frame of one or several auxiliary views, and return a valid divider number. +// Returning NSNotFound means the point is not valid. +- (unsigned int)splitView:(RBSplitView*)sender dividerForPoint:(NSPoint)point inSubview:(RBSplitSubview*)subview; + +// This method is called continuously while a divider is dragged, just before the leading subview is resized. +// Return NO to resize the trailing view by the same amount, YES to resize the containing window by the same amount. +- (BOOL)splitView:(RBSplitView*)sender shouldResizeWindowForDivider:(unsigned int)divider betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing willGrow:(BOOL)grow; + +// This method is called by each subview's drawRect: method, just after filling it with the background color but +// before the contained subviews are drawn. Usually you would use this to draw a frame inside the subview. +- (void)splitView:(RBSplitView*)sender willDrawSubview:(RBSplitSubview*)subview inRect:(NSRect)rect; + +@end + diff --git a/RBSplitView.m b/RBSplitView.m new file mode 100644 index 0000000..d5de6f2 --- /dev/null +++ b/RBSplitView.m @@ -0,0 +1,1734 @@ +// +// RBSplitView.m version 1.1.4 +// RBSplitView +// +// Created by Rainer Brockerhoff on 24/09/2004. +// Copyright 2004-2006 Rainer Brockerhoff. +// Some Rights Reserved under the Creative Commons Attribution License, version 2.5, and/or the MIT License. +// + +#import +#import + +// Please don't remove this copyright notice! +static const unsigned char RBSplitView_Copyright[] __attribute__ ((used)) = + "RBSplitView 1.1.4 Copyright(c)2004-2006 by Rainer Brockerhoff ."; + +// This vector keeps currently used cursors. nil means the default cursor. +static NSCursor* cursors[RBSVCursorTypeCount] = {nil}; + +// Our own fMIN and fMAX +static inline float fMIN(float a,float b) { + return ab?a:b; +} + +@implementation RBSplitView + +// These class methods get and set the cursor used for each type. +// Pass in nil to reset to the default cursor for that type. ++ (NSCursor*)cursor:(RBSVCursorType)type { + if ((type>=0)&&(type=0)&&(type1)&&([[parts objectAtIndex:0] intValue]==subcount)&&(k==subcount)) { + int i; + NSRect frame = [self frame]; + BOOL ishor = [self isHorizontal]; + for (i=0;i