FreeRDP
TouchPointerView.m
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  CGFloat area_width = [_cur_pointer_img size].width / 3.0f;
82  CGFloat area_height = [_cur_pointer_img size].height / 3.0f;
83  for (int i = 0; i < 3; i++)
84  {
85  for (int j = 0; j < 3; j++)
86  {
87  _pointer_areas[j + i * 3] =
88  CGRectMake(j * area_width, i * area_height, area_width, area_height);
89  }
90  }
91 
92  // init gesture recognizers
93  UITapGestureRecognizer *singleTapRecognizer =
94  [[[UITapGestureRecognizer alloc] initWithTarget:self
95  action:@selector(handleSingleTap:)] autorelease];
96  [singleTapRecognizer setNumberOfTouchesRequired:1];
97  [singleTapRecognizer setNumberOfTapsRequired:1];
98 
99  UILongPressGestureRecognizer *dragDropRecognizer = [[[UILongPressGestureRecognizer alloc]
100  initWithTarget:self
101  action:@selector(handleDragDrop:)] autorelease];
102  dragDropRecognizer.minimumPressDuration = 0.4;
103  // dragDropRecognizer.allowableMovement = 1000.0;
104 
105  UILongPressGestureRecognizer *pointerMoveScrollRecognizer =
106  [[[UILongPressGestureRecognizer alloc] initWithTarget:self
107  action:@selector(handlePointerMoveScroll:)]
108  autorelease];
109  pointerMoveScrollRecognizer.minimumPressDuration = 0.15;
110  pointerMoveScrollRecognizer.allowableMovement = 1000.0;
111  [pointerMoveScrollRecognizer requireGestureRecognizerToFail:dragDropRecognizer];
112 
113  [self addGestureRecognizer:singleTapRecognizer];
114  [self addGestureRecognizer:dragDropRecognizer];
115  [self addGestureRecognizer:pointerMoveScrollRecognizer];
116 }
117 
118 - (void)dealloc
119 {
120  [super dealloc];
121  [_default_pointer_img autorelease];
122  [_active_pointer_img autorelease];
123  [_lclick_pointer_img autorelease];
124  [_rclick_pointer_img autorelease];
125  [_scroll_pointer_img autorelease];
126  [_extkeyboard_pointer_img autorelease];
127  [_keyboard_pointer_img autorelease];
128  [_reset_pointer_img autorelease];
129 }
130 
131 #pragma mark - Public interface
132 
133 // positions the pointer on screen if it got offscreen after an orentation change
134 - (void)ensurePointerIsVisible
135 {
136  CGRect bounds = [self bounds];
137  if (_pointer_transformation.tx > (bounds.size.width - _cur_pointer_img.size.width))
138  _pointer_transformation.tx = bounds.size.width - _cur_pointer_img.size.width;
139  if (_pointer_transformation.ty > (bounds.size.height - _cur_pointer_img.size.height))
140  _pointer_transformation.ty = bounds.size.height - _cur_pointer_img.size.height;
141  [self setNeedsDisplay];
142 }
143 
144 // show/hides the touch pointer
145 - (void)setHidden:(BOOL)hidden
146 {
147  [super setHidden:hidden];
148 
149  // if shown center pointer in view
150  if (!hidden)
151  {
152  _pointer_transformation = CGAffineTransformMakeTranslation(
153  ([self bounds].size.width - [_cur_pointer_img size].width) / 2,
154  ([self bounds].size.height - [_cur_pointer_img size].height) / 2);
155  [self setNeedsDisplay];
156  }
157 }
158 
159 - (UIEdgeInsets)getEdgeInsets
160 {
161  return UIEdgeInsetsMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height);
162 }
163 
164 - (CGPoint)getPointerPosition
165 {
166  return CGPointMake(_pointer_transformation.tx, _pointer_transformation.ty);
167 }
168 
169 - (int)getPointerWidth
170 {
171  return [_cur_pointer_img size].width;
172 }
173 
174 - (int)getPointerHeight
175 {
176  return [_cur_pointer_img size].height;
177 }
178 
179 @end
180 
181 @implementation TouchPointerView (Private)
182 
183 - (void)setCurrentPointerImage:(UIImage *)image
184 {
185  _cur_pointer_img = image;
186  [self setNeedsDisplay];
187 }
188 
189 - (void)displayPointerActionImage:(UIImage *)image
190 {
191  [self setCurrentPointerImage:image];
192  [self performSelector:@selector(setCurrentPointerImage:)
193  withObject:_default_pointer_img
194  afterDelay:RESET_DEFAULT_POINTER_IMAGE_DELAY];
195 }
196 
197 // Only override drawRect: if you perform custom drawing.
198 // An empty implementation adversely affects performance during animation.
199 - (void)drawRect:(CGRect)rect
200 {
201  // Drawing code
202  CGContextRef context = UIGraphicsGetCurrentContext();
203  CGContextSaveGState(context);
204  CGContextConcatCTM(context, _pointer_transformation);
205  CGContextDrawImage(
206  context, CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height),
207  [_cur_pointer_img CGImage]);
208  CGContextRestoreGState(context);
209 }
210 
211 // helper that returns YES if the given point is within the pointer
212 - (BOOL)pointInsidePointer:(CGPoint)point
213 {
214  CGRect rec = CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height);
215  return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point);
216 }
217 
218 // helper that returns YES if the given point is within the given pointer area
219 - (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point
220 {
221  CGRect rec = _pointer_areas[area];
222  return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point);
223 }
224 
225 // returns the position of the cursor
226 - (CGPoint)getCursorPosition
227 {
228  CGRect transPointerArea =
229  CGRectApplyAffineTransform(_pointer_areas[POINTER_ACTION_CURSOR], _pointer_transformation);
230  return CGPointMake(CGRectGetMidX(transPointerArea), CGRectGetMidY(transPointerArea));
231 }
232 
233 // this filters events - if the pointer was clicked the scrollview won't get any events
234 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
235 {
236  return [self pointInsidePointer:point];
237 }
238 
239 #pragma mark - Action handlers
240 
241 // handles single tap gestures, returns YES if the event was handled by the pointer, NO otherwise
242 - (void)handleSingleTap:(UITapGestureRecognizer *)gesture
243 {
244  // get touch position within our view
245  CGPoint touchPos = [gesture locationInView:self];
246 
247  // look if pointer was in one of our action areas
248  if ([self pointInsidePointerArea:POINTER_ACTION_CLOSE point:touchPos])
249  [[self delegate] touchPointerClose];
250  else if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos])
251  {
252  [self displayPointerActionImage:_lclick_pointer_img];
253  [[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES];
254  [[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO];
255  }
256  else if ([self pointInsidePointerArea:POINTER_ACTION_RCLICK point:touchPos])
257  {
258  [self displayPointerActionImage:_rclick_pointer_img];
259  [[self delegate] touchPointerRightClick:[self getCursorPosition] down:YES];
260  [[self delegate] touchPointerRightClick:[self getCursorPosition] down:NO];
261  }
262  else if ([self pointInsidePointerArea:POINTER_ACTION_KEYBOARD point:touchPos])
263  {
264  [self displayPointerActionImage:_keyboard_pointer_img];
265  [[self delegate] touchPointerToggleKeyboard];
266  }
267  else if ([self pointInsidePointerArea:POINTER_ACTION_EXTKEYBOARD point:touchPos])
268  {
269  [self displayPointerActionImage:_extkeyboard_pointer_img];
270  [[self delegate] touchPointerToggleExtendedKeyboard];
271  }
272  else if ([self pointInsidePointerArea:POINTER_ACTION_RESET point:touchPos])
273  {
274  [self displayPointerActionImage:_reset_pointer_img];
275  [[self delegate] touchPointerResetSessionView];
276  }
277 }
278 
279 - (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick
280 {
281  if ([gesture state] == UIGestureRecognizerStateBegan)
282  {
283  CGPoint touchPos = [gesture locationInView:self];
284  if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos])
285  {
286  _prev_touch_location = touchPos;
287  _pointer_moving = YES;
288  if (sendClick == YES)
289  {
290  [[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES];
291  [self setCurrentPointerImage:_active_pointer_img];
292  }
293  }
294  else if ([self pointInsidePointerArea:POINTER_ACTION_SCROLL point:touchPos])
295  {
296  [self setCurrentPointerImage:_scroll_pointer_img];
297  _prev_touch_location = touchPos;
298  _pointer_scrolling = YES;
299  }
300  }
301  else if ([gesture state] == UIGestureRecognizerStateChanged)
302  {
303  if (_pointer_moving)
304  {
305  CGPoint touchPos = [gesture locationInView:self];
306  _pointer_transformation = CGAffineTransformTranslate(
307  _pointer_transformation, touchPos.x - _prev_touch_location.x,
308  touchPos.y - _prev_touch_location.y);
309  [[self delegate] touchPointerMove:[self getCursorPosition]];
310  _prev_touch_location = touchPos;
311  [self setNeedsDisplay];
312  }
313  else if (_pointer_scrolling)
314  {
315  CGPoint touchPos = [gesture locationInView:self];
316  float delta = touchPos.y - _prev_touch_location.y;
317  if (delta > GetScrollGestureDelta())
318  {
319  [[self delegate] touchPointerScrollDown:YES];
320  _prev_touch_location = touchPos;
321  }
322  else if (delta < -GetScrollGestureDelta())
323  {
324  [[self delegate] touchPointerScrollDown:NO];
325  _prev_touch_location = touchPos;
326  }
327  }
328  }
329  else if ([gesture state] == UIGestureRecognizerStateEnded)
330  {
331  if (_pointer_moving)
332  {
333  if (sendClick == YES)
334  [[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO];
335  _pointer_moving = NO;
336  [self setCurrentPointerImage:_default_pointer_img];
337  }
338 
339  if (_pointer_scrolling)
340  {
341  [self setCurrentPointerImage:_default_pointer_img];
342  _pointer_scrolling = NO;
343  }
344  }
345 }
346 
347 // handles long press gestures
348 - (void)handleDragDrop:(UILongPressGestureRecognizer *)gesture
349 {
350  [self handlerForGesture:gesture sendClick:YES];
351 }
352 
353 - (void)handlePointerMoveScroll:(UILongPressGestureRecognizer *)gesture
354 {
355  [self handlerForGesture:gesture sendClick:NO];
356 }
357 
358 @end