• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#include "chrome/browser/ui/cocoa/panels/panel_cocoa.h"
6
7#include "base/logging.h"
8#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
9#import "chrome/browser/ui/cocoa/panels/panel_titlebar_view_cocoa.h"
10#import "chrome/browser/ui/cocoa/panels/panel_utils_cocoa.h"
11#import "chrome/browser/ui/cocoa/panels/panel_window_controller_cocoa.h"
12#include "chrome/browser/ui/panels/panel.h"
13#include "chrome/browser/ui/panels/stacked_panel_collection.h"
14#include "content/public/browser/native_web_keyboard_event.h"
15
16using content::NativeWebKeyboardEvent;
17using content::WebContents;
18
19// This creates a shim window class, which in turn creates a Cocoa window
20// controller which in turn creates actual NSWindow by loading a nib.
21// Overall chain of ownership is:
22// PanelWindowControllerCocoa -> PanelCocoa -> Panel.
23// static
24NativePanel* Panel::CreateNativePanel(Panel* panel,
25                                      const gfx::Rect& bounds,
26                                      bool always_on_top) {
27  return new PanelCocoa(panel, bounds, always_on_top);
28}
29
30PanelCocoa::PanelCocoa(Panel* panel,
31                       const gfx::Rect& bounds,
32                       bool always_on_top)
33    : panel_(panel),
34      bounds_(bounds),
35      always_on_top_(always_on_top),
36      is_shown_(false),
37      attention_request_id_(0),
38      corner_style_(panel::ALL_ROUNDED) {
39  controller_ = [[PanelWindowControllerCocoa alloc] initWithPanel:this];
40}
41
42PanelCocoa::~PanelCocoa() {
43}
44
45bool PanelCocoa::IsClosed() const {
46  return !controller_;
47}
48
49void PanelCocoa::ShowPanel() {
50  ShowPanelInactive();
51  ActivatePanel();
52
53  // |-makeKeyAndOrderFront:| won't send |-windowDidBecomeKey:| until we
54  // return to the runloop. This causes extension tests that wait for the
55  // active status change notification to fail, so we send an active status
56  // notification here.
57  panel_->OnActiveStateChanged(true);
58}
59
60void PanelCocoa::ShowPanelInactive() {
61  if (IsClosed())
62    return;
63
64  // This method may be called several times, meaning 'ensure it's shown'.
65  // Animations don't look good when repeated - hence this flag is needed.
66  if (is_shown_) {
67    return;
68  }
69  // A call to SetPanelBounds() before showing it is required to set
70  // the panel frame properly.
71  SetPanelBoundsInstantly(bounds_);
72  is_shown_ = true;
73
74  NSRect finalFrame = cocoa_utils::ConvertRectToCocoaCoordinates(bounds_);
75  [controller_ revealAnimatedWithFrame:finalFrame];
76}
77
78gfx::Rect PanelCocoa::GetPanelBounds() const {
79  return bounds_;
80}
81
82// |bounds| is the platform-independent screen coordinates, with (0,0) at
83// top-left of the primary screen.
84void PanelCocoa::SetPanelBounds(const gfx::Rect& bounds) {
85  setBoundsInternal(bounds, true);
86}
87
88void PanelCocoa::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
89  setBoundsInternal(bounds, false);
90}
91
92void PanelCocoa::setBoundsInternal(const gfx::Rect& bounds, bool animate) {
93  // We will call SetPanelBoundsInstantly() once before showing the panel
94  // and it should set the panel frame correctly.
95  if (bounds_ == bounds && is_shown_)
96    return;
97
98  bounds_ = bounds;
99
100  // Safely ignore calls to animate bounds before the panel is shown to
101  // prevent the window from loading prematurely.
102  if (animate && !is_shown_)
103    return;
104
105  NSRect frame = cocoa_utils::ConvertRectToCocoaCoordinates(bounds);
106  [controller_ setPanelFrame:frame animate:animate];
107}
108
109void PanelCocoa::ClosePanel() {
110  if (IsClosed())
111      return;
112
113  NSWindow* window = [controller_ window];
114  // performClose: contains a nested message loop which can cause reentrancy
115  // if the browser is terminating and closing all the windows.
116  // Use this version that corresponds to protocol of performClose: but does not
117  // spin a nested loop.
118  // TODO(dimich): refactor similar method from BWC and reuse here.
119  if ([controller_ windowShouldClose:window]) {
120    // Make sure that the panel window is not associated with the underlying
121    // stack window because otherwise hiding the panel window could cause all
122    // other panel windows in the same stack to disappear.
123    NSWindow* stackWindow = [window parentWindow];
124    if (stackWindow)
125      [stackWindow removeChildWindow:window];
126
127    [window orderOut:nil];
128    [window close];
129  }
130}
131
132void PanelCocoa::ActivatePanel() {
133  if (!is_shown_)
134    return;
135
136  [controller_ activate];
137}
138
139void PanelCocoa::DeactivatePanel() {
140  [controller_ deactivate];
141}
142
143bool PanelCocoa::IsPanelActive() const {
144  // TODO(dcheng): It seems like a lot of these methods can be called before
145  // our NSWindow is created. Do we really need to check in every one of these
146  // methods if the NSWindow is created, or is there a better way to
147  // gracefully handle this?
148  if (!is_shown_)
149    return false;
150  return [[controller_ window] isMainWindow];
151}
152
153void PanelCocoa::PreventActivationByOS(bool prevent_activation) {
154  [controller_ preventBecomingKeyWindow:prevent_activation];
155  return;
156}
157
158gfx::NativeWindow PanelCocoa::GetNativePanelWindow() {
159  return [controller_ window];
160}
161
162void PanelCocoa::UpdatePanelTitleBar() {
163  if (!is_shown_)
164    return;
165  [controller_ updateTitleBar];
166}
167
168void PanelCocoa::UpdatePanelLoadingAnimations(bool should_animate) {
169  [controller_ updateThrobber:should_animate];
170}
171
172void PanelCocoa::PanelWebContentsFocused(content::WebContents* contents) {
173  // Nothing to do.
174}
175
176void PanelCocoa::PanelCut() {
177  // Nothing to do since we do not have panel-specific system menu on Mac.
178}
179
180void PanelCocoa::PanelCopy() {
181  // Nothing to do since we do not have panel-specific system menu on Mac.
182}
183
184void PanelCocoa::PanelPaste() {
185  // Nothing to do since we do not have panel-specific system menu on Mac.
186}
187
188void PanelCocoa::DrawAttention(bool draw_attention) {
189  DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0);
190
191  PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView];
192  if (draw_attention)
193    [titlebar drawAttention];
194  else
195    [titlebar stopDrawingAttention];
196
197  if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) {
198    if (draw_attention) {
199      DCHECK(!attention_request_id_);
200      attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest];
201    } else {
202      [NSApp cancelUserAttentionRequest:attention_request_id_];
203      attention_request_id_ = 0;
204    }
205  }
206}
207
208bool PanelCocoa::IsDrawingAttention() const {
209  PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView];
210  return [titlebar isDrawingAttention];
211}
212
213void PanelCocoa::HandlePanelKeyboardEvent(
214    const NativeWebKeyboardEvent& event) {
215  if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char)
216    return;
217
218  ChromeEventProcessingWindow* event_window =
219      static_cast<ChromeEventProcessingWindow*>([controller_ window]);
220  DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]);
221  [event_window redispatchKeyEvent:event.os_event];
222}
223
224void PanelCocoa::FullScreenModeChanged(bool is_full_screen) {
225  if (!is_shown_) {
226    // If the panel window is not shown due to that a Chrome tab window is in
227    // fullscreen mode when the panel is being created, we need to show the
228    // panel window now. In addition, its titlebar needs to be updated since it
229    // is not done at the panel creation time.
230    if (!is_full_screen) {
231      ShowPanelInactive();
232      UpdatePanelTitleBar();
233    }
234
235    // No need to proceed when the panel window was not shown previously.
236    // We either show the panel window or do not show it depending on current
237    // full screen state.
238    return;
239  }
240  [controller_ fullScreenModeChanged:is_full_screen];
241}
242
243bool PanelCocoa::IsPanelAlwaysOnTop() const {
244  return always_on_top_;
245}
246
247void PanelCocoa::SetPanelAlwaysOnTop(bool on_top) {
248  if (always_on_top_ == on_top)
249    return;
250  always_on_top_ = on_top;
251  [controller_ updateWindowLevel];
252  [controller_ updateWindowCollectionBehavior];
253}
254
255void PanelCocoa::UpdatePanelMinimizeRestoreButtonVisibility() {
256  [controller_ updateTitleBarMinimizeRestoreButtonVisibility];
257}
258
259void PanelCocoa::SetWindowCornerStyle(panel::CornerStyle corner_style) {
260  corner_style_ = corner_style;
261
262  // TODO(dimich): investigate how to support it on Mac.
263}
264
265void PanelCocoa::MinimizePanelBySystem() {
266  [controller_ miniaturize];
267}
268
269bool PanelCocoa::IsPanelMinimizedBySystem() const {
270  return [controller_ isMiniaturized];
271}
272
273bool PanelCocoa::IsPanelShownOnActiveDesktop() const {
274  return [[controller_ window] isOnActiveSpace];
275}
276
277void PanelCocoa::ShowShadow(bool show) {
278  [controller_ showShadow:show];
279}
280
281void PanelCocoa::PanelExpansionStateChanging(
282    Panel::ExpansionState old_state, Panel::ExpansionState new_state) {
283  [controller_ updateWindowLevel:(new_state != Panel::EXPANDED)];
284}
285
286void PanelCocoa::AttachWebContents(content::WebContents* contents) {
287  [controller_ webContentsInserted:contents];
288}
289
290void PanelCocoa::DetachWebContents(content::WebContents* contents) {
291  [controller_ webContentsDetached:contents];
292}
293
294gfx::Size PanelCocoa::WindowSizeFromContentSize(
295    const gfx::Size& content_size) const {
296  NSRect content = NSMakeRect(0, 0,
297                              content_size.width(), content_size.height());
298  NSRect frame = [controller_ frameRectForContentRect:content];
299  return gfx::Size(NSWidth(frame), NSHeight(frame));
300}
301
302gfx::Size PanelCocoa::ContentSizeFromWindowSize(
303    const gfx::Size& window_size) const {
304  NSRect frame = NSMakeRect(0, 0, window_size.width(), window_size.height());
305  NSRect content = [controller_ contentRectForFrameRect:frame];
306  return gfx::Size(NSWidth(content), NSHeight(content));
307}
308
309int PanelCocoa::TitleOnlyHeight() const {
310  return [controller_ titlebarHeightInScreenCoordinates];
311}
312
313Panel* PanelCocoa::panel() const {
314  return panel_.get();
315}
316
317void PanelCocoa::DidCloseNativeWindow() {
318  DCHECK(!IsClosed());
319  controller_ = NULL;
320  panel_->OnNativePanelClosed();
321}
322
323// NativePanelTesting implementation.
324class CocoaNativePanelTesting : public NativePanelTesting {
325 public:
326  CocoaNativePanelTesting(NativePanel* native_panel);
327  virtual ~CocoaNativePanelTesting() { }
328  // Overridden from NativePanelTesting
329  virtual void PressLeftMouseButtonTitlebar(
330      const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE;
331  virtual void ReleaseMouseButtonTitlebar(
332      panel::ClickModifier modifier) OVERRIDE;
333  virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE;
334  virtual void CancelDragTitlebar() OVERRIDE;
335  virtual void FinishDragTitlebar() OVERRIDE;
336  virtual bool VerifyDrawingAttention() const OVERRIDE;
337  virtual bool VerifyActiveState(bool is_active) OVERRIDE;
338  virtual bool VerifyAppIcon() const OVERRIDE;
339  virtual bool VerifySystemMinimizeState() const OVERRIDE;
340  virtual bool IsWindowVisible() const OVERRIDE;
341  virtual bool IsWindowSizeKnown() const OVERRIDE;
342  virtual bool IsAnimatingBounds() const OVERRIDE;
343  virtual bool IsButtonVisible(
344      panel::TitlebarButtonType button_type) const OVERRIDE;
345  virtual panel::CornerStyle GetWindowCornerStyle() const OVERRIDE;
346  virtual bool EnsureApplicationRunOnForeground() OVERRIDE;
347
348 private:
349  PanelTitlebarViewCocoa* titlebar() const;
350  // Weak, assumed always to outlive this test API object.
351  PanelCocoa* native_panel_window_;
352};
353
354NativePanelTesting* PanelCocoa::CreateNativePanelTesting() {
355  return new CocoaNativePanelTesting(this);
356}
357
358CocoaNativePanelTesting::CocoaNativePanelTesting(NativePanel* native_panel)
359  : native_panel_window_(static_cast<PanelCocoa*>(native_panel)) {
360}
361
362PanelTitlebarViewCocoa* CocoaNativePanelTesting::titlebar() const {
363  return [native_panel_window_->controller_ titlebarView];
364}
365
366void CocoaNativePanelTesting::PressLeftMouseButtonTitlebar(
367    const gfx::Point& mouse_location, panel::ClickModifier modifier) {
368  // Convert from platform-indepedent screen coordinates to Cocoa's screen
369  // coordinates because PanelTitlebarViewCocoa method takes Cocoa's screen
370  // coordinates.
371  int modifierFlags =
372      (modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0);
373  [titlebar() pressLeftMouseButtonTitlebar:
374      cocoa_utils::ConvertPointToCocoaCoordinates(mouse_location)
375           modifiers:modifierFlags];
376}
377
378void CocoaNativePanelTesting::ReleaseMouseButtonTitlebar(
379    panel::ClickModifier modifier) {
380  int modifierFlags =
381      (modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0);
382  [titlebar() releaseLeftMouseButtonTitlebar:modifierFlags];
383}
384
385void CocoaNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) {
386  // Convert from platform-indepedent screen coordinates to Cocoa's screen
387  // coordinates because PanelTitlebarViewCocoa method takes Cocoa's screen
388  // coordinates.
389  [titlebar() dragTitlebar:
390      cocoa_utils::ConvertPointToCocoaCoordinates(mouse_location)];
391}
392
393void CocoaNativePanelTesting::CancelDragTitlebar() {
394  [titlebar() cancelDragTitlebar];
395}
396
397void CocoaNativePanelTesting::FinishDragTitlebar() {
398  [titlebar() finishDragTitlebar];
399}
400
401bool CocoaNativePanelTesting::VerifyDrawingAttention() const {
402  return [titlebar() isDrawingAttention];
403}
404
405bool CocoaNativePanelTesting::VerifyActiveState(bool is_active) {
406  // TODO(jianli): to be implemented.
407  return false;
408}
409
410bool CocoaNativePanelTesting::VerifyAppIcon() const {
411  // Nothing to do since panel does not show dock icon.
412  return true;
413}
414
415bool CocoaNativePanelTesting::VerifySystemMinimizeState() const {
416  // TODO(jianli): to be implemented.
417  return true;
418}
419
420bool CocoaNativePanelTesting::IsWindowVisible() const {
421  return [[native_panel_window_->controller_ window] isVisible];
422}
423
424bool CocoaNativePanelTesting::IsWindowSizeKnown() const {
425  return true;
426}
427
428bool CocoaNativePanelTesting::IsAnimatingBounds() const {
429  if ([native_panel_window_->controller_ isAnimatingBounds])
430    return true;
431  StackedPanelCollection* stack = native_panel_window_->panel()->stack();
432  if (!stack)
433    return false;
434  return stack->IsAnimatingPanelBounds(native_panel_window_->panel());
435}
436
437bool CocoaNativePanelTesting::IsButtonVisible(
438    panel::TitlebarButtonType button_type) const {
439  switch (button_type) {
440    case panel::CLOSE_BUTTON:
441      return ![[titlebar() closeButton] isHidden];
442    case panel::MINIMIZE_BUTTON:
443      return ![[titlebar() minimizeButton] isHidden];
444    case panel::RESTORE_BUTTON:
445      return ![[titlebar() restoreButton] isHidden];
446    default:
447      NOTREACHED();
448  }
449  return false;
450}
451
452panel::CornerStyle CocoaNativePanelTesting::GetWindowCornerStyle() const {
453  return native_panel_window_->corner_style_;
454}
455
456bool CocoaNativePanelTesting::EnsureApplicationRunOnForeground() {
457  if ([NSApp isActive])
458    return true;
459  [NSApp activateIgnoringOtherApps:YES];
460  return [NSApp isActive];
461}
462