1/*
2 RDP Touch Pointer View
3
4 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5
6 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7 If a copy of the MPL was not distributed with this file, You can obtain one at
8 http://mozilla.org/MPL/2.0/.
9 */
10
11#import "TouchPointerView.h"
12#import "Utils.h"
13
14#define RESET_DEFAULT_POINTER_IMAGE_DELAY 0.15
15
16#define POINTER_ACTION_CURSOR 0
17#define POINTER_ACTION_CLOSE 3
18#define POINTER_ACTION_RCLICK 2
19#define POINTER_ACTION_LCLICK 4
20#define POINTER_ACTION_MOVE 4
21#define POINTER_ACTION_SCROLL 5
22#define POINTER_ACTION_KEYBOARD 7
23#define POINTER_ACTION_EXTKEYBOARD 8
24#define POINTER_ACTION_RESET 6
25
26@interface TouchPointerView (Private)
27- (void)setCurrentPointerImage:(UIImage *)image;
28- (void)displayPointerActionImage:(UIImage *)image;
29- (BOOL)pointInsidePointer:(CGPoint)point;
30- (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point;
31- (CGPoint)getCursorPosition;
32- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
33- (void)handleSingleTap:(UITapGestureRecognizer *)gesture;
34- (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick;
35@end
36
37@implementation TouchPointerView
38
39@synthesize delegate = _delegate;
40
41- (void)awakeFromNib
42{
43	[super awakeFromNib];
44
45	// set content mode when rotating (keep aspect ratio)
46	[self setContentMode:UIViewContentModeTopLeft];
47
48	// load touchPointerImage
49	_default_pointer_img = [[UIImage
50	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_default"
51	                                                            ofType:@"png"]] retain];
52	_active_pointer_img = [[UIImage
53	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_active"
54	                                                            ofType:@"png"]] retain];
55	_lclick_pointer_img = [[UIImage
56	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_lclick"
57	                                                            ofType:@"png"]] retain];
58	_rclick_pointer_img = [[UIImage
59	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_rclick"
60	                                                            ofType:@"png"]] retain];
61	_scroll_pointer_img = [[UIImage
62	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_scroll"
63	                                                            ofType:@"png"]] retain];
64	_extkeyboard_pointer_img = [[UIImage
65	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_ext_keyboard"
66	                                                            ofType:@"png"]] retain];
67	_keyboard_pointer_img = [[UIImage
68	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_keyboard"
69	                                                            ofType:@"png"]] retain];
70	_reset_pointer_img = [[UIImage
71	    imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_reset"
72	                                                            ofType:@"png"]] retain];
73	_cur_pointer_img = _default_pointer_img;
74	_pointer_transformation = CGAffineTransformMake(1, 0, 0, 1, 0, 0);
75
76	// init flags
77	_pointer_moving = NO;
78	_pointer_scrolling = NO;
79
80	// create areas array
81	int i, j;
82	CGFloat area_width = [_cur_pointer_img size].width / 3.0f;
83	CGFloat area_height = [_cur_pointer_img size].height / 3.0f;
84	for (i = 0; i < 3; i++)
85	{
86		for (j = 0; j < 3; j++)
87		{
88			_pointer_areas[j + i * 3] =
89			    CGRectMake(j * area_width, i * area_height, area_width, area_height);
90		}
91	}
92
93	// init gesture recognizers
94	UITapGestureRecognizer *singleTapRecognizer =
95	    [[[UITapGestureRecognizer alloc] initWithTarget:self
96	                                             action:@selector(handleSingleTap:)] autorelease];
97	[singleTapRecognizer setNumberOfTouchesRequired:1];
98	[singleTapRecognizer setNumberOfTapsRequired:1];
99
100	UILongPressGestureRecognizer *dragDropRecognizer = [[[UILongPressGestureRecognizer alloc]
101	    initWithTarget:self
102	            action:@selector(handleDragDrop:)] autorelease];
103	dragDropRecognizer.minimumPressDuration = 0.4;
104	//        dragDropRecognizer.allowableMovement = 1000.0;
105
106	UILongPressGestureRecognizer *pointerMoveScrollRecognizer =
107	    [[[UILongPressGestureRecognizer alloc] initWithTarget:self
108	                                                   action:@selector(handlePointerMoveScroll:)]
109	        autorelease];
110	pointerMoveScrollRecognizer.minimumPressDuration = 0.15;
111	pointerMoveScrollRecognizer.allowableMovement = 1000.0;
112	[pointerMoveScrollRecognizer requireGestureRecognizerToFail:dragDropRecognizer];
113
114	[self addGestureRecognizer:singleTapRecognizer];
115	[self addGestureRecognizer:dragDropRecognizer];
116	[self addGestureRecognizer:pointerMoveScrollRecognizer];
117}
118
119- (void)dealloc
120{
121	[super dealloc];
122	[_default_pointer_img autorelease];
123	[_active_pointer_img autorelease];
124	[_lclick_pointer_img autorelease];
125	[_rclick_pointer_img autorelease];
126	[_scroll_pointer_img autorelease];
127	[_extkeyboard_pointer_img autorelease];
128	[_keyboard_pointer_img autorelease];
129	[_reset_pointer_img autorelease];
130}
131
132#pragma mark - Public interface
133
134// positions the pointer on screen if it got offscreen after an orentation change
135- (void)ensurePointerIsVisible
136{
137	CGRect bounds = [self bounds];
138	if (_pointer_transformation.tx > (bounds.size.width - _cur_pointer_img.size.width))
139		_pointer_transformation.tx = bounds.size.width - _cur_pointer_img.size.width;
140	if (_pointer_transformation.ty > (bounds.size.height - _cur_pointer_img.size.height))
141		_pointer_transformation.ty = bounds.size.height - _cur_pointer_img.size.height;
142	[self setNeedsDisplay];
143}
144
145// show/hides the touch pointer
146- (void)setHidden:(BOOL)hidden
147{
148	[super setHidden:hidden];
149
150	// if shown center pointer in view
151	if (!hidden)
152	{
153		_pointer_transformation = CGAffineTransformMakeTranslation(
154		    ([self bounds].size.width - [_cur_pointer_img size].width) / 2,
155		    ([self bounds].size.height - [_cur_pointer_img size].height) / 2);
156		[self setNeedsDisplay];
157	}
158}
159
160- (UIEdgeInsets)getEdgeInsets
161{
162	return UIEdgeInsetsMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height);
163}
164
165- (CGPoint)getPointerPosition
166{
167	return CGPointMake(_pointer_transformation.tx, _pointer_transformation.ty);
168}
169
170- (int)getPointerWidth
171{
172	return [_cur_pointer_img size].width;
173}
174
175- (int)getPointerHeight
176{
177	return [_cur_pointer_img size].height;
178}
179
180@end
181
182@implementation TouchPointerView (Private)
183
184- (void)setCurrentPointerImage:(UIImage *)image
185{
186	_cur_pointer_img = image;
187	[self setNeedsDisplay];
188}
189
190- (void)displayPointerActionImage:(UIImage *)image
191{
192	[self setCurrentPointerImage:image];
193	[self performSelector:@selector(setCurrentPointerImage:)
194	           withObject:_default_pointer_img
195	           afterDelay:RESET_DEFAULT_POINTER_IMAGE_DELAY];
196}
197
198// Only override drawRect: if you perform custom drawing.
199// An empty implementation adversely affects performance during animation.
200- (void)drawRect:(CGRect)rect
201{
202	// Drawing code
203	CGContextRef context = UIGraphicsGetCurrentContext();
204	CGContextSaveGState(context);
205	CGContextConcatCTM(context, _pointer_transformation);
206	CGContextDrawImage(
207	    context, CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height),
208	    [_cur_pointer_img CGImage]);
209	CGContextRestoreGState(context);
210}
211
212// helper that returns YES if the given point is within the pointer
213- (BOOL)pointInsidePointer:(CGPoint)point
214{
215	CGRect rec = CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height);
216	return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point);
217}
218
219// helper that returns YES if the given point is within the given pointer area
220- (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point
221{
222	CGRect rec = _pointer_areas[area];
223	return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point);
224}
225
226// returns the position of the cursor
227- (CGPoint)getCursorPosition
228{
229	CGRect transPointerArea =
230	    CGRectApplyAffineTransform(_pointer_areas[POINTER_ACTION_CURSOR], _pointer_transformation);
231	return CGPointMake(CGRectGetMidX(transPointerArea), CGRectGetMidY(transPointerArea));
232}
233
234// this filters events - if the pointer was clicked the scrollview won't get any events
235- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
236{
237	return [self pointInsidePointer:point];
238}
239
240#pragma mark - Action handlers
241
242// handles single tap gestures, returns YES if the event was handled by the pointer, NO otherwise
243- (void)handleSingleTap:(UITapGestureRecognizer *)gesture
244{
245	// get touch position within our view
246	CGPoint touchPos = [gesture locationInView:self];
247
248	// look if pointer was in one of our action areas
249	if ([self pointInsidePointerArea:POINTER_ACTION_CLOSE point:touchPos])
250		[[self delegate] touchPointerClose];
251	else if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos])
252	{
253		[self displayPointerActionImage:_lclick_pointer_img];
254		[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES];
255		[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO];
256	}
257	else if ([self pointInsidePointerArea:POINTER_ACTION_RCLICK point:touchPos])
258	{
259		[self displayPointerActionImage:_rclick_pointer_img];
260		[[self delegate] touchPointerRightClick:[self getCursorPosition] down:YES];
261		[[self delegate] touchPointerRightClick:[self getCursorPosition] down:NO];
262	}
263	else if ([self pointInsidePointerArea:POINTER_ACTION_KEYBOARD point:touchPos])
264	{
265		[self displayPointerActionImage:_keyboard_pointer_img];
266		[[self delegate] touchPointerToggleKeyboard];
267	}
268	else if ([self pointInsidePointerArea:POINTER_ACTION_EXTKEYBOARD point:touchPos])
269	{
270		[self displayPointerActionImage:_extkeyboard_pointer_img];
271		[[self delegate] touchPointerToggleExtendedKeyboard];
272	}
273	else if ([self pointInsidePointerArea:POINTER_ACTION_RESET point:touchPos])
274	{
275		[self displayPointerActionImage:_reset_pointer_img];
276		[[self delegate] touchPointerResetSessionView];
277	}
278}
279
280- (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick
281{
282	if ([gesture state] == UIGestureRecognizerStateBegan)
283	{
284		CGPoint touchPos = [gesture locationInView:self];
285		if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos])
286		{
287			_prev_touch_location = touchPos;
288			_pointer_moving = YES;
289			if (sendClick == YES)
290			{
291				[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES];
292				[self setCurrentPointerImage:_active_pointer_img];
293			}
294		}
295		else if ([self pointInsidePointerArea:POINTER_ACTION_SCROLL point:touchPos])
296		{
297			[self setCurrentPointerImage:_scroll_pointer_img];
298			_prev_touch_location = touchPos;
299			_pointer_scrolling = YES;
300		}
301	}
302	else if ([gesture state] == UIGestureRecognizerStateChanged)
303	{
304		if (_pointer_moving)
305		{
306			CGPoint touchPos = [gesture locationInView:self];
307			_pointer_transformation = CGAffineTransformTranslate(
308			    _pointer_transformation, touchPos.x - _prev_touch_location.x,
309			    touchPos.y - _prev_touch_location.y);
310			[[self delegate] touchPointerMove:[self getCursorPosition]];
311			_prev_touch_location = touchPos;
312			[self setNeedsDisplay];
313		}
314		else if (_pointer_scrolling)
315		{
316			CGPoint touchPos = [gesture locationInView:self];
317			float delta = touchPos.y - _prev_touch_location.y;
318			if (delta > GetScrollGestureDelta())
319			{
320				[[self delegate] touchPointerScrollDown:YES];
321				_prev_touch_location = touchPos;
322			}
323			else if (delta < -GetScrollGestureDelta())
324			{
325				[[self delegate] touchPointerScrollDown:NO];
326				_prev_touch_location = touchPos;
327			}
328		}
329	}
330	else if ([gesture state] == UIGestureRecognizerStateEnded)
331	{
332		if (_pointer_moving)
333		{
334			if (sendClick == YES)
335				[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO];
336			_pointer_moving = NO;
337			[self setCurrentPointerImage:_default_pointer_img];
338		}
339
340		if (_pointer_scrolling)
341		{
342			[self setCurrentPointerImage:_default_pointer_img];
343			_pointer_scrolling = NO;
344		}
345	}
346}
347
348// handles long press gestures
349- (void)handleDragDrop:(UILongPressGestureRecognizer *)gesture
350{
351	[self handlerForGesture:gesture sendClick:YES];
352}
353
354- (void)handlePointerMoveScroll:(UILongPressGestureRecognizer *)gesture
355{
356	[self handlerForGesture:gesture sendClick:NO];
357}
358
359@end
360