Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / panels / panel_stack_window_cocoa.mm
blob01b5edffdcbc3dfb507ff51f8f5c5281b43e710f
1 // Copyright (c) 2013 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.
5 #include "chrome/browser/ui/cocoa/panels/panel_stack_window_cocoa.h"
7 #include "base/logging.h"
8 #include "base/strings/sys_string_conversions.h"
9 #import "chrome/browser/ui/cocoa/panels/panel_cocoa.h"
10 #import "chrome/browser/ui/cocoa/panels/panel_utils_cocoa.h"
11 #include "chrome/browser/ui/panels/panel.h"
12 #include "chrome/browser/ui/panels/panel_manager.h"
13 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
14 #include "ui/base/cocoa/window_size_constants.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/geometry/vector2d.h"
17 #include "ui/gfx/image/image.h"
18 #include "ui/gfx/image/image_skia.h"
19 #include "ui/gfx/image/image_skia_rep.h"
20 #include "ui/snapshot/snapshot.h"
22 // The delegate class to receive the notification from NSViewAnimation.
23 @interface BatchBoundsAnimationDelegate : NSObject<NSAnimationDelegate> {
24  @private
25   PanelStackWindowCocoa* window_;  // Weak pointer.
28 // Called when NSViewAnimation finishes the animation.
29 - (void)animationDidEnd:(NSAnimation*)animation;
30 @end
32 @implementation BatchBoundsAnimationDelegate
34 - (id)initWithWindow:(PanelStackWindowCocoa*)window {
35   if ((self = [super init]))
36     window_ = window;
37   return self;
40 - (void)animationDidEnd:(NSAnimation*)animation {
41   window_->BoundsUpdateAnimationEnded();
44 @end
46 // static
47 NativePanelStackWindow* NativePanelStackWindow::Create(
48     NativePanelStackWindowDelegate* delegate) {
49   return new PanelStackWindowCocoa(delegate);
52 PanelStackWindowCocoa::PanelStackWindowCocoa(
53     NativePanelStackWindowDelegate* delegate)
54     : delegate_(delegate),
55       attention_request_id_(0),
56       bounds_updates_started_(false),
57       animate_bounds_updates_(false),
58       bounds_animation_(nil) {
59   DCHECK(delegate);
60   bounds_animation_delegate_.reset(
61       [[BatchBoundsAnimationDelegate alloc] initWithWindow:this]);
64 PanelStackWindowCocoa::~PanelStackWindowCocoa() {
67 void PanelStackWindowCocoa::Close() {
68   TerminateBoundsAnimation();
69   [window_ close];
72 void PanelStackWindowCocoa::AddPanel(Panel* panel) {
73   panels_.push_back(panel);
75   EnsureWindowCreated();
77   // Make the stack window own the panel window such that all panels window
78   // could be moved simulatenously when the stack window is moved.
79   [window_ addChildWindow:panel->GetNativeWindow() ordered:NSWindowAbove];
81   UpdateStackWindowBounds();
84 void PanelStackWindowCocoa::RemovePanel(Panel* panel) {
85   if (IsAnimatingPanelBounds()) {
86     // This panel is gone. We should not perform any update to it.
87     bounds_updates_.erase(panel);
88   }
90   panels_.remove(panel);
92   // If the native panel is closed, the native window should already be gone.
93   if (!static_cast<PanelCocoa*>(panel->native_panel())->IsClosed())
94     [window_ removeChildWindow:panel->GetNativeWindow()];
96   UpdateStackWindowBounds();
99 void PanelStackWindowCocoa::MergeWith(NativePanelStackWindow* another) {
100   PanelStackWindowCocoa* another_stack =
101       static_cast<PanelStackWindowCocoa*>(another);
103   for (Panels::const_iterator iter = another_stack->panels_.begin();
104        iter != another_stack->panels_.end(); ++iter) {
105     Panel* panel = *iter;
106     panels_.push_back(panel);
108     // Change the panel window owner.
109     NSWindow* panel_window = panel->GetNativeWindow();
110     [another_stack->window_ removeChildWindow:panel_window];
111     [window_ addChildWindow:panel_window ordered:NSWindowAbove];
112   }
113   another_stack->panels_.clear();
115   UpdateStackWindowBounds();
118 bool PanelStackWindowCocoa::IsEmpty() const {
119   return panels_.empty();
122 bool PanelStackWindowCocoa::HasPanel(Panel* panel) const {
123   return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
126 void PanelStackWindowCocoa::MovePanelsBy(const gfx::Vector2d& delta) {
127   // Moving the background stack window will cause all foreground panels window
128   // being moved simulatenously.
129   gfx::Rect enclosing_bounds = GetStackWindowBounds();
130   enclosing_bounds.Offset(delta);
131   NSRect frame = cocoa_utils::ConvertRectToCocoaCoordinates(enclosing_bounds);
132   [window_ setFrame:frame display:NO];
134   // We also need to update the panel bounds.
135   for (Panels::const_iterator iter = panels_.begin();
136        iter != panels_.end(); ++iter) {
137     Panel* panel = *iter;
138     gfx::Rect bounds = panel->GetBounds();
139     bounds.Offset(delta);
140     panel->SetPanelBoundsInstantly(bounds);
141   }
144 void PanelStackWindowCocoa::BeginBatchUpdatePanelBounds(bool animate) {
145   // If the batch animation is still in progress, continue the animation
146   // with the new target bounds even we want to update the bounds instantly
147   // this time.
148   if (!bounds_updates_started_) {
149     animate_bounds_updates_ = animate;
150     bounds_updates_started_ = true;
151   }
154 void PanelStackWindowCocoa::AddPanelBoundsForBatchUpdate(
155     Panel* panel, const gfx::Rect& new_bounds) {
156   DCHECK(bounds_updates_started_);
158   // No need to track it if no change is needed.
159   if (panel->GetBounds() == new_bounds)
160     return;
162   // Old bounds are stored as the map value.
163   bounds_updates_[panel] = panel->GetBounds();
165   // New bounds are directly applied to the value stored in native panel
166   // window.
167   static_cast<PanelCocoa*>(panel->native_panel())->set_cached_bounds_directly(
168       new_bounds);
171 void PanelStackWindowCocoa::EndBatchUpdatePanelBounds() {
172   DCHECK(bounds_updates_started_);
174   // No need to proceed with the animation when the bounds update list is
175   // empty or animation was not requested.
176   if (bounds_updates_.empty() || !animate_bounds_updates_) {
177     // Set the bounds directly when the update list is not empty.
178     if (!bounds_updates_.empty()) {
179       for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
180            iter != bounds_updates_.end(); ++iter) {
181         Panel* panel = iter->first;
182         NSRect frame =
183             cocoa_utils::ConvertRectToCocoaCoordinates(panel->GetBounds());
184         [panel->GetNativeWindow() setFrame:frame display:YES animate:NO];
185       }
186       bounds_updates_.clear();
187       UpdateStackWindowBounds();
188     }
190     bounds_updates_started_ = false;
191     delegate_->PanelBoundsBatchUpdateCompleted();
192     return;
193   }
195   // Terminate previous animation, if it is still playing.
196   TerminateBoundsAnimation();
198   // Find out if we need the animation for each panel. If the batch updates
199   // consist of only moving all panels by delta offset, moving the background
200   // window would be enough.
202   // If all the panels move and don't resize, just animate the underlying
203   // parent window. Otherwise, animate each individual panel.
204   bool need_to_animate_individual_panels = false;
205   if (bounds_updates_.size() == panels_.size()) {
206     gfx::Vector2d delta;
207     for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
208          iter != bounds_updates_.end(); ++iter) {
209       gfx::Rect old_bounds = iter->second;
210       gfx::Rect new_bounds = iter->first->GetBounds();
212       // Size should not be changed.
213       if (old_bounds.width() != new_bounds.width() ||
214           old_bounds.height() != new_bounds.height()) {
215         need_to_animate_individual_panels = true;
216         break;
217       }
219       // Origin offset should be same.
220       if (iter == bounds_updates_.begin()) {
221         delta = new_bounds.origin() - old_bounds.origin();
222       } else if (!(delta == new_bounds.origin() - old_bounds.origin())) {
223         need_to_animate_individual_panels = true;
224         break;
225       }
226     }
227   } else {
228     need_to_animate_individual_panels = true;
229   }
231   int num_of_animations = 1;
232   if (need_to_animate_individual_panels)
233     num_of_animations += bounds_updates_.size();
234   base::scoped_nsobject<NSMutableArray> animations(
235       [[NSMutableArray alloc] initWithCapacity:num_of_animations]);
237   // Add the animation for each panel in the update list.
238   if (need_to_animate_individual_panels) {
239     for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
240          iter != bounds_updates_.end(); ++iter) {
241       Panel* panel = iter->first;
242       NSRect panel_frame =
243           cocoa_utils::ConvertRectToCocoaCoordinates(panel->GetBounds());
244       NSDictionary* animation = [NSDictionary dictionaryWithObjectsAndKeys:
245           panel->GetNativeWindow(), NSViewAnimationTargetKey,
246           [NSValue valueWithRect:panel_frame], NSViewAnimationEndFrameKey,
247           nil];
248       [animations addObject:animation];
249     }
250   }
252   // Compute the final bounds that enclose all panels after the animation.
253   gfx::Rect enclosing_bounds;
254   for (Panels::const_iterator iter = panels_.begin();
255        iter != panels_.end(); ++iter) {
256     gfx::Rect target_bounds = (*iter)->GetBounds();
257     enclosing_bounds = UnionRects(enclosing_bounds, target_bounds);
258   }
260   // Add the animation for the background stack window.
261   NSRect enclosing_frame =
262       cocoa_utils::ConvertRectToCocoaCoordinates(enclosing_bounds);
263   NSDictionary* stack_animation = [NSDictionary dictionaryWithObjectsAndKeys:
264       window_.get(), NSViewAnimationTargetKey,
265       [NSValue valueWithRect:enclosing_frame], NSViewAnimationEndFrameKey,
266       nil];
267   [animations addObject:stack_animation];
269   // Start all the animations.
270   // |bounds_animation_| is released when the animation ends.
271   bounds_animation_ =
272       [[NSViewAnimation alloc] initWithViewAnimations:animations];
273   [bounds_animation_ setDelegate:bounds_animation_delegate_.get()];
274   [bounds_animation_ setDuration:PanelManager::AdjustTimeInterval(0.18)];
275   [bounds_animation_ setFrameRate:0.0];
276   [bounds_animation_ setAnimationBlockingMode: NSAnimationNonblocking];
277   [bounds_animation_ startAnimation];
280 bool PanelStackWindowCocoa::IsAnimatingPanelBounds() const {
281   return bounds_updates_started_ && animate_bounds_updates_;
284 void PanelStackWindowCocoa::BoundsUpdateAnimationEnded() {
285   bounds_updates_started_ = false;
287   for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
288        iter != bounds_updates_.end(); ++iter) {
289     Panel* panel = iter->first;
290     panel->manager()->OnPanelAnimationEnded(panel);
291   }
292   bounds_updates_.clear();
294   delegate_->PanelBoundsBatchUpdateCompleted();
297 void PanelStackWindowCocoa::Minimize() {
298   // Provide the custom miniwindow image since there is nothing painted for
299   // the background stack window.
300   gfx::Size stack_window_size = GetStackWindowBounds().size();
301   gfx::Canvas canvas(stack_window_size, 1.0f, true);
302   int y = 0;
303   Panels::const_iterator iter = panels_.begin();
304   for (; iter != panels_.end(); ++iter) {
305     Panel* panel = *iter;
306     gfx::Rect snapshot_bounds = gfx::Rect(panel->GetBounds().size());
307     std::vector<unsigned char> png;
308     if (!ui::GrabWindowSnapshot(panel->GetNativeWindow(),
309                                 &png,
310                                 snapshot_bounds))
311       break;
312     gfx::Image snapshot_image = gfx::Image::CreateFrom1xPNGBytes(
313         &(png[0]), png.size());
314     canvas.DrawImageInt(snapshot_image.AsImageSkia(), 0, y);
315     y += snapshot_bounds.height();
316   }
317   if (iter == panels_.end()) {
318     gfx::Image image(gfx::ImageSkia(canvas.ExtractImageRep()));
319     [window_ setMiniwindowImage:image.AsNSImage()];
320   }
322   [window_ miniaturize:nil];
325 bool PanelStackWindowCocoa::IsMinimized() const {
326   return [window_ isMiniaturized];
329 void PanelStackWindowCocoa::DrawSystemAttention(bool draw_attention) {
330   BOOL is_drawing_attention = attention_request_id_ != 0;
331   if (draw_attention == is_drawing_attention)
332     return;
334   if (draw_attention) {
335     attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest];
336   } else {
337     [NSApp cancelUserAttentionRequest:attention_request_id_];
338     attention_request_id_ = 0;
339   }
342 void PanelStackWindowCocoa::OnPanelActivated(Panel* panel) {
343   // Nothing to do.
346 void PanelStackWindowCocoa::TerminateBoundsAnimation() {
347   if (!bounds_animation_)
348     return;
349   [bounds_animation_ stopAnimation];
350   [bounds_animation_ setDelegate:nil];
351   [bounds_animation_ release];
352   bounds_animation_ = nil;
355 gfx::Rect PanelStackWindowCocoa::GetStackWindowBounds() const {
356   gfx::Rect enclosing_bounds;
357   for (Panels::const_iterator iter = panels_.begin();
358        iter != panels_.end(); ++iter) {
359     Panel* panel = *iter;
360     enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds());
361   }
362   return enclosing_bounds;
365 void PanelStackWindowCocoa::UpdateStackWindowBounds() {
366   NSRect enclosing_bounds =
367       cocoa_utils::ConvertRectToCocoaCoordinates(GetStackWindowBounds());
368   [window_ setFrame:enclosing_bounds display:NO];
371 void PanelStackWindowCocoa::EnsureWindowCreated() {
372   if (window_)
373     return;
375   window_.reset(
376       [[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
377                                   styleMask:NSBorderlessWindowMask
378                                     backing:NSBackingStoreBuffered
379                                       defer:NO]);
380   [window_ setBackgroundColor:[NSColor clearColor]];
381   [window_ setHasShadow:YES];
382   [window_ setLevel:NSNormalWindowLevel];
383   [window_ orderFront:nil];
384   [window_ setTitle:base::SysUTF16ToNSString(delegate_->GetTitle())];