| OLD | NEW |
| (Empty) |
| 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 "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" | |
| 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 if (IsAnimatingPanelBounds()) { | |
| 86 // This panel is gone. We should not perform any update to it. | |
| 87 bounds_updates_.erase(panel); | |
| 88 } | |
| 89 | |
| 90 panels_.remove(panel); | |
| 91 | |
| 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()]; | |
| 95 | |
| 96 UpdateStackWindowBounds(); | |
| 97 } | |
| 98 | |
| 99 void PanelStackWindowCocoa::MergeWith(NativePanelStackWindow* another) { | |
| 100 PanelStackWindowCocoa* another_stack = | |
| 101 static_cast<PanelStackWindowCocoa*>(another); | |
| 102 | |
| 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); | |
| 107 | |
| 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(); | |
| 114 | |
| 115 UpdateStackWindowBounds(); | |
| 116 } | |
| 117 | |
| 118 bool PanelStackWindowCocoa::IsEmpty() const { | |
| 119 return panels_.empty(); | |
| 120 } | |
| 121 | |
| 122 bool PanelStackWindowCocoa::HasPanel(Panel* panel) const { | |
| 123 return std::find(panels_.begin(), panels_.end(), panel) != panels_.end(); | |
| 124 } | |
| 125 | |
| 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]; | |
| 133 | |
| 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 } | |
| 142 } | |
| 143 | |
| 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 } | |
| 152 } | |
| 153 | |
| 154 void PanelStackWindowCocoa::AddPanelBoundsForBatchUpdate( | |
| 155 Panel* panel, const gfx::Rect& new_bounds) { | |
| 156 DCHECK(bounds_updates_started_); | |
| 157 | |
| 158 // No need to track it if no change is needed. | |
| 159 if (panel->GetBounds() == new_bounds) | |
| 160 return; | |
| 161 | |
| 162 // Old bounds are stored as the map value. | |
| 163 bounds_updates_[panel] = panel->GetBounds(); | |
| 164 | |
| 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); | |
| 169 } | |
| 170 | |
| 171 void PanelStackWindowCocoa::EndBatchUpdatePanelBounds() { | |
| 172 DCHECK(bounds_updates_started_); | |
| 173 | |
| 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 } | |
| 189 | |
| 190 bounds_updates_started_ = false; | |
| 191 delegate_->PanelBoundsBatchUpdateCompleted(); | |
| 192 return; | |
| 193 } | |
| 194 | |
| 195 // Terminate previous animation, if it is still playing. | |
| 196 TerminateBoundsAnimation(); | |
| 197 | |
| 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. | |
| 201 | |
| 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(); | |
| 211 | |
| 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 } | |
| 218 | |
| 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 } | |
| 230 | |
| 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]); | |
| 236 | |
| 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 } | |
| 251 | |
| 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 } | |
| 259 | |
| 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]; | |
| 268 | |
| 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]; | |
| 278 } | |
| 279 | |
| 280 bool PanelStackWindowCocoa::IsAnimatingPanelBounds() const { | |
| 281 return bounds_updates_started_ && animate_bounds_updates_; | |
| 282 } | |
| 283 | |
| 284 void PanelStackWindowCocoa::BoundsUpdateAnimationEnded() { | |
| 285 bounds_updates_started_ = false; | |
| 286 | |
| 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(); | |
| 293 | |
| 294 delegate_->PanelBoundsBatchUpdateCompleted(); | |
| 295 } | |
| 296 | |
| 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 } | |
| 321 | |
| 322 [window_ miniaturize:nil]; | |
| 323 } | |
| 324 | |
| 325 bool PanelStackWindowCocoa::IsMinimized() const { | |
| 326 return [window_ isMiniaturized]; | |
| 327 } | |
| 328 | |
| 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; | |
| 333 | |
| 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 } | |
| 340 } | |
| 341 | |
| 342 void PanelStackWindowCocoa::OnPanelActivated(Panel* panel) { | |
| 343 // Nothing to do. | |
| 344 } | |
| 345 | |
| 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; | |
| 353 } | |
| 354 | |
| 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; | |
| 363 } | |
| 364 | |
| 365 void PanelStackWindowCocoa::UpdateStackWindowBounds() { | |
| 366 NSRect enclosing_bounds = | |
| 367 cocoa_utils::ConvertRectToCocoaCoordinates(GetStackWindowBounds()); | |
| 368 [window_ setFrame:enclosing_bounds display:NO]; | |
| 369 } | |
| 370 | |
| 371 void PanelStackWindowCocoa::EnsureWindowCreated() { | |
| 372 if (window_) | |
| 373 return; | |
| 374 | |
| 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())]; | |
| 385 } | |
| OLD | NEW |