This source file includes following definitions.
- Round
- GetInitialWidgetBounds
- CompareGdkRectangles
- GdkRectMatchesTabFaviconBounds
- type_
- TabAnimation
- type
- Start
- Stop
- set_layout_on_completion
- GetCurrentTabWidth
- AnimationProgressed
- AnimationEnded
- AnimationCanceled
- GetGapWidth
- GetDuration
- GetWidthForTab
- GenerateStartAndEndWidths
- index_
- GetWidthForTab
- index_
- index
- GetWidthForTab
- AnimationEnded
- start_tab_b_bounds_
- AnimationProgressed
- GetDuration
- AnimationEnded
- GetDuration
- GetWidthForTab
- InitStartState
- index_
- GetDuration
- GetWidthForTab
- to_index_
- AnimationProgressed
- AnimationEnded
- GetGapWidth
- GetDuration
- GetWidthForTab
- layout_factory_
- Init
- Show
- Hide
- IsActiveDropTarget
- Layout
- SchedulePaint
- SetBounds
- UpdateLoadingAnimations
- IsCompatibleWith
- IsAnimating
- DestroyDragController
- DestroyDraggedTab
- GetIdealBounds
- SetVerticalOffset
- GetTabStripOriginForWidget
- GetWidgetForViewID
- TabInsertedAt
- TabDetachedAt
- ActiveTabChanged
- TabSelectionChanged
- TabMoved
- TabChangedAt
- TabReplacedAt
- TabMiniStateChanged
- TabBlockedStateChanged
- IsTabActive
- IsTabSelected
- IsTabDetached
- GetCurrentTabWidths
- IsTabPinned
- ActivateTab
- ToggleTabSelection
- ExtendTabSelection
- CloseTab
- IsCommandEnabledForTab
- ExecuteCommandForTab
- StartHighlightTabsForCommand
- StopHighlightTabsForCommand
- StopAllHighlighting
- MaybeStartDrag
- ContinueDrag
- EndDrag
- HasAvailableDragActions
- GetThemeProvider
- GetTabStripMenuControllerForTab
- WillProcessEvent
- DidProcessEvent
- Observe
- GetTabCount
- GetMiniTabCount
- GetAvailableWidthForTabs
- GetIndexOfTab
- GetTabAt
- GetTabAtAdjustForAnimation
- RemoveTabAt
- HandleGlobalMouseMoveEvent
- GenerateIdealBounds
- LayoutNewTabButton
- GetDesiredTabWidths
- GetTabHOffset
- tab_start_x
- ResizeLayoutTabs
- IsCursorInTabStripZone
- ReStack
- AddMessageLoopObserver
- RemoveMessageLoopObserver
- GetDropBounds
- UpdateDropIndex
- SetDropIndex
- CompleteDrop
- GetDropArrowImage
- point_down
- OnExposeEvent
- SetContainerColorMap
- SetContainerTransparency
- SetContainerShapeMask
- CreateContainer
- DestroyContainer
- StopAnimation
- AnimationLayout
- StartInsertTabAnimation
- StartRemoveTabAnimation
- StartMoveTabAnimation
- StartResizeLayoutAnimation
- StartMiniTabAnimation
- StartMiniMoveTabAnimation
- FinishAnimation
- OnMap
- OnExpose
- OnSizeAllocate
- OnDragMotion
- OnDragDrop
- OnDragLeave
- OnDragDataReceived
- OnNewTabClicked
- SetTabBounds
- CanPaintOnlyFavicons
- PaintOnlyFavicons
- MakeNewTabButton
- SetNewTabButtonBackground
#include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
#include <gtk/gtk.h>
#include <algorithm>
#include <string>
#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "base/i18n/rtl.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete_classifier.h"
#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
#include "chrome/browser/autocomplete/autocomplete_input.h"
#include "chrome/browser/autocomplete/autocomplete_match.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/gtk/browser_window_gtk.h"
#include "chrome/browser/ui/gtk/custom_button.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/gtk/tabs/dragged_tab_controller_gtk.h"
#include "chrome/browser/ui/gtk/tabs/tab_strip_menu_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "grit/ui_resources.h"
#include "ui/base/dragdrop/gtk_dnd_util.h"
#include "ui/base/gtk/gtk_screen_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/gtk_compat.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/point.h"
using base::UserMetricsAction;
using content::WebContents;
namespace {
const int kDefaultAnimationDurationMs = 100;
const int kResizeLayoutAnimationDurationMs = 166;
const int kReorderAnimationDurationMs = 166;
const int kMiniTabAnimationDurationMs = 150;
const int kNewTabButtonHOffset = -5;
const int kNewTabButtonVOffset = 5;
const int kResizeTabsTimeMs = 300;
const int kLayoutAfterSizeAllocateMs = 10;
const int kTabStripAnimationVSlop = 40;
const int kTabHOffset = -16;
const int kTabEdgeRatioInverse = 4;
static int drop_indicator_width;
static int drop_indicator_height;
inline int Round(double x) {
return static_cast<int>(x + 0.5);
}
gfx::Rect GetInitialWidgetBounds(GtkWidget* widget) {
GtkRequisition request;
gtk_widget_size_request(widget, &request);
return gfx::Rect(0, 0, request.width, request.height);
}
int CompareGdkRectangles(const void* p1, const void* p2) {
int p1_x = static_cast<const GdkRectangle*>(p1)->x;
int p2_x = static_cast<const GdkRectangle*>(p2)->x;
if (p1_x < p2_x)
return -1;
else if (p1_x == p2_x)
return 0;
return 1;
}
bool GdkRectMatchesTabFaviconBounds(const GdkRectangle& gdk_rect, TabGtk* tab) {
gfx::Rect favicon_bounds = tab->favicon_bounds();
return gdk_rect.x == favicon_bounds.x() + tab->x() &&
gdk_rect.y == favicon_bounds.y() + tab->y() &&
gdk_rect.width == favicon_bounds.width() &&
gdk_rect.height == favicon_bounds.height();
}
}
class TabStripGtk::TabAnimation : public gfx::AnimationDelegate {
public:
friend class TabStripGtk;
enum Type {
INSERT,
REMOVE,
MOVE,
RESIZE,
MINI,
MINI_MOVE
};
TabAnimation(TabStripGtk* tabstrip, Type type)
: tabstrip_(tabstrip),
animation_(this),
start_selected_width_(0),
start_unselected_width_(0),
end_selected_width_(0),
end_unselected_width_(0),
layout_on_completion_(false),
type_(type) {
}
virtual ~TabAnimation() {}
Type type() const { return type_; }
void Start() {
animation_.SetSlideDuration(GetDuration());
animation_.SetTweenType(gfx::Tween::EASE_OUT);
if (!animation_.IsShowing()) {
animation_.Reset();
animation_.Show();
}
}
void Stop() {
animation_.Stop();
}
void set_layout_on_completion(bool layout_on_completion) {
layout_on_completion_ = layout_on_completion;
}
static double GetCurrentTabWidth(TabStripGtk* tabstrip,
TabStripGtk::TabAnimation* animation,
int index) {
TabGtk* tab = tabstrip->GetTabAt(index);
double tab_width;
if (tab->mini()) {
tab_width = TabGtk::GetMiniWidth();
} else {
double unselected, selected;
tabstrip->GetCurrentTabWidths(&unselected, &selected);
tab_width = tab->IsActive() ? selected : unselected;
}
if (animation) {
double specified_tab_width = animation->GetWidthForTab(index);
if (specified_tab_width != -1)
tab_width = specified_tab_width;
}
return tab_width;
}
virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
tabstrip_->AnimationLayout(end_unselected_width_);
}
virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
tabstrip_->FinishAnimation(this, layout_on_completion_);
}
virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
AnimationEnded(animation);
}
virtual double GetGapWidth(int index) {
return 0;
}
protected:
virtual int GetDuration() const {
return kDefaultAnimationDurationMs;
}
virtual double GetWidthForTab(int index) const {
return -1;
}
void GenerateStartAndEndWidths(int start_tab_count, int end_tab_count,
int start_mini_count,
int end_mini_count) {
tabstrip_->GetDesiredTabWidths(start_tab_count, start_mini_count,
&start_unselected_width_,
&start_selected_width_);
double standard_tab_width =
static_cast<double>(TabRendererGtk::GetStandardSize().width());
if ((end_tab_count - start_tab_count) > 0 &&
start_unselected_width_ < standard_tab_width) {
double minimum_tab_width = static_cast<double>(
TabRendererGtk::GetMinimumUnselectedSize().width());
start_unselected_width_ -= minimum_tab_width / start_tab_count;
}
tabstrip_->GenerateIdealBounds();
tabstrip_->GetDesiredTabWidths(end_tab_count, end_mini_count,
&end_unselected_width_,
&end_selected_width_);
}
TabStripGtk* tabstrip_;
gfx::SlideAnimation animation_;
double start_selected_width_;
double start_unselected_width_;
double end_selected_width_;
double end_unselected_width_;
private:
bool layout_on_completion_;
const Type type_;
DISALLOW_COPY_AND_ASSIGN(TabAnimation);
};
class InsertTabAnimation : public TabStripGtk::TabAnimation {
public:
InsertTabAnimation(TabStripGtk* tabstrip, int index)
: TabAnimation(tabstrip, INSERT),
index_(index) {
int tab_count = tabstrip->GetTabCount();
int end_mini_count = tabstrip->GetMiniTabCount();
int start_mini_count = end_mini_count;
if (index < end_mini_count)
start_mini_count--;
GenerateStartAndEndWidths(tab_count - 1, tab_count, start_mini_count,
end_mini_count);
}
virtual ~InsertTabAnimation() {}
protected:
virtual double GetWidthForTab(int index) const OVERRIDE {
if (index == index_) {
bool is_selected = tabstrip_->model()->active_index() == index;
double start_width, target_width;
if (index < tabstrip_->GetMiniTabCount()) {
start_width = TabGtk::GetMinimumSelectedSize().width();
target_width = TabGtk::GetMiniWidth();
} else {
target_width =
is_selected ? end_unselected_width_ : end_selected_width_;
start_width =
is_selected ? TabGtk::GetMinimumSelectedSize().width() :
TabGtk::GetMinimumUnselectedSize().width();
}
double delta = target_width - start_width;
if (delta > 0)
return start_width + (delta * animation_.GetCurrentValue());
return start_width;
}
if (tabstrip_->GetTabAt(index)->mini())
return TabGtk::GetMiniWidth();
if (tabstrip_->GetTabAt(index)->IsActive()) {
double delta = end_selected_width_ - start_selected_width_;
return start_selected_width_ + (delta * animation_.GetCurrentValue());
}
double delta = end_unselected_width_ - start_unselected_width_;
return start_unselected_width_ + (delta * animation_.GetCurrentValue());
}
private:
int index_;
DISALLOW_COPY_AND_ASSIGN(InsertTabAnimation);
};
class RemoveTabAnimation : public TabStripGtk::TabAnimation {
public:
RemoveTabAnimation(TabStripGtk* tabstrip, int index, WebContents* contents)
: TabAnimation(tabstrip, REMOVE),
index_(index) {
int tab_count = tabstrip->GetTabCount();
int start_mini_count = tabstrip->GetMiniTabCount();
int end_mini_count = start_mini_count;
if (index < start_mini_count)
end_mini_count--;
GenerateStartAndEndWidths(tab_count, tab_count - 1, start_mini_count,
end_mini_count);
set_layout_on_completion(start_mini_count > 0 &&
(end_mini_count == 0 ||
(start_mini_count == end_mini_count &&
tab_count == start_mini_count + 1)));
}
virtual ~RemoveTabAnimation() {}
int index() const { return index_; }
protected:
virtual double GetWidthForTab(int index) const OVERRIDE {
TabGtk* tab = tabstrip_->GetTabAt(index);
if (index == index_) {
if (tab->mini()) {
return animation_.CurrentValueBetween(TabGtk::GetMiniWidth(),
-kTabHOffset);
}
double start_width = start_unselected_width_;
double target_width =
std::max(abs(kTabHOffset),
TabGtk::GetMinimumUnselectedSize().width() + kTabHOffset);
return animation_.CurrentValueBetween(start_width, target_width);
}
if (tab->mini())
return TabGtk::GetMiniWidth();
if (tabstrip_->available_width_for_tabs_ != -1 &&
index_ != tabstrip_->GetTabCount() - 1) {
return TabStripGtk::TabAnimation::GetWidthForTab(index);
}
if (tab->IsActive()) {
double delta = end_selected_width_ - start_selected_width_;
return start_selected_width_ + (delta * animation_.GetCurrentValue());
}
double delta = end_unselected_width_ - start_unselected_width_;
return start_unselected_width_ + (delta * animation_.GetCurrentValue());
}
virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
tabstrip_->RemoveTabAt(index_);
TabStripGtk::TabAnimation::AnimationEnded(animation);
}
private:
int index_;
DISALLOW_COPY_AND_ASSIGN(RemoveTabAnimation);
};
class MoveTabAnimation : public TabStripGtk::TabAnimation {
public:
MoveTabAnimation(TabStripGtk* tabstrip, int tab_a_index, int tab_b_index)
: TabAnimation(tabstrip, MOVE),
start_tab_a_bounds_(tabstrip_->GetIdealBounds(tab_b_index)),
start_tab_b_bounds_(tabstrip_->GetIdealBounds(tab_a_index)) {
tab_a_ = tabstrip_->GetTabAt(tab_a_index);
tab_b_ = tabstrip_->GetTabAt(tab_b_index);
set_layout_on_completion(true);
}
virtual ~MoveTabAnimation() {}
virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
double distance = start_tab_b_bounds_.x() - start_tab_a_bounds_.x();
double delta = distance * animation_.GetCurrentValue();
double new_x = start_tab_a_bounds_.x() + delta;
gfx::Rect bounds(Round(new_x), start_tab_a_bounds_.y(), tab_a_->width(),
tab_a_->height());
tabstrip_->SetTabBounds(tab_a_, bounds);
distance = start_tab_a_bounds_.x() - start_tab_b_bounds_.x();
delta = distance * animation_.GetCurrentValue();
new_x = start_tab_b_bounds_.x() + delta;
bounds = gfx::Rect(Round(new_x), start_tab_b_bounds_.y(), tab_b_->width(),
tab_b_->height());
tabstrip_->SetTabBounds(tab_b_, bounds);
}
protected:
virtual int GetDuration() const OVERRIDE {
return kReorderAnimationDurationMs;
}
private:
TabGtk* tab_a_;
TabGtk* tab_b_;
gfx::Rect start_tab_a_bounds_;
gfx::Rect start_tab_b_bounds_;
DISALLOW_COPY_AND_ASSIGN(MoveTabAnimation);
};
class ResizeLayoutAnimation : public TabStripGtk::TabAnimation {
public:
explicit ResizeLayoutAnimation(TabStripGtk* tabstrip)
: TabAnimation(tabstrip, RESIZE) {
int tab_count = tabstrip->GetTabCount();
int mini_tab_count = tabstrip->GetMiniTabCount();
GenerateStartAndEndWidths(tab_count, tab_count, mini_tab_count,
mini_tab_count);
InitStartState();
}
virtual ~ResizeLayoutAnimation() {}
virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
tabstrip_->needs_resize_layout_ = false;
TabStripGtk::TabAnimation::AnimationEnded(animation);
}
protected:
virtual int GetDuration() const OVERRIDE {
return kResizeLayoutAnimationDurationMs;
}
virtual double GetWidthForTab(int index) const OVERRIDE {
TabGtk* tab = tabstrip_->GetTabAt(index);
if (tab->mini())
return TabGtk::GetMiniWidth();
if (tab->IsActive()) {
return animation_.CurrentValueBetween(start_selected_width_,
end_selected_width_);
}
return animation_.CurrentValueBetween(start_unselected_width_,
end_unselected_width_);
}
private:
void InitStartState() {
for (int i = 0; i < tabstrip_->GetTabCount(); ++i) {
TabGtk* current_tab = tabstrip_->GetTabAt(i);
if (!current_tab->mini()) {
if (current_tab->IsActive()) {
start_selected_width_ = current_tab->width();
} else {
start_unselected_width_ = current_tab->width();
}
}
}
}
DISALLOW_COPY_AND_ASSIGN(ResizeLayoutAnimation);
};
class MiniTabAnimation : public TabStripGtk::TabAnimation {
public:
MiniTabAnimation(TabStripGtk* tabstrip, int index)
: TabAnimation(tabstrip, MINI),
index_(index) {
int tab_count = tabstrip->GetTabCount();
int start_mini_count = tabstrip->GetMiniTabCount();
int end_mini_count = start_mini_count;
if (tabstrip->GetTabAt(index)->mini())
start_mini_count--;
else
start_mini_count++;
tabstrip_->GetTabAt(index)->set_animating_mini_change(true);
GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count,
end_mini_count);
}
protected:
virtual int GetDuration() const OVERRIDE {
return kMiniTabAnimationDurationMs;
}
virtual double GetWidthForTab(int index) const OVERRIDE {
TabGtk* tab = tabstrip_->GetTabAt(index);
if (index == index_) {
if (tab->mini()) {
return animation_.CurrentValueBetween(
start_selected_width_,
static_cast<double>(TabGtk::GetMiniWidth()));
} else {
return animation_.CurrentValueBetween(
static_cast<double>(TabGtk::GetMiniWidth()),
end_selected_width_);
}
} else if (tab->mini()) {
return TabGtk::GetMiniWidth();
}
if (tab->IsActive()) {
return animation_.CurrentValueBetween(start_selected_width_,
end_selected_width_);
}
return animation_.CurrentValueBetween(start_unselected_width_,
end_unselected_width_);
}
private:
int index_;
DISALLOW_COPY_AND_ASSIGN(MiniTabAnimation);
};
class MiniMoveAnimation : public TabStripGtk::TabAnimation {
public:
MiniMoveAnimation(TabStripGtk* tabstrip,
int from_index,
int to_index,
const gfx::Rect& start_bounds)
: TabAnimation(tabstrip, MINI_MOVE),
tab_(tabstrip->GetTabAt(to_index)),
start_bounds_(start_bounds),
from_index_(from_index),
to_index_(to_index) {
int tab_count = tabstrip->GetTabCount();
int start_mini_count = tabstrip->GetMiniTabCount();
int end_mini_count = start_mini_count;
if (tabstrip->GetTabAt(to_index)->mini())
start_mini_count--;
else
start_mini_count++;
GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count,
end_mini_count);
target_bounds_ = tabstrip->GetIdealBounds(to_index);
tab_->set_animating_mini_change(true);
}
virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
TabAnimation::AnimationProgressed(animation);
int x = animation_.CurrentValueBetween(start_bounds_.x(),
target_bounds_.x());
int width = animation_.CurrentValueBetween(start_bounds_.width(),
target_bounds_.width());
gfx::Rect tab_bounds(x, start_bounds_.y(), width,
start_bounds_.height());
tabstrip_->SetTabBounds(tab_, tab_bounds);
}
virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
tabstrip_->needs_resize_layout_ = false;
TabStripGtk::TabAnimation::AnimationEnded(animation);
}
virtual double GetGapWidth(int index) OVERRIDE {
if (to_index_ < from_index_) {
if (index == to_index_) {
double current_size =
animation_.CurrentValueBetween(0, target_bounds_.width());
if (current_size < -kTabHOffset)
return -(current_size + kTabHOffset);
} else if (index == from_index_ + 1) {
return animation_.CurrentValueBetween(start_bounds_.width(), 0);
}
} else {
if (index == from_index_) {
return animation_.CurrentValueBetween(
TabGtk::GetMiniWidth() + kTabHOffset, 0);
}
}
return 0;
}
protected:
virtual int GetDuration() const OVERRIDE {
return kReorderAnimationDurationMs;
}
virtual double GetWidthForTab(int index) const OVERRIDE {
TabGtk* tab = tabstrip_->GetTabAt(index);
if (index == to_index_)
return animation_.CurrentValueBetween(0, target_bounds_.width());
if (tab->mini())
return TabGtk::GetMiniWidth();
if (tab->IsActive()) {
return animation_.CurrentValueBetween(start_selected_width_,
end_selected_width_);
}
return animation_.CurrentValueBetween(start_unselected_width_,
end_unselected_width_);
}
private:
TabGtk* tab_;
gfx::Rect start_bounds_;
gfx::Rect target_bounds_;
int from_index_;
int to_index_;
DISALLOW_COPY_AND_ASSIGN(MiniMoveAnimation);
};
const int TabStripGtk::mini_to_non_mini_gap_ = 3;
TabStripGtk::TabStripGtk(TabStripModel* model, BrowserWindowGtk* window)
: current_unselected_width_(TabGtk::GetStandardSize().width()),
current_selected_width_(TabGtk::GetStandardSize().width()),
available_width_for_tabs_(-1),
needs_resize_layout_(false),
tab_vertical_offset_(0),
model_(model),
window_(window),
theme_service_(GtkThemeService::GetFrom(model->profile())),
added_as_message_loop_observer_(false),
hover_tab_selector_(model),
weak_factory_(this),
layout_factory_(this) {
}
TabStripGtk::~TabStripGtk() {
model_->RemoveObserver(this);
tabstrip_.Destroy();
std::vector<TabData>::iterator iterator = tab_data_.begin();
for (; iterator < tab_data_.end(); iterator++) {
delete iterator->tab;
}
tab_data_.clear();
RemoveMessageLoopObserver();
}
void TabStripGtk::Init() {
model_->AddObserver(this);
tabstrip_.Own(gtk_fixed_new());
ViewIDUtil::SetID(tabstrip_.get(), VIEW_ID_TAB_STRIP);
gtk_widget_set_size_request(tabstrip_.get(), 0,
TabGtk::GetMinimumUnselectedSize().height());
gtk_widget_set_app_paintable(tabstrip_.get(), TRUE);
gtk_drag_dest_set(tabstrip_.get(), GTK_DEST_DEFAULT_ALL,
NULL, 0,
static_cast<GdkDragAction>(
GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
static const int targets[] = { ui::TEXT_URI_LIST,
ui::NETSCAPE_URL,
ui::TEXT_PLAIN,
-1 };
ui::SetDestTargetList(tabstrip_.get(), targets);
g_signal_connect(tabstrip_.get(), "map",
G_CALLBACK(OnMapThunk), this);
g_signal_connect(tabstrip_.get(), "expose-event",
G_CALLBACK(OnExposeThunk), this);
g_signal_connect(tabstrip_.get(), "size-allocate",
G_CALLBACK(OnSizeAllocateThunk), this);
g_signal_connect(tabstrip_.get(), "drag-motion",
G_CALLBACK(OnDragMotionThunk), this);
g_signal_connect(tabstrip_.get(), "drag-drop",
G_CALLBACK(OnDragDropThunk), this);
g_signal_connect(tabstrip_.get(), "drag-leave",
G_CALLBACK(OnDragLeaveThunk), this);
g_signal_connect(tabstrip_.get(), "drag-data-received",
G_CALLBACK(OnDragDataReceivedThunk), this);
newtab_button_.reset(MakeNewTabButton());
newtab_surface_bounds_.SetRect(0, 0, newtab_button_->SurfaceWidth(),
newtab_button_->SurfaceHeight());
gtk_widget_show_all(tabstrip_.get());
bounds_ = GetInitialWidgetBounds(tabstrip_.get());
if (drop_indicator_width == 0) {
GdkPixbuf* drop_image = GetDropArrowImage(true)->ToGdkPixbuf();
drop_indicator_width = gdk_pixbuf_get_width(drop_image);
drop_indicator_height = gdk_pixbuf_get_height(drop_image);
}
registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
content::Source<ThemeService>(theme_service_));
theme_service_->InitThemesFor(this);
ViewIDUtil::SetDelegateForWidget(widget(), this);
}
void TabStripGtk::Show() {
gtk_widget_show(tabstrip_.get());
}
void TabStripGtk::Hide() {
gtk_widget_hide(tabstrip_.get());
}
bool TabStripGtk::IsActiveDropTarget() const {
for (int i = 0; i < GetTabCount(); ++i) {
TabGtk* tab = GetTabAt(i);
if (tab->dragging())
return true;
}
return false;
}
void TabStripGtk::Layout() {
StopAnimation();
GenerateIdealBounds();
int tab_count = GetTabCount();
int tab_right = 0;
for (int i = 0; i < tab_count; ++i) {
const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds;
TabGtk* tab = GetTabAt(i);
tab->set_animating_mini_change(false);
tab->set_vertical_offset(tab_vertical_offset_);
SetTabBounds(tab, bounds);
tab_right = bounds.right();
tab_right += GetTabHOffset(i + 1);
}
LayoutNewTabButton(static_cast<double>(tab_right), current_unselected_width_);
}
void TabStripGtk::SchedulePaint() {
gtk_widget_queue_draw(tabstrip_.get());
}
void TabStripGtk::SetBounds(const gfx::Rect& bounds) {
bounds_ = bounds;
}
void TabStripGtk::UpdateLoadingAnimations() {
for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
TabGtk* current_tab = GetTabAt(i);
if (current_tab->closing()) {
--index;
} else {
TabRendererGtk::AnimationState state;
content::WebContents* web_contents = model_->GetWebContentsAt(index);
if (!web_contents|| !web_contents->IsLoading()) {
state = TabGtk::ANIMATION_NONE;
} else if (web_contents->IsWaitingForResponse()) {
state = TabGtk::ANIMATION_WAITING;
} else {
state = TabGtk::ANIMATION_LOADING;
}
if (current_tab->ValidateLoadingAnimation(state)) {
gfx::Rect favicon_bounds = current_tab->favicon_bounds();
gtk_widget_queue_draw_area(tabstrip_.get(),
favicon_bounds.x() + current_tab->x(),
favicon_bounds.y() + current_tab->y(),
favicon_bounds.width(),
favicon_bounds.height());
}
}
}
}
bool TabStripGtk::IsCompatibleWith(TabStripGtk* other) {
return model_->profile() == other->model()->profile();
}
bool TabStripGtk::IsAnimating() const {
return active_animation_.get() != NULL;
}
void TabStripGtk::DestroyDragController() {
drag_controller_.reset();
}
void TabStripGtk::DestroyDraggedTab(TabGtk* tab) {
StopAnimation();
std::vector<TabData>::iterator it = tab_data_.begin();
for (; it != tab_data_.end(); ++it) {
if (it->tab == tab) {
if (!model_->closing_all())
NOTREACHED() << "Leaving in an inconsistent state!";
tab_data_.erase(it);
break;
}
}
gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), tab->widget());
base::MessageLoop::current()->DeleteSoon(FROM_HERE, tab);
Layout();
}
gfx::Rect TabStripGtk::GetIdealBounds(int index) {
DCHECK(index >= 0 && index < GetTabCount());
return tab_data_.at(index).ideal_bounds;
}
void TabStripGtk::SetVerticalOffset(int offset) {
tab_vertical_offset_ = offset;
Layout();
}
gfx::Point TabStripGtk::GetTabStripOriginForWidget(GtkWidget* target) {
int x, y;
GtkAllocation widget_allocation;
gtk_widget_get_allocation(widget(), &widget_allocation);
if (!gtk_widget_translate_coordinates(widget(), target,
-widget_allocation.x, 0, &x, &y)) {
if (!gtk_widget_translate_coordinates(
gtk_widget_get_toplevel(widget()), target, 0, 0, &x, &y)) {
NOTREACHED();
}
}
if (!gtk_widget_get_has_window(target)) {
GtkAllocation target_allocation;
gtk_widget_get_allocation(target, &target_allocation);
x += target_allocation.x;
y += target_allocation.y;
}
return gfx::Point(x, y);
}
GtkWidget* TabStripGtk::GetWidgetForViewID(ViewID view_id) {
if (GetTabCount() > 0) {
if (view_id == VIEW_ID_TAB_LAST) {
return GetTabAt(GetTabCount() - 1)->widget();
} else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
int index = view_id - VIEW_ID_TAB_0;
if (index >= 0 && index < GetTabCount()) {
return GetTabAt(index)->widget();
} else {
return NULL;
}
}
}
return NULL;
}
void TabStripGtk::TabInsertedAt(WebContents* contents,
int index,
bool foreground) {
TRACE_EVENT0("ui::gtk", "TabStripGtk::TabInsertedAt");
DCHECK(contents);
DCHECK(index == TabStripModel::kNoTab || model_->ContainsIndex(index));
StopAnimation();
bool contains_tab = false;
TabGtk* tab = NULL;
if (IsDragSessionActive()) {
tab = drag_controller_->GetDraggedTabForContents(contents);
if (tab) {
tab->set_closing(false);
tab->ValidateLoadingAnimation(TabRendererGtk::ANIMATION_NONE);
tab->SetVisible(true);
}
std::vector<TabData>::const_iterator iter = tab_data_.begin();
for (; iter != tab_data_.end() && !contains_tab; ++iter) {
if (iter->tab == tab)
contains_tab = true;
}
}
if (!tab)
tab = new TabGtk(this);
if (!contains_tab) {
TabData d = { tab, gfx::Rect() };
tab_data_.insert(tab_data_.begin() + index, d);
tab->UpdateData(contents, model_->IsAppTab(index), false);
}
tab->set_mini(model_->IsMiniTab(index));
tab->set_app(model_->IsAppTab(index));
tab->SetBlocked(model_->IsTabBlocked(index));
if (gtk_widget_get_parent(tab->widget()) != tabstrip_.get())
gtk_fixed_put(GTK_FIXED(tabstrip_.get()), tab->widget(), 0, 0);
if (GetTabCount() > 1) {
StartInsertTabAnimation(index);
active_animation_->AnimationProgressed(NULL);
} else {
Layout();
}
ReStack();
}
void TabStripGtk::TabDetachedAt(WebContents* contents, int index) {
GenerateIdealBounds();
StartRemoveTabAnimation(index, contents);
GetTabAt(index)->set_closing(true);
}
void TabStripGtk::ActiveTabChanged(WebContents* old_contents,
WebContents* new_contents,
int index,
int reason) {
TRACE_EVENT0("ui::gtk", "TabStripGtk::ActiveTabChanged");
ReStack();
}
void TabStripGtk::TabSelectionChanged(TabStripModel* tab_strip_model,
const ui::ListSelectionModel& old_model) {
bool tiny_tabs = current_unselected_width_ != current_selected_width_;
if (!IsAnimating() && (!needs_resize_layout_ || tiny_tabs))
Layout();
if (model_->active_index() >= 0)
GetTabAt(model_->active_index())->SchedulePaint();
if (old_model.active() >= 0) {
GetTabAt(old_model.active())->SchedulePaint();
GetTabAt(old_model.active())->StopMiniTabTitleAnimation();
}
std::vector<int> indices_affected;
std::insert_iterator<std::vector<int> > it1(indices_affected,
indices_affected.begin());
std::set_symmetric_difference(
old_model.selected_indices().begin(),
old_model.selected_indices().end(),
model_->selection_model().selected_indices().begin(),
model_->selection_model().selected_indices().end(),
it1);
for (std::vector<int>::iterator it = indices_affected.begin();
it != indices_affected.end(); ++it) {
if (*it != model_->active_index() && *it != old_model.active())
GetTabAtAdjustForAnimation(*it)->SchedulePaint();
}
ui::ListSelectionModel::SelectedIndices no_longer_selected =
base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
old_model.selected_indices(),
model_->selection_model().selected_indices());
for (std::vector<int>::iterator it = no_longer_selected.begin();
it != no_longer_selected.end(); ++it) {
GetTabAtAdjustForAnimation(*it)->StopMiniTabTitleAnimation();
}
}
void TabStripGtk::TabMoved(WebContents* contents,
int from_index,
int to_index) {
gfx::Rect start_bounds = GetIdealBounds(from_index);
TabGtk* tab = GetTabAt(from_index);
tab_data_.erase(tab_data_.begin() + from_index);
TabData data = {tab, gfx::Rect()};
tab->set_mini(model_->IsMiniTab(to_index));
tab->SetBlocked(model_->IsTabBlocked(to_index));
tab_data_.insert(tab_data_.begin() + to_index, data);
GenerateIdealBounds();
StartMoveTabAnimation(from_index, to_index);
ReStack();
}
void TabStripGtk::TabChangedAt(WebContents* contents,
int index,
TabChangeType change_type) {
TabGtk* tab = GetTabAtAdjustForAnimation(index);
if (change_type == TITLE_NOT_LOADING) {
if (tab->mini() && !tab->IsActive())
tab->StartMiniTabTitleAnimation();
return;
}
tab->UpdateData(contents,
model_->IsAppTab(index),
change_type == LOADING_ONLY);
tab->UpdateFromModel();
}
void TabStripGtk::TabReplacedAt(TabStripModel* tab_strip_model,
WebContents* old_contents,
WebContents* new_contents,
int index) {
TabChangedAt(new_contents, index, ALL);
}
void TabStripGtk::TabMiniStateChanged(WebContents* contents, int index) {
if (GetTabAt(index)->mini() == model_->IsMiniTab(index))
return;
GetTabAt(index)->set_mini(model_->IsMiniTab(index));
if (window_ && window_->window() &&
gtk_widget_get_visible(GTK_WIDGET(window_->window()))) {
StartMiniTabAnimation(index);
} else {
Layout();
}
}
void TabStripGtk::TabBlockedStateChanged(WebContents* contents, int index) {
GetTabAt(index)->SetBlocked(model_->IsTabBlocked(index));
}
bool TabStripGtk::IsTabActive(const TabGtk* tab) const {
if (tab->closing())
return false;
return GetIndexOfTab(tab) == model_->active_index();
}
bool TabStripGtk::IsTabSelected(const TabGtk* tab) const {
if (tab->closing())
return false;
return model_->IsTabSelected(GetIndexOfTab(tab));
}
bool TabStripGtk::IsTabDetached(const TabGtk* tab) const {
if (drag_controller_.get())
return drag_controller_->IsTabDetached(tab);
return false;
}
void TabStripGtk::GetCurrentTabWidths(double* unselected_width,
double* selected_width) const {
*unselected_width = current_unselected_width_;
*selected_width = current_selected_width_;
}
bool TabStripGtk::IsTabPinned(const TabGtk* tab) const {
if (tab->closing())
return false;
return model_->IsTabPinned(GetIndexOfTab(tab));
}
void TabStripGtk::ActivateTab(TabGtk* tab) {
int index = GetIndexOfTab(tab);
if (model_->ContainsIndex(index))
model_->ActivateTabAt(index, true);
}
void TabStripGtk::ToggleTabSelection(TabGtk* tab) {
int index = GetIndexOfTab(tab);
model_->ToggleSelectionAt(index);
}
void TabStripGtk::ExtendTabSelection(TabGtk* tab) {
int index = GetIndexOfTab(tab);
if (model_->ContainsIndex(index))
model_->ExtendSelectionTo(index);
}
void TabStripGtk::CloseTab(TabGtk* tab) {
int tab_index = GetIndexOfTab(tab);
if (model_->ContainsIndex(tab_index)) {
TabGtk* last_tab = GetTabAt(GetTabCount() - 1);
available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab);
needs_resize_layout_ = true;
AddMessageLoopObserver();
model_->CloseWebContentsAt(tab_index,
TabStripModel::CLOSE_USER_GESTURE |
TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
}
}
bool TabStripGtk::IsCommandEnabledForTab(
TabStripModel::ContextMenuCommand command_id, const TabGtk* tab) const {
int index = GetIndexOfTab(tab);
if (model_->ContainsIndex(index))
return model_->IsContextMenuCommandEnabled(index, command_id);
return false;
}
void TabStripGtk::ExecuteCommandForTab(
TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
int index = GetIndexOfTab(tab);
if (model_->ContainsIndex(index))
model_->ExecuteContextMenuCommand(index, command_id);
}
void TabStripGtk::StartHighlightTabsForCommand(
TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
if (command_id == TabStripModel::CommandCloseOtherTabs ||
command_id == TabStripModel::CommandCloseTabsToRight) {
NOTIMPLEMENTED();
}
}
void TabStripGtk::StopHighlightTabsForCommand(
TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
if (command_id == TabStripModel::CommandCloseTabsToRight ||
command_id == TabStripModel::CommandCloseOtherTabs) {
StopAllHighlighting();
}
}
void TabStripGtk::StopAllHighlighting() {
NOTIMPLEMENTED();
}
void TabStripGtk::MaybeStartDrag(TabGtk* tab, const gfx::Point& point) {
if (IsAnimating() || tab->closing() || !HasAvailableDragActions())
return;
std::vector<TabGtk*> tabs;
for (size_t i = 0; i < model()->selection_model().size(); i++) {
TabGtk* selected_tab =
GetTabAtAdjustForAnimation(
model()->selection_model().selected_indices()[i]);
if (!selected_tab->closing())
tabs.push_back(selected_tab);
}
drag_controller_.reset(new DraggedTabControllerGtk(this, tab, tabs));
drag_controller_->CaptureDragInfo(point);
}
void TabStripGtk::ContinueDrag(GdkDragContext* context) {
if (drag_controller_.get())
drag_controller_->Drag();
}
bool TabStripGtk::EndDrag(bool canceled) {
return drag_controller_.get() ? drag_controller_->EndDrag(canceled) : false;
}
bool TabStripGtk::HasAvailableDragActions() const {
return model_->delegate()->GetDragActions() != 0;
}
GtkThemeService* TabStripGtk::GetThemeProvider() {
return theme_service_;
}
TabStripMenuController* TabStripGtk::GetTabStripMenuControllerForTab(
TabGtk* tab) {
return new TabStripMenuController(tab, model(), GetIndexOfTab(tab));
}
void TabStripGtk::WillProcessEvent(GdkEvent* event) {
}
void TabStripGtk::DidProcessEvent(GdkEvent* event) {
switch (event->type) {
case GDK_MOTION_NOTIFY:
case GDK_LEAVE_NOTIFY:
HandleGlobalMouseMoveEvent();
break;
default:
break;
}
}
void TabStripGtk::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
SetNewTabButtonBackground();
}
int TabStripGtk::GetTabCount() const {
return static_cast<int>(tab_data_.size());
}
int TabStripGtk::GetMiniTabCount() const {
int mini_count = 0;
for (size_t i = 0; i < tab_data_.size(); ++i) {
if (tab_data_[i].tab->mini())
mini_count++;
else
return mini_count;
}
return mini_count;
}
int TabStripGtk::GetAvailableWidthForTabs(TabGtk* last_tab) const {
if (!base::i18n::IsRTL())
return last_tab->x() - bounds_.x() + last_tab->width();
else
return bounds_.width() - last_tab->x();
}
int TabStripGtk::GetIndexOfTab(const TabGtk* tab) const {
for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
TabGtk* current_tab = GetTabAt(i);
if (current_tab->closing()) {
--index;
} else if (current_tab == tab) {
return index;
}
}
return -1;
}
TabGtk* TabStripGtk::GetTabAt(int index) const {
DCHECK_GE(index, 0);
DCHECK_LT(index, GetTabCount());
return tab_data_.at(index).tab;
}
TabGtk* TabStripGtk::GetTabAtAdjustForAnimation(int index) const {
if (active_animation_.get() &&
active_animation_->type() == TabAnimation::REMOVE &&
index >=
static_cast<RemoveTabAnimation*>(active_animation_.get())->index()) {
index++;
}
return GetTabAt(index);
}
void TabStripGtk::RemoveTabAt(int index) {
TabGtk* removed = tab_data_.at(index).tab;
tab_data_.erase(tab_data_.begin() + index);
if (!removed->dragging()) {
gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), removed->widget());
delete removed;
}
}
void TabStripGtk::HandleGlobalMouseMoveEvent() {
if (!IsCursorInTabStripZone()) {
if (!weak_factory_.HasWeakPtrs()) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&TabStripGtk::ResizeLayoutTabs,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kResizeTabsTimeMs));
}
} else {
weak_factory_.InvalidateWeakPtrs();
}
}
void TabStripGtk::GenerateIdealBounds() {
int tab_count = GetTabCount();
double unselected, selected;
GetDesiredTabWidths(tab_count, GetMiniTabCount(), &unselected, &selected);
current_unselected_width_ = unselected;
current_selected_width_ = selected;
int tab_height = TabGtk::GetStandardSize().height();
double tab_x = tab_start_x();
for (int i = 0; i < tab_count; ++i) {
TabGtk* tab = GetTabAt(i);
double tab_width = unselected;
if (tab->mini())
tab_width = TabGtk::GetMiniWidth();
else if (tab->IsActive())
tab_width = selected;
double end_of_tab = tab_x + tab_width;
int rounded_tab_x = Round(tab_x);
gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
tab_height);
tab_data_.at(i).ideal_bounds = state;
tab_x = end_of_tab + GetTabHOffset(i + 1);
}
}
void TabStripGtk::LayoutNewTabButton(double last_tab_right,
double unselected_width) {
GtkWidget* toplevel = gtk_widget_get_ancestor(widget(), GTK_TYPE_WINDOW);
bool is_maximized = false;
if (toplevel) {
GdkWindow* gdk_window = gtk_widget_get_window(toplevel);
is_maximized = (gdk_window_get_state(gdk_window) &
GDK_WINDOW_STATE_MAXIMIZED) != 0;
}
int y = is_maximized ? 0 : kNewTabButtonVOffset;
int height = newtab_surface_bounds_.height() + kNewTabButtonVOffset - y;
gfx::Rect bounds(0, y, newtab_surface_bounds_.width(), height);
int delta = abs(Round(unselected_width) - TabGtk::GetStandardSize().width());
if (delta > 1 && !needs_resize_layout_) {
bounds.set_x(bounds_.width() - newtab_button_->WidgetAllocation().width);
} else {
bounds.set_x(Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset);
}
bounds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds));
gtk_fixed_move(GTK_FIXED(tabstrip_.get()), newtab_button_->widget(),
bounds.x(), bounds.y());
gtk_widget_set_size_request(newtab_button_->widget(), bounds.width(),
bounds.height());
}
void TabStripGtk::GetDesiredTabWidths(int tab_count,
int mini_tab_count,
double* unselected_width,
double* selected_width) const {
DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
const double min_unselected_width =
TabGtk::GetMinimumUnselectedSize().width();
const double min_selected_width =
TabGtk::GetMinimumSelectedSize().width();
*unselected_width = min_unselected_width;
*selected_width = min_selected_width;
if (tab_count == 0) {
return;
}
GtkAllocation tabstrip_allocation;
gtk_widget_get_allocation(tabstrip_.get(), &tabstrip_allocation);
int available_width = tabstrip_allocation.width;
if (available_width_for_tabs_ < 0) {
available_width = bounds_.width();
available_width -=
(kNewTabButtonHOffset + newtab_button_->WidgetAllocation().width);
} else {
available_width = available_width_for_tabs_;
}
if (mini_tab_count > 0) {
available_width -= mini_tab_count * (TabGtk::GetMiniWidth() + kTabHOffset);
tab_count -= mini_tab_count;
if (tab_count == 0) {
*selected_width = *unselected_width = TabGtk::GetStandardSize().width();
return;
}
available_width -= mini_to_non_mini_gap_;
}
const int total_offset = kTabHOffset * (tab_count - 1);
const double desired_tab_width = std::min(
(static_cast<double>(available_width - total_offset) /
static_cast<double>(tab_count)),
static_cast<double>(TabGtk::GetStandardSize().width()));
*unselected_width = std::max(desired_tab_width, min_unselected_width);
*selected_width = std::max(desired_tab_width, min_selected_width);
if (tab_count > 1) {
if ((min_unselected_width < min_selected_width) &&
(desired_tab_width < min_selected_width)) {
double calc_width =
static_cast<double>(
available_width - total_offset - min_selected_width) /
static_cast<double>(tab_count - 1);
*unselected_width = std::max(calc_width, min_unselected_width);
} else if ((min_unselected_width > min_selected_width) &&
(desired_tab_width < min_unselected_width)) {
*selected_width = std::max(available_width - total_offset -
(min_unselected_width * (tab_count - 1)), min_selected_width);
}
}
}
int TabStripGtk::GetTabHOffset(int tab_index) {
if (tab_index < GetTabCount() && GetTabAt(tab_index - 1)->mini() &&
!GetTabAt(tab_index)->mini()) {
return mini_to_non_mini_gap_ + kTabHOffset;
}
return kTabHOffset;
}
int TabStripGtk::tab_start_x() const {
return 0;
}
void TabStripGtk::ResizeLayoutTabs() {
weak_factory_.InvalidateWeakPtrs();
layout_factory_.InvalidateWeakPtrs();
RemoveMessageLoopObserver();
available_width_for_tabs_ = -1;
int mini_tab_count = GetMiniTabCount();
if (mini_tab_count == GetTabCount()) {
return;
}
TabGtk* first_tab = GetTabAt(mini_tab_count);
double unselected, selected;
GetDesiredTabWidths(GetTabCount(), mini_tab_count, &unselected, &selected);
int w = Round(first_tab->IsActive() ? selected : unselected);
if (abs(first_tab->width() - w) > 1)
StartResizeLayoutAnimation();
}
bool TabStripGtk::IsCursorInTabStripZone() const {
gfx::Point tabstrip_topleft;
gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &tabstrip_topleft);
gfx::Rect bds = bounds();
bds.set_origin(tabstrip_topleft);
bds.set_height(bds.height() + kTabStripAnimationVSlop);
GdkScreen* screen = gdk_screen_get_default();
GdkDisplay* display = gdk_screen_get_display(screen);
gint x, y;
gdk_display_get_pointer(display, NULL, &x, &y, NULL);
gfx::Point cursor_point(x, y);
return bds.Contains(cursor_point);
}
void TabStripGtk::ReStack() {
TRACE_EVENT0("ui::gtk", "TabStripGtk::ReStack");
if (!gtk_widget_get_realized(tabstrip_.get())) {
return;
}
int tab_count = GetTabCount();
TabGtk* active_tab = NULL;
for (int i = tab_count - 1; i >= 0; --i) {
TabGtk* tab = GetTabAt(i);
if (tab->IsActive())
active_tab = tab;
else
tab->Raise();
}
if (active_tab)
active_tab->Raise();
}
void TabStripGtk::AddMessageLoopObserver() {
if (!added_as_message_loop_observer_) {
base::MessageLoopForUI::current()->AddObserver(this);
added_as_message_loop_observer_ = true;
}
}
void TabStripGtk::RemoveMessageLoopObserver() {
if (added_as_message_loop_observer_) {
base::MessageLoopForUI::current()->RemoveObserver(this);
added_as_message_loop_observer_ = false;
}
}
gfx::Rect TabStripGtk::GetDropBounds(int drop_index,
bool drop_before,
bool* is_beneath) {
DCHECK_NE(drop_index, -1);
int center_x;
if (drop_index < GetTabCount()) {
TabGtk* tab = GetTabAt(drop_index);
gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get());
if (drop_before)
center_x = bounds.x() - (kTabHOffset / 2);
else
center_x = bounds.x() + (bounds.width() / 2);
} else {
TabGtk* last_tab = GetTabAt(drop_index - 1);
gfx::Rect bounds = last_tab->GetNonMirroredBounds(tabstrip_.get());
center_x = bounds.x() + bounds.width() + (kTabHOffset / 2);
}
center_x = gtk_util::MirroredXCoordinate(tabstrip_.get(), center_x);
gfx::Point drop_loc(center_x - drop_indicator_width / 2,
-drop_indicator_height);
gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &drop_loc);
gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
drop_indicator_height);
*is_beneath = true;
if (*is_beneath)
drop_bounds.Offset(0, drop_bounds.height() + bounds().height());
return drop_bounds;
}
void TabStripGtk::UpdateDropIndex(GdkDragContext* context, gint x, gint y) {
x = gtk_util::MirroredXCoordinate(tabstrip_.get(), x);
for (int i = GetMiniTabCount(); i < GetTabCount(); ++i) {
TabGtk* tab = GetTabAt(i);
gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get());
const int tab_max_x = bounds.x() + bounds.width();
const int hot_width = bounds.width() / kTabEdgeRatioInverse;
if (x < tab_max_x) {
if (x < bounds.x() + hot_width)
SetDropIndex(i, true);
else if (x >= tab_max_x - hot_width)
SetDropIndex(i + 1, true);
else
SetDropIndex(i, false);
return;
}
}
SetDropIndex(GetTabCount(), true);
}
void TabStripGtk::SetDropIndex(int index, bool drop_before) {
bool is_beneath;
gfx::Rect drop_bounds = GetDropBounds(index, drop_before, &is_beneath);
if (index != -1 && !drop_before)
hover_tab_selector_.StartTabTransition(index);
else
hover_tab_selector_.CancelTabTransition();
if (!drop_info_.get()) {
drop_info_.reset(new DropInfo(index, drop_before, !is_beneath));
} else {
if (!GTK_IS_WIDGET(drop_info_->container)) {
drop_info_->CreateContainer();
} else if (drop_info_->drop_index == index &&
drop_info_->drop_before == drop_before) {
return;
}
drop_info_->drop_index = index;
drop_info_->drop_before = drop_before;
if (is_beneath == drop_info_->point_down) {
drop_info_->point_down = !is_beneath;
drop_info_->drop_arrow = GetDropArrowImage(drop_info_->point_down);
}
}
gtk_window_move(GTK_WINDOW(drop_info_->container),
drop_bounds.x(), drop_bounds.y());
gtk_window_resize(GTK_WINDOW(drop_info_->container),
drop_bounds.width(), drop_bounds.height());
}
bool TabStripGtk::CompleteDrop(const guchar* data, bool is_plain_text) {
if (!drop_info_.get())
return false;
const int drop_index = drop_info_->drop_index;
const bool drop_before = drop_info_->drop_before;
drop_info_.reset();
hover_tab_selector_.CancelTabTransition();
GURL url;
if (is_plain_text) {
AutocompleteMatch match;
AutocompleteClassifierFactory::GetForProfile(model_->profile())->Classify(
base::UTF8ToUTF16(reinterpret_cast<const char*>(data)),
false, false, AutocompleteInput::INVALID_SPEC, &match, NULL);
url = match.destination_url;
} else {
std::string url_string(reinterpret_cast<const char*>(data));
url = GURL(url_string.substr(0, url_string.find_first_of('\n')));
}
if (!url.is_valid())
return false;
chrome::NavigateParams params(window()->browser(), url,
content::PAGE_TRANSITION_LINK);
params.tabstrip_index = drop_index;
if (drop_before) {
params.disposition = NEW_FOREGROUND_TAB;
} else {
params.disposition = CURRENT_TAB;
params.source_contents = model_->GetWebContentsAt(drop_index);
}
chrome::Navigate(¶ms);
return true;
}
gfx::Image* TabStripGtk::GetDropArrowImage(bool is_down) {
return &ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
}
TabStripGtk::DropInfo::DropInfo(int drop_index, bool drop_before,
bool point_down)
: drop_index(drop_index),
drop_before(drop_before),
point_down(point_down) {
CreateContainer();
drop_arrow = GetDropArrowImage(point_down);
}
TabStripGtk::DropInfo::~DropInfo() {
DestroyContainer();
}
gboolean TabStripGtk::DropInfo::OnExposeEvent(GtkWidget* widget,
GdkEventExpose* event) {
TRACE_EVENT0("ui::gtk", "TabStripGtk::DropInfo::OnExposeEvent");
if (ui::IsScreenComposited()) {
SetContainerTransparency();
} else {
SetContainerShapeMask();
}
cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
gdk_cairo_rectangle(cr, &event->area);
cairo_clip(cr);
drop_arrow->ToCairo()->SetSource(cr, widget, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
return FALSE;
}
void TabStripGtk::DropInfo::SetContainerColorMap() {
GdkScreen* screen = gtk_widget_get_screen(container);
GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen);
if (!colormap)
colormap = gdk_screen_get_rgb_colormap(screen);
gtk_widget_set_colormap(container, colormap);
}
void TabStripGtk::DropInfo::SetContainerTransparency() {
cairo_t* cairo_context = gdk_cairo_create(gtk_widget_get_window(container));
if (!cairo_context)
return;
cairo_scale(cairo_context, static_cast<double>(drop_indicator_width),
static_cast<double>(drop_indicator_height));
cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f);
cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
cairo_paint(cairo_context);
cairo_destroy(cairo_context);
}
void TabStripGtk::DropInfo::SetContainerShapeMask() {
GdkPixmap* pixmap = gdk_pixmap_new(NULL,
drop_indicator_width,
drop_indicator_height, 1);
cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(pixmap));
cairo_set_source_rgba(cairo_context, 1, 1, 1, 0);
cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
gdk_cairo_set_source_pixbuf(cairo_context, drop_arrow->ToGdkPixbuf(), 0, 0);
cairo_paint(cairo_context);
cairo_destroy(cairo_context);
GdkWindow* gdk_window = gtk_widget_get_window(container);
gdk_window_shape_combine_mask(gdk_window, pixmap, 0, 0);
g_object_unref(pixmap);
}
void TabStripGtk::DropInfo::CreateContainer() {
container = gtk_window_new(GTK_WINDOW_POPUP);
SetContainerColorMap();
gtk_widget_set_app_paintable(container, TRUE);
g_signal_connect(container, "expose-event",
G_CALLBACK(OnExposeEventThunk), this);
gtk_widget_add_events(container, GDK_STRUCTURE_MASK);
gtk_window_move(GTK_WINDOW(container), 0, 0);
gtk_window_resize(GTK_WINDOW(container),
drop_indicator_width, drop_indicator_height);
gtk_widget_show_all(container);
}
void TabStripGtk::DropInfo::DestroyContainer() {
if (GTK_IS_WIDGET(container))
gtk_widget_destroy(container);
}
void TabStripGtk::StopAnimation() {
if (active_animation_.get())
active_animation_->Stop();
}
void TabStripGtk::AnimationLayout(double unselected_width) {
int tab_height = TabGtk::GetStandardSize().height();
double tab_x = tab_start_x();
for (int i = 0; i < GetTabCount(); ++i) {
TabAnimation* animation = active_animation_.get();
if (animation)
tab_x += animation->GetGapWidth(i);
double tab_width = TabAnimation::GetCurrentTabWidth(this, animation, i);
double end_of_tab = tab_x + tab_width;
int rounded_tab_x = Round(tab_x);
TabGtk* tab = GetTabAt(i);
gfx::Rect bounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
tab_height);
SetTabBounds(tab, bounds);
tab_x = end_of_tab + GetTabHOffset(i + 1);
}
LayoutNewTabButton(tab_x, unselected_width);
}
void TabStripGtk::StartInsertTabAnimation(int index) {
available_width_for_tabs_ = -1;
StopAnimation();
active_animation_.reset(new InsertTabAnimation(this, index));
active_animation_->Start();
}
void TabStripGtk::StartRemoveTabAnimation(int index, WebContents* contents) {
if (active_animation_.get()) {
active_animation_->set_layout_on_completion(false);
active_animation_->Stop();
}
active_animation_.reset(new RemoveTabAnimation(this, index, contents));
active_animation_->Start();
}
void TabStripGtk::StartMoveTabAnimation(int from_index, int to_index) {
StopAnimation();
active_animation_.reset(new MoveTabAnimation(this, from_index, to_index));
active_animation_->Start();
}
void TabStripGtk::StartResizeLayoutAnimation() {
StopAnimation();
active_animation_.reset(new ResizeLayoutAnimation(this));
active_animation_->Start();
}
void TabStripGtk::StartMiniTabAnimation(int index) {
StopAnimation();
active_animation_.reset(new MiniTabAnimation(this, index));
active_animation_->Start();
}
void TabStripGtk::StartMiniMoveTabAnimation(int from_index,
int to_index,
const gfx::Rect& start_bounds) {
StopAnimation();
active_animation_.reset(
new MiniMoveAnimation(this, from_index, to_index, start_bounds));
active_animation_->Start();
}
void TabStripGtk::FinishAnimation(TabStripGtk::TabAnimation* animation,
bool layout) {
active_animation_.reset(NULL);
for (int i = 0, count = GetTabCount(); i < count; ++i)
GetTabAt(i)->set_animating_mini_change(false);
if (layout)
Layout();
}
void TabStripGtk::OnMap(GtkWidget* widget) {
ReStack();
}
gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) {
TRACE_EVENT0("ui::gtk", "TabStripGtk::OnExpose");
if (gdk_region_empty(event->region))
return TRUE;
GdkRectangle* rects;
gint num_rects;
gdk_region_get_rectangles(event->region, &rects, &num_rects);
qsort(rects, num_rects, sizeof(GdkRectangle), CompareGdkRectangles);
std::vector<int> tabs_to_repaint;
if (!IsDragSessionActive() &&
CanPaintOnlyFavicons(rects, num_rects, &tabs_to_repaint)) {
PaintOnlyFavicons(event, tabs_to_repaint);
g_free(rects);
return TRUE;
}
g_free(rects);
if (active_animation_.get() || drag_controller_.get()) {
event->area.width = bounds_.width();
} else {
event->area.width += event->area.x;
}
event->area.x = 0;
event->area.y = 0;
event->area.height = bounds_.height();
gdk_region_union_with_rect(event->region, &event->area);
gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
newtab_button_->widget(), event);
TabGtk* selected_tab = NULL;
int tab_count = GetTabCount();
for (int i = tab_count - 1; i >= 0; --i) {
TabGtk* tab = GetTabAt(i);
if (!tab->IsActive()) {
gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
tab->widget(), event);
} else {
selected_tab = tab;
}
}
if (selected_tab) {
gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
selected_tab->widget(), event);
}
return TRUE;
}
void TabStripGtk::OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) {
TRACE_EVENT0("ui::gtk", "TabStripGtk::OnSizeAllocate");
gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
allocation->width, allocation->height);
if (bounds_ == bounds)
return;
SetBounds(bounds);
if (GetTabCount() == 0)
return;
if (GetTabCount() == 1) {
Layout();
} else if (!layout_factory_.HasWeakPtrs()) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&TabStripGtk::Layout, layout_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kLayoutAfterSizeAllocateMs));
}
}
gboolean TabStripGtk::OnDragMotion(GtkWidget* widget, GdkDragContext* context,
gint x, gint y, guint time) {
UpdateDropIndex(context, x, y);
return TRUE;
}
gboolean TabStripGtk::OnDragDrop(GtkWidget* widget, GdkDragContext* context,
gint x, gint y, guint time) {
if (!drop_info_.get())
return FALSE;
GdkAtom target = gtk_drag_dest_find_target(widget, context, NULL);
if (target != GDK_NONE)
gtk_drag_finish(context, FALSE, FALSE, time);
else
gtk_drag_get_data(widget, context, target, time);
return TRUE;
}
gboolean TabStripGtk::OnDragLeave(GtkWidget* widget, GdkDragContext* context,
guint time) {
drop_info_->DestroyContainer();
hover_tab_selector_.CancelTabTransition();
return FALSE;
}
gboolean TabStripGtk::OnDragDataReceived(GtkWidget* widget,
GdkDragContext* context,
gint x, gint y,
GtkSelectionData* data,
guint info, guint time) {
bool success = false;
if (info == ui::TEXT_URI_LIST ||
info == ui::NETSCAPE_URL ||
info == ui::TEXT_PLAIN) {
success = CompleteDrop(gtk_selection_data_get_data(data),
info == ui::TEXT_PLAIN);
}
gtk_drag_finish(context, success, FALSE, time);
return TRUE;
}
void TabStripGtk::OnNewTabClicked(GtkWidget* widget) {
GdkEvent* event = gtk_get_current_event();
DCHECK_EQ(event->type, GDK_BUTTON_RELEASE);
int mouse_button = event->button.button;
gdk_event_free(event);
switch (mouse_button) {
case 1:
content::RecordAction(UserMetricsAction("NewTab_Button"));
UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
TabStripModel::NEW_TAB_ENUM_COUNT);
model_->delegate()->AddTabAt(GURL(), -1, true);
break;
case 2: {
GURL url;
if (!gtk_util::URLFromPrimarySelection(model_->profile(), &url))
return;
Browser* browser = window_->browser();
DCHECK(browser);
chrome::AddSelectedTabWithURL(
browser, url, content::PAGE_TRANSITION_TYPED);
break;
}
default:
NOTREACHED() << "Got click on new tab button with unhandled mouse "
<< "button " << mouse_button;
}
}
void TabStripGtk::SetTabBounds(TabGtk* tab, const gfx::Rect& bounds) {
gfx::Rect bds = bounds;
bds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds));
tab->SetBounds(bds);
gtk_fixed_move(GTK_FIXED(tabstrip_.get()), tab->widget(),
bds.x(), bds.y());
}
bool TabStripGtk::CanPaintOnlyFavicons(const GdkRectangle* rects,
int num_rects, std::vector<int>* tabs_to_paint) {
int t = 0;
for (int r = 0; r < num_rects; ++r) {
while (t < GetTabCount()) {
TabGtk* tab = GetTabAt(t);
if (GdkRectMatchesTabFaviconBounds(rects[r], tab) &&
tab->ShouldShowIcon()) {
tabs_to_paint->push_back(t);
++t;
break;
}
++t;
}
}
return static_cast<int>(tabs_to_paint->size()) == num_rects;
}
void TabStripGtk::PaintOnlyFavicons(GdkEventExpose* event,
const std::vector<int>& tabs_to_paint) {
cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(event->window));
for (size_t i = 0; i < tabs_to_paint.size(); ++i) {
cairo_save(cr);
GetTabAt(tabs_to_paint[i])->PaintFaviconArea(tabstrip_.get(), cr);
cairo_restore(cr);
}
cairo_destroy(cr);
}
CustomDrawButton* TabStripGtk::MakeNewTabButton() {
CustomDrawButton* button = new CustomDrawButton(IDR_NEWTAB_BUTTON,
IDR_NEWTAB_BUTTON_P, IDR_NEWTAB_BUTTON_H, 0);
gtk_widget_set_tooltip_text(button->widget(),
l10n_util::GetStringUTF8(IDS_TOOLTIP_NEW_TAB).c_str());
gtk_util::SetButtonTriggersNavigation(button->widget());
g_signal_connect(button->widget(), "clicked",
G_CALLBACK(OnNewTabClickedThunk), this);
gtk_widget_set_can_focus(button->widget(), FALSE);
gtk_fixed_put(GTK_FIXED(tabstrip_.get()), button->widget(), 0, 0);
return button;
}
void TabStripGtk::SetNewTabButtonBackground() {
SkColor color = theme_service_->GetColor(
ThemeProperties::COLOR_BUTTON_BACKGROUND);
SkBitmap background = theme_service_->GetImageNamed(
IDR_THEME_WINDOW_CONTROL_BACKGROUND).AsBitmap();
SkBitmap mask = theme_service_->GetImageNamed(
IDR_NEWTAB_BUTTON_MASK).AsBitmap();
newtab_button_->SetBackground(color, background, mask);
}