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/tabs/tab_strip_model_order_controller.h"
6
7#include "content/public/browser/web_contents.h"
8
9///////////////////////////////////////////////////////////////////////////////
10// TabStripModelOrderController, public:
11
12TabStripModelOrderController::TabStripModelOrderController(
13    TabStripModel* tabstrip)
14    : tabstrip_(tabstrip) {
15  tabstrip_->AddObserver(this);
16}
17
18TabStripModelOrderController::~TabStripModelOrderController() {
19  tabstrip_->RemoveObserver(this);
20}
21
22int TabStripModelOrderController::DetermineInsertionIndex(
23    ui::PageTransition transition,
24    bool foreground) {
25  int tab_count = tabstrip_->count();
26  if (!tab_count)
27    return 0;
28
29  // NOTE: TabStripModel enforces that all non-mini-tabs occur after mini-tabs,
30  // so we don't have to check here too.
31  if (transition == ui::PAGE_TRANSITION_LINK &&
32      tabstrip_->active_index() != -1) {
33    if (foreground) {
34      // If the page was opened in the foreground by a link click in another
35      // tab, insert it adjacent to the tab that opened that link.
36      return tabstrip_->active_index() + 1;
37    }
38    content::WebContents* opener = tabstrip_->GetActiveWebContents();
39    // Get the index of the next item opened by this tab, and insert after
40    // it...
41    int index = tabstrip_->GetIndexOfLastWebContentsOpenedBy(
42        opener, tabstrip_->active_index());
43    if (index != TabStripModel::kNoTab)
44      return index + 1;
45    // Otherwise insert adjacent to opener...
46    return tabstrip_->active_index() + 1;
47  }
48  // In other cases, such as Ctrl+T, open at the end of the strip.
49  return tabstrip_->count();
50}
51
52int TabStripModelOrderController::DetermineNewSelectedIndex(
53    int removing_index) const {
54  int tab_count = tabstrip_->count();
55  DCHECK(removing_index >= 0 && removing_index < tab_count);
56  content::WebContents* parent_opener =
57      tabstrip_->GetOpenerOfWebContentsAt(removing_index);
58  // First see if the index being removed has any "child" tabs. If it does, we
59  // want to select the first in that child group, not the next tab in the same
60  // group of the removed tab.
61  content::WebContents* removed_contents =
62      tabstrip_->GetWebContentsAt(removing_index);
63  // The parent opener should never be the same as the controller being removed.
64  DCHECK(parent_opener != removed_contents);
65  int index = tabstrip_->GetIndexOfNextWebContentsOpenedBy(removed_contents,
66                                                           removing_index,
67                                                           false);
68  if (index != TabStripModel::kNoTab)
69    return GetValidIndex(index, removing_index);
70
71  if (parent_opener) {
72    // If the tab was in a group, shift selection to the next tab in the group.
73    int index = tabstrip_->GetIndexOfNextWebContentsOpenedBy(parent_opener,
74                                                             removing_index,
75                                                             false);
76    if (index != TabStripModel::kNoTab)
77      return GetValidIndex(index, removing_index);
78
79    // If we can't find a subsequent group member, just fall back to the
80    // parent_opener itself. Note that we use "group" here since opener is
81    // reset by select operations..
82    index = tabstrip_->GetIndexOfWebContents(parent_opener);
83    if (index != TabStripModel::kNoTab)
84      return GetValidIndex(index, removing_index);
85  }
86
87  // No opener set, fall through to the default handler...
88  int selected_index = tabstrip_->active_index();
89  if (selected_index >= (tab_count - 1))
90    return selected_index - 1;
91
92  return selected_index;
93}
94
95void TabStripModelOrderController::ActiveTabChanged(
96    content::WebContents* old_contents,
97    content::WebContents* new_contents,
98    int index,
99    int reason) {
100  content::WebContents* old_opener = NULL;
101  if (old_contents) {
102    int index = tabstrip_->GetIndexOfWebContents(old_contents);
103    if (index != TabStripModel::kNoTab) {
104      old_opener = tabstrip_->GetOpenerOfWebContentsAt(index);
105
106      // Forget any group/opener relationships that need to be reset whenever
107      // selection changes (see comment in TabStripModel::AddWebContentsAt).
108      if (tabstrip_->ShouldResetGroupOnSelect(old_contents))
109        tabstrip_->ForgetGroup(old_contents);
110    }
111  }
112  content::WebContents* new_opener = tabstrip_->GetOpenerOfWebContentsAt(index);
113
114  if ((reason & CHANGE_REASON_USER_GESTURE) && new_opener != old_opener &&
115      ((old_contents == NULL && new_opener == NULL) ||
116          new_opener != old_contents) &&
117      ((new_contents == NULL && old_opener == NULL) ||
118          old_opener != new_contents)) {
119    tabstrip_->ForgetAllOpeners();
120  }
121}
122
123///////////////////////////////////////////////////////////////////////////////
124// TabStripModelOrderController, private:
125
126int TabStripModelOrderController::GetValidIndex(
127    int index, int removing_index) const {
128  if (removing_index < index)
129    index = std::max(0, index - 1);
130  return index;
131}
132