5#import <UIKit/UIGestureRecognizerSubclass.h>
6#import <UIKit/UITextView.h>
8#include <QtGui/QGuiApplication>
9#include <QtGui/QInputMethod>
10#include <QtGui/QStyleHints>
12#include <QtGui/private/qinputmethod_p.h>
13#include <QtCore/private/qobject_p.h>
14#include <QtCore/private/qcore_mac_p.h>
30 return static_cast<QInputMethodPrivate *>(QObjectPrivate::get(QGuiApplication::inputMethod()))->platformInputContext();
35 if (!QGuiApplication::focusObject())
38 QInputMethodQueryEvent query(Qt::ImAnchorPosition | Qt::ImCursorPosition);
39 QGuiApplication::sendEvent(QGuiApplication::focusObject(), &query);
40 int anchorPos = query.value(Qt::ImAnchorPosition).toInt();
41 int cursorPos = query.value(Qt::ImCursorPosition).toInt();
42 return {anchorPos, cursorPos};
48 return selection.first != selection.second;
53 [CATransaction begin];
54 [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
56 [CATransaction commit];
61
62
63
64@interface QIOSEditMenu : NSObject
65@property (nonatomic, assign) BOOL visible;
66@property (nonatomic, readonly) BOOL isHiding;
67@property (nonatomic, readonly) BOOL shownByUs;
68@property (nonatomic, assign) BOOL reshowAfterHidden;
71@implementation QIOSEditMenu
75 if (self = [super init]) {
76 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
78 [center addObserverForName:UIMenuControllerWillHideMenuNotification
79 object:nil queue:nil usingBlock:^(NSNotification *) {
83 [center addObserverForName:UIMenuControllerDidHideMenuNotification
84 object:nil queue:nil usingBlock:^(NSNotification *) {
87 if (self.reshowAfterHidden) {
90 self.reshowAfterHidden = NO;
91 dispatch_async(dispatch_get_main_queue (), ^{ self.visible = YES; });
94 [center addObserverForName:UIKeyboardDidHideNotification object:nil queue:nil
95 usingBlock:^(NSNotification *) {
106 [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:nil];
112 return [UIMenuController sharedMenuController].menuVisible;
115- (
void)setVisible:(BOOL)visible
117 if (visible == self.visible)
127 QRectF cr = QPlatformInputContext::cursorRectangle();
128 QRectF ar = QPlatformInputContext::anchorRectangle();
130 CGRect targetRect = cr.united(ar).toCGRect();
131 UIView *focusView =
reinterpret_cast<UIView *>(qApp->focusWindow()->winId());
132 [[UIMenuController sharedMenuController] setTargetRect:targetRect inView:focusView];
133 [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
135 [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
141void showEditMenu(UIView *focusView, QPoint touchPos)
143 const bool mouseTriggered =
false;
144 const Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier;
145 QWindow *qtWindow = quiview_cast(focusView).platformWindow->window();
146 const auto globalTouchPos = qtWindow->mapToGlobal(touchPos);
147 const bool contextMenuEventAccepted = QWindowSystemInterface::handleContextMenuEvent<
148 QWindowSystemInterface::SynchronousDelivery>(qtWindow, mouseTriggered, touchPos,
149 globalTouchPos, keyboardModifiers);
151 if (!contextMenuEventAccepted) {
155 QIOSTextInputOverlay::s_editMenu.visible = YES;
161@interface QIOSLoupeLayer : CALayer
162@property (nonatomic, retain) UIView *targetView;
163@property (nonatomic, assign) CGPoint focalPoint;
164@property (nonatomic, assign) BOOL visible;
167@implementation QIOSLoupeLayer {
168 UIView *_snapshotView;
169 BOOL _pendingSnapshotUpdate;
170 UIView *_loupeImageView;
171 CALayer *_containerLayer;
172 CGFloat _loupeOffset;
176- (instancetype)initWithSize:(CGSize)size cornerRadius:(CGFloat)cornerRadius bottomOffset:(CGFloat)bottomOffset
178 if (self = [super init]) {
179 _loupeOffset = bottomOffset + (size.height / 2);
181 _pendingSnapshotUpdate = YES;
182 _updateTimer.setInterval(100);
183 _updateTimer.setSingleShot(
true);
184 QObject::connect(&_updateTimer, &QTimer::timeout, [self](){ [self updateSnapshot]; });
187 self.frame = CGRectMake(0, 0, size.width, size.height);
188 self.cornerRadius = cornerRadius;
189 self.shadowColor = [[UIColor grayColor] CGColor];
190 self.shadowOffset = CGSizeMake(0, 1);
191 self.shadowRadius = 2.0;
192 self.shadowOpacity = 0.75;
193 self.transform = CATransform3DMakeScale(0, 0, 0);
196 _containerLayer = [[CALayer
new] autorelease];
197 _containerLayer.frame = self.bounds;
198 _containerLayer.cornerRadius = cornerRadius;
199 _containerLayer.masksToBounds = YES;
200 [self addSublayer:_containerLayer];
203 const CGFloat inset = 30;
204 CALayer *topShadeLayer = [[CALayer
new] autorelease];
205 topShadeLayer.frame = CGRectOffset(CGRectInset(self.bounds, -inset, -inset), 0, inset / 2);
206 topShadeLayer.borderWidth = inset / 2;
207 topShadeLayer.cornerRadius = cornerRadius;
208 topShadeLayer.borderColor = [[UIColor blackColor] CGColor];
209 topShadeLayer.shadowColor = [[UIColor blackColor] CGColor];
210 topShadeLayer.shadowOffset = CGSizeMake(0, 0);
211 topShadeLayer.shadowRadius = 15.0;
212 topShadeLayer.shadowOpacity = 0.6;
214 CALayer *mask = [[CALayer
new] autorelease];
215 mask.frame = CGRectOffset(self.bounds, inset, inset / 2);
216 mask.backgroundColor = [[UIColor blackColor] CGColor];
217 mask.cornerRadius = cornerRadius;
218 topShadeLayer.mask = mask;
219 [self addSublayer:topShadeLayer];
224 CALayer *borderLayer = [[CALayer
new] autorelease];
225 borderLayer.frame = self.bounds;
226 borderLayer.borderWidth = 0.75;
227 borderLayer.cornerRadius = cornerRadius;
228 borderLayer.borderColor = [[UIColor lightGrayColor] CGColor];
229 [self addSublayer:borderLayer];
241- (
void)setVisible:(BOOL)visible
243 if (_visible == visible)
248 dispatch_async(dispatch_get_main_queue (), ^{
251 self.transform = _visible ? CATransform3DMakeScale(1, 1, 1) : CATransform3DMakeScale(0.0, 0.0, 1);
255- (
void)updateSnapshot
257 _pendingSnapshotUpdate = YES;
258 [self setNeedsDisplay];
261- (
void)setFocalPoint:(CGPoint)point
264 [self updateSnapshot];
268 _updateTimer.start();
277 executeBlockWithoutAnimation(^{
278 if (_pendingSnapshotUpdate) {
279 UIView *newSnapshot = [_targetView snapshotViewAfterScreenUpdates:NO];
280 [_snapshotView.layer removeFromSuperlayer];
281 [_snapshotView release];
282 _snapshotView = [newSnapshot retain];
283 [_containerLayer addSublayer:_snapshotView.layer];
284 _pendingSnapshotUpdate = NO;
287 self.position = CGPointMake(_focalPoint.x, _focalPoint.y - _loupeOffset);
289 const CGFloat loupeScale = 1.5;
290 CGFloat x = -(_focalPoint.x * loupeScale) + self.frame.size.width / 2;
291 CGFloat y = -(_focalPoint.y * loupeScale) + self.frame.size.height / 2;
292 CGFloat w = _targetView.frame.size.width * loupeScale;
293 CGFloat h = _targetView.frame.size.height * loupeScale;
294 _snapshotView.layer.frame = CGRectMake(x, y, w, h);
302@interface QIOSHandleLayer : CALayer <CAAnimationDelegate>
303@property (nonatomic, assign) CGRect cursorRectangle;
304@property (nonatomic, assign) CGFloat handleScale;
305@property (nonatomic, assign) BOOL visible;
306@property (nonatomic, copy) Block onAnimationDidStop;
309@implementation QIOSHandleLayer {
310 CALayer *_handleCursorLayer;
311 CALayer *_handleKnobLayer;
312 Qt::Edge _selectionEdge;
317- (instancetype)initWithKnobAtEdge:(Qt::Edge)selectionEdge
319 if (self = [super init]) {
320 CGColorRef bgColor = [UIColor colorWithRed:0.1 green:0.4 blue:0.9 alpha:1].CGColor;
321 _selectionEdge = selectionEdge;
322 self.handleScale = 0;
324 _handleCursorLayer = [[CALayer
new] autorelease];
325 _handleCursorLayer.masksToBounds = YES;
326 _handleCursorLayer.backgroundColor = bgColor;
327 [self addSublayer:_handleCursorLayer];
329 _handleKnobLayer = [[CALayer
new] autorelease];
330 _handleKnobLayer.masksToBounds = YES;
331 _handleKnobLayer.backgroundColor = bgColor;
332 _handleKnobLayer.cornerRadius = kKnobWidth / 2;
333 [self addSublayer:_handleKnobLayer];
338+ (BOOL)needsDisplayForKey:(NSString *)key
340 if ([key isEqualToString:@
"handleScale"])
342 return [super needsDisplayForKey:key];
345- (id<CAAction>)actionForKey:(NSString *)key
347 if ([key isEqualToString:@
"handleScale"]) {
350 CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:key];
351 [animation setDuration:0.5];
352 animation.values = @[@(0.0f), @(1.3f), @(1.3f), @(1.0f)];
353 animation.keyTimes = @[@(0.0f), @(0.3f), @(0.9f), @(1.0f)];
356 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
357 [animation setDelegate:self];
358 animation.fromValue = [self valueForKey:key];
359 [animation setDuration:0.2];
363 return [super actionForKey:key];
366- (
void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
370 if (self.onAnimationDidStop)
371 self.onAnimationDidStop();
374- (
void)setVisible:(BOOL)visible
376 if (visible == _visible)
381 self.handleScale = visible ? 1 : 0;
384- (
void)setCursorRectangle:(CGRect)cursorRect
386 if (CGRectEqualToRect(_cursorRectangle, cursorRect))
389 _cursorRectangle = cursorRect;
391 executeBlockWithoutAnimation(^{
392 [self setNeedsDisplay];
393 [self displayIfNeeded];
399 CGFloat cursorWidth = 2;
400 CGPoint origin = _cursorRectangle.origin;
401 CGSize size = _cursorRectangle.size;
402 CGFloat scale = ((QIOSHandleLayer *)[self presentationLayer]).handleScale;
403 CGFloat edgeAdjustment = (_selectionEdge == Qt::LeftEdge) ? 0.5 - cursorWidth : -0.5;
405 CGFloat cursorX = origin.x + (size.width / 2) + edgeAdjustment;
406 CGFloat cursorY = origin.y;
407 CGFloat knobX = cursorX - (kKnobWidth - cursorWidth) / 2;
408 CGFloat knobY = origin.y + ((_selectionEdge == Qt::LeftEdge) ? -kKnobWidth : size.height);
410 _handleCursorLayer.frame = CGRectMake(cursorX, cursorY, cursorWidth, size.height);
411 _handleKnobLayer.frame = CGRectMake(knobX, knobY, kKnobWidth, kKnobWidth);
412 _handleCursorLayer.transform = CATransform3DMakeScale(1, scale, scale);
413 _handleKnobLayer.transform = CATransform3DMakeScale(scale, scale, scale);
421
422
423
424
425@interface QIOSLoupeRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
426@property (nonatomic, assign) QPointF focalPoint;
427@property (nonatomic, assign) BOOL dragTriggersGesture;
428@property (nonatomic, readonly) UIView *focusView;
431@implementation QIOSLoupeRecognizer {
432 QIOSLoupeLayer *_loupeLayer;
433 UIView *_desktopView;
434 CGPoint _firstTouchPoint;
435 CGPoint _lastTouchPoint;
436 QTimer _triggerStateBeganTimer;
437 int _originalCursorFlashTime;
442 if (self = [super initWithTarget:self action:@selector(gestureStateChanged)]) {
444 _triggerStateBeganTimer.setInterval(QGuiApplication::styleHints()->startDragTime());
445 _triggerStateBeganTimer.setSingleShot(
true);
446 QObject::connect(&_triggerStateBeganTimer, &QTimer::timeout, [=](){
447 self.state = UIGestureRecognizerStateBegan;
454- (
void)setEnabled:(BOOL)enabled
456 if (enabled == self.enabled)
459 [super setEnabled:enabled];
462 _focusView = [
reinterpret_cast<UIView *>(qApp->focusWindow()->winId()) retain];
463 _desktopView = [presentationWindow(
nullptr).rootViewController.view retain];
464 Q_ASSERT(_focusView && _desktopView && _desktopView.superview);
465 [_desktopView addGestureRecognizer:self];
467 [_desktopView removeGestureRecognizer:self];
468 [_desktopView release];
470 [_focusView release];
472 _triggerStateBeganTimer.stop();
474 [_loupeLayer removeFromSuperlayer];
475 [_loupeLayer release];
481- (
void)gestureStateChanged
483 switch (self.state) {
484 case UIGestureRecognizerStateBegan:
486 _originalCursorFlashTime = QGuiApplication::styleHints()->cursorFlashTime();
487 QGuiApplication::styleHints()->setCursorFlashTime(0);
490 [self updateFocalPoint:QPointF::fromCGPoint(_lastTouchPoint)];
491 _loupeLayer.visible = YES;
492 QIOSTextInputOverlay::s_editMenu.visible = NO;
494 case UIGestureRecognizerStateChanged:
496 [self updateFocalPoint:QPointF::fromCGPoint(_lastTouchPoint)];
498 case UIGestureRecognizerStateEnded: {
500 QGuiApplication::styleHints()->setCursorFlashTime(_originalCursorFlashTime);
501 const QPoint touchPos = QPointF::fromCGPoint(_lastTouchPoint).toPoint();
502 showEditMenu(_focusView, touchPos);
503 _loupeLayer.visible = NO;
507 _loupeLayer.visible = NO;
516 _loupeLayer = [[self createLoupeLayer] retain];
517 _loupeLayer.targetView = _desktopView;
518 [_desktopView.superview.layer addSublayer:_loupeLayer];
523 return QPointF::fromCGPoint([_loupeLayer.targetView convertPoint:_loupeLayer.focalPoint toView:_focusView]);
526- (
void)setFocalPoint:(QPointF)point
528 _loupeLayer.focalPoint = [_loupeLayer.targetView convertPoint:point.toCGPoint() fromView:_focusView];
531- (
void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
533 [super touchesBegan:touches withEvent:event];
534 if ([event allTouches].count > 1) {
536 self.state = UIGestureRecognizerStateFailed;
540 _firstTouchPoint = [
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView];
541 _lastTouchPoint = _firstTouchPoint;
545 if ([self acceptTouchesBegan:QPointF::fromCGPoint(_firstTouchPoint)])
546 _triggerStateBeganTimer.start();
548 self.state = UIGestureRecognizerStateFailed;
551- (
void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
553 [super touchesMoved:touches withEvent:event];
554 _lastTouchPoint = [
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView];
556 if (self.state == UIGestureRecognizerStatePossible) {
560 int startDragDistance = QGuiApplication::styleHints()->startDragDistance();
561 int dragDistance = hypot(_firstTouchPoint.x - _lastTouchPoint.x, _firstTouchPoint.y - _lastTouchPoint.y);
562 if (dragDistance > startDragDistance) {
563 _triggerStateBeganTimer.stop();
564 self.state = self.dragTriggersGesture ? UIGestureRecognizerStateBegan : UIGestureRecognizerStateFailed;
567 self.state = UIGestureRecognizerStateChanged;
571- (
void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
573 [super touchesEnded:touches withEvent:event];
574 _triggerStateBeganTimer.stop();
575 _lastTouchPoint = [
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView];
576 self.state = self.state == UIGestureRecognizerStatePossible ? UIGestureRecognizerStateFailed : UIGestureRecognizerStateEnded;
579- (
void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
581 [super touchesCancelled:touches withEvent:event];
582 _triggerStateBeganTimer.stop();
583 _lastTouchPoint = [
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView];
584 self.state = UIGestureRecognizerStateCancelled;
589- (BOOL)acceptTouchesBegan:(QPointF)touchPoint
591 Q_UNUSED(touchPoint);
596- (QIOSLoupeLayer *)createLoupeLayer
602- (
void)updateFocalPoint:(QPointF)touchPoint
604 Q_UNUSED(touchPoint);
613
614
615
616
617@interface QIOSCursorRecognizer : QIOSLoupeRecognizer
620@implementation QIOSCursorRecognizer
622- (QIOSLoupeLayer *)createLoupeLayer
624 return [[[QIOSLoupeLayer alloc] initWithSize:CGSizeMake(120, 120) cornerRadius:60 bottomOffset:4] autorelease];
627- (BOOL)acceptTouchesBegan:(QPointF)touchPoint
629 QRectF inputRect = QPlatformInputContext::inputItemRectangle();
630 return !hasSelection() && inputRect.contains(touchPoint);
633- (
void)updateFocalPoint:(QPointF)touchPoint
635 self.focalPoint = touchPoint;
637 const int currentCursorPos = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt();
638 const int newCursorPos = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, touchPoint).toInt();
639 if (newCursorPos != currentCursorPos)
640 QPlatformInputContext::setSelectionOnFocusObject(touchPoint, touchPoint);
648
649
650
651
652@interface QIOSSelectionRecognizer : QIOSLoupeRecognizer
655@implementation QIOSSelectionRecognizer {
656 CALayer *_clipRectLayer;
657 QIOSHandleLayer *_cursorLayer;
658 QIOSHandleLayer *_anchorLayer;
659 QPointF _touchOffset;
663 QTimer _updateSelectionTimer;
664 QMetaObject::Connection _cursorConnection;
665 QMetaObject::Connection _anchorConnection;
666 QMetaObject::Connection _clipRectConnection;
671 if (self = [super init]) {
672 self.delaysTouchesBegan = YES;
673 self.dragTriggersGesture = YES;
674 _multiLine = QInputMethod::queryFocusObject(Qt::ImHints, QVariant()).toUInt() & Qt::ImhMultiLine;
675 _updateSelectionTimer.setInterval(1);
676 _updateSelectionTimer.setSingleShot(
true);
677 QObject::connect(&_updateSelectionTimer, &QTimer::timeout, [self](){ [self updateSelection]; });
683- (
void)setEnabled:(BOOL)enabled
685 if (enabled == self.enabled)
688 [super setEnabled:enabled];
692 _clipRectLayer = [CALayer
new];
693 _clipRectLayer.masksToBounds = YES;
694 [self.focusView.layer addSublayer:_clipRectLayer];
697 _cursorLayer = [[[QIOSHandleLayer alloc] initWithKnobAtEdge:Qt::RightEdge] autorelease];
698 _anchorLayer = [[[QIOSHandleLayer alloc] initWithKnobAtEdge:Qt::LeftEdge] autorelease];
699 bool selection = hasSelection();
700 _cursorLayer.visible = selection;
701 _anchorLayer.visible = selection;
702 [_clipRectLayer addSublayer:_cursorLayer];
703 [_clipRectLayer addSublayer:_anchorLayer];
710 QInputMethod *im = QGuiApplication::inputMethod();
711 void(QTimer::*start)(
void) = &QTimer::start;
712 _cursorConnection = QObject::connect(im, &QInputMethod::cursorRectangleChanged, &_updateSelectionTimer, start);
713 _anchorConnection = QObject::connect(im, &QInputMethod::anchorRectangleChanged, &_updateSelectionTimer, start);
714 _clipRectConnection = QObject::connect(im, &QInputMethod::inputItemClipRectangleChanged, &_updateSelectionTimer, start);
716 [self updateSelection];
724 __block CALayer *clipRectLayer = _clipRectLayer;
725 __block
int handleCount = 2;
727 if (--handleCount == 0) {
728 [clipRectLayer removeFromSuperlayer];
729 [clipRectLayer release];
733 _cursorLayer.onAnimationDidStop = block;
734 _anchorLayer.onAnimationDidStop = block;
735 _cursorLayer.visible = NO;
736 _anchorLayer.visible = NO;
741 _updateSelectionTimer.stop();
743 QObject::disconnect(_cursorConnection);
744 QObject::disconnect(_anchorConnection);
745 QObject::disconnect(_clipRectConnection);
747 if (QIOSTextInputOverlay::s_editMenu.shownByUs)
748 QIOSTextInputOverlay::s_editMenu.visible = NO;
752- (QIOSLoupeLayer *)createLoupeLayer
754 CGSize loupeSize = CGSizeMake(123, 33);
755 CGSize arrowSize = CGSizeMake(25, 12);
756 CGFloat loupeOffset = arrowSize.height + 20;
759 QIOSLoupeLayer *loupeLayer = [[[QIOSLoupeLayer alloc] initWithSize:loupeSize cornerRadius:5 bottomOffset:loupeOffset] autorelease];
760 CAShapeLayer *arrowLayer = [[[CAShapeLayer alloc] init] autorelease];
763 UIBezierPath *path = [[UIBezierPath
new] autorelease];
764 [path moveToPoint:CGPointMake(0, 0)];
765 [path addLineToPoint:CGPointMake(arrowSize.width / 2, arrowSize.height)];
766 [path addLineToPoint:CGPointMake(arrowSize.width, 0)];
768 arrowLayer.frame = CGRectMake((loupeSize.width - arrowSize.width) / 2, loupeSize.height - 1, arrowSize.width, arrowSize.height);
769 arrowLayer.path = path.CGPath;
770 arrowLayer.backgroundColor = [[UIColor whiteColor] CGColor];
771 arrowLayer.strokeColor = [[UIColor lightGrayColor] CGColor];
772 arrowLayer.lineWidth = 0.75 * 2;
773 arrowLayer.fillColor = nil;
775 CAShapeLayer *mask = [[CAShapeLayer
new] autorelease];
776 mask.frame = arrowLayer.bounds;
777 mask.path = path.CGPath;
778 arrowLayer.mask = mask;
780 [loupeLayer addSublayer:arrowLayer];
785- (BOOL)acceptTouchesBegan:(QPointF)touchPoint
791 const int handleRadius = 50;
792 QPointF cursorCenter = QPlatformInputContext::cursorRectangle().center();
793 QPointF anchorCenter = QPlatformInputContext::anchorRectangle().center();
794 QPointF cursorOffset = QPointF(cursorCenter.x() - touchPoint.x(), cursorCenter.y() - touchPoint.y());
795 QPointF anchorOffset = QPointF(anchorCenter.x() - touchPoint.x(), anchorCenter.y() - touchPoint.y());
796 double cursorDist = hypot(cursorOffset.x(), cursorOffset.y());
797 double anchorDist = hypot(anchorOffset.x(), anchorOffset.y());
799 if (cursorDist > handleRadius && anchorDist > handleRadius)
802 if (cursorDist < anchorDist) {
803 _touchOffset = cursorOffset;
807 _touchOffset = anchorOffset;
815- (
void)updateFocalPoint:(QPointF)touchPoint
817 touchPoint += _touchOffset;
820 SelectionPair selection = querySelection();
821 int touchTextPos = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, touchPoint).toInt();
825 selection.second = (touchTextPos > selection.first) ? touchTextPos : selection.first + 1;
827 selection.first = (touchTextPos < selection.second) ? touchTextPos : selection.second - 1;
830 QList<QInputMethodEvent::Attribute> imAttributes;
831 imAttributes.append(QInputMethodEvent::Attribute(
832 QInputMethodEvent::Selection, selection.first, selection.second - selection.first, QVariant()));
833 QInputMethodEvent event(QString(), imAttributes);
834 QGuiApplication::sendEvent(qApp->focusObject(), &event);
837 QRectF handleRect = _dragOnCursor ?
838 QPlatformInputContext::cursorRectangle() :
839 QPlatformInputContext::anchorRectangle();
840 self.focalPoint = QPointF(touchPoint.x(), handleRect.center().y());
843- (
void)updateSelection
845 if (!hasSelection()) {
846 if (_cursorLayer.visible) {
847 _cursorLayer.visible = NO;
848 _anchorLayer.visible = NO;
850 if (QIOSTextInputOverlay::s_editMenu.shownByUs)
851 QIOSTextInputOverlay::s_editMenu.visible = NO;
855 if (!_cursorLayer.visible && QIOSTextInputOverlay::s_editMenu.isHiding) {
860 QIOSTextInputOverlay::s_editMenu.reshowAfterHidden = YES;
864 QRectF inputRect = QPlatformInputContext::inputItemClipRectangle();
865 CGRect cursorRect = QPlatformInputContext::cursorRectangle().toCGRect();
866 CGRect anchorRect = QPlatformInputContext::anchorRectangle().toCGRect();
871 int margin = kKnobWidth + 5;
872 inputRect.adjust(-margin / 2, -margin, margin / 2, margin);
875 executeBlockWithoutAnimation(^{ _clipRectLayer.frame = inputRect.toCGRect(); });
876 _cursorLayer.cursorRectangle = [self.focusView.layer convertRect:cursorRect toLayer:_clipRectLayer];
877 _anchorLayer.cursorRectangle = [self.focusView.layer convertRect:anchorRect toLayer:_clipRectLayer];
878 _cursorLayer.visible = YES;
879 _anchorLayer.visible = YES;
887
888
889
890
891@interface QIOSTapRecognizer : UITapGestureRecognizer
894@implementation QIOSTapRecognizer {
895 int _cursorPosOnPress;
896 bool _menuShouldBeVisible;
902 if (self = [super initWithTarget:self action:@selector(gestureStateChanged)]) {
909- (
void)setEnabled:(BOOL)enabled
911 if (enabled == self.enabled)
914 [super setEnabled:enabled];
917 _focusView = [
reinterpret_cast<UIView *>(qApp->focusWindow()->winId()) retain];
918 [_focusView addGestureRecognizer:self];
920 [_focusView removeGestureRecognizer:self];
921 [_focusView release];
926- (
void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
928 [super touchesBegan:touches withEvent:event];
930 QRectF inputRect = QPlatformInputContext::inputItemClipRectangle();
931 QPointF touchPos = QPointF::fromCGPoint([
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView]);
932 const bool touchInsideInputArea = inputRect.contains(touchPos);
934 if (touchInsideInputArea && hasSelection()) {
940 self.state = UIGestureRecognizerStateFailed;
944 if (QIOSTextInputOverlay::s_editMenu.visible) {
956 if (!touchInsideInputArea) {
959 self.state = UIGestureRecognizerStateFailed;
966 _cursorPosOnPress = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt();
969- (
void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
971 if (QIOSTextInputOverlay::s_editMenu.visible) {
972 _menuShouldBeVisible =
false;
974 QPointF touchPos = QPointF::fromCGPoint([
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView]);
975 int cursorPosOnRelease = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, touchPos).toInt();
977 if (cursorPosOnRelease == _cursorPosOnPress) {
984 _menuShouldBeVisible =
true;
985 self.state = UIGestureRecognizerStateFailed;
986 dispatch_async(dispatch_get_main_queue(), ^{
987 if (_menuShouldBeVisible)
988 showEditMenu(_focusView, touchPos.toPoint());
990 QIOSTextInputOverlay::s_editMenu.visible =
false;
996 self.state = UIGestureRecognizerStateFailed;
1000 [super touchesEnded:touches withEvent:event];
1003- (
void)gestureStateChanged
1005 if (self.state != UIGestureRecognizerStateEnded)
1008 QIOSTextInputOverlay::s_editMenu.visible = _menuShouldBeVisible;
1017QIOSEditMenu *QIOSTextInputOverlay::s_editMenu =
nullptr;
1019QIOSTextInputOverlay::QIOSTextInputOverlay()
1020 : m_cursorRecognizer(
nullptr)
1021 , m_selectionRecognizer(
nullptr)
1022 , m_openMenuOnTapRecognizer(
nullptr)
1024 if (qt_apple_isApplicationExtension()) {
1025 qWarning() <<
"text input overlays disabled in application extensions";
1029 connect(qApp, &QGuiApplication::focusObjectChanged,
this, &QIOSTextInputOverlay::updateFocusObject);
1032QIOSTextInputOverlay::~QIOSTextInputOverlay()
1035 disconnect(qApp, 0,
this, 0);
1038void QIOSTextInputOverlay::updateFocusObject()
1042 if (m_cursorRecognizer) {
1043 m_cursorRecognizer.enabled = NO;
1044 [m_cursorRecognizer release];
1045 m_cursorRecognizer =
nullptr;
1047 if (m_selectionRecognizer) {
1048 m_selectionRecognizer.enabled = NO;
1049 [m_selectionRecognizer release];
1050 m_selectionRecognizer =
nullptr;
1052 if (m_openMenuOnTapRecognizer) {
1053 m_openMenuOnTapRecognizer.enabled = NO;
1054 [m_openMenuOnTapRecognizer release];
1055 m_openMenuOnTapRecognizer =
nullptr;
1059 [s_editMenu release];
1060 s_editMenu =
nullptr;
1063 const QVariant hintsVariant = QGuiApplication::inputMethod()->queryFocusObject(Qt::ImHints, QVariant());
1064 const Qt::InputMethodHints hints = Qt::InputMethodHints(hintsVariant.toUInt());
1065 if (hints & Qt::ImhNoTextHandles)
1080 const bool inputAccepted = platformInputContext()->inputMethodAccepted();
1081 const bool readOnly = QGuiApplication::inputMethod()->queryFocusObject(Qt::ImReadOnly, QVariant()).toBool();
1083 if (inputAccepted || readOnly) {
1084 if (!(hints & Qt::ImhNoEditMenu))
1085 s_editMenu = [QIOSEditMenu
new];
1086 m_selectionRecognizer = [QIOSSelectionRecognizer
new];
1087 m_openMenuOnTapRecognizer = [QIOSTapRecognizer
new];
1088 m_selectionRecognizer.enabled = YES;
1089 m_openMenuOnTapRecognizer.enabled = YES;
1092 if (inputAccepted) {
1093 m_cursorRecognizer = [QIOSCursorRecognizer
new];
1094 m_cursorRecognizer.enabled = YES;
static SelectionPair querySelection()
static const CGFloat kKnobWidth
static bool hasSelection()
std::pair< int, int > SelectionPair
static void executeBlockWithoutAnimation(Block block)
static QPlatformInputContext * platformInputContext()