//
// TagsView.m
// Perforce
//
// Created by Adam Czubernat on 28/11/2013.
// Copyright (c) 2013 Perforce Software, Inc. All rights reserved.
//
#import "TagsView.h"
NSString * const P4PasteboardTypeTag = @"P4PasteboardTypeTag";
@interface TagsView () <NSTextFieldDelegate> {
__weak P4Item *item;
NSArray *tags;
NSMutableArray *tagViews;
NSView *hoveredTagView;
NSPopover *popover;
BOOL allowsAdding;
BOOL allowsRemoving;
CGSize spacing;
NSEdgeInsets inset;
__weak IBOutlet NSView *mainView;
IBOutlet NSView *prototypeView;
__weak IBOutlet NSTextField *prototypeTitle;
IBOutlet NSButton *removeButton;
IBOutlet NSView *newButtonView;
IBOutlet NSView *addView;
__weak IBOutlet NSTextField *textfield;
__weak IBOutlet NSButton *addButton;
__weak IBOutlet NSProgressIndicator *indicator;
}
- (void)initialize;
- (void)layoutTags;
- (void)addTagWithTitle:(NSString *)title;
- (void)showTagHover:(NSView *)tagView;
- (void)hideTagHover;
- (void)setHover:(BOOL)hover tag:(NSView *)tagView;
- (IBAction)newButtonPressed:(id)sender;
- (IBAction)removeButtonPressed:(id)sender;
- (IBAction)addButtonPressed:(id)sender;
@end
@implementation TagsView
- (id)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
static NSNib *nib;
if (!nib)
nib = [[NSNib alloc] initWithNibNamed:
NSStringFromClass([self class]) bundle:nil];
NSArray *objects;
if ([nib instantiateNibWithOwner:self topLevelObjects:&objects]) {
[self setSubviews:[mainView.subviews copy]];
[self initialize];
}
}
return self;
}
- (void)initialize {
prototypeTitle.textColor = [NSUserDefaults colorForKey:kColorBackgroundTagDark];
spacing = (NSSize) { 3.0f, 8.0f };
inset = (NSEdgeInsets) { 0.0f, 0.0f, spacing.height, 0.0f };
allowsAdding = YES;
allowsRemoving = YES;
[prototypeView removeFromSuperview];
[removeButton removeFromSuperview];
[newButtonView removeFromSuperview];
}
#pragma mark - Public
- (void)setItem:(P4Item *)anItem {
item = anItem;
tags = item.tags;
if (self.superview)
[self layoutTags];
}
- (void)setTags:(NSArray *)aTags {
tags = aTags;
if (self.superview)
[self layoutTags];
}
- (void)setAllowsAdding:(BOOL)allow {
allowsAdding = allow;
}
- (void)setAllowsRemoving:(BOOL)allow {
allowsRemoving = allow;
}
- (NSEdgeInsets)contentInsets {
return inset;
}
- (void)setContentInsets:(NSEdgeInsets)contentInsets {
inset = contentInsets;
[self layoutTags];
}
- (NSSize)spacing {
return spacing;
}
- (void)setSpacing:(NSSize)aSpacing {
spacing = aSpacing;
[self layoutTags];
}
#pragma mark - Overrides
- (void)viewWillMoveToSuperview:(NSView *)newSuperview {
[super viewWillMoveToSuperview:newSuperview];
if (tags)
[self layoutTags];
}
- (void)mouseEntered:(NSEvent *)event {
NSInteger idx = [(NSNumber *)event.userData integerValue];
NSView *view = [tagViews objectAtIndex:idx];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:@selector(showTagHover:) withObject:view afterDelay:0.5f];
}
- (void)mouseExited:(NSEvent *)event {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self hideTagHover];
}
#pragma mark - Private
- (void)layoutTags {
[tagViews makeObjectsPerformSelector:@selector(removeFromSuperview)];
tagViews = [NSMutableArray arrayWithCapacity:tags.count];
// Color pending tags
if ([item.metadata objectForKey:@"openattr-tags"]) {
[prototypeTitle setTextColor:[NSUserDefaults colorForKey:kColorStatusCheckedOut]];
}
CGRect frame = prototypeView.frame;
frame.origin = (CGPoint) { inset.left,
self.frame.size.height - frame.size.height - inset.top };
prototypeView.frame = frame;
for (NSString *tag in tags)
[self addTagWithTitle:tag];
frame = newButtonView.frame;
frame.origin = prototypeView.frame.origin;
if (allowsAdding) {
newButtonView.frame = frame;
[self addSubview:newButtonView];
[tagViews addObject:newButtonView];
}
CGFloat bottom = frame.origin.y - inset.bottom;
CGRect bounds = self.frame;
bounds.origin.y += bottom;
bounds.size.height -= bottom;
self.frame = bounds;
}
- (void)addTagWithTitle:(NSString *)title {
NSInteger idx = [tagViews count];
CGRect frame = prototypeView.frame;
prototypeTitle.stringValue = title;
CGSize size = [prototypeTitle.attributedStringValue size];
frame.size.width = 8.0f + ceilf(size.width) + 8.0f;
CGFloat rightMargin = inset.right + 20.0f;
if (allowsAdding)
rightMargin += newButtonView.frame.size.width;
if (CGRectGetMaxX(frame) > self.frame.size.width - rightMargin) {
frame.origin.x = inset.left;
frame.origin.y -= frame.size.height + spacing.height;
}
prototypeView.frame = frame;
NSData *archivedView = [NSKeyedArchiver archivedDataWithRootObject:prototypeView];
NSView *copy = [NSKeyedUnarchiver unarchiveObjectWithData:archivedView];
[self addSubview:copy];
[tagViews addObject:copy];
frame = prototypeView.frame;
frame.origin.x += frame.size.width + spacing.width;
prototypeView.frame = frame;
if (!allowsRemoving)
return;
NSTrackingArea *trackingArea;
trackingArea = [[NSTrackingArea alloc]
initWithRect:CGRectZero
options:NSTrackingInVisibleRect |
NSTrackingMouseEnteredAndExited |
NSTrackingActiveInKeyWindow
owner:self
userInfo:(id)@(idx)];
[copy addTrackingArea:trackingArea];
}
- (void)mouseDragged:(NSEvent *)theEvent {
CGPoint point = [self convertPoint:theEvent.locationInWindow fromView:nil];
NSString *tag;
NSView *view = nil;
for (NSInteger idx = 0; idx < tagViews.count; idx++) {
NSView *tagView = [tagViews objectAtIndex:idx];
if (CGRectContainsPoint(tagView.frame, point)) {
view = tagView;
tag = [tags objectAtIndex:idx];
break;
}
}
if (!view)
return;
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
[pboard declareTypes:@[ P4PasteboardTypeTag ] owner:self];
[pboard setString:tag forType:P4PasteboardTypeTag];
NSBitmapImageRep *bitmap = [view bitmapImageRepForCachingDisplayInRect:view.bounds];
[view cacheDisplayInRect:view.bounds toBitmapImageRep:bitmap];
NSImage *image = [[NSImage alloc] init];
[image addRepresentation:bitmap];
// Making image appear exactly on subview position
// CGPoint offset = [self convertPoint:point toView:view];
// point.x -= offset.x;
// point.y -= offset.y;
[self dragImage:image
at:point
offset:CGSizeZero
event:theEvent
pasteboard:pboard
source:self
slideBack:YES];
}
- (void)showTagHover:(NSView *)tag {
if (hoveredTagView)
[self hideTagHover];
else
[self setHover:YES tag:tag];
}
- (void)hideTagHover {
if (!hoveredTagView)
return;
[self setHover:NO tag:hoveredTagView];
}
- (void)setHover:(BOOL)hover tag:(NSView *)tag {
NSInteger idx = [tagViews indexOfObjectIdenticalTo:tag];
if (hover) {
CGRect removeFrame = removeButton.frame;
removeFrame.origin.x = tag.frame.size.width + 20.0f - removeFrame.size.width;
removeButton.frame = removeFrame;
[tag addSubview:removeButton];
} else {
hoveredTagView = nil;
[removeButton removeFromSuperview];
}
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.1];
[[NSAnimationContext currentContext] setCompletionHandler:^{
if (hover)
hoveredTagView = tag;
}];
CGRect frame = tag.frame;
frame.size.width += hover ? 20.0f : -20.0f;
[tag.animator setFrame:frame];
for (NSView *next;
(++idx < tagViews.count && (next = [tagViews objectAtIndex:idx]) &&
next.frame.origin.y == tag.frame.origin.y);) {
CGRect frame = next.frame;
frame.origin.x += hover ? 20.0f : -20.0f;
[next.animator setFrame:frame];
}
[NSAnimationContext endGrouping];
}
- (void)controlTextDidChange:(NSNotification *)obj {
NSString *text = textfield.stringValue;
if (text.length >= 3 &&
[text rangeOfCharacterFromSet:
[NSCharacterSet whitespaceCharacterSet]].location == NSNotFound)
[addButton setEnabled:YES];
else
[addButton setEnabled:NO];
}
- (IBAction)newButtonPressed:(id)sender {
NSViewController *controller = [[NSViewController alloc] init];
[controller setView:addView];
popover = [[NSPopover alloc] init];
[popover setBehavior:NSPopoverBehaviorSemitransient];
[popover setContentViewController:controller];
[popover showRelativeToRect:[sender frame]
ofView:[sender superview] preferredEdge:NSMinXEdge];
textfield.stringValue = @"";
[addButton setEnabled:NO];
}
- (IBAction)removeButtonPressed:(id)sender {
if (!hoveredTagView)
return;
NSInteger idx = [tagViews indexOfObjectIdenticalTo:hoveredTagView];
NSString *tag = [tags objectAtIndex:idx];
[item
performAction:@selector(removeTag:)
object:tag
delegate:[self.window windowController]];
}
- (IBAction)addButtonPressed:(id)sender {
[popover close];
// [indicator startAnimation:nil];
// [addButton setHidden:YES];
// [textfield setEditable:NO];
// [textfield setSelectable:NO];
[item
performAction:@selector(addTag:)
object:textfield.stringValue
delegate:[self.window windowController]];
// [popover setBehavior:NSPopoverBehaviorApplicationDefined];
// NSWindow *popoverWindow = popover.contentViewController.view.window;
// [NSApp runModalForWindow:popoverWindow];
}
//- (void)action:(id)action didFinish:(NSArray *)response error:(NSError *)error {
// [popover close];
// popover = nil;
// [NSApp stopModal];
//
// [addButton setEnabled:NO];
// [textfield setEditable:YES];
//
// if (error) {
// NSAlert *alert = [NSAlert alertWithError:error];
// [alert setMessageText:@"\nAction failed"];
// [alert setInformativeText:error.localizedDescription];
// [alert setIcon:[[NSImage alloc] init]];
// [alert
// beginSheetModalForWindow:self.window
// modalDelegate:nil
// didEndSelector:nil
// contextInfo:NULL];
// return;
// }
//}
@end