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 QInputMethodQueryEvent query(Qt::ImAnchorPosition | Qt::ImCursorPosition);
36 QGuiApplication::sendEvent(QGuiApplication::focusObject(), &query);
37 int anchorPos = query.value(Qt::ImAnchorPosition).toInt();
38 int cursorPos = query.value(Qt::ImCursorPosition).toInt();
39 return {anchorPos, cursorPos};
45 return selection.first != selection.second;
50 [CATransaction begin];
51 [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
53 [CATransaction commit];
58
59
60
61@interface QIOSEditMenu : NSObject
62@property (nonatomic, assign) BOOL visible;
63@property (nonatomic, readonly) BOOL isHiding;
64@property (nonatomic, readonly) BOOL shownByUs;
65@property (nonatomic, assign) BOOL reshowAfterHidden;
68@implementation QIOSEditMenu
72 if (self = [super init]) {
73 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
75 [center addObserverForName:UIMenuControllerWillHideMenuNotification
76 object:nil queue:nil usingBlock:^(NSNotification *) {
80 [center addObserverForName:UIMenuControllerDidHideMenuNotification
81 object:nil queue:nil usingBlock:^(NSNotification *) {
84 if (self.reshowAfterHidden) {
87 self.reshowAfterHidden = NO;
88 dispatch_async(dispatch_get_main_queue (), ^{ self.visible = YES; });
91 [center addObserverForName:UIKeyboardDidHideNotification object:nil queue:nil
92 usingBlock:^(NSNotification *) {
103 [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:nil];
109 return [UIMenuController sharedMenuController].menuVisible;
112- (
void)setVisible:(BOOL)visible
114 if (visible == self.visible)
124 QRectF cr = QPlatformInputContext::cursorRectangle();
125 QRectF ar = QPlatformInputContext::anchorRectangle();
127 CGRect targetRect = cr.united(ar).toCGRect();
128 UIView *focusView =
reinterpret_cast<UIView *>(qApp->focusWindow()->winId());
129 [[UIMenuController sharedMenuController] setTargetRect:targetRect inView:focusView];
130 [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
132 [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
138void showEditMenu(UIView *focusView, QPoint touchPos)
140 const bool mouseTriggered =
false;
141 const Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier;
142 QWindow *qtWindow = quiview_cast(focusView).platformWindow->window();
143 const auto globalTouchPos = qtWindow->mapToGlobal(touchPos);
144 const bool contextMenuEventAccepted = QWindowSystemInterface::handleContextMenuEvent<
145 QWindowSystemInterface::SynchronousDelivery>(qtWindow, mouseTriggered, touchPos,
146 globalTouchPos, keyboardModifiers);
148 if (!contextMenuEventAccepted) {
152 QIOSTextInputOverlay::s_editMenu.visible = YES;
158@interface QIOSLoupeLayer : CALayer
159@property (nonatomic, retain) UIView *targetView;
160@property (nonatomic, assign) CGPoint focalPoint;
161@property (nonatomic, assign) BOOL visible;
164@implementation QIOSLoupeLayer {
165 UIView *_snapshotView;
166 BOOL _pendingSnapshotUpdate;
167 UIView *_loupeImageView;
168 CALayer *_containerLayer;
169 CGFloat _loupeOffset;
173- (instancetype)initWithSize:(CGSize)size cornerRadius:(CGFloat)cornerRadius bottomOffset:(CGFloat)bottomOffset
175 if (self = [super init]) {
176 _loupeOffset = bottomOffset + (size.height / 2);
178 _pendingSnapshotUpdate = YES;
179 _updateTimer.setInterval(100);
180 _updateTimer.setSingleShot(
true);
181 QObject::connect(&_updateTimer, &QTimer::timeout, [self](){ [self updateSnapshot]; });
184 self.frame = CGRectMake(0, 0, size.width, size.height);
185 self.cornerRadius = cornerRadius;
186 self.shadowColor = [[UIColor grayColor] CGColor];
187 self.shadowOffset = CGSizeMake(0, 1);
188 self.shadowRadius = 2.0;
189 self.shadowOpacity = 0.75;
190 self.transform = CATransform3DMakeScale(0, 0, 0);
193 _containerLayer = [[CALayer
new] autorelease];
194 _containerLayer.frame = self.bounds;
195 _containerLayer.cornerRadius = cornerRadius;
196 _containerLayer.masksToBounds = YES;
197 [self addSublayer:_containerLayer];
200 const CGFloat inset = 30;
201 CALayer *topShadeLayer = [[CALayer
new] autorelease];
202 topShadeLayer.frame = CGRectOffset(CGRectInset(self.bounds, -inset, -inset), 0, inset / 2);
203 topShadeLayer.borderWidth = inset / 2;
204 topShadeLayer.cornerRadius = cornerRadius;
205 topShadeLayer.borderColor = [[UIColor blackColor] CGColor];
206 topShadeLayer.shadowColor = [[UIColor blackColor] CGColor];
207 topShadeLayer.shadowOffset = CGSizeMake(0, 0);
208 topShadeLayer.shadowRadius = 15.0;
209 topShadeLayer.shadowOpacity = 0.6;
211 CALayer *mask = [[CALayer
new] autorelease];
212 mask.frame = CGRectOffset(self.bounds, inset, inset / 2);
213 mask.backgroundColor = [[UIColor blackColor] CGColor];
214 mask.cornerRadius = cornerRadius;
215 topShadeLayer.mask = mask;
216 [self addSublayer:topShadeLayer];
221 CALayer *borderLayer = [[CALayer
new] autorelease];
222 borderLayer.frame = self.bounds;
223 borderLayer.borderWidth = 0.75;
224 borderLayer.cornerRadius = cornerRadius;
225 borderLayer.borderColor = [[UIColor lightGrayColor] CGColor];
226 [self addSublayer:borderLayer];
238- (
void)setVisible:(BOOL)visible
240 if (_visible == visible)
245 dispatch_async(dispatch_get_main_queue (), ^{
248 self.transform = _visible ? CATransform3DMakeScale(1, 1, 1) : CATransform3DMakeScale(0.0, 0.0, 1);
252- (
void)updateSnapshot
254 _pendingSnapshotUpdate = YES;
255 [self setNeedsDisplay];
258- (
void)setFocalPoint:(CGPoint)point
261 [self updateSnapshot];
265 _updateTimer.start();
274 executeBlockWithoutAnimation(^{
275 if (_pendingSnapshotUpdate) {
276 UIView *newSnapshot = [_targetView snapshotViewAfterScreenUpdates:NO];
277 [_snapshotView.layer removeFromSuperlayer];
278 [_snapshotView release];
279 _snapshotView = [newSnapshot retain];
280 [_containerLayer addSublayer:_snapshotView.layer];
281 _pendingSnapshotUpdate = NO;
284 self.position = CGPointMake(_focalPoint.x, _focalPoint.y - _loupeOffset);
286 const CGFloat loupeScale = 1.5;
287 CGFloat x = -(_focalPoint.x * loupeScale) + self.frame.size.width / 2;
288 CGFloat y = -(_focalPoint.y * loupeScale) + self.frame.size.height / 2;
289 CGFloat w = _targetView.frame.size.width * loupeScale;
290 CGFloat h = _targetView.frame.size.height * loupeScale;
291 _snapshotView.layer.frame = CGRectMake(x, y, w, h);
299@interface QIOSHandleLayer : CALayer <CAAnimationDelegate>
300@property (nonatomic, assign) CGRect cursorRectangle;
301@property (nonatomic, assign) CGFloat handleScale;
302@property (nonatomic, assign) BOOL visible;
303@property (nonatomic, copy) Block onAnimationDidStop;
306@implementation QIOSHandleLayer {
307 CALayer *_handleCursorLayer;
308 CALayer *_handleKnobLayer;
309 Qt::Edge _selectionEdge;
314- (instancetype)initWithKnobAtEdge:(Qt::Edge)selectionEdge
316 if (self = [super init]) {
317 CGColorRef bgColor = [UIColor colorWithRed:0.1 green:0.4 blue:0.9 alpha:1].CGColor;
318 _selectionEdge = selectionEdge;
319 self.handleScale = 0;
321 _handleCursorLayer = [[CALayer
new] autorelease];
322 _handleCursorLayer.masksToBounds = YES;
323 _handleCursorLayer.backgroundColor = bgColor;
324 [self addSublayer:_handleCursorLayer];
326 _handleKnobLayer = [[CALayer
new] autorelease];
327 _handleKnobLayer.masksToBounds = YES;
328 _handleKnobLayer.backgroundColor = bgColor;
329 _handleKnobLayer.cornerRadius = kKnobWidth / 2;
330 [self addSublayer:_handleKnobLayer];
335+ (BOOL)needsDisplayForKey:(NSString *)key
337 if ([key isEqualToString:@
"handleScale"])
339 return [super needsDisplayForKey:key];
342- (id<CAAction>)actionForKey:(NSString *)key
344 if ([key isEqualToString:@
"handleScale"]) {
347 CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:key];
348 [animation setDuration:0.5];
349 animation.values = @[@(0.0f), @(1.3f), @(1.3f), @(1.0f)];
350 animation.keyTimes = @[@(0.0f), @(0.3f), @(0.9f), @(1.0f)];
353 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
354 [animation setDelegate:self];
355 animation.fromValue = [self valueForKey:key];
356 [animation setDuration:0.2];
360 return [super actionForKey:key];
363- (
void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
367 if (self.onAnimationDidStop)
368 self.onAnimationDidStop();
371- (
void)setVisible:(BOOL)visible
373 if (visible == _visible)
378 self.handleScale = visible ? 1 : 0;
381- (
void)setCursorRectangle:(CGRect)cursorRect
383 if (CGRectEqualToRect(_cursorRectangle, cursorRect))
386 _cursorRectangle = cursorRect;
388 executeBlockWithoutAnimation(^{
389 [self setNeedsDisplay];
390 [self displayIfNeeded];
396 CGFloat cursorWidth = 2;
397 CGPoint origin = _cursorRectangle.origin;
398 CGSize size = _cursorRectangle.size;
399 CGFloat scale = ((QIOSHandleLayer *)[self presentationLayer]).handleScale;
400 CGFloat edgeAdjustment = (_selectionEdge == Qt::LeftEdge) ? 0.5 - cursorWidth : -0.5;
402 CGFloat cursorX = origin.x + (size.width / 2) + edgeAdjustment;
403 CGFloat cursorY = origin.y;
404 CGFloat knobX = cursorX - (kKnobWidth - cursorWidth) / 2;
405 CGFloat knobY = origin.y + ((_selectionEdge == Qt::LeftEdge) ? -kKnobWidth : size.height);
407 _handleCursorLayer.frame = CGRectMake(cursorX, cursorY, cursorWidth, size.height);
408 _handleKnobLayer.frame = CGRectMake(knobX, knobY, kKnobWidth, kKnobWidth);
409 _handleCursorLayer.transform = CATransform3DMakeScale(1, scale, scale);
410 _handleKnobLayer.transform = CATransform3DMakeScale(scale, scale, scale);
418
419
420
421
422@interface QIOSLoupeRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
423@property (nonatomic, assign) QPointF focalPoint;
424@property (nonatomic, assign) BOOL dragTriggersGesture;
425@property (nonatomic, readonly) UIView *focusView;
428@implementation QIOSLoupeRecognizer {
429 QIOSLoupeLayer *_loupeLayer;
430 UIView *_desktopView;
431 CGPoint _firstTouchPoint;
432 CGPoint _lastTouchPoint;
433 QTimer _triggerStateBeganTimer;
434 int _originalCursorFlashTime;
439 if (self = [super initWithTarget:self action:@selector(gestureStateChanged)]) {
441 _triggerStateBeganTimer.setInterval(QGuiApplication::styleHints()->startDragTime());
442 _triggerStateBeganTimer.setSingleShot(
true);
443 QObject::connect(&_triggerStateBeganTimer, &QTimer::timeout, [=](){
444 self.state = UIGestureRecognizerStateBegan;
451- (
void)setEnabled:(BOOL)enabled
453 if (enabled == self.enabled)
456 [super setEnabled:enabled];
459 _focusView = [
reinterpret_cast<UIView *>(qApp->focusWindow()->winId()) retain];
460 _desktopView = [presentationWindow(
nullptr).rootViewController.view retain];
461 Q_ASSERT(_focusView && _desktopView && _desktopView.superview);
462 [_desktopView addGestureRecognizer:self];
464 [_desktopView removeGestureRecognizer:self];
465 [_desktopView release];
467 [_focusView release];
469 _triggerStateBeganTimer.stop();
471 [_loupeLayer removeFromSuperlayer];
472 [_loupeLayer release];
478- (
void)gestureStateChanged
480 switch (self.state) {
481 case UIGestureRecognizerStateBegan:
483 _originalCursorFlashTime = QGuiApplication::styleHints()->cursorFlashTime();
484 QGuiApplication::styleHints()->setCursorFlashTime(0);
487 [self updateFocalPoint:QPointF::fromCGPoint(_lastTouchPoint)];
488 _loupeLayer.visible = YES;
489 QIOSTextInputOverlay::s_editMenu.visible = NO;
491 case UIGestureRecognizerStateChanged:
493 [self updateFocalPoint:QPointF::fromCGPoint(_lastTouchPoint)];
495 case UIGestureRecognizerStateEnded: {
497 QGuiApplication::styleHints()->setCursorFlashTime(_originalCursorFlashTime);
498 const QPoint touchPos = QPointF::fromCGPoint(_lastTouchPoint).toPoint();
499 showEditMenu(_focusView, touchPos);
500 _loupeLayer.visible = NO;
504 _loupeLayer.visible = NO;
513 _loupeLayer = [[self createLoupeLayer] retain];
514 _loupeLayer.targetView = _desktopView;
515 [_desktopView.superview.layer addSublayer:_loupeLayer];
520 return QPointF::fromCGPoint([_loupeLayer.targetView convertPoint:_loupeLayer.focalPoint toView:_focusView]);
523- (
void)setFocalPoint:(QPointF)point
525 _loupeLayer.focalPoint = [_loupeLayer.targetView convertPoint:point.toCGPoint() fromView:_focusView];
528- (
void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
530 [super touchesBegan:touches withEvent:event];
531 if ([event allTouches].count > 1) {
533 self.state = UIGestureRecognizerStateFailed;
537 _firstTouchPoint = [
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView];
538 _lastTouchPoint = _firstTouchPoint;
542 if ([self acceptTouchesBegan:QPointF::fromCGPoint(_firstTouchPoint)])
543 _triggerStateBeganTimer.start();
545 self.state = UIGestureRecognizerStateFailed;
548- (
void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
550 [super touchesMoved:touches withEvent:event];
551 _lastTouchPoint = [
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView];
553 if (self.state == UIGestureRecognizerStatePossible) {
557 int startDragDistance = QGuiApplication::styleHints()->startDragDistance();
558 int dragDistance = hypot(_firstTouchPoint.x - _lastTouchPoint.x, _firstTouchPoint.y - _lastTouchPoint.y);
559 if (dragDistance > startDragDistance) {
560 _triggerStateBeganTimer.stop();
561 self.state = self.dragTriggersGesture ? UIGestureRecognizerStateBegan : UIGestureRecognizerStateFailed;
564 self.state = UIGestureRecognizerStateChanged;
568- (
void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
570 [super touchesEnded:touches withEvent:event];
571 _triggerStateBeganTimer.stop();
572 _lastTouchPoint = [
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView];
573 self.state = self.state == UIGestureRecognizerStatePossible ? UIGestureRecognizerStateFailed : UIGestureRecognizerStateEnded;
576- (
void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
578 [super touchesCancelled:touches withEvent:event];
579 _triggerStateBeganTimer.stop();
580 _lastTouchPoint = [
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView];
581 self.state = UIGestureRecognizerStateCancelled;
586- (BOOL)acceptTouchesBegan:(QPointF)touchPoint
588 Q_UNUSED(touchPoint);
593- (QIOSLoupeLayer *)createLoupeLayer
599- (
void)updateFocalPoint:(QPointF)touchPoint
601 Q_UNUSED(touchPoint);
610
611
612
613
614@interface QIOSCursorRecognizer : QIOSLoupeRecognizer
617@implementation QIOSCursorRecognizer
619- (QIOSLoupeLayer *)createLoupeLayer
621 return [[[QIOSLoupeLayer alloc] initWithSize:CGSizeMake(120, 120) cornerRadius:60 bottomOffset:4] autorelease];
624- (BOOL)acceptTouchesBegan:(QPointF)touchPoint
626 QRectF inputRect = QPlatformInputContext::inputItemRectangle();
627 return !hasSelection() && inputRect.contains(touchPoint);
630- (
void)updateFocalPoint:(QPointF)touchPoint
632 self.focalPoint = touchPoint;
634 const int currentCursorPos = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt();
635 const int newCursorPos = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, touchPoint).toInt();
636 if (newCursorPos != currentCursorPos)
637 QPlatformInputContext::setSelectionOnFocusObject(touchPoint, touchPoint);
645
646
647
648
649@interface QIOSSelectionRecognizer : QIOSLoupeRecognizer
652@implementation QIOSSelectionRecognizer {
653 CALayer *_clipRectLayer;
654 QIOSHandleLayer *_cursorLayer;
655 QIOSHandleLayer *_anchorLayer;
656 QPointF _touchOffset;
660 QTimer _updateSelectionTimer;
661 QMetaObject::Connection _cursorConnection;
662 QMetaObject::Connection _anchorConnection;
663 QMetaObject::Connection _clipRectConnection;
668 if (self = [super init]) {
669 self.delaysTouchesBegan = YES;
670 self.dragTriggersGesture = YES;
671 _multiLine = QInputMethod::queryFocusObject(Qt::ImHints, QVariant()).toUInt() & Qt::ImhMultiLine;
672 _updateSelectionTimer.setInterval(1);
673 _updateSelectionTimer.setSingleShot(
true);
674 QObject::connect(&_updateSelectionTimer, &QTimer::timeout, [self](){ [self updateSelection]; });
680- (
void)setEnabled:(BOOL)enabled
682 if (enabled == self.enabled)
685 [super setEnabled:enabled];
689 _clipRectLayer = [CALayer
new];
690 _clipRectLayer.masksToBounds = YES;
691 [self.focusView.layer addSublayer:_clipRectLayer];
694 _cursorLayer = [[[QIOSHandleLayer alloc] initWithKnobAtEdge:Qt::RightEdge] autorelease];
695 _anchorLayer = [[[QIOSHandleLayer alloc] initWithKnobAtEdge:Qt::LeftEdge] autorelease];
696 bool selection = hasSelection();
697 _cursorLayer.visible = selection;
698 _anchorLayer.visible = selection;
699 [_clipRectLayer addSublayer:_cursorLayer];
700 [_clipRectLayer addSublayer:_anchorLayer];
707 QInputMethod *im = QGuiApplication::inputMethod();
708 void(QTimer::*start)(
void) = &QTimer::start;
709 _cursorConnection = QObject::connect(im, &QInputMethod::cursorRectangleChanged, &_updateSelectionTimer, start);
710 _anchorConnection = QObject::connect(im, &QInputMethod::anchorRectangleChanged, &_updateSelectionTimer, start);
711 _clipRectConnection = QObject::connect(im, &QInputMethod::inputItemClipRectangleChanged, &_updateSelectionTimer, start);
713 [self updateSelection];
721 __block CALayer *clipRectLayer = _clipRectLayer;
722 __block
int handleCount = 2;
724 if (--handleCount == 0) {
725 [clipRectLayer removeFromSuperlayer];
726 [clipRectLayer release];
730 _cursorLayer.onAnimationDidStop = block;
731 _anchorLayer.onAnimationDidStop = block;
732 _cursorLayer.visible = NO;
733 _anchorLayer.visible = NO;
738 _updateSelectionTimer.stop();
740 QObject::disconnect(_cursorConnection);
741 QObject::disconnect(_anchorConnection);
742 QObject::disconnect(_clipRectConnection);
744 if (QIOSTextInputOverlay::s_editMenu.shownByUs)
745 QIOSTextInputOverlay::s_editMenu.visible = NO;
749- (QIOSLoupeLayer *)createLoupeLayer
751 CGSize loupeSize = CGSizeMake(123, 33);
752 CGSize arrowSize = CGSizeMake(25, 12);
753 CGFloat loupeOffset = arrowSize.height + 20;
756 QIOSLoupeLayer *loupeLayer = [[[QIOSLoupeLayer alloc] initWithSize:loupeSize cornerRadius:5 bottomOffset:loupeOffset] autorelease];
757 CAShapeLayer *arrowLayer = [[[CAShapeLayer alloc] init] autorelease];
760 UIBezierPath *path = [[UIBezierPath
new] autorelease];
761 [path moveToPoint:CGPointMake(0, 0)];
762 [path addLineToPoint:CGPointMake(arrowSize.width / 2, arrowSize.height)];
763 [path addLineToPoint:CGPointMake(arrowSize.width, 0)];
765 arrowLayer.frame = CGRectMake((loupeSize.width - arrowSize.width) / 2, loupeSize.height - 1, arrowSize.width, arrowSize.height);
766 arrowLayer.path = path.CGPath;
767 arrowLayer.backgroundColor = [[UIColor whiteColor] CGColor];
768 arrowLayer.strokeColor = [[UIColor lightGrayColor] CGColor];
769 arrowLayer.lineWidth = 0.75 * 2;
770 arrowLayer.fillColor = nil;
772 CAShapeLayer *mask = [[CAShapeLayer
new] autorelease];
773 mask.frame = arrowLayer.bounds;
774 mask.path = path.CGPath;
775 arrowLayer.mask = mask;
777 [loupeLayer addSublayer:arrowLayer];
782- (BOOL)acceptTouchesBegan:(QPointF)touchPoint
788 const int handleRadius = 50;
789 QPointF cursorCenter = QPlatformInputContext::cursorRectangle().center();
790 QPointF anchorCenter = QPlatformInputContext::anchorRectangle().center();
791 QPointF cursorOffset = QPointF(cursorCenter.x() - touchPoint.x(), cursorCenter.y() - touchPoint.y());
792 QPointF anchorOffset = QPointF(anchorCenter.x() - touchPoint.x(), anchorCenter.y() - touchPoint.y());
793 double cursorDist = hypot(cursorOffset.x(), cursorOffset.y());
794 double anchorDist = hypot(anchorOffset.x(), anchorOffset.y());
796 if (cursorDist > handleRadius && anchorDist > handleRadius)
799 if (cursorDist < anchorDist) {
800 _touchOffset = cursorOffset;
804 _touchOffset = anchorOffset;
812- (
void)updateFocalPoint:(QPointF)touchPoint
814 touchPoint += _touchOffset;
817 SelectionPair selection = querySelection();
818 int touchTextPos = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, touchPoint).toInt();
822 selection.second = (touchTextPos > selection.first) ? touchTextPos : selection.first + 1;
824 selection.first = (touchTextPos < selection.second) ? touchTextPos : selection.second - 1;
827 QList<QInputMethodEvent::Attribute> imAttributes;
828 imAttributes.append(QInputMethodEvent::Attribute(
829 QInputMethodEvent::Selection, selection.first, selection.second - selection.first, QVariant()));
830 QInputMethodEvent event(QString(), imAttributes);
831 QGuiApplication::sendEvent(qApp->focusObject(), &event);
834 QRectF handleRect = _dragOnCursor ?
835 QPlatformInputContext::cursorRectangle() :
836 QPlatformInputContext::anchorRectangle();
837 self.focalPoint = QPointF(touchPoint.x(), handleRect.center().y());
840- (
void)updateSelection
842 if (!hasSelection()) {
843 if (_cursorLayer.visible) {
844 _cursorLayer.visible = NO;
845 _anchorLayer.visible = NO;
847 if (QIOSTextInputOverlay::s_editMenu.shownByUs)
848 QIOSTextInputOverlay::s_editMenu.visible = NO;
852 if (!_cursorLayer.visible && QIOSTextInputOverlay::s_editMenu.isHiding) {
857 QIOSTextInputOverlay::s_editMenu.reshowAfterHidden = YES;
861 QRectF inputRect = QPlatformInputContext::inputItemClipRectangle();
862 CGRect cursorRect = QPlatformInputContext::cursorRectangle().toCGRect();
863 CGRect anchorRect = QPlatformInputContext::anchorRectangle().toCGRect();
868 int margin = kKnobWidth + 5;
869 inputRect.adjust(-margin / 2, -margin, margin / 2, margin);
872 executeBlockWithoutAnimation(^{ _clipRectLayer.frame = inputRect.toCGRect(); });
873 _cursorLayer.cursorRectangle = [self.focusView.layer convertRect:cursorRect toLayer:_clipRectLayer];
874 _anchorLayer.cursorRectangle = [self.focusView.layer convertRect:anchorRect toLayer:_clipRectLayer];
875 _cursorLayer.visible = YES;
876 _anchorLayer.visible = YES;
884
885
886
887
888@interface QIOSTapRecognizer : UITapGestureRecognizer
891@implementation QIOSTapRecognizer {
892 int _cursorPosOnPress;
893 bool _menuShouldBeVisible;
899 if (self = [super initWithTarget:self action:@selector(gestureStateChanged)]) {
906- (
void)setEnabled:(BOOL)enabled
908 if (enabled == self.enabled)
911 [super setEnabled:enabled];
914 _focusView = [
reinterpret_cast<UIView *>(qApp->focusWindow()->winId()) retain];
915 [_focusView addGestureRecognizer:self];
917 [_focusView removeGestureRecognizer:self];
918 [_focusView release];
923- (
void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
925 [super touchesBegan:touches withEvent:event];
927 QRectF inputRect = QPlatformInputContext::inputItemClipRectangle();
928 QPointF touchPos = QPointF::fromCGPoint([
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView]);
929 const bool touchInsideInputArea = inputRect.contains(touchPos);
931 if (touchInsideInputArea && hasSelection()) {
937 self.state = UIGestureRecognizerStateFailed;
941 if (QIOSTextInputOverlay::s_editMenu.visible) {
953 if (!touchInsideInputArea) {
956 self.state = UIGestureRecognizerStateFailed;
963 _cursorPosOnPress = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt();
966- (
void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
968 if (QIOSTextInputOverlay::s_editMenu.visible) {
969 _menuShouldBeVisible =
false;
971 QPointF touchPos = QPointF::fromCGPoint([
static_cast<UITouch *>([touches anyObject]) locationInView:_focusView]);
972 int cursorPosOnRelease = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, touchPos).toInt();
974 if (cursorPosOnRelease == _cursorPosOnPress) {
981 _menuShouldBeVisible =
true;
982 self.state = UIGestureRecognizerStateFailed;
983 dispatch_async(dispatch_get_main_queue(), ^{
984 if (_menuShouldBeVisible)
985 showEditMenu(_focusView, touchPos.toPoint());
987 QIOSTextInputOverlay::s_editMenu.visible =
false;
993 self.state = UIGestureRecognizerStateFailed;
997 [super touchesEnded:touches withEvent:event];
1000- (
void)gestureStateChanged
1002 if (self.state != UIGestureRecognizerStateEnded)
1005 QIOSTextInputOverlay::s_editMenu.visible = _menuShouldBeVisible;
1014QIOSEditMenu *QIOSTextInputOverlay::s_editMenu =
nullptr;
1016QIOSTextInputOverlay::QIOSTextInputOverlay()
1017 : m_cursorRecognizer(
nullptr)
1018 , m_selectionRecognizer(
nullptr)
1019 , m_openMenuOnTapRecognizer(
nullptr)
1021 if (qt_apple_isApplicationExtension()) {
1022 qWarning() <<
"text input overlays disabled in application extensions";
1026 connect(qApp, &QGuiApplication::focusObjectChanged,
this, &QIOSTextInputOverlay::updateFocusObject);
1029QIOSTextInputOverlay::~QIOSTextInputOverlay()
1032 disconnect(qApp, 0,
this, 0);
1035void QIOSTextInputOverlay::updateFocusObject()
1039 if (m_cursorRecognizer) {
1040 m_cursorRecognizer.enabled = NO;
1041 [m_cursorRecognizer release];
1042 m_cursorRecognizer =
nullptr;
1044 if (m_selectionRecognizer) {
1045 m_selectionRecognizer.enabled = NO;
1046 [m_selectionRecognizer release];
1047 m_selectionRecognizer =
nullptr;
1049 if (m_openMenuOnTapRecognizer) {
1050 m_openMenuOnTapRecognizer.enabled = NO;
1051 [m_openMenuOnTapRecognizer release];
1052 m_openMenuOnTapRecognizer =
nullptr;
1056 [s_editMenu release];
1057 s_editMenu =
nullptr;
1060 const QVariant hintsVariant = QGuiApplication::inputMethod()->queryFocusObject(Qt::ImHints, QVariant());
1061 const Qt::InputMethodHints hints = Qt::InputMethodHints(hintsVariant.toUInt());
1062 if (hints & Qt::ImhNoTextHandles)
1077 const bool inputAccepted = platformInputContext()->inputMethodAccepted();
1078 const bool readOnly = QGuiApplication::inputMethod()->queryFocusObject(Qt::ImReadOnly, QVariant()).toBool();
1080 if (inputAccepted || readOnly) {
1081 if (!(hints & Qt::ImhNoEditMenu))
1082 s_editMenu = [QIOSEditMenu
new];
1083 m_selectionRecognizer = [QIOSSelectionRecognizer
new];
1084 m_openMenuOnTapRecognizer = [QIOSTapRecognizer
new];
1085 m_selectionRecognizer.enabled = YES;
1086 m_openMenuOnTapRecognizer.enabled = YES;
1089 if (inputAccepted) {
1090 m_cursorRecognizer = [QIOSCursorRecognizer
new];
1091 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()