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/panels/panel.h"
6
7#include "base/logging.h"
8#include "base/message_loop/message_loop.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/app/chrome_command_ids.h"
11#include "chrome/browser/chrome_notification_types.h"
12#include "chrome/browser/devtools/devtools_window.h"
13#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
14#include "chrome/browser/extensions/api/tabs/tabs_windows_api.h"
15#include "chrome/browser/extensions/api/tabs/windows_event_router.h"
16#include "chrome/browser/extensions/extension_service.h"
17#include "chrome/browser/extensions/extension_system.h"
18#include "chrome/browser/extensions/extension_tab_util.h"
19#include "chrome/browser/extensions/image_loader.h"
20#include "chrome/browser/extensions/window_controller.h"
21#include "chrome/browser/extensions/window_controller_list.h"
22#include "chrome/browser/lifetime/application_lifetime.h"
23#include "chrome/browser/profiles/profile.h"
24#include "chrome/browser/themes/theme_service.h"
25#include "chrome/browser/themes/theme_service_factory.h"
26#include "chrome/browser/ui/panels/native_panel.h"
27#include "chrome/browser/ui/panels/panel_collection.h"
28#include "chrome/browser/ui/panels/panel_host.h"
29#include "chrome/browser/ui/panels/panel_manager.h"
30#include "chrome/browser/ui/panels/stacked_panel_collection.h"
31#include "chrome/browser/web_applications/web_app.h"
32#include "chrome/common/extensions/extension.h"
33#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
34#include "content/public/browser/notification_service.h"
35#include "content/public/browser/notification_source.h"
36#include "content/public/browser/notification_types.h"
37#include "content/public/browser/render_view_host.h"
38#include "content/public/browser/user_metrics.h"
39#include "content/public/browser/web_contents.h"
40#include "ui/gfx/image/image.h"
41#include "ui/gfx/rect.h"
42
43using content::RenderViewHost;
44using content::UserMetricsAction;
45
46namespace panel_internal {
47
48class PanelExtensionWindowController : public extensions::WindowController {
49 public:
50  PanelExtensionWindowController(Panel* panel, Profile* profile);
51  virtual ~PanelExtensionWindowController();
52
53  // Overridden from extensions::WindowController.
54  virtual int GetWindowId() const OVERRIDE;
55  virtual std::string GetWindowTypeText() const OVERRIDE;
56  virtual base::DictionaryValue* CreateWindowValueWithTabs(
57      const extensions::Extension* extension) const OVERRIDE;
58  virtual base::DictionaryValue* CreateTabValue(
59      const extensions::Extension* extension, int tab_index) const OVERRIDE;
60  virtual bool CanClose(Reason* reason) const OVERRIDE;
61  virtual void SetFullscreenMode(bool is_fullscreen,
62                                 const GURL& extension_url) const OVERRIDE;
63  virtual bool IsVisibleToExtension(
64      const extensions::Extension* extension) const OVERRIDE;
65
66 private:
67  Panel* panel_;  // Weak pointer. Owns us.
68  DISALLOW_COPY_AND_ASSIGN(PanelExtensionWindowController);
69};
70
71PanelExtensionWindowController::PanelExtensionWindowController(
72    Panel* panel, Profile* profile)
73    : extensions::WindowController(panel, profile),
74      panel_(panel) {
75  extensions::WindowControllerList::GetInstance()->AddExtensionWindow(this);
76}
77
78PanelExtensionWindowController::~PanelExtensionWindowController() {
79  extensions::WindowControllerList::GetInstance()->RemoveExtensionWindow(this);
80}
81
82int PanelExtensionWindowController::GetWindowId() const {
83  return static_cast<int>(panel_->session_id().id());
84}
85
86std::string PanelExtensionWindowController::GetWindowTypeText() const {
87  return extensions::tabs_constants::kWindowTypeValuePanel;
88}
89
90base::DictionaryValue*
91PanelExtensionWindowController::CreateWindowValueWithTabs(
92    const extensions::Extension* extension) const {
93  base::DictionaryValue* result = CreateWindowValue();
94
95  DCHECK(IsVisibleToExtension(extension));
96  DictionaryValue* tab_value = CreateTabValue(extension, 0);
97  if (tab_value) {
98    base::ListValue* tab_list = new ListValue();
99    tab_list->Append(tab_value);
100    result->Set(extensions::tabs_constants::kTabsKey, tab_list);
101  }
102  return result;
103}
104
105base::DictionaryValue* PanelExtensionWindowController::CreateTabValue(
106    const extensions::Extension* extension, int tab_index) const {
107  if (tab_index > 0)
108    return NULL;
109
110  content::WebContents* web_contents = panel_->GetWebContents();
111  if (!web_contents)
112    return NULL;
113
114  DCHECK(IsVisibleToExtension(extension));
115  DictionaryValue* tab_value = new DictionaryValue();
116  tab_value->SetInteger(extensions::tabs_constants::kIdKey,
117                        SessionID::IdForTab(web_contents));
118  tab_value->SetInteger(extensions::tabs_constants::kIndexKey, 0);
119  tab_value->SetInteger(extensions::tabs_constants::kWindowIdKey,
120                        SessionID::IdForWindowContainingTab(web_contents));
121  tab_value->SetString(
122      extensions::tabs_constants::kUrlKey, web_contents->GetURL().spec());
123  tab_value->SetString(extensions::tabs_constants::kStatusKey,
124                       ExtensionTabUtil::GetTabStatusText(
125                           web_contents->IsLoading()));
126  tab_value->SetBoolean(
127      extensions::tabs_constants::kActiveKey, panel_->IsActive());
128  tab_value->SetBoolean(extensions::tabs_constants::kSelectedKey, true);
129  tab_value->SetBoolean(extensions::tabs_constants::kHighlightedKey, true);
130  tab_value->SetBoolean(extensions::tabs_constants::kPinnedKey, false);
131  tab_value->SetString(
132      extensions::tabs_constants::kTitleKey, web_contents->GetTitle());
133  tab_value->SetBoolean(
134      extensions::tabs_constants::kIncognitoKey,
135      web_contents->GetBrowserContext()->IsOffTheRecord());
136  return tab_value;
137}
138
139bool PanelExtensionWindowController::CanClose(Reason* reason) const {
140  return true;
141}
142
143void PanelExtensionWindowController::SetFullscreenMode(
144    bool is_fullscreen, const GURL& extension_url) const {
145  // Do nothing. Panels cannot be fullscreen.
146}
147
148bool PanelExtensionWindowController::IsVisibleToExtension(
149    const extensions::Extension* extension) const {
150  return extension->id() == panel_->extension_id();
151}
152
153}  // namespace internal
154
155Panel::~Panel() {
156  DCHECK(!collection_);
157  // Invoked by native panel destructor. Do not access native_panel_ here.
158  chrome::EndKeepAlive();  // Remove shutdown prevention.
159}
160
161PanelManager* Panel::manager() const {
162  return PanelManager::GetInstance();
163}
164
165const std::string Panel::extension_id() const {
166  return web_app::GetExtensionIdFromApplicationName(app_name_);
167}
168
169CommandUpdater* Panel::command_updater() {
170  return &command_updater_;
171}
172
173Profile* Panel::profile() const {
174  return profile_;
175}
176
177const extensions::Extension* Panel::GetExtension() const {
178  ExtensionService* extension_service =
179      extensions::ExtensionSystem::Get(profile())->extension_service();
180  if (!extension_service || !extension_service->is_ready())
181    return NULL;
182  return extension_service->GetExtensionById(extension_id(), false);
183}
184
185content::WebContents* Panel::GetWebContents() const {
186  return panel_host_.get() ? panel_host_->web_contents() : NULL;
187}
188
189void Panel::SetExpansionState(ExpansionState new_state) {
190  if (expansion_state_ == new_state)
191    return;
192  native_panel_->PanelExpansionStateChanging(expansion_state_, new_state);
193  expansion_state_ = new_state;
194
195  manager()->OnPanelExpansionStateChanged(this);
196
197  DCHECK(initialized_ && collection_ != NULL);
198  native_panel_->PreventActivationByOS(collection_->IsPanelMinimized(this));
199  UpdateMinimizeRestoreButtonVisibility();
200
201  content::NotificationService::current()->Notify(
202      chrome::NOTIFICATION_PANEL_CHANGED_EXPANSION_STATE,
203      content::Source<Panel>(this),
204      content::NotificationService::NoDetails());
205}
206
207bool Panel::IsDrawingAttention() const {
208  return native_panel_->IsDrawingAttention();
209}
210
211void Panel::FullScreenModeChanged(bool is_full_screen) {
212  native_panel_->FullScreenModeChanged(is_full_screen);
213}
214
215int Panel::TitleOnlyHeight() const {
216  return native_panel_->TitleOnlyHeight();
217}
218
219bool Panel::CanShowMinimizeButton() const {
220  return collection_ && collection_->CanShowMinimizeButton(this);
221}
222
223bool Panel::CanShowRestoreButton() const {
224  return collection_ && collection_->CanShowRestoreButton(this);
225}
226
227bool Panel::IsActive() const {
228  return native_panel_->IsPanelActive();
229}
230
231bool Panel::IsMaximized() const {
232  // Size of panels is managed by PanelManager, they are never 'zoomed'.
233  return false;
234}
235
236bool Panel::IsMinimized() const {
237  return !collection_ || collection_->IsPanelMinimized(this);
238}
239
240bool Panel::IsFullscreen() const {
241  return false;
242}
243
244gfx::NativeWindow Panel::GetNativeWindow() {
245  return native_panel_->GetNativePanelWindow();
246}
247
248gfx::Rect Panel::GetRestoredBounds() const {
249  gfx::Rect bounds = native_panel_->GetPanelBounds();
250  bounds.set_y(bounds.bottom() - full_size_.height());
251  bounds.set_x(bounds.right() - full_size_.width());
252  bounds.set_size(full_size_);
253  return bounds;
254}
255
256ui::WindowShowState Panel::GetRestoredState() const {
257  return ui::SHOW_STATE_NORMAL;
258}
259
260gfx::Rect Panel::GetBounds() const {
261  return native_panel_->GetPanelBounds();
262}
263
264void Panel::Show() {
265  if (manager()->display_settings_provider()->is_full_screen() || !collection_)
266    return;
267
268  native_panel_->ShowPanel();
269}
270
271void Panel::Hide() {
272  // Not implemented.
273}
274
275void Panel::ShowInactive() {
276  if (manager()->display_settings_provider()->is_full_screen() || !collection_)
277    return;
278
279  native_panel_->ShowPanelInactive();
280}
281
282// Close() may be called multiple times if the panel window is not ready to
283// close on the first attempt.
284void Panel::Close() {
285  native_panel_->ClosePanel();
286}
287
288void Panel::Activate() {
289  if (!collection_)
290    return;
291
292  collection_->ActivatePanel(this);
293  native_panel_->ActivatePanel();
294}
295
296void Panel::Deactivate() {
297  native_panel_->DeactivatePanel();
298}
299
300void Panel::Maximize() {
301  Restore();
302}
303
304void Panel::Minimize() {
305  if (collection_)
306    collection_->MinimizePanel(this);
307}
308
309bool Panel::IsMinimizedBySystem() const {
310  return native_panel_->IsPanelMinimizedBySystem();
311}
312
313bool Panel::IsShownOnActiveDesktop() const {
314  return native_panel_->IsPanelShownOnActiveDesktop();
315}
316
317void Panel::ShowShadow(bool show) {
318  native_panel_->ShowShadow(show);
319}
320
321void Panel::Restore() {
322  if (collection_)
323    collection_->RestorePanel(this);
324}
325
326void Panel::SetBounds(const gfx::Rect& bounds) {
327  // Ignore bounds position as the panel manager controls all positioning.
328  if (!collection_)
329    return;
330  collection_->ResizePanelWindow(this, bounds.size());
331  SetAutoResizable(false);
332}
333
334void Panel::FlashFrame(bool draw_attention) {
335  if (IsDrawingAttention() == draw_attention || !collection_)
336    return;
337
338  // Don't draw attention for an active panel.
339  if (draw_attention && IsActive())
340    return;
341
342  // Invoking native panel to draw attention must be done before informing the
343  // panel collection because it needs to check internal state of the panel to
344  // determine if the panel has been drawing attention.
345  native_panel_->DrawAttention(draw_attention);
346  collection_->OnPanelAttentionStateChanged(this);
347}
348
349bool Panel::IsAlwaysOnTop() const {
350  return native_panel_->IsPanelAlwaysOnTop();
351}
352
353void Panel::ExecuteCommandWithDisposition(int id,
354                                          WindowOpenDisposition disposition) {
355  DCHECK(command_updater_.IsCommandEnabled(id)) << "Invalid/disabled command "
356                                                << id;
357
358  if (!GetWebContents())
359    return;
360
361  switch (id) {
362    // Navigation
363    case IDC_RELOAD:
364      panel_host_->Reload();
365      break;
366    case IDC_RELOAD_IGNORING_CACHE:
367      panel_host_->ReloadIgnoringCache();
368      break;
369    case IDC_STOP:
370      panel_host_->StopLoading();
371      break;
372
373    // Window management
374    case IDC_CLOSE_WINDOW:
375      content::RecordAction(UserMetricsAction("CloseWindow"));
376      Close();
377      break;
378    case IDC_EXIT:
379      content::RecordAction(UserMetricsAction("Exit"));
380      chrome::AttemptUserExit();
381      break;
382
383    // Clipboard
384    case IDC_COPY:
385      content::RecordAction(UserMetricsAction("Copy"));
386      native_panel_->PanelCopy();
387      break;
388    case IDC_CUT:
389      content::RecordAction(UserMetricsAction("Cut"));
390      native_panel_->PanelCut();
391      break;
392    case IDC_PASTE:
393      content::RecordAction(UserMetricsAction("Paste"));
394      native_panel_->PanelPaste();
395      break;
396
397    // Zoom
398    case IDC_ZOOM_PLUS:
399      panel_host_->Zoom(content::PAGE_ZOOM_IN);
400      break;
401    case IDC_ZOOM_NORMAL:
402      panel_host_->Zoom(content::PAGE_ZOOM_RESET);
403      break;
404    case IDC_ZOOM_MINUS:
405      panel_host_->Zoom(content::PAGE_ZOOM_OUT);
406      break;
407
408    // DevTools
409    case IDC_DEV_TOOLS:
410      content::RecordAction(UserMetricsAction("DevTools_ToggleWindow"));
411      DevToolsWindow::ToggleDevToolsWindow(
412          GetWebContents()->GetRenderViewHost(),
413          true,
414          DEVTOOLS_TOGGLE_ACTION_SHOW);
415      break;
416    case IDC_DEV_TOOLS_CONSOLE:
417      content::RecordAction(UserMetricsAction("DevTools_ToggleConsole"));
418      DevToolsWindow::ToggleDevToolsWindow(
419          GetWebContents()->GetRenderViewHost(),
420          true,
421          DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE);
422      break;
423
424    default:
425      LOG(WARNING) << "Received unimplemented command: " << id;
426      break;
427  }
428}
429
430void Panel::Observe(int type,
431                    const content::NotificationSource& source,
432                    const content::NotificationDetails& details) {
433  switch (type) {
434    case content::NOTIFICATION_WEB_CONTENTS_SWAPPED:
435      ConfigureAutoResize(content::Source<content::WebContents>(source).ptr());
436      break;
437    case chrome::NOTIFICATION_EXTENSION_UNLOADED:
438      if (content::Details<extensions::UnloadedExtensionInfo>(
439              details)->extension->id() == extension_id())
440        Close();
441      break;
442    case chrome::NOTIFICATION_APP_TERMINATING:
443      Close();
444      break;
445    default:
446      NOTREACHED() << "Received unexpected notification " << type;
447  }
448}
449
450void Panel::OnTitlebarClicked(panel::ClickModifier modifier) {
451  if (collection_)
452    collection_->OnPanelTitlebarClicked(this, modifier);
453
454  // Normally the system activates a window when the titlebar is clicked.
455  // However, we prevent system activation of minimized panels, thus the
456  // activation may not have occurred. Also, some OSes (Windows) will
457  // activate a minimized panel on mouse-down regardless of our attempts to
458  // prevent system activation. Attention state is not cleared in that case.
459  // See Panel::OnActiveStateChanged().
460  // Therefore, we ensure activation and clearing of attention state if the
461  // panel has been expanded. If the panel is in a stack, the titlebar click
462  // might minimize the panel and we do not want to activate it to make it
463  // expand again.
464  // These are no-ops if no changes are needed.
465  if (IsMinimized())
466    return;
467  Activate();
468  FlashFrame(false);
469}
470
471void Panel::OnMinimizeButtonClicked(panel::ClickModifier modifier) {
472  if (collection_)
473    collection_->OnMinimizeButtonClicked(this, modifier);
474}
475
476void Panel::OnRestoreButtonClicked(panel::ClickModifier modifier) {
477  // Clicking the restore button has the same behavior as clicking the titlebar.
478  OnTitlebarClicked(modifier);
479}
480
481void Panel::OnWindowSizeAvailable() {
482  ConfigureAutoResize(GetWebContents());
483}
484
485void Panel::OnNativePanelClosed() {
486  // Ensure previously enqueued OnImageLoaded callbacks are ignored.
487  image_loader_ptr_factory_.InvalidateWeakPtrs();
488  registrar_.RemoveAll();
489  manager()->OnPanelClosed(this);
490  DCHECK(!collection_);
491}
492
493StackedPanelCollection* Panel::stack() const {
494  return collection_ && collection_->type() == PanelCollection::STACKED ?
495      static_cast<StackedPanelCollection*>(collection_) : NULL;
496}
497
498panel::Resizability Panel::CanResizeByMouse() const {
499  if (!collection_)
500    return panel::NOT_RESIZABLE;
501
502  return collection_->GetPanelResizability(this);
503}
504
505void Panel::Initialize(const GURL& url,
506                       const gfx::Rect& bounds,
507                       bool always_on_top) {
508  DCHECK(!initialized_);
509  DCHECK(!collection_);  // Cannot be added to a collection until fully created.
510  DCHECK_EQ(EXPANDED, expansion_state_);
511  DCHECK(!bounds.IsEmpty());
512  initialized_ = true;
513  full_size_ = bounds.size();
514  native_panel_ = CreateNativePanel(this, bounds, always_on_top);
515
516  extension_window_controller_.reset(
517      new panel_internal::PanelExtensionWindowController(this, profile_));
518
519  InitCommandState();
520
521  // Set up hosting for web contents.
522  panel_host_.reset(new PanelHost(this, profile_));
523  panel_host_->Init(url);
524  content::WebContents* web_contents = GetWebContents();
525  // The contents might be NULL for most of our tests.
526  if (web_contents)
527    native_panel_->AttachWebContents(web_contents);
528
529  // Close when the extension is unloaded or the browser is exiting.
530  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
531                 content::Source<Profile>(profile_));
532  registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
533                 content::NotificationService::AllSources());
534  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
535                 content::Source<ThemeService>(
536                    ThemeServiceFactory::GetForProfile(profile_)));
537
538  // Prevent the browser process from shutting down while this window is open.
539  chrome::StartKeepAlive();
540
541  UpdateAppIcon();
542}
543
544void Panel::SetPanelBounds(const gfx::Rect& bounds) {
545  if (bounds != native_panel_->GetPanelBounds())
546    native_panel_->SetPanelBounds(bounds);
547}
548
549void Panel::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
550  native_panel_->SetPanelBoundsInstantly(bounds);
551}
552
553void Panel::LimitSizeToWorkArea(const gfx::Rect& work_area) {
554  int max_width = manager()->GetMaxPanelWidth(work_area);
555  int max_height = manager()->GetMaxPanelHeight(work_area);
556
557  // If the custom max size is used, ensure that it does not exceed the display
558  // area.
559  if (max_size_policy_ == CUSTOM_MAX_SIZE) {
560    int current_max_width = max_size_.width();
561    if (current_max_width > max_width)
562      max_width = std::min(current_max_width, work_area.width());
563    int current_max_height = max_size_.height();
564    if (current_max_height > max_height)
565      max_height = std::min(current_max_height, work_area.height());
566  }
567
568  SetSizeRange(min_size_, gfx::Size(max_width, max_height));
569
570  // Ensure that full size does not exceed max size.
571  full_size_ = ClampSize(full_size_);
572}
573
574void Panel::SetAutoResizable(bool resizable) {
575  if (auto_resizable_ == resizable)
576    return;
577
578  auto_resizable_ = resizable;
579  content::WebContents* web_contents = GetWebContents();
580  if (auto_resizable_) {
581    if (web_contents)
582      EnableWebContentsAutoResize(web_contents);
583  } else {
584    if (web_contents) {
585      registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_SWAPPED,
586                        content::Source<content::WebContents>(web_contents));
587
588      // NULL might be returned if the tab has not been added.
589      RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
590      if (render_view_host)
591        render_view_host->DisableAutoResize(full_size_);
592    }
593  }
594}
595
596void Panel::EnableWebContentsAutoResize(content::WebContents* web_contents) {
597  DCHECK(web_contents);
598  ConfigureAutoResize(web_contents);
599
600  // We also need to know when the render view host changes in order
601  // to turn on auto-resize notifications in the new render view host.
602  if (!registrar_.IsRegistered(
603          this, content::NOTIFICATION_WEB_CONTENTS_SWAPPED,
604          content::Source<content::WebContents>(web_contents))) {
605    registrar_.Add(
606        this,
607        content::NOTIFICATION_WEB_CONTENTS_SWAPPED,
608        content::Source<content::WebContents>(web_contents));
609  }
610}
611
612void Panel::OnContentsAutoResized(const gfx::Size& new_content_size) {
613  DCHECK(auto_resizable_);
614  if (!collection_)
615    return;
616
617  gfx::Size new_window_size =
618      native_panel_->WindowSizeFromContentSize(new_content_size);
619
620  // Ignore content auto resizes until window frame size is known.
621  // This reduces extra resizes when panel is first shown.
622  // After window frame size is known, it will trigger another content
623  // auto resize.
624  if (new_content_size == new_window_size)
625    return;
626
627  collection_->ResizePanelWindow(this, new_window_size);
628}
629
630void Panel::OnWindowResizedByMouse(const gfx::Rect& new_bounds) {
631  if (collection_)
632    collection_->OnPanelResizedByMouse(this, new_bounds);
633}
634
635void Panel::SetSizeRange(const gfx::Size& min_size, const gfx::Size& max_size) {
636  if (min_size == min_size_ && max_size == max_size_)
637    return;
638
639  DCHECK(min_size.width() <= max_size.width());
640  DCHECK(min_size.height() <= max_size.height());
641  min_size_ = min_size;
642  max_size_ = max_size;
643
644  ConfigureAutoResize(GetWebContents());
645}
646
647void Panel::IncreaseMaxSize(const gfx::Size& desired_panel_size) {
648  gfx::Size new_max_size = max_size_;
649  if (new_max_size.width() < desired_panel_size.width())
650    new_max_size.set_width(desired_panel_size.width());
651  if (new_max_size.height() < desired_panel_size.height())
652    new_max_size.set_height(desired_panel_size.height());
653
654  SetSizeRange(min_size_, new_max_size);
655}
656
657void Panel::HandleKeyboardEvent(const content::NativeWebKeyboardEvent& event) {
658  native_panel_->HandlePanelKeyboardEvent(event);
659}
660
661void Panel::SetAlwaysOnTop(bool on_top) {
662  native_panel_->SetPanelAlwaysOnTop(on_top);
663}
664
665void Panel::SetPreviewMode(bool in_preview) {
666  DCHECK_NE(in_preview_mode_, in_preview);
667  in_preview_mode_ = in_preview;
668}
669
670void Panel::EnableResizeByMouse(bool enable) {
671  DCHECK(native_panel_);
672  native_panel_->EnableResizeByMouse(enable);
673}
674
675void Panel::UpdateMinimizeRestoreButtonVisibility() {
676  native_panel_->UpdatePanelMinimizeRestoreButtonVisibility();
677}
678
679gfx::Size Panel::ClampSize(const gfx::Size& size) const {
680  // The panel width:
681  // * cannot grow or shrink to go beyond [min_width, max_width]
682  int new_width = size.width();
683  if (new_width > max_size_.width())
684    new_width = max_size_.width();
685  if (new_width < min_size_.width())
686    new_width = min_size_.width();
687
688  // The panel height:
689  // * cannot grow or shrink to go beyond [min_height, max_height]
690  int new_height = size.height();
691  if (new_height > max_size_.height())
692    new_height = max_size_.height();
693  if (new_height < min_size_.height())
694    new_height = min_size_.height();
695
696  return gfx::Size(new_width, new_height);
697}
698
699void Panel::OnActiveStateChanged(bool active) {
700  // Clear attention state when an expanded panel becomes active.
701  // On some systems (e.g. Win), mouse-down activates a panel regardless of
702  // its expansion state. However, we don't want to clear draw attention if
703  // contents are not visible. In that scenario, if the mouse-down results
704  // in a mouse-click, draw attention will be cleared then.
705  // See Panel::OnTitlebarClicked().
706  if (active && IsDrawingAttention() && !IsMinimized())
707    FlashFrame(false);
708
709  if (collection_)
710    collection_->OnPanelActiveStateChanged(this);
711
712  // Send extension event about window changing active state.
713  extensions::TabsWindowsAPI* tabs_windows_api =
714      extensions::TabsWindowsAPI::Get(profile());
715  if (tabs_windows_api) {
716    tabs_windows_api->windows_event_router()->OnActiveWindowChanged(
717        active ? extension_window_controller_.get() : NULL);
718  }
719
720  content::NotificationService::current()->Notify(
721      chrome::NOTIFICATION_PANEL_CHANGED_ACTIVE_STATUS,
722      content::Source<Panel>(this),
723      content::NotificationService::NoDetails());
724}
725
726void Panel::OnPanelStartUserResizing() {
727  SetAutoResizable(false);
728  SetPreviewMode(true);
729  max_size_policy_ = CUSTOM_MAX_SIZE;
730}
731
732void Panel::OnPanelEndUserResizing() {
733  SetPreviewMode(false);
734}
735
736bool Panel::ShouldCloseWindow() {
737  return true;
738}
739
740void Panel::OnWindowClosing() {
741  if (GetWebContents()) {
742    native_panel_->DetachWebContents(GetWebContents());
743    panel_host_->DestroyWebContents();
744  }
745}
746
747bool Panel::ExecuteCommandIfEnabled(int id) {
748  if (command_updater()->SupportsCommand(id) &&
749      command_updater()->IsCommandEnabled(id)) {
750    ExecuteCommandWithDisposition(id, CURRENT_TAB);
751    return true;
752  }
753  return false;
754}
755
756string16 Panel::GetWindowTitle() const {
757  content::WebContents* contents = GetWebContents();
758  string16 title;
759
760  // |contents| can be NULL during the window's creation.
761  if (contents) {
762    title = contents->GetTitle();
763    FormatTitleForDisplay(&title);
764  }
765
766  if (title.empty())
767    title = UTF8ToUTF16(app_name());
768
769  return title;
770}
771
772gfx::Image Panel::GetCurrentPageIcon() const {
773  return panel_host_->GetPageIcon();
774}
775
776void Panel::UpdateTitleBar() {
777  native_panel_->UpdatePanelTitleBar();
778}
779
780void Panel::LoadingStateChanged(bool is_loading) {
781  command_updater_.UpdateCommandEnabled(IDC_STOP, is_loading);
782  native_panel_->UpdatePanelLoadingAnimations(is_loading);
783  UpdateTitleBar();
784}
785
786void Panel::WebContentsFocused(content::WebContents* contents) {
787  native_panel_->PanelWebContentsFocused(contents);
788}
789
790void Panel::MoveByInstantly(const gfx::Vector2d& delta_origin) {
791  gfx::Rect bounds = GetBounds();
792  bounds.Offset(delta_origin);
793  SetPanelBoundsInstantly(bounds);
794}
795
796void Panel::SetWindowCornerStyle(panel::CornerStyle corner_style) {
797  native_panel_->SetWindowCornerStyle(corner_style);
798}
799
800void Panel::MinimizeBySystem() {
801  native_panel_->MinimizePanelBySystem();
802}
803
804Panel::Panel(Profile* profile, const std::string& app_name,
805             const gfx::Size& min_size, const gfx::Size& max_size)
806    : app_name_(app_name),
807      profile_(profile),
808      collection_(NULL),
809      initialized_(false),
810      min_size_(min_size),
811      max_size_(max_size),
812      max_size_policy_(DEFAULT_MAX_SIZE),
813      auto_resizable_(false),
814      in_preview_mode_(false),
815      native_panel_(NULL),
816      attention_mode_(USE_PANEL_ATTENTION),
817      expansion_state_(EXPANDED),
818      command_updater_(this),
819      image_loader_ptr_factory_(this) {
820}
821
822void Panel::OnImageLoaded(const gfx::Image& image) {
823  if (!image.IsEmpty()) {
824    app_icon_ = image;
825    native_panel_->UpdatePanelTitleBar();
826  }
827
828  content::NotificationService::current()->Notify(
829      chrome::NOTIFICATION_PANEL_APP_ICON_LOADED,
830      content::Source<Panel>(this),
831      content::NotificationService::NoDetails());
832}
833
834void Panel::InitCommandState() {
835  // All supported commands whose state isn't set automagically some other way
836  // (like Stop during a page load) must have their state initialized here,
837  // otherwise they will be forever disabled.
838
839  // Navigation commands
840  command_updater_.UpdateCommandEnabled(IDC_RELOAD, true);
841  command_updater_.UpdateCommandEnabled(IDC_RELOAD_IGNORING_CACHE, true);
842
843  // Window management commands
844  command_updater_.UpdateCommandEnabled(IDC_CLOSE_WINDOW, true);
845  command_updater_.UpdateCommandEnabled(IDC_EXIT, true);
846
847  // Zoom
848  command_updater_.UpdateCommandEnabled(IDC_ZOOM_MENU, true);
849  command_updater_.UpdateCommandEnabled(IDC_ZOOM_PLUS, true);
850  command_updater_.UpdateCommandEnabled(IDC_ZOOM_NORMAL, true);
851  command_updater_.UpdateCommandEnabled(IDC_ZOOM_MINUS, true);
852
853  // Clipboard
854  command_updater_.UpdateCommandEnabled(IDC_COPY, true);
855  command_updater_.UpdateCommandEnabled(IDC_CUT, true);
856  command_updater_.UpdateCommandEnabled(IDC_PASTE, true);
857
858  // DevTools
859  command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS, true);
860  command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS_CONSOLE, true);
861}
862
863void Panel::ConfigureAutoResize(content::WebContents* web_contents) {
864  if (!auto_resizable_ || !web_contents)
865    return;
866
867  // NULL might be returned if the tab has not been added.
868  RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
869  if (!render_view_host)
870    return;
871
872  render_view_host->EnableAutoResize(
873      min_size_,
874      native_panel_->ContentSizeFromWindowSize(max_size_));
875}
876
877void Panel::UpdateAppIcon() {
878  const extensions::Extension* extension = GetExtension();
879  if (!extension)
880    return;
881
882  extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile());
883  loader->LoadImageAsync(
884      extension,
885      extensions::IconsInfo::GetIconResource(
886          extension,
887          extension_misc::EXTENSION_ICON_SMALL,
888          ExtensionIconSet::MATCH_BIGGER),
889      gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
890                extension_misc::EXTENSION_ICON_SMALL),
891      base::Bind(&Panel::OnImageLoaded,
892                 image_loader_ptr_factory_.GetWeakPtr()));
893}
894
895// static
896void Panel::FormatTitleForDisplay(string16* title) {
897  size_t current_index = 0;
898  size_t match_index;
899  while ((match_index = title->find(L'\n', current_index)) != string16::npos) {
900    title->replace(match_index, 1, string16());
901    current_index = match_index;
902  }
903}
904