1// Copyright (c) 2011 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/views/tabs/browser_tab_strip_controller.h"
6
7#include "base/auto_reset.h"
8#include "base/command_line.h"
9#include "chrome/browser/extensions/extension_tab_helper.h"
10#include "chrome/browser/metrics/user_metrics.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/tabs/tab_strip_model.h"
13#include "chrome/browser/ui/browser.h"
14#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
15#include "chrome/browser/ui/tabs/tab_menu_model.h"
16#include "chrome/browser/ui/views/tabs/base_tab_strip.h"
17#include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
18#include "chrome/common/url_constants.h"
19#include "content/browser/renderer_host/render_view_host.h"
20#include "content/browser/tab_contents/tab_contents.h"
21#include "content/common/notification_service.h"
22#include "views/controls/menu/menu_2.h"
23#include "views/widget/widget.h"
24
25static TabRendererData::NetworkState TabContentsNetworkState(
26    TabContents* contents) {
27  if (!contents || !contents->is_loading())
28    return TabRendererData::NETWORK_STATE_NONE;
29  if (contents->waiting_for_response())
30    return TabRendererData::NETWORK_STATE_WAITING;
31  return TabRendererData::NETWORK_STATE_LOADING;
32}
33
34class BrowserTabStripController::TabContextMenuContents
35    : public ui::SimpleMenuModel::Delegate {
36 public:
37  TabContextMenuContents(BaseTab* tab,
38                         BrowserTabStripController* controller)
39      : ALLOW_THIS_IN_INITIALIZER_LIST(
40          model_(this,
41                 controller->model_,
42                 controller->tabstrip_->GetModelIndexOfBaseTab(tab))),
43        tab_(tab),
44        controller_(controller),
45        last_command_(TabStripModel::CommandFirst) {
46    Build();
47  }
48  virtual ~TabContextMenuContents() {
49    menu_->CancelMenu();
50    if (controller_)
51      controller_->tabstrip_->StopAllHighlighting();
52  }
53
54  void Cancel() {
55    controller_ = NULL;
56  }
57
58  void RunMenuAt(const gfx::Point& point) {
59    menu_->RunMenuAt(point, views::Menu2::ALIGN_TOPLEFT);
60    // We could be gone now. Assume |this| is junk!
61  }
62
63  // Overridden from ui::SimpleMenuModel::Delegate:
64  virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
65    return controller_->IsCommandCheckedForTab(
66        static_cast<TabStripModel::ContextMenuCommand>(command_id),
67        tab_);
68  }
69  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
70    return controller_->IsCommandEnabledForTab(
71        static_cast<TabStripModel::ContextMenuCommand>(command_id),
72        tab_);
73  }
74  virtual bool GetAcceleratorForCommandId(
75      int command_id,
76      ui::Accelerator* accelerator) OVERRIDE {
77    int browser_cmd;
78    return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
79                                                             &browser_cmd) ?
80        controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
81                                                            accelerator) :
82        false;
83  }
84  virtual void CommandIdHighlighted(int command_id) OVERRIDE {
85    controller_->StopHighlightTabsForCommand(last_command_, tab_);
86    last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
87    controller_->StartHighlightTabsForCommand(last_command_, tab_);
88  }
89  virtual void ExecuteCommand(int command_id) OVERRIDE {
90    // Executing the command destroys |this|, and can also end up destroying
91    // |controller_| (e.g. for |CommandUseVerticalTabs|). So stop the highlights
92    // before executing the command.
93    controller_->tabstrip_->StopAllHighlighting();
94    controller_->ExecuteCommandForTab(
95        static_cast<TabStripModel::ContextMenuCommand>(command_id),
96        tab_);
97  }
98
99  virtual void MenuClosed() OVERRIDE {
100    if (controller_)
101      controller_->tabstrip_->StopAllHighlighting();
102  }
103
104 private:
105  void Build() {
106    menu_.reset(new views::Menu2(&model_));
107  }
108
109  TabMenuModel model_;
110  scoped_ptr<views::Menu2> menu_;
111
112  // The tab we're showing a menu for.
113  BaseTab* tab_;
114
115  // A pointer back to our hosting controller, for command state information.
116  BrowserTabStripController* controller_;
117
118  // The last command that was selected, so that we can start/stop highlighting
119  // appropriately as the user moves through the menu.
120  TabStripModel::ContextMenuCommand last_command_;
121
122  DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
123};
124
125////////////////////////////////////////////////////////////////////////////////
126// BrowserTabStripController, public:
127
128BrowserTabStripController::BrowserTabStripController(Browser* browser,
129                                                     TabStripModel* model)
130    : model_(model),
131      tabstrip_(NULL),
132      browser_(browser) {
133  model_->AddObserver(this);
134
135  notification_registrar_.Add(this,
136      NotificationType::TAB_CLOSEABLE_STATE_CHANGED,
137      NotificationService::AllSources());
138}
139
140BrowserTabStripController::~BrowserTabStripController() {
141  // When we get here the TabStrip is being deleted. We need to explicitly
142  // cancel the menu, otherwise it may try to invoke something on the tabstrip
143  // from it's destructor.
144  if (context_menu_contents_.get())
145    context_menu_contents_->Cancel();
146
147  model_->RemoveObserver(this);
148}
149
150void BrowserTabStripController::InitFromModel(BaseTabStrip* tabstrip) {
151  tabstrip_ = tabstrip;
152  // Walk the model, calling our insertion observer method for each item within
153  // it.
154  for (int i = 0; i < model_->count(); ++i)
155    TabInsertedAt(model_->GetTabContentsAt(i), i, model_->active_index() == i);
156}
157
158bool BrowserTabStripController::IsCommandEnabledForTab(
159    TabStripModel::ContextMenuCommand command_id,
160    BaseTab* tab) const {
161  int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
162  return model_->ContainsIndex(model_index) ?
163      model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
164}
165
166bool BrowserTabStripController::IsCommandCheckedForTab(
167    TabStripModel::ContextMenuCommand command_id,
168    BaseTab* tab) const {
169  int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
170  return model_->ContainsIndex(model_index) ?
171      model_->IsContextMenuCommandChecked(model_index, command_id) : false;
172}
173
174void BrowserTabStripController::ExecuteCommandForTab(
175    TabStripModel::ContextMenuCommand command_id,
176    BaseTab* tab) {
177  int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
178  if (model_->ContainsIndex(model_index))
179    model_->ExecuteContextMenuCommand(model_index, command_id);
180}
181
182bool BrowserTabStripController::IsTabPinned(BaseTab* tab) const {
183  return IsTabPinned(tabstrip_->GetModelIndexOfBaseTab(tab));
184}
185
186int BrowserTabStripController::GetCount() const {
187  return model_->count();
188}
189
190bool BrowserTabStripController::IsValidIndex(int index) const {
191  return model_->ContainsIndex(index);
192}
193
194bool BrowserTabStripController::IsActiveTab(int model_index) const {
195  return model_->active_index() == model_index;
196}
197
198bool BrowserTabStripController::IsTabSelected(int model_index) const {
199  return model_->IsTabSelected(model_index);
200}
201
202bool BrowserTabStripController::IsTabPinned(int model_index) const {
203  return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
204}
205
206bool BrowserTabStripController::IsTabCloseable(int model_index) const {
207  return !model_->ContainsIndex(model_index) ||
208      model_->delegate()->CanCloseTab();
209}
210
211bool BrowserTabStripController::IsNewTabPage(int model_index) const {
212  return model_->ContainsIndex(model_index) &&
213      model_->GetTabContentsAt(model_index)->tab_contents()->GetURL() ==
214      GURL(chrome::kChromeUINewTabURL);
215}
216
217void BrowserTabStripController::SelectTab(int model_index) {
218  model_->ActivateTabAt(model_index, true);
219}
220
221void BrowserTabStripController::ExtendSelectionTo(int model_index) {
222  model_->ExtendSelectionTo(model_index);
223}
224
225void BrowserTabStripController::ToggleSelected(int model_index) {
226  model_->ToggleSelectionAt(model_index);
227}
228
229void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
230  model_->AddSelectionFromAnchorTo(model_index);
231}
232
233void BrowserTabStripController::CloseTab(int model_index) {
234  tabstrip_->PrepareForCloseAt(model_index);
235  model_->CloseTabContentsAt(model_index,
236                             TabStripModel::CLOSE_USER_GESTURE |
237                             TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
238}
239
240void BrowserTabStripController::ShowContextMenuForTab(BaseTab* tab,
241                                                      const gfx::Point& p) {
242  context_menu_contents_.reset(new TabContextMenuContents(tab, this));
243  context_menu_contents_->RunMenuAt(p);
244}
245
246void BrowserTabStripController::UpdateLoadingAnimations() {
247  // Don't use the model count here as it's possible for this to be invoked
248  // before we've applied an update from the model (Browser::TabInsertedAt may
249  // be processed before us and invokes this).
250  for (int tab_index = 0, tab_count = tabstrip_->tab_count();
251       tab_index < tab_count; ++tab_index) {
252    BaseTab* tab = tabstrip_->base_tab_at_tab_index(tab_index);
253    int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
254    if (model_->ContainsIndex(model_index)) {
255      TabContentsWrapper* contents = model_->GetTabContentsAt(model_index);
256      tab->UpdateLoadingAnimation(
257          TabContentsNetworkState(contents->tab_contents()));
258    }
259  }
260}
261
262int BrowserTabStripController::HasAvailableDragActions() const {
263  return model_->delegate()->GetDragActions();
264}
265
266void BrowserTabStripController::PerformDrop(bool drop_before,
267                                            int index,
268                                            const GURL& url) {
269  browser::NavigateParams params(browser_, url, PageTransition::LINK);
270  params.tabstrip_index = index;
271
272  if (drop_before) {
273    UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"),
274                              model_->profile());
275    params.disposition = NEW_FOREGROUND_TAB;
276  } else {
277    UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLOnTab"),
278                              model_->profile());
279    params.disposition = CURRENT_TAB;
280    params.source_contents = model_->GetTabContentsAt(index);
281  }
282
283  browser::Navigate(&params);
284}
285
286bool BrowserTabStripController::IsCompatibleWith(BaseTabStrip* other) const {
287  Profile* other_profile =
288      static_cast<BrowserTabStripController*>(other->controller())->profile();
289  return other_profile == profile();
290}
291
292void BrowserTabStripController::CreateNewTab() {
293  UserMetrics::RecordAction(UserMetricsAction("NewTab_Button"),
294                            model_->profile());
295
296  model_->delegate()->AddBlankTab(true);
297}
298
299////////////////////////////////////////////////////////////////////////////////
300// BrowserTabStripController, TabStripModelObserver implementation:
301
302void BrowserTabStripController::TabInsertedAt(TabContentsWrapper* contents,
303                                              int model_index,
304                                              bool active) {
305  DCHECK(contents);
306  DCHECK(model_index == TabStripModel::kNoTab ||
307         model_->ContainsIndex(model_index));
308
309  TabRendererData data;
310  SetTabRendererDataFromModel(contents->tab_contents(), model_index, &data);
311  tabstrip_->AddTabAt(model_index, data);
312}
313
314void BrowserTabStripController::TabDetachedAt(TabContentsWrapper* contents,
315                                              int model_index) {
316  tabstrip_->RemoveTabAt(model_index);
317}
318
319void BrowserTabStripController::TabSelectedAt(TabContentsWrapper* old_contents,
320                                              TabContentsWrapper* contents,
321                                              int model_index,
322                                              bool user_gesture) {
323  tabstrip_->SelectTabAt(model_->GetIndexOfTabContents(old_contents),
324                         model_index);
325}
326
327void BrowserTabStripController::TabMoved(TabContentsWrapper* contents,
328                                         int from_model_index,
329                                         int to_model_index) {
330  // Update the data first as the pinned state may have changed.
331  TabRendererData data;
332  SetTabRendererDataFromModel(contents->tab_contents(), to_model_index, &data);
333  tabstrip_->SetTabData(from_model_index, data);
334
335  tabstrip_->MoveTab(from_model_index, to_model_index);
336}
337
338void BrowserTabStripController::TabChangedAt(TabContentsWrapper* contents,
339                                             int model_index,
340                                             TabChangeType change_type) {
341  if (change_type == TITLE_NOT_LOADING) {
342    tabstrip_->TabTitleChangedNotLoading(model_index);
343    // We'll receive another notification of the change asynchronously.
344    return;
345  }
346
347  SetTabDataAt(contents, model_index);
348}
349
350void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
351                                              TabContentsWrapper* old_contents,
352                                              TabContentsWrapper* new_contents,
353                                              int model_index) {
354  SetTabDataAt(new_contents, model_index);
355}
356
357void BrowserTabStripController::TabPinnedStateChanged(
358    TabContentsWrapper* contents,
359    int model_index) {
360  // Currently none of the renderers render pinned state differently.
361}
362
363void BrowserTabStripController::TabMiniStateChanged(
364    TabContentsWrapper* contents,
365    int model_index) {
366  SetTabDataAt(contents, model_index);
367}
368
369void BrowserTabStripController::TabBlockedStateChanged(
370    TabContentsWrapper* contents,
371    int model_index) {
372  SetTabDataAt(contents, model_index);
373}
374
375void BrowserTabStripController::SetTabDataAt(
376    TabContentsWrapper* contents,
377    int model_index) {
378  TabRendererData data;
379  SetTabRendererDataFromModel(contents->tab_contents(), model_index, &data);
380  tabstrip_->SetTabData(model_index, data);
381}
382
383void BrowserTabStripController::SetTabRendererDataFromModel(
384    TabContents* contents,
385    int model_index,
386    TabRendererData* data) {
387  SkBitmap* app_icon = NULL;
388  TabContentsWrapper* wrapper =
389      TabContentsWrapper::GetCurrentWrapperForContents(contents);
390
391  // Extension App icons are slightly larger than favicons, so only allow
392  // them if permitted by the model.
393  if (model_->delegate()->LargeIconsPermitted())
394    app_icon = wrapper->extension_tab_helper()->GetExtensionAppIcon();
395
396  if (app_icon)
397    data->favicon = *app_icon;
398  else
399    data->favicon = contents->GetFavicon();
400  data->network_state = TabContentsNetworkState(contents);
401  data->title = contents->GetTitle();
402  data->url = contents->GetURL();
403  data->loading = contents->is_loading();
404  data->crashed_status = contents->crashed_status();
405  data->incognito = contents->profile()->IsOffTheRecord();
406  data->show_icon = contents->ShouldDisplayFavicon();
407  data->mini = model_->IsMiniTab(model_index);
408  data->blocked = model_->IsTabBlocked(model_index);
409  data->app = wrapper->extension_tab_helper()->is_app();
410}
411
412void BrowserTabStripController::StartHighlightTabsForCommand(
413    TabStripModel::ContextMenuCommand command_id,
414    BaseTab* tab) {
415  if (command_id == TabStripModel::CommandCloseOtherTabs ||
416      command_id == TabStripModel::CommandCloseTabsToRight) {
417    int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
418    if (IsValidIndex(model_index)) {
419      std::vector<int> indices =
420          model_->GetIndicesClosedByCommand(model_index, command_id);
421      for (std::vector<int>::const_iterator i = indices.begin();
422           i != indices.end(); ++i) {
423        tabstrip_->StartHighlight(*i);
424      }
425    }
426  }
427}
428
429void BrowserTabStripController::StopHighlightTabsForCommand(
430    TabStripModel::ContextMenuCommand command_id,
431    BaseTab* tab) {
432  if (command_id == TabStripModel::CommandCloseTabsToRight ||
433      command_id == TabStripModel::CommandCloseOtherTabs) {
434    // Just tell all Tabs to stop pulsing - it's safe.
435    tabstrip_->StopAllHighlighting();
436  }
437}
438
439////////////////////////////////////////////////////////////////////////////////
440// BrowserTabStripController, NotificationObserver implementation:
441
442void BrowserTabStripController::Observe(NotificationType type,
443    const NotificationSource& source, const NotificationDetails& details) {
444  DCHECK(type.value == NotificationType::TAB_CLOSEABLE_STATE_CHANGED);
445  // Note that this notification may be fired during a model mutation and
446  // possibly before the tabstrip has processed the change.
447  // Here, we just re-layout each existing tab to reflect the change in its
448  // closeable state, and then schedule paint for entire tabstrip.
449  for (int i = 0; i < tabstrip_->tab_count(); ++i) {
450    tabstrip_->base_tab_at_tab_index(i)->Layout();
451  }
452  tabstrip_->SchedulePaint();
453}
454