- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / panels / panel_stack_window_cocoa.mm
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.
4
5 #include "chrome/browser/ui/cocoa/panels/panel_stack_window_cocoa.h"
6
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 "chrome/browser/ui/window_snapshot/window_snapshot.h"
15 #include "ui/base/cocoa/window_size_constants.h"
16 #include "ui/gfx/canvas.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/gfx/vector2d.h"
21
22 // The delegate class to receive the notification from NSViewAnimation.
23 @interface BatchBoundsAnimationDelegate : NSObject<NSAnimationDelegate> {
24  @private
25   PanelStackWindowCocoa* window_;  // Weak pointer.
26 }
27
28 // Called when NSViewAnimation finishes the animation.
29 - (void)animationDidEnd:(NSAnimation*)animation;
30 @end
31
32 @implementation BatchBoundsAnimationDelegate
33
34 - (id)initWithWindow:(PanelStackWindowCocoa*)window {
35   if ((self = [super init]))
36     window_ = window;
37   return self;
38 }
39
40 - (void)animationDidEnd:(NSAnimation*)animation {
41   window_->BoundsUpdateAnimationEnded();
42 }
43
44 @end
45
46 // static
47 NativePanelStackWindow* NativePanelStackWindow::Create(
48     NativePanelStackWindowDelegate* delegate) {
49   return new PanelStackWindowCocoa(delegate);
50 }
51
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]);
62 }
63
64 PanelStackWindowCocoa::~PanelStackWindowCocoa() {
65 }
66
67 void PanelStackWindowCocoa::Close() {
68   TerminateBoundsAnimation();
69   [window_ close];
70 }
71
72 void PanelStackWindowCocoa::AddPanel(Panel* panel) {
73   panels_.push_back(panel);
74
75   EnsureWindowCreated();
76
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];
80
81   UpdateStackWindowBounds();
82 }
83
84 void PanelStackWindowCocoa::RemovePanel(Panel* panel) {
85   panels_.remove(panel);
86
87   // If the native panel is closed, the native window should already be gone.
88   if (!static_cast<PanelCocoa*>(panel->native_panel())->IsClosed())
89     [window_ removeChildWindow:panel->GetNativeWindow()];
90
91   UpdateStackWindowBounds();
92 }
93
94 void PanelStackWindowCocoa::MergeWith(NativePanelStackWindow* another) {
95   PanelStackWindowCocoa* another_stack =
96       static_cast<PanelStackWindowCocoa*>(another);
97
98   for (Panels::const_iterator iter = another_stack->panels_.begin();
99        iter != another_stack->panels_.end(); ++iter) {
100     Panel* panel = *iter;
101     panels_.push_back(panel);
102
103     // Change the panel window owner.
104     NSWindow* panel_window = panel->GetNativeWindow();
105     [another_stack->window_ removeChildWindow:panel_window];
106     [window_ addChildWindow:panel_window ordered:NSWindowAbove];
107   }
108   another_stack->panels_.clear();
109
110   UpdateStackWindowBounds();
111 }
112
113 bool PanelStackWindowCocoa::IsEmpty() const {
114   return panels_.empty();
115 }
116
117 bool PanelStackWindowCocoa::HasPanel(Panel* panel) const {
118   return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
119 }
120
121 void PanelStackWindowCocoa::MovePanelsBy(const gfx::Vector2d& delta) {
122   // Moving the background stack window will cause all foreground panels window
123   // being moved simulatenously.
124   gfx::Rect enclosing_bounds = GetStackWindowBounds();
125   enclosing_bounds.Offset(delta);
126   NSRect frame = cocoa_utils::ConvertRectToCocoaCoordinates(enclosing_bounds);
127   [window_ setFrame:frame display:NO];
128
129   // We also need to update the panel bounds.
130   for (Panels::const_iterator iter = panels_.begin();
131        iter != panels_.end(); ++iter) {
132     Panel* panel = *iter;
133     gfx::Rect bounds = panel->GetBounds();
134     bounds.Offset(delta);
135     panel->SetPanelBoundsInstantly(bounds);
136   }
137 }
138
139 void PanelStackWindowCocoa::BeginBatchUpdatePanelBounds(bool animate) {
140   // If the batch animation is still in progress, continue the animation
141   // with the new target bounds even we want to update the bounds instantly
142   // this time.
143   if (!bounds_updates_started_) {
144     animate_bounds_updates_ = animate;
145     bounds_updates_started_ = true;
146   }
147 }
148
149 void PanelStackWindowCocoa::AddPanelBoundsForBatchUpdate(
150     Panel* panel, const gfx::Rect& new_bounds) {
151   DCHECK(bounds_updates_started_);
152
153   // No need to track it if no change is needed.
154   if (panel->GetBounds() == new_bounds)
155     return;
156
157   // Old bounds are stored as the map value.
158   bounds_updates_[panel] = panel->GetBounds();
159
160   // New bounds are directly applied to the value stored in native panel
161   // window.
162   static_cast<PanelCocoa*>(panel->native_panel())->set_cached_bounds_directly(
163       new_bounds);
164 }
165
166 void PanelStackWindowCocoa::EndBatchUpdatePanelBounds() {
167   DCHECK(bounds_updates_started_);
168
169   // No need to proceed with the animation when the bounds update list is
170   // empty or animation was not requested.
171   if (bounds_updates_.empty() || !animate_bounds_updates_) {
172     // Set the bounds directly when the update list is not empty.
173     if (!bounds_updates_.empty()) {
174       for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
175            iter != bounds_updates_.end(); ++iter) {
176         Panel* panel = iter->first;
177         NSRect frame =
178             cocoa_utils::ConvertRectToCocoaCoordinates(panel->GetBounds());
179         [panel->GetNativeWindow() setFrame:frame display:YES animate:NO];
180       }
181       bounds_updates_.clear();
182       UpdateStackWindowBounds();
183     }
184
185     bounds_updates_started_ = false;
186     delegate_->PanelBoundsBatchUpdateCompleted();
187     return;
188   }
189
190   // Terminate previous animation, if it is still playing.
191   TerminateBoundsAnimation();
192
193   // Find out if we need the animation for each panel. If the batch updates
194   // consist of only moving all panels by delta offset, moving the background
195   // window would be enough.
196
197   // If all the panels move and don't resize, just animate the underlying
198   // parent window. Otherwise, animate each individual panel.
199   bool need_to_animate_individual_panels = false;
200   if (bounds_updates_.size() == panels_.size()) {
201     gfx::Vector2d delta;
202     for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
203          iter != bounds_updates_.end(); ++iter) {
204       gfx::Rect old_bounds = iter->second;
205       gfx::Rect new_bounds = iter->first->GetBounds();
206
207       // Size should not be changed.
208       if (old_bounds.width() != new_bounds.width() ||
209           old_bounds.height() != new_bounds.height()) {
210         need_to_animate_individual_panels = true;
211         break;
212       }
213
214       // Origin offset should be same.
215       if (iter == bounds_updates_.begin()) {
216         delta = new_bounds.origin() - old_bounds.origin();
217       } else if (!(delta == new_bounds.origin() - old_bounds.origin())) {
218         need_to_animate_individual_panels = true;
219         break;
220       }
221     }
222   } else {
223     need_to_animate_individual_panels = true;
224   }
225
226   int num_of_animations = 1;
227   if (need_to_animate_individual_panels)
228     num_of_animations += bounds_updates_.size();
229   base::scoped_nsobject<NSMutableArray> animations(
230       [[NSMutableArray alloc] initWithCapacity:num_of_animations]);
231
232   // Add the animation for each panel in the update list.
233   if (need_to_animate_individual_panels) {
234     for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
235          iter != bounds_updates_.end(); ++iter) {
236       Panel* panel = iter->first;
237       NSRect panel_frame =
238           cocoa_utils::ConvertRectToCocoaCoordinates(panel->GetBounds());
239       NSDictionary* animation = [NSDictionary dictionaryWithObjectsAndKeys:
240           panel->GetNativeWindow(), NSViewAnimationTargetKey,
241           [NSValue valueWithRect:panel_frame], NSViewAnimationEndFrameKey,
242           nil];
243       [animations addObject:animation];
244     }
245   }
246
247   // Compute the final bounds that enclose all panels after the animation.
248   gfx::Rect enclosing_bounds;
249   for (Panels::const_iterator iter = panels_.begin();
250        iter != panels_.end(); ++iter) {
251     gfx::Rect target_bounds = (*iter)->GetBounds();
252     enclosing_bounds = UnionRects(enclosing_bounds, target_bounds);
253   }
254
255   // Add the animation for the background stack window.
256   NSRect enclosing_frame =
257       cocoa_utils::ConvertRectToCocoaCoordinates(enclosing_bounds);
258   NSDictionary* stack_animation = [NSDictionary dictionaryWithObjectsAndKeys:
259       window_.get(), NSViewAnimationTargetKey,
260       [NSValue valueWithRect:enclosing_frame], NSViewAnimationEndFrameKey,
261       nil];
262   [animations addObject:stack_animation];
263
264   // Start all the animations.
265   // |bounds_animation_| is released when the animation ends.
266   bounds_animation_ =
267       [[NSViewAnimation alloc] initWithViewAnimations:animations];
268   [bounds_animation_ setDelegate:bounds_animation_delegate_.get()];
269   [bounds_animation_ setDuration:PanelManager::AdjustTimeInterval(0.18)];
270   [bounds_animation_ setFrameRate:0.0];
271   [bounds_animation_ setAnimationBlockingMode: NSAnimationNonblocking];
272   [bounds_animation_ startAnimation];
273 }
274
275 bool PanelStackWindowCocoa::IsAnimatingPanelBounds() const {
276   return bounds_updates_started_ && animate_bounds_updates_;
277 }
278
279 void PanelStackWindowCocoa::BoundsUpdateAnimationEnded() {
280   bounds_updates_started_ = false;
281
282   for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
283        iter != bounds_updates_.end(); ++iter) {
284     Panel* panel = iter->first;
285     panel->manager()->OnPanelAnimationEnded(panel);
286   }
287   bounds_updates_.clear();
288
289   delegate_->PanelBoundsBatchUpdateCompleted();
290 }
291
292 void PanelStackWindowCocoa::Minimize() {
293   // Provide the custom miniwindow image since there is nothing painted for
294   // the background stack window.
295   gfx::Size stack_window_size = GetStackWindowBounds().size();
296   gfx::Canvas canvas(stack_window_size, 1.0f, true);
297   int y = 0;
298   Panels::const_iterator iter = panels_.begin();
299   for (; iter != panels_.end(); ++iter) {
300     Panel* panel = *iter;
301     gfx::Rect snapshot_bounds = gfx::Rect(panel->GetBounds().size());
302     std::vector<unsigned char> png;
303     if (!chrome::GrabWindowSnapshotForUser(panel->GetNativeWindow(),
304                                            &png,
305                                            snapshot_bounds)) {
306       break;
307     }
308     gfx::Image snapshot_image = gfx::Image::CreateFrom1xPNGBytes(
309         &(png[0]), png.size());
310     canvas.DrawImageInt(snapshot_image.AsImageSkia(), 0, y);
311     y += snapshot_bounds.height();
312   }
313   if (iter == panels_.end()) {
314     gfx::Image image(gfx::ImageSkia(canvas.ExtractImageRep()));
315     [window_ setMiniwindowImage:image.AsNSImage()];
316   }
317
318   [window_ miniaturize:nil];
319 }
320
321 bool PanelStackWindowCocoa::IsMinimized() const {
322   return [window_ isMiniaturized];
323 }
324
325 void PanelStackWindowCocoa::DrawSystemAttention(bool draw_attention) {
326   BOOL is_drawing_attention = attention_request_id_ != 0;
327   if (draw_attention == is_drawing_attention)
328     return;
329
330   if (draw_attention) {
331     attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest];
332   } else {
333     [NSApp cancelUserAttentionRequest:attention_request_id_];
334     attention_request_id_ = 0;
335   }
336 }
337
338 void PanelStackWindowCocoa::OnPanelActivated(Panel* panel) {
339   // Nothing to do.
340 }
341
342 void PanelStackWindowCocoa::TerminateBoundsAnimation() {
343   if (!bounds_animation_)
344     return;
345   [bounds_animation_ stopAnimation];
346   [bounds_animation_ setDelegate:nil];
347   [bounds_animation_ release];
348   bounds_animation_ = nil;
349 }
350
351 gfx::Rect PanelStackWindowCocoa::GetStackWindowBounds() const {
352   gfx::Rect enclosing_bounds;
353   for (Panels::const_iterator iter = panels_.begin();
354        iter != panels_.end(); ++iter) {
355     Panel* panel = *iter;
356     enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds());
357   }
358   return enclosing_bounds;
359 }
360
361 void PanelStackWindowCocoa::UpdateStackWindowBounds() {
362   NSRect enclosing_bounds =
363       cocoa_utils::ConvertRectToCocoaCoordinates(GetStackWindowBounds());
364   [window_ setFrame:enclosing_bounds display:NO];
365 }
366
367 void PanelStackWindowCocoa::EnsureWindowCreated() {
368   if (window_)
369     return;
370
371   window_.reset(
372       [[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
373                                   styleMask:NSBorderlessWindowMask
374                                     backing:NSBackingStoreBuffered
375                                       defer:NO]);
376   [window_ setBackgroundColor:[NSColor clearColor]];
377   [window_ setHasShadow:YES];
378   [window_ setLevel:NSNormalWindowLevel];
379   [window_ orderFront:nil];
380   [window_ setTitle:base::SysUTF16ToNSString(delegate_->GetTitle())];
381 }