FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
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