Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / location_bar / content_setting_decoration.mm
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/ui/cocoa/location_bar/content_setting_decoration.h"
6
7 #include <algorithm>
8
9 #include "base/prefs/pref_service.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/content_settings/tab_specific_content_settings.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h"
15 #include "chrome/browser/ui/browser_list.h"
16 #import "chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.h"
17 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
18 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
19 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
20 #include "chrome/browser/ui/content_settings/content_setting_image_model.h"
21 #include "content/public/browser/web_contents.h"
22 #include "grit/theme_resources.h"
23 #include "net/base/net_util.h"
24 #include "ui/base/cocoa/appkit_utils.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/image/image.h"
28 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
29
30 using content::WebContents;
31
32 namespace {
33
34 // The bubble point should look like it points to the bottom of the respective
35 // icon. The offset should be 2px.
36 const CGFloat kPageBubblePointYOffset = 2.0;
37
38 // Duration of animation, 3 seconds. The ContentSettingAnimationState breaks
39 // this up into different states of varying lengths.
40 const NSTimeInterval kAnimationDuration = 3.0;
41
42 // Interval of the animation timer, 60Hz.
43 const NSTimeInterval kAnimationInterval = 1.0 / 60.0;
44
45 // The % of time it takes to open or close the animating text, ie at 0.2, the
46 // opening takes 20% of the whole animation and the closing takes 20%. The
47 // remainder of the animation is with the text at full width.
48 const double kInMotionInterval = 0.2;
49
50 // Used to create a % complete of the "in motion" part of the animation, eg
51 // it should be 1.0 (100%) when the progress is 0.2.
52 const double kInMotionMultiplier = 1.0 / kInMotionInterval;
53
54 // Padding for the animated text with respect to the image.
55 const CGFloat kTextMarginPadding = 4;
56 const CGFloat kIconMarginPadding = 2;
57 const CGFloat kBorderPadding = 3;
58
59 // Different states in which the animation can be. In |kOpening|, the text
60 // is getting larger. In |kOpen|, the text should be displayed at full size.
61 // In |kClosing|, the text is again getting smaller. The durations in which
62 // the animation remains in each state are internal to
63 // |ContentSettingAnimationState|.
64 enum AnimationState {
65   kNoAnimation,
66   kOpening,
67   kOpen,
68   kClosing
69 };
70
71 }  // namespace
72
73
74 // An ObjC class that handles the multiple states of the text animation and
75 // bridges NSTimer calls back to the ContentSettingDecoration that owns it.
76 // Should be lazily instantiated to only exist when the decoration requires
77 // animation.
78 // NOTE: One could make this class more generic, but this class only exists
79 // because CoreAnimation cannot be used (there are no views to work with).
80 @interface ContentSettingAnimationState : NSObject {
81  @private
82   ContentSettingDecoration* owner_;  // Weak, owns this.
83   double progress_;  // Counter, [0..1], with aninmation progress.
84   NSTimer* timer_;  // Animation timer. Owns this, owned by the run loop.
85 }
86
87 // [0..1], the current progress of the animation. -animationState will return
88 // |kNoAnimation| when progress is <= 0 or >= 1. Useful when state is
89 // |kOpening| or |kClosing| as a multiplier for displaying width. Don't use
90 // to track state transitions, use -animationState instead.
91 @property (readonly, nonatomic) double progress;
92
93 // Designated initializer. |owner| must not be nil. Animation timer will start
94 // as soon as the object is created.
95 - (id)initWithOwner:(ContentSettingDecoration*)owner;
96
97 // Returns the current animation state based on how much time has elapsed.
98 - (AnimationState)animationState;
99
100 // Call when |owner| is going away or the animation needs to be stopped.
101 // Ensures that any dangling references are cleared. Can be called multiple
102 // times.
103 - (void)stopAnimation;
104
105 @end
106
107 @implementation ContentSettingAnimationState
108
109 @synthesize progress = progress_;
110
111 - (id)initWithOwner:(ContentSettingDecoration*)owner {
112   self = [super init];
113   if (self) {
114     owner_ = owner;
115     timer_ = [NSTimer scheduledTimerWithTimeInterval:kAnimationInterval
116                                               target:self
117                                             selector:@selector(timerFired:)
118                                             userInfo:nil
119                                              repeats:YES];
120   }
121   return self;
122 }
123
124 - (void)dealloc {
125   DCHECK(!timer_);
126   [super dealloc];
127 }
128
129 // Clear weak references and stop the timer.
130 - (void)stopAnimation {
131   owner_ = nil;
132   [timer_ invalidate];
133   timer_ = nil;
134 }
135
136 // Returns the current state based on how much time has elapsed.
137 - (AnimationState)animationState {
138   if (progress_ <= 0.0 || progress_ >= 1.0)
139     return kNoAnimation;
140   if (progress_ <= kInMotionInterval)
141     return kOpening;
142   if (progress_ >= 1.0 - kInMotionInterval)
143     return kClosing;
144   return kOpen;
145 }
146
147 - (void)timerFired:(NSTimer*)timer {
148   // Increment animation progress, normalized to [0..1].
149   progress_ += kAnimationInterval / kAnimationDuration;
150   progress_ = std::min(progress_, 1.0);
151   owner_->AnimationTimerFired();
152   // Stop timer if it has reached the end of its life.
153   if (progress_ >= 1.0)
154     [self stopAnimation];
155 }
156
157 @end
158
159
160 ContentSettingDecoration::ContentSettingDecoration(
161     ContentSettingsType settings_type,
162     LocationBarViewMac* owner,
163     Profile* profile)
164     : content_setting_image_model_(
165           ContentSettingImageModel::CreateContentSettingImageModel(
166               settings_type)),
167       owner_(owner),
168       profile_(profile),
169       text_width_(0.0) {
170 }
171
172 ContentSettingDecoration::~ContentSettingDecoration() {
173   // Just in case the timer is still holding onto the animation object, force
174   // cleanup so it can't get back to |this|.
175   [animation_ stopAnimation];
176 }
177
178 bool ContentSettingDecoration::UpdateFromWebContents(
179     WebContents* web_contents) {
180   bool was_visible = IsVisible();
181   int old_icon = content_setting_image_model_->get_icon();
182   content_setting_image_model_->UpdateFromWebContents(web_contents);
183   SetVisible(content_setting_image_model_->is_visible());
184   bool decoration_changed = was_visible != IsVisible() ||
185       old_icon != content_setting_image_model_->get_icon();
186   if (IsVisible()) {
187     // TODO(thakis): We should use pdfs for these icons on OSX.
188     // http://crbug.com/35847
189     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
190     SetImage(rb.GetNativeImageNamed(
191         content_setting_image_model_->get_icon()).ToNSImage());
192     SetToolTip(base::SysUTF8ToNSString(
193         content_setting_image_model_->get_tooltip()));
194
195     // Check if there is an animation and start it if it hasn't yet started.
196     bool has_animated_text =
197         content_setting_image_model_->explanatory_string_id();
198
199     // Check if the animation has already run.
200     TabSpecificContentSettings* content_settings =
201         TabSpecificContentSettings::FromWebContents(web_contents);
202     ContentSettingsType content_type =
203         content_setting_image_model_->get_content_settings_type();
204     bool ran_animation = content_settings->IsBlockageIndicated(content_type);
205
206     if (has_animated_text && !ran_animation && !animation_) {
207       // Mark the animation as having been run.
208       content_settings->SetBlockageHasBeenIndicated(content_type);
209       // Start animation, its timer will drive reflow. Note the text is
210       // cached so it is not allowed to change during the animation.
211       animation_.reset(
212           [[ContentSettingAnimationState alloc] initWithOwner:this]);
213       animated_text_ = CreateAnimatedText();
214       text_width_ = MeasureTextWidth();
215     } else if (!has_animated_text) {
216       // Decoration no longer has animation, stop it (ok to always do this).
217       [animation_ stopAnimation];
218       animation_.reset();
219     }
220   } else {
221     // Decoration no longer visible, stop/clear animation.
222     [animation_ stopAnimation];
223     animation_.reset(nil);
224   }
225   return decoration_changed;
226 }
227
228 CGFloat ContentSettingDecoration::MeasureTextWidth() {
229   return [animated_text_ size].width;
230 }
231
232 base::scoped_nsobject<NSAttributedString>
233 ContentSettingDecoration::CreateAnimatedText() {
234   NSString* text =
235       l10n_util::GetNSString(
236           content_setting_image_model_->explanatory_string_id());
237   base::scoped_nsobject<NSMutableParagraphStyle> style(
238       [[NSMutableParagraphStyle alloc] init]);
239   // Set line break mode to clip the text, otherwise drawInRect: won't draw a
240   // word if it doesn't fit in the bounding box.
241   [style setLineBreakMode:NSLineBreakByClipping];
242   NSDictionary* attributes = @{ NSFontAttributeName : GetFont(),
243                                 NSParagraphStyleAttributeName : style };
244   return base::scoped_nsobject<NSAttributedString>(
245       [[NSAttributedString alloc] initWithString:text attributes:attributes]);
246 }
247
248 NSPoint ContentSettingDecoration::GetBubblePointInFrame(NSRect frame) {
249   // Compute the frame as if there is no animation pill in the Omnibox. Place
250   // the bubble where the icon would be without animation, so when the animation
251   // ends, the bubble is pointing in the right place.
252   NSSize image_size = [GetImage() size];
253   frame.origin.x += frame.size.width - image_size.width;
254   frame.size = image_size;
255
256   const NSRect draw_frame = GetDrawRectInFrame(frame);
257   return NSMakePoint(NSMidX(draw_frame),
258                      NSMaxY(draw_frame) + kPageBubblePointYOffset);
259 }
260
261 bool ContentSettingDecoration::AcceptsMousePress() {
262   return true;
263 }
264
265 bool ContentSettingDecoration::OnMousePressed(NSRect frame, NSPoint location) {
266   // Get host. This should be shared on linux/win/osx medium-term.
267   Browser* browser = owner_->browser();
268   WebContents* web_contents = owner_->GetWebContents();
269   if (!web_contents)
270     return true;
271
272   // Find point for bubble's arrow in screen coordinates.
273   // TODO(shess): |owner_| is only being used to fetch |field|.
274   // Consider passing in |control_view|.  Or refactoring to be
275   // consistent with other decorations (which don't currently bring up
276   // their bubble directly).
277   AutocompleteTextField* field = owner_->GetAutocompleteTextField();
278   NSPoint anchor = GetBubblePointInFrame(frame);
279   anchor = [field convertPoint:anchor toView:nil];
280   anchor = [[field window] convertBaseToScreen:anchor];
281
282   // Open bubble.
283   ContentSettingBubbleModel* model =
284       ContentSettingBubbleModel::CreateContentSettingBubbleModel(
285           browser->content_setting_bubble_model_delegate(),
286           web_contents, profile_,
287           content_setting_image_model_->get_content_settings_type());
288   [ContentSettingBubbleController showForModel:model
289                                    webContents:web_contents
290                                   parentWindow:[field window]
291                                     anchoredAt:anchor];
292   return true;
293 }
294
295 NSString* ContentSettingDecoration::GetToolTip() {
296   return tooltip_.get();
297 }
298
299 void ContentSettingDecoration::SetToolTip(NSString* tooltip) {
300   tooltip_.reset([tooltip retain]);
301 }
302
303 // Override to handle the case where there is text to display during the
304 // animation. The width is based on the animator's progress.
305 CGFloat ContentSettingDecoration::GetWidthForSpace(CGFloat width) {
306   CGFloat preferred_width = ImageDecoration::GetWidthForSpace(width);
307   if (animation_.get()) {
308     AnimationState state = [animation_ animationState];
309     if (state != kNoAnimation) {
310       CGFloat progress = [animation_ progress];
311       // Add the margins, fixed for all animation states.
312       preferred_width += kIconMarginPadding + kTextMarginPadding;
313       // Add the width of the text based on the state of the animation.
314       switch (state) {
315         case kOpening:
316           preferred_width += text_width_ * kInMotionMultiplier * progress;
317           break;
318         case kOpen:
319           preferred_width += text_width_;
320           break;
321         case kClosing:
322           preferred_width += text_width_ * kInMotionMultiplier * (1 - progress);
323           break;
324         default:
325           // Do nothing.
326           break;
327       }
328     }
329   }
330   return preferred_width;
331 }
332
333 void ContentSettingDecoration::DrawInFrame(NSRect frame, NSView* control_view) {
334   if ([animation_ animationState] != kNoAnimation) {
335     NSRect background_rect = NSInsetRect(frame, 0.0, kBorderPadding);
336     const ui::NinePartImageIds image_ids =
337         IMAGE_GRID(IDR_OMNIBOX_CONTENT_SETTING_BUBBLE);
338     ui::DrawNinePartImage(
339         background_rect, image_ids, NSCompositeSourceOver, 1.0, true);
340
341     // Draw the icon.
342     NSImage* icon = GetImage();
343     NSRect icon_rect = background_rect;
344     if (icon) {
345       icon_rect.origin.x += kIconMarginPadding;
346       icon_rect.size.width = [icon size].width;
347       ImageDecoration::DrawInFrame(icon_rect, control_view);
348     }
349
350     NSRect remainder = frame;
351     remainder.origin.x = NSMaxX(icon_rect);
352     remainder.size.width = NSMaxX(background_rect) - NSMinX(remainder);
353     DrawAttributedString(animated_text_, remainder);
354   } else {
355     // No animation, draw the image as normal.
356     ImageDecoration::DrawInFrame(frame, control_view);
357   }
358 }
359
360 void ContentSettingDecoration::AnimationTimerFired() {
361   owner_->Layout();
362   // Even after the animation completes, the |animator_| object should be kept
363   // alive to prevent the animation from re-appearing if the page opens
364   // additional popups later. The animator will be cleared when the decoration
365   // hides, indicating something has changed with the WebContents (probably
366   // navigation).
367 }