Blob Blame History Raw
/*
 RDP Touch Pointer View

 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz

 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
 If a copy of the MPL was not distributed with this file, You can obtain one at
 http://mozilla.org/MPL/2.0/.
 */

#import "TouchPointerView.h"
#import "Utils.h"

#define RESET_DEFAULT_POINTER_IMAGE_DELAY 0.15

#define POINTER_ACTION_CURSOR 0
#define POINTER_ACTION_CLOSE 3
#define POINTER_ACTION_RCLICK 2
#define POINTER_ACTION_LCLICK 4
#define POINTER_ACTION_MOVE 4
#define POINTER_ACTION_SCROLL 5
#define POINTER_ACTION_KEYBOARD 7
#define POINTER_ACTION_EXTKEYBOARD 8
#define POINTER_ACTION_RESET 6

@interface TouchPointerView (Private)
- (void)setCurrentPointerImage:(UIImage *)image;
- (void)displayPointerActionImage:(UIImage *)image;
- (BOOL)pointInsidePointer:(CGPoint)point;
- (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point;
- (CGPoint)getCursorPosition;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
- (void)handleSingleTap:(UITapGestureRecognizer *)gesture;
- (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick;
@end

@implementation TouchPointerView

@synthesize delegate = _delegate;

- (void)awakeFromNib
{
	[super awakeFromNib];

	// set content mode when rotating (keep aspect ratio)
	[self setContentMode:UIViewContentModeTopLeft];

	// load touchPointerImage
	_default_pointer_img = [[UIImage
	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_default"
	                                                            ofType:@"png"]] retain];
	_active_pointer_img = [[UIImage
	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_active"
	                                                            ofType:@"png"]] retain];
	_lclick_pointer_img = [[UIImage
	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_lclick"
	                                                            ofType:@"png"]] retain];
	_rclick_pointer_img = [[UIImage
	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_rclick"
	                                                            ofType:@"png"]] retain];
	_scroll_pointer_img = [[UIImage
	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_scroll"
	                                                            ofType:@"png"]] retain];
	_extkeyboard_pointer_img = [[UIImage
	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_ext_keyboard"
	                                                            ofType:@"png"]] retain];
	_keyboard_pointer_img = [[UIImage
	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_keyboard"
	                                                            ofType:@"png"]] retain];
	_reset_pointer_img = [[UIImage
	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_reset"
	                                                            ofType:@"png"]] retain];
	_cur_pointer_img = _default_pointer_img;
	_pointer_transformation = CGAffineTransformMake(1, 0, 0, 1, 0, 0);

	// init flags
	_pointer_moving = NO;
	_pointer_scrolling = NO;

	// create areas array
	int i, j;
	CGFloat area_width = [_cur_pointer_img size].width / 3.0f;
	CGFloat area_height = [_cur_pointer_img size].height / 3.0f;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			_pointer_areas[j + i * 3] =
			    CGRectMake(j * area_width, i * area_height, area_width, area_height);
		}
	}

	// init gesture recognizers
	UITapGestureRecognizer *singleTapRecognizer =
	    [[[UITapGestureRecognizer alloc] initWithTarget:self
	                                             action:@selector(handleSingleTap:)] autorelease];
	[singleTapRecognizer setNumberOfTouchesRequired:1];
	[singleTapRecognizer setNumberOfTapsRequired:1];

	UILongPressGestureRecognizer *dragDropRecognizer = [[[UILongPressGestureRecognizer alloc]
	    initWithTarget:self
	            action:@selector(handleDragDrop:)] autorelease];
	dragDropRecognizer.minimumPressDuration = 0.4;
	//        dragDropRecognizer.allowableMovement = 1000.0;

	UILongPressGestureRecognizer *pointerMoveScrollRecognizer =
	    [[[UILongPressGestureRecognizer alloc] initWithTarget:self
	                                                   action:@selector(handlePointerMoveScroll:)]
	        autorelease];
	pointerMoveScrollRecognizer.minimumPressDuration = 0.15;
	pointerMoveScrollRecognizer.allowableMovement = 1000.0;
	[pointerMoveScrollRecognizer requireGestureRecognizerToFail:dragDropRecognizer];

	[self addGestureRecognizer:singleTapRecognizer];
	[self addGestureRecognizer:dragDropRecognizer];
	[self addGestureRecognizer:pointerMoveScrollRecognizer];
}

- (void)dealloc
{
	[super dealloc];
	[_default_pointer_img autorelease];
	[_active_pointer_img autorelease];
	[_lclick_pointer_img autorelease];
	[_rclick_pointer_img autorelease];
	[_scroll_pointer_img autorelease];
	[_extkeyboard_pointer_img autorelease];
	[_keyboard_pointer_img autorelease];
	[_reset_pointer_img autorelease];
}

#pragma mark - Public interface

// positions the pointer on screen if it got offscreen after an orentation change
- (void)ensurePointerIsVisible
{
	CGRect bounds = [self bounds];
	if (_pointer_transformation.tx > (bounds.size.width - _cur_pointer_img.size.width))
		_pointer_transformation.tx = bounds.size.width - _cur_pointer_img.size.width;
	if (_pointer_transformation.ty > (bounds.size.height - _cur_pointer_img.size.height))
		_pointer_transformation.ty = bounds.size.height - _cur_pointer_img.size.height;
	[self setNeedsDisplay];
}

// show/hides the touch pointer
- (void)setHidden:(BOOL)hidden
{
	[super setHidden:hidden];

	// if shown center pointer in view
	if (!hidden)
	{
		_pointer_transformation = CGAffineTransformMakeTranslation(
		    ([self bounds].size.width - [_cur_pointer_img size].width) / 2,
		    ([self bounds].size.height - [_cur_pointer_img size].height) / 2);
		[self setNeedsDisplay];
	}
}

- (UIEdgeInsets)getEdgeInsets
{
	return UIEdgeInsetsMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height);
}

- (CGPoint)getPointerPosition
{
	return CGPointMake(_pointer_transformation.tx, _pointer_transformation.ty);
}

- (int)getPointerWidth
{
	return [_cur_pointer_img size].width;
}

- (int)getPointerHeight
{
	return [_cur_pointer_img size].height;
}

@end

@implementation TouchPointerView (Private)

- (void)setCurrentPointerImage:(UIImage *)image
{
	_cur_pointer_img = image;
	[self setNeedsDisplay];
}

- (void)displayPointerActionImage:(UIImage *)image
{
	[self setCurrentPointerImage:image];
	[self performSelector:@selector(setCurrentPointerImage:)
	           withObject:_default_pointer_img
	           afterDelay:RESET_DEFAULT_POINTER_IMAGE_DELAY];
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
	// Drawing code
	CGContextRef context = UIGraphicsGetCurrentContext();
	CGContextSaveGState(context);
	CGContextConcatCTM(context, _pointer_transformation);
	CGContextDrawImage(
	    context, CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height),
	    [_cur_pointer_img CGImage]);
	CGContextRestoreGState(context);
}

// helper that returns YES if the given point is within the pointer
- (BOOL)pointInsidePointer:(CGPoint)point
{
	CGRect rec = CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height);
	return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point);
}

// helper that returns YES if the given point is within the given pointer area
- (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point
{
	CGRect rec = _pointer_areas[area];
	return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point);
}

// returns the position of the cursor
- (CGPoint)getCursorPosition
{
	CGRect transPointerArea =
	    CGRectApplyAffineTransform(_pointer_areas[POINTER_ACTION_CURSOR], _pointer_transformation);
	return CGPointMake(CGRectGetMidX(transPointerArea), CGRectGetMidY(transPointerArea));
}

// this filters events - if the pointer was clicked the scrollview won't get any events
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
	return [self pointInsidePointer:point];
}

#pragma mark - Action handlers

// handles single tap gestures, returns YES if the event was handled by the pointer, NO otherwise
- (void)handleSingleTap:(UITapGestureRecognizer *)gesture
{
	// get touch position within our view
	CGPoint touchPos = [gesture locationInView:self];

	// look if pointer was in one of our action areas
	if ([self pointInsidePointerArea:POINTER_ACTION_CLOSE point:touchPos])
		[[self delegate] touchPointerClose];
	else if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos])
	{
		[self displayPointerActionImage:_lclick_pointer_img];
		[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES];
		[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO];
	}
	else if ([self pointInsidePointerArea:POINTER_ACTION_RCLICK point:touchPos])
	{
		[self displayPointerActionImage:_rclick_pointer_img];
		[[self delegate] touchPointerRightClick:[self getCursorPosition] down:YES];
		[[self delegate] touchPointerRightClick:[self getCursorPosition] down:NO];
	}
	else if ([self pointInsidePointerArea:POINTER_ACTION_KEYBOARD point:touchPos])
	{
		[self displayPointerActionImage:_keyboard_pointer_img];
		[[self delegate] touchPointerToggleKeyboard];
	}
	else if ([self pointInsidePointerArea:POINTER_ACTION_EXTKEYBOARD point:touchPos])
	{
		[self displayPointerActionImage:_extkeyboard_pointer_img];
		[[self delegate] touchPointerToggleExtendedKeyboard];
	}
	else if ([self pointInsidePointerArea:POINTER_ACTION_RESET point:touchPos])
	{
		[self displayPointerActionImage:_reset_pointer_img];
		[[self delegate] touchPointerResetSessionView];
	}
}

- (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick
{
	if ([gesture state] == UIGestureRecognizerStateBegan)
	{
		CGPoint touchPos = [gesture locationInView:self];
		if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos])
		{
			_prev_touch_location = touchPos;
			_pointer_moving = YES;
			if (sendClick == YES)
			{
				[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES];
				[self setCurrentPointerImage:_active_pointer_img];
			}
		}
		else if ([self pointInsidePointerArea:POINTER_ACTION_SCROLL point:touchPos])
		{
			[self setCurrentPointerImage:_scroll_pointer_img];
			_prev_touch_location = touchPos;
			_pointer_scrolling = YES;
		}
	}
	else if ([gesture state] == UIGestureRecognizerStateChanged)
	{
		if (_pointer_moving)
		{
			CGPoint touchPos = [gesture locationInView:self];
			_pointer_transformation = CGAffineTransformTranslate(
			    _pointer_transformation, touchPos.x - _prev_touch_location.x,
			    touchPos.y - _prev_touch_location.y);
			[[self delegate] touchPointerMove:[self getCursorPosition]];
			_prev_touch_location = touchPos;
			[self setNeedsDisplay];
		}
		else if (_pointer_scrolling)
		{
			CGPoint touchPos = [gesture locationInView:self];
			float delta = touchPos.y - _prev_touch_location.y;
			if (delta > GetScrollGestureDelta())
			{
				[[self delegate] touchPointerScrollDown:YES];
				_prev_touch_location = touchPos;
			}
			else if (delta < -GetScrollGestureDelta())
			{
				[[self delegate] touchPointerScrollDown:NO];
				_prev_touch_location = touchPos;
			}
		}
	}
	else if ([gesture state] == UIGestureRecognizerStateEnded)
	{
		if (_pointer_moving)
		{
			if (sendClick == YES)
				[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO];
			_pointer_moving = NO;
			[self setCurrentPointerImage:_default_pointer_img];
		}

		if (_pointer_scrolling)
		{
			[self setCurrentPointerImage:_default_pointer_img];
			_pointer_scrolling = NO;
		}
	}
}

// handles long press gestures
- (void)handleDragDrop:(UILongPressGestureRecognizer *)gesture
{
	[self handlerForGesture:gesture sendClick:YES];
}

- (void)handlePointerMoveScroll:(UILongPressGestureRecognizer *)gesture
{
	[self handlerForGesture:gesture sendClick:NO];
}

@end