Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ui/base/ios/cru_context_menu_controller.h" | 5 #import "ui/base/ios/cru_context_menu_controller.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/ios/ios_util.h" | |
| 10 #include "base/ios/weak_nsobject.h" | 9 #include "base/ios/weak_nsobject.h" |
| 11 #include "base/logging.h" | 10 #include "base/logging.h" |
| 12 #import "base/mac/scoped_nsobject.h" | 11 #import "base/mac/scoped_nsobject.h" |
| 13 #include "ui/base/device_form_factor.h" | 12 #include "ui/base/device_form_factor.h" |
| 14 #import "ui/base/ios/cru_context_menu_holder.h" | 13 #import "ui/base/ios/cru_context_menu_holder.h" |
| 15 #include "ui/base/l10n/l10n_util.h" | 14 #include "ui/base/l10n/l10n_util.h" |
| 16 #import "ui/gfx/ios/NSString+CrStringDrawing.h" | |
| 17 #include "ui/strings/grit/ui_strings.h" | 15 #include "ui/strings/grit/ui_strings.h" |
| 18 | 16 |
| 19 namespace { | |
| 20 | |
| 21 // Returns the screen's height in points. | |
| 22 CGFloat GetScreenHeight() { | |
| 23 DCHECK(!base::ios::IsRunningOnIOS8OrLater()); | |
| 24 switch ([[UIApplication sharedApplication] statusBarOrientation]) { | |
| 25 case UIInterfaceOrientationLandscapeLeft: | |
| 26 case UIInterfaceOrientationLandscapeRight: | |
| 27 return CGRectGetWidth([[UIScreen mainScreen] bounds]); | |
| 28 case UIInterfaceOrientationPortraitUpsideDown: | |
| 29 case UIInterfaceOrientationPortrait: | |
| 30 case UIInterfaceOrientationUnknown: | |
| 31 return CGRectGetHeight([[UIScreen mainScreen] bounds]); | |
| 32 } | |
| 33 } | |
| 34 | |
| 35 } // namespace | |
| 36 | |
| 37 // Abstracts system implementation of popovers and action sheets. | 17 // Abstracts system implementation of popovers and action sheets. |
| 38 @protocol CRUContextMenuControllerImpl<NSObject> | 18 @protocol CRUContextMenuControllerImpl<NSObject> |
| 39 | 19 |
| 40 // Whether the context menu is visible. | 20 // Whether the context menu is visible. |
| 41 @property(nonatomic, readonly, getter=isVisible) BOOL visible; | 21 @property(nonatomic, readonly, getter=isVisible) BOOL visible; |
| 42 | 22 |
| 43 // Displays a context menu. | 23 // Displays a context menu. |
| 44 - (void)showWithHolder:(CRUContextMenuHolder*)menuHolder | 24 - (void)showWithHolder:(CRUContextMenuHolder*)menuHolder |
| 45 atPoint:(CGPoint)localPoint | 25 atPoint:(CGPoint)localPoint |
| 46 inView:(UIView*)view; | 26 inView:(UIView*)view; |
| 47 @end | 27 @end |
| 48 | 28 |
| 49 // Backs up CRUContextMenuController on iOS 7 by using UIActionSheet. | 29 // Backs up CRUContextMenuController by using UIAlertController. |
| 50 @interface CRUActionSheetController | |
| 51 : NSObject<CRUContextMenuControllerImpl, UIActionSheetDelegate> { | |
| 52 // The action sheet used to display the UI. | |
| 53 base::scoped_nsobject<UIActionSheet> _sheet; | |
| 54 // Holds all the titles and actions for the menu. | |
| 55 base::scoped_nsobject<CRUContextMenuHolder> _menuHolder; | |
| 56 } | |
| 57 @end | |
| 58 | |
| 59 // Backs up CRUContextMenuController on iOS 8 and higher by using | |
| 60 // UIAlertController. | |
| 61 @interface CRUAlertController : NSObject<CRUContextMenuControllerImpl> | 30 @interface CRUAlertController : NSObject<CRUContextMenuControllerImpl> |
|
Eugene But (OOO till 7-30)
2016/02/17 15:06:24
There is no need for using a bridge pattern now an
| |
| 62 // Redefined to readwrite. | 31 // Redefined to readwrite. |
| 63 @property(nonatomic, readwrite, getter=isVisible) BOOL visible; | 32 @property(nonatomic, readwrite, getter=isVisible) BOOL visible; |
| 64 @end | 33 @end |
| 65 | 34 |
| 66 // Displays a context menu. Implements Bridge pattern. | 35 // Displays a context menu. Implements Bridge pattern. |
| 67 @implementation CRUContextMenuController { | 36 @implementation CRUContextMenuController { |
| 68 // Implementation specific for iOS version. | 37 // Implementation specific for iOS version. |
| 69 base::scoped_nsprotocol<id<CRUContextMenuControllerImpl>> _impl; | 38 base::scoped_nsprotocol<id<CRUContextMenuControllerImpl>> _impl; |
| 70 } | 39 } |
| 71 | 40 |
| 72 - (BOOL)isVisible { | 41 - (BOOL)isVisible { |
| 73 return [_impl isVisible]; | 42 return [_impl isVisible]; |
| 74 } | 43 } |
| 75 | 44 |
| 76 - (instancetype)init { | 45 - (instancetype)init { |
| 77 self = [super init]; | 46 self = [super init]; |
| 78 if (self) { | 47 if (self) { |
| 79 if (base::ios::IsRunningOnIOS8OrLater()) { | 48 _impl.reset([[CRUAlertController alloc] init]); |
| 80 _impl.reset([[CRUAlertController alloc] init]); | |
| 81 } else { | |
| 82 _impl.reset([[CRUActionSheetController alloc] init]); | |
| 83 } | |
| 84 } | 49 } |
| 85 return self; | 50 return self; |
| 86 } | 51 } |
| 87 | 52 |
| 88 - (void)showWithHolder:(CRUContextMenuHolder*)menuHolder | 53 - (void)showWithHolder:(CRUContextMenuHolder*)menuHolder |
| 89 atPoint:(CGPoint)point | 54 atPoint:(CGPoint)point |
| 90 inView:(UIView*)view { | 55 inView:(UIView*)view { |
| 91 DCHECK(menuHolder.itemCount); | 56 DCHECK(menuHolder.itemCount); |
| 92 // Check that the view is still visible on screen, otherwise just return and | 57 // Check that the view is still visible on screen, otherwise just return and |
| 93 // don't show the context menu. | 58 // don't show the context menu. |
| 94 if (![view window] && ![view isKindOfClass:[UIWindow class]]) | 59 if (![view window] && ![view isKindOfClass:[UIWindow class]]) |
| 95 return; | 60 return; |
| 96 [_impl showWithHolder:menuHolder atPoint:point inView:view]; | 61 [_impl showWithHolder:menuHolder atPoint:point inView:view]; |
| 97 } | 62 } |
| 98 | 63 |
| 99 @end | 64 @end |
| 100 | 65 |
| 101 #pragma mark - iOS 7 | |
| 102 | |
| 103 @implementation CRUActionSheetController | |
| 104 @synthesize visible = _visible; | |
| 105 | |
| 106 - (void)dealloc { | |
| 107 if (_visible) { | |
| 108 // Context menu must be dismissed explicitly if it is still visible. | |
| 109 NSUInteger cancelButtonIndex = [_menuHolder itemCount]; | |
| 110 [_sheet dismissWithClickedButtonIndex:cancelButtonIndex animated:NO]; | |
| 111 } | |
| 112 [super dealloc]; | |
| 113 } | |
| 114 | |
| 115 - (void)showWithHolder:(CRUContextMenuHolder*)menuHolder | |
| 116 atPoint:(CGPoint)point | |
| 117 inView:(UIView*)view { | |
| 118 // If the content of UIActionSheet does not fit the screen then scrollbars | |
| 119 // are added to the menu items area. If that's the case, elide the title to | |
| 120 // avoid having scrollbars for menu items. | |
| 121 CGSize spaceAvailableForTitle = | |
| 122 [self sizeForTitleThatFitsMenuWithHolder:menuHolder | |
| 123 atPoint:point | |
| 124 inView:view]; | |
| 125 NSString* menuTitle = menuHolder.menuTitle; | |
| 126 if (menuTitle) { | |
| 127 // Show at least one line of text, even if that means the action sheet's | |
| 128 // items will need to scroll. | |
| 129 const CGFloat kMinimumVerticalSpace = 21; | |
| 130 spaceAvailableForTitle.height = | |
| 131 std::max(kMinimumVerticalSpace, spaceAvailableForTitle.height); | |
| 132 menuTitle = [menuTitle cr_stringByElidingToFitSize:spaceAvailableForTitle]; | |
| 133 } | |
| 134 | |
| 135 // Present UIActionSheet. | |
| 136 _sheet.reset( | |
| 137 [self newActionSheetWithHolder:menuHolder title:menuTitle delegate:self]); | |
| 138 [_sheet setCancelButtonIndex:menuHolder.itemCount]; | |
| 139 [_sheet showFromRect:CGRectMake(point.x, point.y, 1.0, 1.0) | |
| 140 inView:view | |
| 141 animated:YES]; | |
| 142 | |
| 143 _menuHolder.reset([menuHolder retain]); | |
| 144 _visible = YES; | |
| 145 } | |
| 146 | |
| 147 #pragma mark Implementation | |
| 148 | |
| 149 // Returns an approximation of the free space available for the title of an | |
| 150 // actionSheet filled with |menu| shown in |view| at |point|. | |
| 151 - (CGSize)sizeForTitleThatFitsMenuWithHolder:(CRUContextMenuHolder*)menuHolder | |
| 152 atPoint:(CGPoint)point | |
| 153 inView:(UIView*)view { | |
| 154 // Create a dummy UIActionSheet. | |
| 155 base::scoped_nsobject<UIActionSheet> dummySheet( | |
| 156 [self newActionSheetWithHolder:menuHolder title:nil delegate:nil]); | |
| 157 // Temporarily add the dummy UIActionSheet to |view|. | |
| 158 [dummySheet showFromRect:CGRectMake(point.x, point.y, 1.0, 1.0) | |
| 159 inView:view | |
| 160 animated:NO]; | |
| 161 // On iPad the actionsheet is positioned under or over |point| (as opposed | |
| 162 // to next to it) when the user clicks within approximately 200 points of | |
| 163 // respectively the top or bottom edge. This reduces the amount of vertical | |
| 164 // space available for the title, hence the large padding on ipad. | |
| 165 const CGFloat kPaddingiPad = 200; | |
| 166 const CGFloat kPaddingiPhone = 20; | |
| 167 BOOL isIPad = ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET; | |
| 168 const CGFloat padding = isIPad ? kPaddingiPad : kPaddingiPhone; | |
| 169 // A title uses the full width of the actionsheet and all the vertical | |
| 170 // space on the screen. | |
| 171 CGSize result = CGSizeMake( | |
| 172 CGRectGetWidth([dummySheet frame]), | |
| 173 GetScreenHeight() - CGRectGetHeight([dummySheet frame]) - padding); | |
| 174 [dummySheet dismissWithClickedButtonIndex:0 animated:NO]; | |
| 175 return result; | |
| 176 } | |
| 177 | |
| 178 // Returns an UIActionSheet. Callers responsible for releasing returned object. | |
| 179 - (UIActionSheet*)newActionSheetWithHolder:(CRUContextMenuHolder*)menuHolder | |
| 180 title:(NSString*)title | |
| 181 delegate:(id<UIActionSheetDelegate>)delegate { | |
| 182 UIActionSheet* sheet = [[UIActionSheet alloc] initWithTitle:title | |
| 183 delegate:delegate | |
| 184 cancelButtonTitle:nil | |
| 185 destructiveButtonTitle:nil | |
| 186 otherButtonTitles:nil]; | |
| 187 | |
| 188 for (NSString* itemTitle in menuHolder.itemTitles) { | |
| 189 [sheet addButtonWithTitle:itemTitle]; | |
| 190 } | |
| 191 [sheet addButtonWithTitle:l10n_util::GetNSString(IDS_APP_CANCEL)]; | |
| 192 return sheet; | |
| 193 } | |
| 194 | |
| 195 #pragma mark UIActionSheetDelegate | |
| 196 | |
| 197 // Called when the action sheet is dismissed in the modal context menu sheet. | |
| 198 // There is no way to dismiss the sheet without going through this method. Note | |
| 199 // that on iPad this method is called with the index of an nonexistent cancel | |
| 200 // button when the user taps outside the sheet. | |
| 201 - (void)actionSheet:(UIActionSheet*)actionSheet | |
| 202 didDismissWithButtonIndex:(NSInteger)buttonIndex { | |
| 203 NSUInteger unsignedButtonIndex = buttonIndex; | |
| 204 // Assumes "cancel" button is last in order. | |
| 205 if (unsignedButtonIndex < [_menuHolder itemCount]) | |
| 206 [_menuHolder performActionAtIndex:unsignedButtonIndex]; | |
| 207 _menuHolder.reset(); | |
| 208 _visible = NO; | |
| 209 } | |
| 210 | |
| 211 // Called when the user chooses a button in the modal context menu sheet. Note | |
| 212 // that on iPad this method is called with the index of an nonexistent cancel | |
| 213 // button when the user taps outside the sheet. | |
| 214 - (void)actionSheet:(UIActionSheet*)actionSheet | |
| 215 clickedButtonAtIndex:(NSInteger)buttonIndex { | |
| 216 // Some use cases (e.g. opening a new tab on handset) should not wait for the | |
| 217 // action sheet to animate away before executing the action. | |
| 218 if ([_menuHolder shouldDismissImmediatelyOnClickedAtIndex:buttonIndex]) { | |
| 219 [_sheet dismissWithClickedButtonIndex:buttonIndex animated:NO]; | |
| 220 } | |
| 221 } | |
| 222 | |
| 223 @end | |
| 224 | |
| 225 #pragma mark - iOS8 and higher | |
| 226 | |
| 227 @implementation CRUAlertController | 66 @implementation CRUAlertController |
| 228 @synthesize visible = _visible; | 67 @synthesize visible = _visible; |
| 229 | 68 |
| 230 - (CGSize)sizeForTitleThatFitsMenuWithHolder:(CRUContextMenuHolder*)menuHolder | 69 - (CGSize)sizeForTitleThatFitsMenuWithHolder:(CRUContextMenuHolder*)menuHolder |
| 231 atPoint:(CGPoint)point | 70 atPoint:(CGPoint)point |
| 232 inView:(UIView*)view { | 71 inView:(UIView*)view { |
| 233 // Presenting and dismissing a dummy UIAlertController flushes a screen. | 72 // Presenting and dismissing a dummy UIAlertController flushes a screen. |
| 234 // As a workaround return an estimation of the space available depending | 73 // As a workaround return an estimation of the space available depending |
| 235 // on the device's type. | 74 // on the device's type. |
| 236 const CGFloat kAvailableWidth = 320; | 75 const CGFloat kAvailableWidth = 320; |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 279 | 118 |
| 280 // Present sheet/popover using controller that is added to view hierarchy. | 119 // Present sheet/popover using controller that is added to view hierarchy. |
| 281 UIViewController* topController = view.window.rootViewController; | 120 UIViewController* topController = view.window.rootViewController; |
| 282 while (topController.presentedViewController) | 121 while (topController.presentedViewController) |
| 283 topController = topController.presentedViewController; | 122 topController = topController.presentedViewController; |
| 284 [topController presentViewController:alert animated:YES completion:nil]; | 123 [topController presentViewController:alert animated:YES completion:nil]; |
| 285 self.visible = YES; | 124 self.visible = YES; |
| 286 } | 125 } |
| 287 | 126 |
| 288 @end | 127 @end |
| OLD | NEW |