chromiumのタブをいじる(3)

この記事は東京大学電子情報工学科3年の後期実験である「大規模ソフトウェアを手探る」で行われた班活動を記事にしたものです．
以下の記事の続きになっています．
[chromiumのタブをいじる(１)](url)

## 実行環境
Ubuntu 18.04.5 (LTS)


## やりたいこと
現在アクティブなタブだけでなく最近アクティブだった$n$件のタブの幅を広くする.
(すでに達成した「アクティブなタブを広くする」は$n=1$に相当します)
## 必要な情報
タブを見ていた履歴を管理する変数は見つけられなかったので,自作することにします.
それにあたって必要そうな情報を探していきます.
### タブの幅の変え方
前の記事で紹介したように,tab_strip_layout_helper.cc:243で定義されるboundsを変更することでタブの幅を変えることができます.
### タブに変更が加えられたときに呼ばれる関数
tab_strip_layout_helper.ccに必要そうなヘルパーは入っているのでそれを利用します.具体的には以下の3つです.
#### 新規作成(tab_strip_layout_helper.cc:105)
```cpp

void TabStripLayoutHelper::InsertTabAt(int model_index, Tab* tab, TabPinned pinned) {
    ...
}
```

#### 削除(tab_strip_layout_helper.cc:114)
```cpp
void TabStripLayoutHelper::RemoveTabAt(int model_index, Tab* tab) {
    ...
}
```
#### アクティブなタブが変更(tab_strip_layout_helper.cc:210)
```cpp
void TabStripLayoutHelper::SetActiveTab(int prev_active_index, int new_active_index) {
    ...
}
```

## 実装方法
以下の仕様で実装しました.
### 仕様

* 存在するすべてのタブに対して開かれた順番を(新しくアクティブになった順に重複しないように)記録する
* 新規作成,削除,アクティブなタブが変更のたびに記録を更新し,最新のn件のタブが広くなるようにタブ幅を計算,更新
    * 私の環境ではタブが7個まではフルサイズで表示されたので,8個以上になったときに幅が広くなるようにしました.
 
### 詳しい仕様

* タブがアクティブになった履歴を管理するクラスとしてActiveTabRecordクラスを作成.
    * タブは左から順に0,1,2,...とインデックスがつけられるので,そのインデックスの入った配列をメンバ変数に持ち,メンバ関数でこれを更新します.
    * 配列の前のほうが最近新しくアクティブになったタブです.
        * [3,2,1,0]なら0,1,2,3の順にアクティブになったことを表します.
* タブに変更が加わったときに変更の内容に応じて履歴を更新します.
    * 新規作成の時,追加されたタブのインデックス以上のインデックスの値をインクリメント.
    * 削除の時,削除されたタブのインデックスを配列から除去し,削除されたタブのインデックス以上のインデックスの値をデクリメント.
    * アクティブなタブが変わった時,アクティブになったタブのインデックスを履歴の先頭に移動/追加.
* 履歴の最近の$n$件の幅が広くなるように,タブ全体で使える幅と幅の比からUpdateIdealBounds()でタブ幅を計算し,更新します.

### 履歴更新の例
履歴の更新が分かりづらいので例を示します.

1. 履歴が[6,5,4,3,2,1,0]のときに2の右隣にタブを新しく作ると,履歴は[3,7,6,5,4,2,1,0]になってほしいです.これは以下で実現されます.
    - 新規作成で3以上は+1になるから,  [6,5,4,3,2,1,0] -> [7,6,5,4,2,1,0]
    - アクティブなタブが変わるので3が先頭に追加されて,  [7,6,5,4,2,1,0] ->  [3,7,6,5,4,2,1,0]
2.  履歴が[2,6,0,3,1,4,5]のときに今アクティブな2のタブを削除すると,履歴は[2,5,0,1,3,4]になってほしいです.(アクティブなタブを削除した時はその右隣のタブが新しくアクティブになります)これは以下で実現されます.
    - 削除で2を除去して,2以上は-1になるから,  [2,6,0,3,1,4,5] -> [6,0,3,1,4,5] -> [5,0,2,1,3,4]
    - アクティブなタブが変わるので2が先頭に移動して,  [5,0,2,1,3,4] ->  [2,5,0,1,3,4]

## 変更箇所
変更したソースコードを示します.(関係のないところは適宜省略しています.)

```cpp:tab_strip_layout_helper.cc
...

inline ActiveTabRecord::ActiveTabRecord() {
}

inline ActiveTabRecord::~ActiveTabRecord() {
}
...

void TabStripLayoutHelper::InsertTabAt(int model_index,
                                       Tab* tab,
                                       TabPinned pinned) {
  atr.Insert(model_index);
  ...
}

void TabStripLayoutHelper::RemoveTabAt(int model_index, Tab* tab) {
  atr.Remove(model_index);
  ...
}
...

void TabStripLayoutHelper::SetActiveTab(int prev_active_index,
                                        int new_active_index) {
  ...
  atr.Activate(new_active_index);
  ...
}
...

int TabStripLayoutHelper::UpdateIdealBounds(int available_width) {
  std::vector<gfx::Rect> bounds = CalculateIdealBounds(available_width);
  DCHECK_EQ(slots_.size(), bounds.size());

  views::ViewModelT<Tab>* tabs = get_tabs_callback_.Run();
  const int active_tab_model_index = controller_->GetActiveIndex();
  const int active_tab_slot_index =
      controller_->IsValidIndex(active_tab_model_index)
          ? GetSlotIndexForTabModelIndex(
                active_tab_model_index,
                tabs->view_at(active_tab_model_index)->group())
          : TabStripModel::kNoTab;
  int current_tab_model_index = 0;
  constexpr double wide_to_narrow_ratio = 3;
  constexpr int overlap = 17;
  int num_of_tabs = int{bounds.size()};
  int x_of_tab = 0;
  int tab_width = 0;
  int N = int{atr.GetSize()};
  int narrow_tab_width = (available_width + overlap * (num_of_tabs - 1)) / (num_of_tabs + wide_to_narrow_ratio * N - N);
  //int wide_tab_width = wide_to_narrow_ratio * narrow_tab_width;
  int wide_tab_width = (available_width - narrow_tab_width * (num_of_tabs - N) + overlap * (num_of_tabs-1)) / N;
  for (int i = 0; i < num_of_tabs; ++i) {
    const TabSlot& slot = slots_[i];
    switch (slot.type) {
      case ViewType::kTab:
        if (!slot.animation->IsClosing()) {
          if (num_of_tabs > 7){
            if (atr.WasActive(i) == true || i == active_tab_slot_index){
              tab_width = wide_tab_width;
            } else {
              tab_width = narrow_tab_width;
            }
              bounds[i].set_width(tab_width);
              bounds[i].set_x(x_of_tab); 
              x_of_tab += tab_width - overlap;
          }
          tabs->set_ideal_bounds(current_tab_model_index, bounds[i]);
          UpdateCachedTabWidth(i, bounds[i].width(),
                               i == active_tab_slot_index);
          ++current_tab_model_index;
        }
        break;
      case ViewType::kGroupHeader:
        if (!slot.view->dragging())
          group_header_ideal_bounds_[slot.view->group().value()] = bounds[i];
        break;
    }
  }
  //atr.Show();

  return bounds.back().right();
}

...
```
```cpp:tab_strip_layout_helper.h
...

#include <deque>
#include <iostream>

...

class ActiveTabRecord;

namespace tab_groups {
class TabGroupId;
}

class ActiveTabRecord {
  public:
    ActiveTabRecord();
    ~ActiveTabRecord();
    bool WasActive(int index) {
      for (auto itr = record.begin();itr-record.begin() < long{size};itr++) {
          if (*itr == index) {
              return true;
          }
      }
      return false;
    }
    void Activate(int index) {
      for (auto itr = record.begin();itr != record.end();) {
          if (*itr == index) {
              itr = record.erase(itr);
          } else {
              itr++;
          }
      }
      record.push_front(index);
    }
    void Insert(int index){
      for (auto itr = record.begin();itr != record.end();itr++) {
          if (*itr > index) {
              (*itr)++;
          }
      }
    }
    void Remove(int index){
      for(auto itr = record.begin();itr != record.end();itr++) {
          if (*itr == index) {
              itr = record.erase(itr);
              itr--;
          }
          if (*itr > index) {
              (*itr)--;
          }
      }
    }
    void Show() {
        for (auto d : record) {
            std::cout << d;
        }
        std::cout << std::endl;
    }
    std::size_t GetSize() {
      return size;
    }
    void SetSize(std::size_t N) {
      size = N;
    }
  private:
    std::deque<int> record;
    std::size_t size = 5;
};

...

  ActiveTabRecord atr;
};

```
## デモ
実際に動いている様子です.アクティブになったのが新しい5個のタブ幅が広くなっているのがわかると思います.
![hoge.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/839506/9fc4fc94-7a80-e9cd-ebdd-f5f804bea359.gif)

## 感想
複数人で協力して調査/開発したことで大規模ソフトウェアでも比較的スムーズに改変することができました.
日頃使っているソフトウェアを多少強引ではあったものの,変更できたのはいい経験になったと思います.

## 別のテーマ

アクティブタブを拡大する機能の有効/無効を切り替えるショートカットを作成する.
[chromiumのタブをいじる(2)](https://qiita.com/Hideyu/items/4a02531b7f7ce871fbcd)

グループ機能,ピン留め機能のバグを取り除く.
[chromiumのタブをいじる(4)](https://qiita.com/kons_9/items/8e972449dbc9efa6d35f)