panel.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/app/chrome_command_ids.h"
11#include "chrome/browser/devtools/devtools_window.h"
12#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
13#include "chrome/browser/extensions/api/tabs/tabs_windows_api.h"
14#include "chrome/browser/extensions/api/tabs/windows_event_router.h"
15#include "chrome/browser/extensions/extension_service.h"
16#include "chrome/browser/extensions/extension_system.h"
17#include "chrome/browser/extensions/extension_tab_util.h"
18#include "chrome/browser/extensions/image_loader.h"
19#include "chrome/browser/extensions/window_controller.h"
20#include "chrome/browser/extensions/window_controller_list.h"
21#include "chrome/browser/lifetime/application_lifetime.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/browser/themes/theme_service.h"
24#include "chrome/browser/themes/theme_service_factory.h"
25#include "chrome/browser/ui/panels/native_panel.h"
26#include "chrome/browser/ui/panels/panel_collection.h"
27#include "chrome/browser/ui/panels/panel_host.h"
28#include "chrome/browser/ui/panels/panel_manager.h"
29#include "chrome/browser/ui/panels/stacked_panel_collection.h"
30#include "chrome/browser/web_applications/web_app.h"
31#include "chrome/common/chrome_notification_types.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
313void Panel::ShowShadow(bool show) {
314  native_panel_->ShowShadow(show);
315}
316
317void Panel::Restore() {
318  if (collection_)
319    collection_->RestorePanel(this);
320}
321
322void Panel::SetBounds(const gfx::Rect& bounds) {
323  // Ignore bounds position as the panel manager controls all positioning.
324  if (!collection_)
325    return;
326  collection_->ResizePanelWindow(this, bounds.size());
327  SetAutoResizable(false);
328}
329
330void Panel::FlashFrame(bool draw_attention) {
331  if (IsDrawingAttention() == draw_attention || !collection_)
332    return;
333
334  // Don't draw attention for an active panel.
335  if (draw_attention && IsActive())
336    return;
337
338  // Invoking native panel to draw attention must be done before informing the
339  // panel collection because it needs to check internal state of the panel to
340  // determine if the panel has been drawing attention.
341  native_panel_->DrawAttention(draw_attention);
342  collection_->OnPanelAttentionStateChanged(this);
343}
344
345bool Panel::IsAlwaysOnTop() const {
346  return native_panel_->IsPanelAlwaysOnTop();
347}
348
349void Panel::ExecuteCommandWithDisposition(int id,
350                                          WindowOpenDisposition disposition) {
351  DCHECK(command_updater_.IsCommandEnabled(id)) << "Invalid/disabled command "
352                                                << id;
353
354  if (!GetWebContents())
355    return;
356
357  switch (id) {
358    // Navigation
359    case IDC_RELOAD:
360      panel_host_->Reload();
361      break;
362    case IDC_RELOAD_IGNORING_CACHE:
363      panel_host_->ReloadIgnoringCache();
364      break;
365    case IDC_STOP:
366      panel_host_->StopLoading();
367      break;
368
369    // Window management
370    case IDC_CLOSE_WINDOW:
371      content::RecordAction(UserMetricsAction("CloseWindow"));
372      Close();
373      break;
374    case IDC_EXIT:
375      content::RecordAction(UserMetricsAction("Exit"));
376      chrome::AttemptUserExit();
377      break;
378
379    // Clipboard
380    case IDC_COPY:
381      content::RecordAction(UserMetricsAction("Copy"));
382      native_panel_->PanelCopy();
383      break;
384    case IDC_CUT:
385      content::RecordAction(UserMetricsAction("Cut"));
386      native_panel_->PanelCut();
387      break;
388    case IDC_PASTE:
389      content::RecordAction(UserMetricsAction("Paste"));
390      native_panel_->PanelPaste();
391      break;
392
393    // Zoom
394    case IDC_ZOOM_PLUS:
395      panel_host_->Zoom(content::PAGE_ZOOM_IN);
396      break;
397    case IDC_ZOOM_NORMAL:
398      panel_host_->Zoom(content::PAGE_ZOOM_RESET);
399      break;
400    case IDC_ZOOM_MINUS:
401      panel_host_->Zoom(content::PAGE_ZOOM_OUT);
402      break;
403
404    // DevTools
405    case IDC_DEV_TOOLS:
406      content::RecordAction(UserMetricsAction("DevTools_ToggleWindow"));
407      DevToolsWindow::ToggleDevToolsWindow(
408          GetWebContents()->GetRenderViewHost(),
409          true,
410          DEVTOOLS_TOGGLE_ACTION_SHOW);
411      break;
412    case IDC_DEV_TOOLS_CONSOLE:
413      content::RecordAction(UserMetricsAction("DevTools_ToggleConsole"));
414      DevToolsWindow::ToggleDevToolsWindow(
415          GetWebContents()->GetRenderViewHost(),
416          true,
417          DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE);
418      break;
419
420    default:
421      LOG(WARNING) << "Received unimplemented command: " << id;
422      break;
423  }
424}
425
426void Panel::Observe(int type,
427                    const content::NotificationSource& source,
428                    const content::NotificationDetails& details) {
429  switch (type) {
430    case content::NOTIFICATION_WEB_CONTENTS_SWAPPED:
431      ConfigureAutoResize(content::Source<content::WebContents>(source).ptr());
432      break;
433    case chrome::NOTIFICATION_EXTENSION_UNLOADED:
434      if (content::Details<extensions::UnloadedExtensionInfo>(
435              details)->extension->id() == extension_id())
436        Close();
437      break;
438    case chrome::NOTIFICATION_APP_TERMINATING:
439      Close();
440      break;
441    default:
442      NOTREACHED() << "Received unexpected notification " << type;
443  }
444}
445
446void Panel::OnTitlebarClicked(panel::ClickModifier modifier) {
447  if (collection_)
448    collection_->OnPanelTitlebarClicked(this, modifier);
449
450  // Normally the system activates a window when the titlebar is clicked.
451  // However, we prevent system activation of minimized panels, thus the
452  // activation may not have occurred. Also, some OSes (Windows) will
453  // activate a minimized panel on mouse-down regardless of our attempts to
454  // prevent system activation. Attention state is not cleared in that case.
455  // See Panel::OnActiveStateChanged().
456  // Therefore, we ensure activation and clearing of attention state if the
457  // panel has been expanded. If the panel is in a stack, the titlebar click
458  // might minimize the panel and we do not want to activate it to make it
459  // expand again.
460  // These are no-ops if no changes are needed.
461  if (IsMinimized())
462    return;
463  Activate();
464  FlashFrame(false);
465}
466
467void Panel::OnMinimizeButtonClicked(panel::ClickModifier modifier) {
468  if (collection_)
469    collection_->OnMinimizeButtonClicked(this, modifier);
470}
471
472void Panel::OnRestoreButtonClicked(panel::ClickModifier modifier) {
473  // Clicking the restore button has the same behavior as clicking the titlebar.
474  OnTitlebarClicked(modifier);
475}
476
477void Panel::OnWindowSizeAvailable() {
478  ConfigureAutoResize(GetWebContents());
479}
480
481void Panel::OnNativePanelClosed() {
482  // Ensure previously enqueued OnImageLoaded callbacks are ignored.
483  image_loader_ptr_factory_.InvalidateWeakPtrs();
484  registrar_.RemoveAll();
485  manager()->OnPanelClosed(this);
486  DCHECK(!collection_);
487}
488
489StackedPanelCollection* Panel::stack() const {
490  return collection_ && collection_->type() == PanelCollection::STACKED ?
491      static_cast<StackedPanelCollection*>(collection_) : NULL;
492}
493
494panel::Resizability Panel::CanResizeByMouse() const {
495  if (!collection_)
496    return panel::NOT_RESIZABLE;
497
498  return collection_->GetPanelResizability(this);
499}
500
501void Panel::Initialize(const GURL& url,
502                       const gfx::Rect& bounds,
503                       bool always_on_top) {
504  DCHECK(!initialized_);
505  DCHECK(!collection_);  // Cannot be added to a collection until fully created.
506  DCHECK_EQ(EXPANDED, expansion_state_);
507  DCHECK(!bounds.IsEmpty());
508  initialized_ = true;
509  full_size_ = bounds.size();
510  native_panel_ = CreateNativePanel(this, bounds, always_on_top);
511
512  extension_window_controller_.reset(
513      new panel_internal::PanelExtensionWindowController(this, profile_));
514
515  InitCommandState();
516
517  // Set up hosting for web contents.
518  panel_host_.reset(new PanelHost(this, profile_));
519  panel_host_->Init(url);
520  content::WebContents* web_contents = GetWebContents();
521  // The contents might be NULL for most of our tests.
522  if (web_contents)
523    native_panel_->AttachWebContents(web_contents);
524
525  // Close when the extension is unloaded or the browser is exiting.
526  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
527                 content::Source<Profile>(profile_));
528  registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
529                 content::NotificationService::AllSources());
530  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
531                 content::Source<ThemeService>(
532                    ThemeServiceFactory::GetForProfile(profile_)));
533
534  // Prevent the browser process from shutting down while this window is open.
535  chrome::StartKeepAlive();
536
537  UpdateAppIcon();
538}
539
540void Panel::SetPanelBounds(const gfx::Rect& bounds) {
541  if (bounds != native_panel_->GetPanelBounds())
542    native_panel_->SetPanelBounds(bounds);
543}
544
545void Panel::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
546  native_panel_->SetPanelBoundsInstantly(bounds);
547}
548
549void Panel::LimitSizeToWorkArea(const gfx::Rect& work_area) {
550  int max_width = manager()->GetMaxPanelWidth(work_area);
551  int max_height = manager()->GetMaxPanelHeight(work_area);
552
553  // If the custom max size is used, ensure that it does not exceed the display
554  // area.
555  if (max_size_policy_ == CUSTOM_MAX_SIZE) {
556    int current_max_width = max_size_.width();
557    if (current_max_width > max_width)
558      max_width = std::min(current_max_width, work_area.width());
559    int current_max_height = max_size_.height();
560    if (current_max_height > max_height)
561      max_height = std::min(current_max_height, work_area.height());
562  }
563
564  SetSizeRange(min_size_, gfx::Size(max_width, max_height));
565
566  // Ensure that full size does not exceed max size.
567  full_size_ = ClampSize(full_size_);
568}
569
570void Panel::SetAutoResizable(bool resizable) {
571  if (auto_resizable_ == resizable)
572    return;
573
574  auto_resizable_ = resizable;
575  content::WebContents* web_contents = GetWebContents();
576  if (auto_resizable_) {
577    if (web_contents)
578      EnableWebContentsAutoResize(web_contents);
579  } else {
580    if (web_contents) {
581      registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_SWAPPED,
582                        content::Source<content::WebContents>(web_contents));
583
584      // NULL might be returned if the tab has not been added.
585      RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
586      if (render_view_host)
587        render_view_host->DisableAutoResize(full_size_);
588    }
589  }
590}
591
592void Panel::EnableWebContentsAutoResize(content::WebContents* web_contents) {
593  DCHECK(web_contents);
594  ConfigureAutoResize(web_contents);
595
596  // We also need to know when the render view host changes in order
597  // to turn on auto-resize notifications in the new render view host.
598  if (!registrar_.IsRegistered(
599          this, content::NOTIFICATION_WEB_CONTENTS_SWAPPED,
600          content::Source<content::WebContents>(web_contents))) {
601    registrar_.Add(
602        this,
603        content::NOTIFICATION_WEB_CONTENTS_SWAPPED,
604        content::Source<content::WebContents>(web_contents));
605  }
606}
607
608void Panel::OnContentsAutoResized(const gfx::Size& new_content_size) {
609  DCHECK(auto_resizable_);
610  if (!collection_)
611    return;
612
613  gfx::Size new_window_size =
614      native_panel_->WindowSizeFromContentSize(new_content_size);
615
616  // Ignore content auto resizes until window frame size is known.
617  // This reduces extra resizes when panel is first shown.
618  // After window frame size is known, it will trigger another content
619  // auto resize.
620  if (new_content_size == new_window_size)
621    return;
622
623  collection_->ResizePanelWindow(this, new_window_size);
624}
625
626void Panel::OnWindowResizedByMouse(const gfx::Rect& new_bounds) {
627  if (collection_)
628    collection_->OnPanelResizedByMouse(this, new_bounds);
629}
630
631void Panel::SetSizeRange(const gfx::Size& min_size, const gfx::Size& max_size) {
632  if (min_size == min_size_ && max_size == max_size_)
633    return;
634
635  DCHECK(min_size.width() <= max_size.width());
636  DCHECK(min_size.height() <= max_size.height());
637  min_size_ = min_size;
638  max_size_ = max_size;
639
640  ConfigureAutoResize(GetWebContents());
641}
642
643void Panel::IncreaseMaxSize(const gfx::Size& desired_panel_size) {
644  gfx::Size new_max_size = max_size_;
645  if (new_max_size.width() < desired_panel_size.width())
646    new_max_size.set_width(desired_panel_size.width());
647  if (new_max_size.height() < desired_panel_size.height())
648    new_max_size.set_height(desired_panel_size.height());
649
650  SetSizeRange(min_size_, new_max_size);
651}
652
653void Panel::HandleKeyboardEvent(const content::NativeWebKeyboardEvent& event) {
654  native_panel_->HandlePanelKeyboardEvent(event);
655}
656
657void Panel::SetAlwaysOnTop(bool on_top) {
658  native_panel_->SetPanelAlwaysOnTop(on_top);
659}
660
661void Panel::SetPreviewMode(bool in_preview) {
662  DCHECK_NE(in_preview_mode_, in_preview);
663  in_preview_mode_ = in_preview;
664}
665
666void Panel::EnableResizeByMouse(bool enable) {
667  DCHECK(native_panel_);
668  native_panel_->EnableResizeByMouse(enable);
669}
670
671void Panel::UpdateMinimizeRestoreButtonVisibility() {
672  native_panel_->UpdatePanelMinimizeRestoreButtonVisibility();
673}
674
675gfx::Size Panel::ClampSize(const gfx::Size& size) const {
676  // The panel width:
677  // * cannot grow or shrink to go beyond [min_width, max_width]
678  int new_width = size.width();
679  if (new_width > max_size_.width())
680    new_width = max_size_.width();
681  if (new_width < min_size_.width())
682    new_width = min_size_.width();
683
684  // The panel height:
685  // * cannot grow or shrink to go beyond [min_height, max_height]
686  int new_height = size.height();
687  if (new_height > max_size_.height())
688    new_height = max_size_.height();
689  if (new_height < min_size_.height())
690    new_height = min_size_.height();
691
692  return gfx::Size(new_width, new_height);
693}
694
695void Panel::OnActiveStateChanged(bool active) {
696  // Clear attention state when an expanded panel becomes active.
697  // On some systems (e.g. Win), mouse-down activates a panel regardless of
698  // its expansion state. However, we don't want to clear draw attention if
699  // contents are not visible. In that scenario, if the mouse-down results
700  // in a mouse-click, draw attention will be cleared then.
701  // See Panel::OnTitlebarClicked().
702  if (active && IsDrawingAttention() && !IsMinimized())
703    FlashFrame(false);
704
705  if (collection_)
706    collection_->OnPanelActiveStateChanged(this);
707
708  // Send extension event about window changing active state.
709  extensions::TabsWindowsAPI* tabs_windows_api =
710      extensions::TabsWindowsAPI::Get(profile());
711  if (tabs_windows_api) {
712    tabs_windows_api->windows_event_router()->OnActiveWindowChanged(
713        active ? extension_window_controller_.get() : NULL);
714  }
715
716  content::NotificationService::current()->Notify(
717      chrome::NOTIFICATION_PANEL_CHANGED_ACTIVE_STATUS,
718      content::Source<Panel>(this),
719      content::NotificationService::NoDetails());
720}
721
722void Panel::OnPanelStartUserResizing() {
723  SetAutoResizable(false);
724  SetPreviewMode(true);
725  max_size_policy_ = CUSTOM_MAX_SIZE;
726}
727
728void Panel::OnPanelEndUserResizing() {
729  SetPreviewMode(false);
730}
731
732bool Panel::ShouldCloseWindow() {
733  return true;
734}
735
736void Panel::OnWindowClosing() {
737  if (GetWebContents()) {
738    native_panel_->DetachWebContents(GetWebContents());
739    panel_host_->DestroyWebContents();
740  }
741}
742
743bool Panel::ExecuteCommandIfEnabled(int id) {
744  if (command_updater()->SupportsCommand(id) &&
745      command_updater()->IsCommandEnabled(id)) {
746    ExecuteCommandWithDisposition(id, CURRENT_TAB);
747    return true;
748  }
749  return false;
750}
751
752string16 Panel::GetWindowTitle() const {
753  content::WebContents* contents = GetWebContents();
754  string16 title;
755
756  // |contents| can be NULL during the window's creation.
757  if (contents) {
758    title = contents->GetTitle();
759    FormatTitleForDisplay(&title);
760  }
761
762  if (title.empty())
763    title = UTF8ToUTF16(app_name());
764
765  return title;
766}
767
768gfx::Image Panel::GetCurrentPageIcon() const {
769  return panel_host_->GetPageIcon();
770}
771
772void Panel::UpdateTitleBar() {
773  native_panel_->UpdatePanelTitleBar();
774}
775
776void Panel::LoadingStateChanged(bool is_loading) {
777  command_updater_.UpdateCommandEnabled(IDC_STOP, is_loading);
778  native_panel_->UpdatePanelLoadingAnimations(is_loading);
779  UpdateTitleBar();
780}
781
782void Panel::WebContentsFocused(content::WebContents* contents) {
783  native_panel_->PanelWebContentsFocused(contents);
784}
785
786void Panel::MoveByInstantly(const gfx::Vector2d& delta_origin) {
787  gfx::Rect bounds = GetBounds();
788  bounds.Offset(delta_origin);
789  SetPanelBoundsInstantly(bounds);
790}
791
792void Panel::SetWindowCornerStyle(panel::CornerStyle corner_style) {
793  native_panel_->SetWindowCornerStyle(corner_style);
794}
795
796void Panel::MinimizeBySystem() {
797  native_panel_->MinimizePanelBySystem();
798}
799
800Panel::Panel(Profile* profile, const std::string& app_name,
801             const gfx::Size& min_size, const gfx::Size& max_size)
802    : app_name_(app_name),
803      profile_(profile),
804      collection_(NULL),
805      initialized_(false),
806      min_size_(min_size),
807      max_size_(max_size),
808      max_size_policy_(DEFAULT_MAX_SIZE),
809      auto_resizable_(false),
810      in_preview_mode_(false),
811      native_panel_(NULL),
812      attention_mode_(USE_PANEL_ATTENTION),
813      expansion_state_(EXPANDED),
814      command_updater_(this),
815      image_loader_ptr_factory_(this) {
816}
817
818void Panel::OnImageLoaded(const gfx::Image& image) {
819  if (!image.IsEmpty()) {
820    app_icon_ = image;
821    native_panel_->UpdatePanelTitleBar();
822  }
823
824  content::NotificationService::current()->Notify(
825      chrome::NOTIFICATION_PANEL_APP_ICON_LOADED,
826      content::Source<Panel>(this),
827      content::NotificationService::NoDetails());
828}
829
830void Panel::InitCommandState() {
831  // All supported commands whose state isn't set automagically some other way
832  // (like Stop during a page load) must have their state initialized here,
833  // otherwise they will be forever disabled.
834
835  // Navigation commands
836  command_updater_.UpdateCommandEnabled(IDC_RELOAD, true);
837  command_updater_.UpdateCommandEnabled(IDC_RELOAD_IGNORING_CACHE, true);
838
839  // Window management commands
840  command_updater_.UpdateCommandEnabled(IDC_CLOSE_WINDOW, true);
841  command_updater_.UpdateCommandEnabled(IDC_EXIT, true);
842
843  // Zoom
844  command_updater_.UpdateCommandEnabled(IDC_ZOOM_MENU, true);
845  command_updater_.UpdateCommandEnabled(IDC_ZOOM_PLUS, true);
846  command_updater_.UpdateCommandEnabled(IDC_ZOOM_NORMAL, true);
847  command_updater_.UpdateCommandEnabled(IDC_ZOOM_MINUS, true);
848
849  // Clipboard
850  command_updater_.UpdateCommandEnabled(IDC_COPY, true);
851  command_updater_.UpdateCommandEnabled(IDC_CUT, true);
852  command_updater_.UpdateCommandEnabled(IDC_PASTE, true);
853
854  // DevTools
855  command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS, true);
856  command_updater_.UpdateCommandEnabled(IDC_DEV_TOOLS_CONSOLE, true);
857}
858
859void Panel::ConfigureAutoResize(content::WebContents* web_contents) {
860  if (!auto_resizable_ || !web_contents)
861    return;
862
863  // NULL might be returned if the tab has not been added.
864  RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
865  if (!render_view_host)
866    return;
867
868  render_view_host->EnableAutoResize(
869      min_size_,
870      native_panel_->ContentSizeFromWindowSize(max_size_));
871}
872
873void Panel::UpdateAppIcon() {
874  const extensions::Extension* extension = GetExtension();
875  if (!extension)
876    return;
877
878  extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile());
879  loader->LoadImageAsync(
880      extension,
881      extensions::IconsInfo::GetIconResource(
882          extension,
883          extension_misc::EXTENSION_ICON_SMALL,
884          ExtensionIconSet::MATCH_BIGGER),
885      gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
886                extension_misc::EXTENSION_ICON_SMALL),
887      base::Bind(&Panel::OnImageLoaded,
888                 image_loader_ptr_factory_.GetWeakPtr()));
889}
890
891// static
892void Panel::FormatTitleForDisplay(string16* title) {
893  size_t current_index = 0;
894  size_t match_index;
895  while ((match_index = title->find(L'\n', current_index)) != string16::npos) {
896    title->replace(match_index, 1, string16());
897    current_index = match_index;
898  }
899}
900