| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/gtk/tabs/tab_renderer_gtk.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <utility> | |
| 9 | |
| 10 #include "app/l10n_util.h" | |
| 11 #include "app/resource_bundle.h" | |
| 12 #include "base/utf_string_conversions.h" | |
| 13 #include "chrome/browser/defaults.h" | |
| 14 #include "chrome/browser/gtk/bookmark_utils_gtk.h" | |
| 15 #include "chrome/browser/gtk/custom_button.h" | |
| 16 #include "chrome/browser/gtk/gtk_theme_provider.h" | |
| 17 #include "chrome/browser/gtk/gtk_util.h" | |
| 18 #include "chrome/browser/profiles/profile.h" | |
| 19 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 20 #include "chrome/browser/ui/browser.h" | |
| 21 #include "chrome/common/notification_service.h" | |
| 22 #include "gfx/canvas_skia_paint.h" | |
| 23 #include "gfx/favicon_size.h" | |
| 24 #include "gfx/platform_font_gtk.h" | |
| 25 #include "gfx/skbitmap_operations.h" | |
| 26 #include "grit/app_resources.h" | |
| 27 #include "grit/generated_resources.h" | |
| 28 #include "grit/theme_resources.h" | |
| 29 #include "ui/base/animation/slide_animation.h" | |
| 30 #include "ui/base/animation/throb_animation.h" | |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 const int kLeftPadding = 16; | |
| 35 const int kTopPadding = 6; | |
| 36 const int kRightPadding = 15; | |
| 37 const int kBottomPadding = 5; | |
| 38 const int kDropShadowHeight = 2; | |
| 39 const int kFavIconTitleSpacing = 4; | |
| 40 const int kTitleCloseButtonSpacing = 5; | |
| 41 const int kStandardTitleWidth = 175; | |
| 42 const int kDropShadowOffset = 2; | |
| 43 const int kInactiveTabBackgroundOffsetY = 15; | |
| 44 | |
| 45 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If | |
| 46 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab | |
| 47 // is rendered as a normal tab. This is done to avoid having the title | |
| 48 // immediately disappear when transitioning a tab from normal to mini-tab. | |
| 49 const int kMiniTabRendererAsNormalTabWidth = | |
| 50 browser_defaults::kMiniTabWidth + 30; | |
| 51 | |
| 52 // The tab images are designed to overlap the toolbar by 1 pixel. For now we | |
| 53 // don't actually overlap the toolbar, so this is used to know how many pixels | |
| 54 // at the bottom of the tab images are to be ignored. | |
| 55 const int kToolbarOverlap = 1; | |
| 56 | |
| 57 // How long the hover state takes. | |
| 58 const int kHoverDurationMs = 90; | |
| 59 | |
| 60 // How opaque to make the hover state (out of 1). | |
| 61 const double kHoverOpacity = 0.33; | |
| 62 | |
| 63 // Max opacity for the mini-tab title change animation. | |
| 64 const double kMiniTitleChangeThrobOpacity = 0.75; | |
| 65 | |
| 66 // Duration for when the title of an inactive mini-tab changes. | |
| 67 const int kMiniTitleChangeThrobDuration = 1000; | |
| 68 | |
| 69 const SkScalar kTabCapWidth = 15; | |
| 70 const SkScalar kTabTopCurveWidth = 4; | |
| 71 const SkScalar kTabBottomCurveWidth = 3; | |
| 72 | |
| 73 // The vertical and horizontal offset used to position the close button | |
| 74 // in the tab. TODO(jhawkins): Ask pkasting what the Fuzz is about. | |
| 75 const int kCloseButtonVertFuzz = 0; | |
| 76 const int kCloseButtonHorzFuzz = 5; | |
| 77 | |
| 78 SkBitmap* crashed_fav_icon = NULL; | |
| 79 | |
| 80 // Gets the bounds of |widget| relative to |parent|. | |
| 81 gfx::Rect GetWidgetBoundsRelativeToParent(GtkWidget* parent, | |
| 82 GtkWidget* widget) { | |
| 83 gfx::Point parent_pos = gtk_util::GetWidgetScreenPosition(parent); | |
| 84 gfx::Point widget_pos = gtk_util::GetWidgetScreenPosition(widget); | |
| 85 return gfx::Rect(widget_pos.x() - parent_pos.x(), | |
| 86 widget_pos.y() - parent_pos.y(), | |
| 87 widget->allocation.width, widget->allocation.height); | |
| 88 } | |
| 89 | |
| 90 } // namespace | |
| 91 | |
| 92 TabRendererGtk::LoadingAnimation::Data::Data(ThemeProvider* theme_provider) { | |
| 93 // The loading animation image is a strip of states. Each state must be | |
| 94 // square, so the height must divide the width evenly. | |
| 95 loading_animation_frames = theme_provider->GetBitmapNamed(IDR_THROBBER); | |
| 96 DCHECK(loading_animation_frames); | |
| 97 DCHECK_EQ(loading_animation_frames->width() % | |
| 98 loading_animation_frames->height(), 0); | |
| 99 loading_animation_frame_count = | |
| 100 loading_animation_frames->width() / | |
| 101 loading_animation_frames->height(); | |
| 102 | |
| 103 waiting_animation_frames = | |
| 104 theme_provider->GetBitmapNamed(IDR_THROBBER_WAITING); | |
| 105 DCHECK(waiting_animation_frames); | |
| 106 DCHECK_EQ(waiting_animation_frames->width() % | |
| 107 waiting_animation_frames->height(), 0); | |
| 108 waiting_animation_frame_count = | |
| 109 waiting_animation_frames->width() / | |
| 110 waiting_animation_frames->height(); | |
| 111 | |
| 112 waiting_to_loading_frame_count_ratio = | |
| 113 waiting_animation_frame_count / | |
| 114 loading_animation_frame_count; | |
| 115 // TODO(beng): eventually remove this when we have a proper themeing system. | |
| 116 // themes not supporting IDR_THROBBER_WAITING are causing this | |
| 117 // value to be 0 which causes DIV0 crashes. The value of 5 | |
| 118 // matches the current bitmaps in our source. | |
| 119 if (waiting_to_loading_frame_count_ratio == 0) | |
| 120 waiting_to_loading_frame_count_ratio = 5; | |
| 121 } | |
| 122 | |
| 123 TabRendererGtk::LoadingAnimation::Data::Data( | |
| 124 int loading, int waiting, int waiting_to_loading) | |
| 125 : waiting_animation_frames(NULL), | |
| 126 loading_animation_frames(NULL), | |
| 127 loading_animation_frame_count(loading), | |
| 128 waiting_animation_frame_count(waiting), | |
| 129 waiting_to_loading_frame_count_ratio(waiting_to_loading) { | |
| 130 } | |
| 131 | |
| 132 bool TabRendererGtk::initialized_ = false; | |
| 133 TabRendererGtk::TabImage TabRendererGtk::tab_active_ = {0}; | |
| 134 TabRendererGtk::TabImage TabRendererGtk::tab_inactive_ = {0}; | |
| 135 TabRendererGtk::TabImage TabRendererGtk::tab_alpha_ = {0}; | |
| 136 gfx::Font* TabRendererGtk::title_font_ = NULL; | |
| 137 int TabRendererGtk::title_font_height_ = 0; | |
| 138 int TabRendererGtk::close_button_width_ = 0; | |
| 139 int TabRendererGtk::close_button_height_ = 0; | |
| 140 SkColor TabRendererGtk::selected_title_color_ = SK_ColorBLACK; | |
| 141 SkColor TabRendererGtk::unselected_title_color_ = SkColorSetRGB(64, 64, 64); | |
| 142 | |
| 143 //////////////////////////////////////////////////////////////////////////////// | |
| 144 // TabRendererGtk::LoadingAnimation, public: | |
| 145 // | |
| 146 TabRendererGtk::LoadingAnimation::LoadingAnimation( | |
| 147 ThemeProvider* theme_provider) | |
| 148 : data_(new Data(theme_provider)), | |
| 149 theme_provider_(theme_provider), | |
| 150 animation_state_(ANIMATION_NONE), | |
| 151 animation_frame_(0) { | |
| 152 registrar_.Add(this, | |
| 153 NotificationType::BROWSER_THEME_CHANGED, | |
| 154 NotificationService::AllSources()); | |
| 155 } | |
| 156 | |
| 157 TabRendererGtk::LoadingAnimation::LoadingAnimation( | |
| 158 const LoadingAnimation::Data& data) | |
| 159 : data_(new Data(data)), | |
| 160 theme_provider_(NULL), | |
| 161 animation_state_(ANIMATION_NONE), | |
| 162 animation_frame_(0) { | |
| 163 } | |
| 164 | |
| 165 TabRendererGtk::LoadingAnimation::~LoadingAnimation() {} | |
| 166 | |
| 167 bool TabRendererGtk::LoadingAnimation::ValidateLoadingAnimation( | |
| 168 AnimationState animation_state) { | |
| 169 bool has_changed = false; | |
| 170 if (animation_state_ != animation_state) { | |
| 171 // The waiting animation is the reverse of the loading animation, but at a | |
| 172 // different rate - the following reverses and scales the animation_frame_ | |
| 173 // so that the frame is at an equivalent position when going from one | |
| 174 // animation to the other. | |
| 175 if (animation_state_ == ANIMATION_WAITING && | |
| 176 animation_state == ANIMATION_LOADING) { | |
| 177 animation_frame_ = data_->loading_animation_frame_count - | |
| 178 (animation_frame_ / data_->waiting_to_loading_frame_count_ratio); | |
| 179 } | |
| 180 animation_state_ = animation_state; | |
| 181 has_changed = true; | |
| 182 } | |
| 183 | |
| 184 if (animation_state_ != ANIMATION_NONE) { | |
| 185 animation_frame_ = (animation_frame_ + 1) % | |
| 186 ((animation_state_ == ANIMATION_WAITING) ? | |
| 187 data_->waiting_animation_frame_count : | |
| 188 data_->loading_animation_frame_count); | |
| 189 has_changed = true; | |
| 190 } else { | |
| 191 animation_frame_ = 0; | |
| 192 } | |
| 193 return has_changed; | |
| 194 } | |
| 195 | |
| 196 void TabRendererGtk::LoadingAnimation::Observe( | |
| 197 NotificationType type, | |
| 198 const NotificationSource& source, | |
| 199 const NotificationDetails& details) { | |
| 200 DCHECK(type == NotificationType::BROWSER_THEME_CHANGED); | |
| 201 data_.reset(new Data(theme_provider_)); | |
| 202 } | |
| 203 | |
| 204 //////////////////////////////////////////////////////////////////////////////// | |
| 205 // FaviconCrashAnimation | |
| 206 // | |
| 207 // A custom animation subclass to manage the favicon crash animation. | |
| 208 class TabRendererGtk::FavIconCrashAnimation : public ui::LinearAnimation, | |
| 209 public ui::AnimationDelegate { | |
| 210 public: | |
| 211 explicit FavIconCrashAnimation(TabRendererGtk* target) | |
| 212 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::LinearAnimation(1000, 25, this)), | |
| 213 target_(target) { | |
| 214 } | |
| 215 virtual ~FavIconCrashAnimation() {} | |
| 216 | |
| 217 // ui::Animation overrides: | |
| 218 virtual void AnimateToState(double state) { | |
| 219 const double kHidingOffset = 27; | |
| 220 | |
| 221 if (state < .5) { | |
| 222 target_->SetFavIconHidingOffset( | |
| 223 static_cast<int>(floor(kHidingOffset * 2.0 * state))); | |
| 224 } else { | |
| 225 target_->DisplayCrashedFavIcon(); | |
| 226 target_->SetFavIconHidingOffset( | |
| 227 static_cast<int>( | |
| 228 floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset)))); | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 // ui::AnimationDelegate overrides: | |
| 233 virtual void AnimationCanceled(const ui::Animation* animation) { | |
| 234 target_->SetFavIconHidingOffset(0); | |
| 235 } | |
| 236 | |
| 237 private: | |
| 238 TabRendererGtk* target_; | |
| 239 | |
| 240 DISALLOW_COPY_AND_ASSIGN(FavIconCrashAnimation); | |
| 241 }; | |
| 242 | |
| 243 //////////////////////////////////////////////////////////////////////////////// | |
| 244 // TabRendererGtk, public: | |
| 245 | |
| 246 TabRendererGtk::TabRendererGtk(ThemeProvider* theme_provider) | |
| 247 : showing_icon_(false), | |
| 248 showing_close_button_(false), | |
| 249 fav_icon_hiding_offset_(0), | |
| 250 should_display_crashed_favicon_(false), | |
| 251 loading_animation_(theme_provider), | |
| 252 background_offset_x_(0), | |
| 253 background_offset_y_(kInactiveTabBackgroundOffsetY), | |
| 254 close_button_color_(0) { | |
| 255 InitResources(); | |
| 256 | |
| 257 tab_.Own(gtk_fixed_new()); | |
| 258 gtk_widget_set_app_paintable(tab_.get(), TRUE); | |
| 259 g_signal_connect(tab_.get(), "expose-event", | |
| 260 G_CALLBACK(OnExposeEventThunk), this); | |
| 261 g_signal_connect(tab_.get(), "size-allocate", | |
| 262 G_CALLBACK(OnSizeAllocateThunk), this); | |
| 263 close_button_.reset(MakeCloseButton()); | |
| 264 gtk_widget_show(tab_.get()); | |
| 265 | |
| 266 hover_animation_.reset(new ui::SlideAnimation(this)); | |
| 267 hover_animation_->SetSlideDuration(kHoverDurationMs); | |
| 268 | |
| 269 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, | |
| 270 NotificationService::AllSources()); | |
| 271 } | |
| 272 | |
| 273 TabRendererGtk::~TabRendererGtk() { | |
| 274 tab_.Destroy(); | |
| 275 for (BitmapCache::iterator it = cached_bitmaps_.begin(); | |
| 276 it != cached_bitmaps_.end(); ++it) { | |
| 277 delete it->second.bitmap; | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 void TabRendererGtk::UpdateData(TabContents* contents, | |
| 282 bool app, | |
| 283 bool loading_only) { | |
| 284 DCHECK(contents); | |
| 285 theme_provider_ = GtkThemeProvider::GetFrom(contents->profile()); | |
| 286 | |
| 287 if (!loading_only) { | |
| 288 data_.title = contents->GetTitle(); | |
| 289 data_.off_the_record = contents->profile()->IsOffTheRecord(); | |
| 290 data_.crashed = contents->is_crashed(); | |
| 291 | |
| 292 SkBitmap* app_icon = contents->GetExtensionAppIcon(); | |
| 293 if (app_icon) | |
| 294 data_.favicon = *app_icon; | |
| 295 else | |
| 296 data_.favicon = contents->GetFavIcon(); | |
| 297 | |
| 298 data_.app = app; | |
| 299 // This is kind of a hacky way to determine whether our icon is the default | |
| 300 // favicon. But the plumbing that would be necessary to do it right would | |
| 301 // be a good bit of work and would sully code for other platforms which | |
| 302 // don't care to custom-theme the favicon. Hopefully the default favicon | |
| 303 // will eventually be chromium-themable and this code will go away. | |
| 304 data_.is_default_favicon = | |
| 305 (data_.favicon.pixelRef() == | |
| 306 ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
| 307 IDR_DEFAULT_FAVICON)->pixelRef()); | |
| 308 } | |
| 309 | |
| 310 // Loading state also involves whether we show the favicon, since that's where | |
| 311 // we display the throbber. | |
| 312 data_.loading = contents->is_loading(); | |
| 313 data_.show_icon = contents->ShouldDisplayFavIcon(); | |
| 314 } | |
| 315 | |
| 316 void TabRendererGtk::UpdateFromModel() { | |
| 317 // Force a layout, since the tab may have grown a favicon. | |
| 318 Layout(); | |
| 319 SchedulePaint(); | |
| 320 | |
| 321 if (data_.crashed) { | |
| 322 if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation()) | |
| 323 StartCrashAnimation(); | |
| 324 } else { | |
| 325 if (IsPerformingCrashAnimation()) | |
| 326 StopCrashAnimation(); | |
| 327 ResetCrashedFavIcon(); | |
| 328 } | |
| 329 } | |
| 330 | |
| 331 void TabRendererGtk::SetBlocked(bool blocked) { | |
| 332 if (data_.blocked == blocked) | |
| 333 return; | |
| 334 data_.blocked = blocked; | |
| 335 // TODO(zelidrag) bug 32399: Make tabs pulse on Linux as well. | |
| 336 } | |
| 337 | |
| 338 bool TabRendererGtk::is_blocked() const { | |
| 339 return data_.blocked; | |
| 340 } | |
| 341 | |
| 342 bool TabRendererGtk::IsSelected() const { | |
| 343 return true; | |
| 344 } | |
| 345 | |
| 346 bool TabRendererGtk::IsVisible() const { | |
| 347 return GTK_WIDGET_FLAGS(tab_.get()) & GTK_VISIBLE; | |
| 348 } | |
| 349 | |
| 350 void TabRendererGtk::SetVisible(bool visible) const { | |
| 351 if (visible) { | |
| 352 gtk_widget_show(tab_.get()); | |
| 353 if (data_.mini) | |
| 354 gtk_widget_show(close_button_->widget()); | |
| 355 } else { | |
| 356 gtk_widget_hide_all(tab_.get()); | |
| 357 } | |
| 358 } | |
| 359 | |
| 360 bool TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) { | |
| 361 return loading_animation_.ValidateLoadingAnimation(animation_state); | |
| 362 } | |
| 363 | |
| 364 void TabRendererGtk::PaintFavIconArea(GdkEventExpose* event) { | |
| 365 DCHECK(ShouldShowIcon()); | |
| 366 | |
| 367 // The paint area is the favicon bounds, but we're painting into the gdk | |
| 368 // window belonging to the tabstrip. So the coordinates are relative to the | |
| 369 // top left of the tab strip. | |
| 370 event->area.x = x() + favicon_bounds_.x(); | |
| 371 event->area.y = y() + favicon_bounds_.y(); | |
| 372 event->area.width = favicon_bounds_.width(); | |
| 373 event->area.height = favicon_bounds_.height(); | |
| 374 gfx::CanvasSkiaPaint canvas(event, false); | |
| 375 | |
| 376 // The actual paint methods expect 0, 0 to be the tab top left (see | |
| 377 // PaintTab). | |
| 378 canvas.TranslateInt(x(), y()); | |
| 379 | |
| 380 // Paint the background behind the favicon. | |
| 381 int theme_id; | |
| 382 int offset_y = 0; | |
| 383 if (IsSelected()) { | |
| 384 theme_id = IDR_THEME_TOOLBAR; | |
| 385 } else { | |
| 386 if (!data_.off_the_record) { | |
| 387 theme_id = IDR_THEME_TAB_BACKGROUND; | |
| 388 } else { | |
| 389 theme_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO; | |
| 390 } | |
| 391 if (!theme_provider_->HasCustomImage(theme_id)) | |
| 392 offset_y = background_offset_y_; | |
| 393 } | |
| 394 SkBitmap* tab_bg = theme_provider_->GetBitmapNamed(theme_id); | |
| 395 canvas.TileImageInt(*tab_bg, | |
| 396 x() + favicon_bounds_.x(), offset_y + favicon_bounds_.y(), | |
| 397 favicon_bounds_.x(), favicon_bounds_.y(), | |
| 398 favicon_bounds_.width(), favicon_bounds_.height()); | |
| 399 | |
| 400 if (!IsSelected()) { | |
| 401 double throb_value = GetThrobValue(); | |
| 402 if (throb_value > 0) { | |
| 403 SkRect bounds; | |
| 404 bounds.set(favicon_bounds_.x(), favicon_bounds_.y(), | |
| 405 favicon_bounds_.right(), favicon_bounds_.bottom()); | |
| 406 canvas.saveLayerAlpha(&bounds, static_cast<int>(throb_value * 0xff), | |
| 407 SkCanvas::kARGB_ClipLayer_SaveFlag); | |
| 408 canvas.drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode); | |
| 409 SkBitmap* active_bg = theme_provider_->GetBitmapNamed(IDR_THEME_TOOLBAR); | |
| 410 canvas.TileImageInt(*active_bg, | |
| 411 x() + favicon_bounds_.x(), favicon_bounds_.y(), | |
| 412 favicon_bounds_.x(), favicon_bounds_.y(), | |
| 413 favicon_bounds_.width(), favicon_bounds_.height()); | |
| 414 canvas.restore(); | |
| 415 } | |
| 416 } | |
| 417 | |
| 418 // Now paint the icon. | |
| 419 PaintIcon(&canvas); | |
| 420 } | |
| 421 | |
| 422 bool TabRendererGtk::ShouldShowIcon() const { | |
| 423 if (mini() && height() >= GetMinimumUnselectedSize().height()) { | |
| 424 return true; | |
| 425 } else if (!data_.show_icon) { | |
| 426 return false; | |
| 427 } else if (IsSelected()) { | |
| 428 // The selected tab clips favicon before close button. | |
| 429 return IconCapacity() >= 2; | |
| 430 } | |
| 431 // Non-selected tabs clip close button before favicon. | |
| 432 return IconCapacity() >= 1; | |
| 433 } | |
| 434 | |
| 435 // static | |
| 436 gfx::Size TabRendererGtk::GetMinimumUnselectedSize() { | |
| 437 InitResources(); | |
| 438 | |
| 439 gfx::Size minimum_size; | |
| 440 minimum_size.set_width(kLeftPadding + kRightPadding); | |
| 441 // Since we use bitmap images, the real minimum height of the image is | |
| 442 // defined most accurately by the height of the end cap images. | |
| 443 minimum_size.set_height(tab_active_.image_l->height() - kToolbarOverlap); | |
| 444 return minimum_size; | |
| 445 } | |
| 446 | |
| 447 // static | |
| 448 gfx::Size TabRendererGtk::GetMinimumSelectedSize() { | |
| 449 gfx::Size minimum_size = GetMinimumUnselectedSize(); | |
| 450 minimum_size.set_width(kLeftPadding + kFavIconSize + kRightPadding); | |
| 451 return minimum_size; | |
| 452 } | |
| 453 | |
| 454 // static | |
| 455 gfx::Size TabRendererGtk::GetStandardSize() { | |
| 456 gfx::Size standard_size = GetMinimumUnselectedSize(); | |
| 457 standard_size.Enlarge(kFavIconTitleSpacing + kStandardTitleWidth, 0); | |
| 458 return standard_size; | |
| 459 } | |
| 460 | |
| 461 // static | |
| 462 int TabRendererGtk::GetMiniWidth() { | |
| 463 return browser_defaults::kMiniTabWidth; | |
| 464 } | |
| 465 | |
| 466 // static | |
| 467 int TabRendererGtk::GetContentHeight() { | |
| 468 // The height of the content of the Tab is the largest of the favicon, | |
| 469 // the title text and the close button graphic. | |
| 470 int content_height = std::max(kFavIconSize, title_font_height_); | |
| 471 return std::max(content_height, close_button_height_); | |
| 472 } | |
| 473 | |
| 474 // static | |
| 475 void TabRendererGtk::LoadTabImages() { | |
| 476 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
| 477 | |
| 478 tab_alpha_.image_l = rb.GetBitmapNamed(IDR_TAB_ALPHA_LEFT); | |
| 479 tab_alpha_.image_r = rb.GetBitmapNamed(IDR_TAB_ALPHA_RIGHT); | |
| 480 | |
| 481 tab_active_.image_l = rb.GetBitmapNamed(IDR_TAB_ACTIVE_LEFT); | |
| 482 tab_active_.image_c = rb.GetBitmapNamed(IDR_TAB_ACTIVE_CENTER); | |
| 483 tab_active_.image_r = rb.GetBitmapNamed(IDR_TAB_ACTIVE_RIGHT); | |
| 484 tab_active_.l_width = tab_active_.image_l->width(); | |
| 485 tab_active_.r_width = tab_active_.image_r->width(); | |
| 486 | |
| 487 tab_inactive_.image_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT); | |
| 488 tab_inactive_.image_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER); | |
| 489 tab_inactive_.image_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT); | |
| 490 tab_inactive_.l_width = tab_inactive_.image_l->width(); | |
| 491 tab_inactive_.r_width = tab_inactive_.image_r->width(); | |
| 492 | |
| 493 close_button_width_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->width(); | |
| 494 close_button_height_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->height(); | |
| 495 } | |
| 496 | |
| 497 // static | |
| 498 void TabRendererGtk::SetSelectedTitleColor(SkColor color) { | |
| 499 selected_title_color_ = color; | |
| 500 } | |
| 501 | |
| 502 // static | |
| 503 void TabRendererGtk::SetUnselectedTitleColor(SkColor color) { | |
| 504 unselected_title_color_ = color; | |
| 505 } | |
| 506 | |
| 507 gfx::Rect TabRendererGtk::GetNonMirroredBounds(GtkWidget* parent) const { | |
| 508 // The tabstrip widget is a windowless widget so the tab widget's allocation | |
| 509 // is relative to the browser titlebar. We need the bounds relative to the | |
| 510 // tabstrip. | |
| 511 gfx::Rect bounds = GetWidgetBoundsRelativeToParent(parent, widget()); | |
| 512 bounds.set_x(gtk_util::MirroredLeftPointForRect(parent, bounds)); | |
| 513 return bounds; | |
| 514 } | |
| 515 | |
| 516 gfx::Rect TabRendererGtk::GetRequisition() const { | |
| 517 return gfx::Rect(requisition_.x(), requisition_.y(), | |
| 518 requisition_.width(), requisition_.height()); | |
| 519 } | |
| 520 | |
| 521 void TabRendererGtk::StartMiniTabTitleAnimation() { | |
| 522 if (!mini_title_animation_.get()) { | |
| 523 mini_title_animation_.reset(new ui::ThrobAnimation(this)); | |
| 524 mini_title_animation_->SetThrobDuration(kMiniTitleChangeThrobDuration); | |
| 525 } | |
| 526 | |
| 527 if (!mini_title_animation_->is_animating()) { | |
| 528 mini_title_animation_->StartThrobbing(2); | |
| 529 } else if (mini_title_animation_->cycles_remaining() <= 2) { | |
| 530 // The title changed while we're already animating. Add at most one more | |
| 531 // cycle. This is done in an attempt to smooth out pages that continuously | |
| 532 // change the title. | |
| 533 mini_title_animation_->set_cycles_remaining( | |
| 534 mini_title_animation_->cycles_remaining() + 2); | |
| 535 } | |
| 536 } | |
| 537 | |
| 538 void TabRendererGtk::StopMiniTabTitleAnimation() { | |
| 539 if (mini_title_animation_.get()) | |
| 540 mini_title_animation_->Stop(); | |
| 541 } | |
| 542 | |
| 543 void TabRendererGtk::SetBounds(const gfx::Rect& bounds) { | |
| 544 requisition_ = bounds; | |
| 545 gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height()); | |
| 546 } | |
| 547 | |
| 548 void TabRendererGtk::Observe(NotificationType type, | |
| 549 const NotificationSource& source, | |
| 550 const NotificationDetails& details) { | |
| 551 DCHECK(type == NotificationType::BROWSER_THEME_CHANGED); | |
| 552 | |
| 553 // Clear our cache when we receive a theme change notification because it | |
| 554 // contains cached bitmaps based off the previous theme. | |
| 555 for (BitmapCache::iterator it = cached_bitmaps_.begin(); | |
| 556 it != cached_bitmaps_.end(); ++it) { | |
| 557 delete it->second.bitmap; | |
| 558 } | |
| 559 cached_bitmaps_.clear(); | |
| 560 } | |
| 561 | |
| 562 //////////////////////////////////////////////////////////////////////////////// | |
| 563 // TabRendererGtk, protected: | |
| 564 | |
| 565 string16 TabRendererGtk::GetTitle() const { | |
| 566 return data_.title; | |
| 567 } | |
| 568 | |
| 569 /////////////////////////////////////////////////////////////////////////////// | |
| 570 // TabRendererGtk, ui::AnimationDelegate implementation: | |
| 571 | |
| 572 void TabRendererGtk::AnimationProgressed(const ui::Animation* animation) { | |
| 573 gtk_widget_queue_draw(tab_.get()); | |
| 574 } | |
| 575 | |
| 576 void TabRendererGtk::AnimationCanceled(const ui::Animation* animation) { | |
| 577 AnimationEnded(animation); | |
| 578 } | |
| 579 | |
| 580 void TabRendererGtk::AnimationEnded(const ui::Animation* animation) { | |
| 581 gtk_widget_queue_draw(tab_.get()); | |
| 582 } | |
| 583 | |
| 584 //////////////////////////////////////////////////////////////////////////////// | |
| 585 // TabRendererGtk, private: | |
| 586 | |
| 587 void TabRendererGtk::StartCrashAnimation() { | |
| 588 if (!crash_animation_.get()) | |
| 589 crash_animation_.reset(new FavIconCrashAnimation(this)); | |
| 590 crash_animation_->Stop(); | |
| 591 crash_animation_->Start(); | |
| 592 } | |
| 593 | |
| 594 void TabRendererGtk::StopCrashAnimation() { | |
| 595 if (!crash_animation_.get()) | |
| 596 return; | |
| 597 crash_animation_->Stop(); | |
| 598 } | |
| 599 | |
| 600 bool TabRendererGtk::IsPerformingCrashAnimation() const { | |
| 601 return crash_animation_.get() && crash_animation_->is_animating(); | |
| 602 } | |
| 603 | |
| 604 void TabRendererGtk::SetFavIconHidingOffset(int offset) { | |
| 605 fav_icon_hiding_offset_ = offset; | |
| 606 SchedulePaint(); | |
| 607 } | |
| 608 | |
| 609 void TabRendererGtk::DisplayCrashedFavIcon() { | |
| 610 should_display_crashed_favicon_ = true; | |
| 611 } | |
| 612 | |
| 613 void TabRendererGtk::ResetCrashedFavIcon() { | |
| 614 should_display_crashed_favicon_ = false; | |
| 615 } | |
| 616 | |
| 617 void TabRendererGtk::Paint(gfx::Canvas* canvas) { | |
| 618 // Don't paint if we're narrower than we can render correctly. (This should | |
| 619 // only happen during animations). | |
| 620 if (width() < GetMinimumUnselectedSize().width() && !mini()) | |
| 621 return; | |
| 622 | |
| 623 // See if the model changes whether the icons should be painted. | |
| 624 const bool show_icon = ShouldShowIcon(); | |
| 625 const bool show_close_button = ShouldShowCloseBox(); | |
| 626 if (show_icon != showing_icon_ || | |
| 627 show_close_button != showing_close_button_) | |
| 628 Layout(); | |
| 629 | |
| 630 PaintTabBackground(canvas); | |
| 631 | |
| 632 if (!mini() || width() > kMiniTabRendererAsNormalTabWidth) | |
| 633 PaintTitle(canvas); | |
| 634 | |
| 635 if (show_icon) | |
| 636 PaintIcon(canvas); | |
| 637 } | |
| 638 | |
| 639 SkBitmap TabRendererGtk::PaintBitmap() { | |
| 640 gfx::CanvasSkia canvas(width(), height(), false); | |
| 641 Paint(&canvas); | |
| 642 return canvas.ExtractBitmap(); | |
| 643 } | |
| 644 | |
| 645 cairo_surface_t* TabRendererGtk::PaintToSurface() { | |
| 646 gfx::CanvasSkia canvas(width(), height(), false); | |
| 647 Paint(&canvas); | |
| 648 return cairo_surface_reference(cairo_get_target(canvas.beginPlatformPaint())); | |
| 649 } | |
| 650 | |
| 651 void TabRendererGtk::SchedulePaint() { | |
| 652 gtk_widget_queue_draw(tab_.get()); | |
| 653 } | |
| 654 | |
| 655 gfx::Rect TabRendererGtk::GetLocalBounds() { | |
| 656 return gfx::Rect(0, 0, bounds_.width(), bounds_.height()); | |
| 657 } | |
| 658 | |
| 659 void TabRendererGtk::Layout() { | |
| 660 gfx::Rect local_bounds = GetLocalBounds(); | |
| 661 if (local_bounds.IsEmpty()) | |
| 662 return; | |
| 663 local_bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding); | |
| 664 | |
| 665 // Figure out who is tallest. | |
| 666 int content_height = GetContentHeight(); | |
| 667 | |
| 668 // Size the Favicon. | |
| 669 showing_icon_ = ShouldShowIcon(); | |
| 670 if (showing_icon_) { | |
| 671 int favicon_top = kTopPadding + (content_height - kFavIconSize) / 2; | |
| 672 favicon_bounds_.SetRect(local_bounds.x(), favicon_top, | |
| 673 kFavIconSize, kFavIconSize); | |
| 674 if ((mini() || data_.animating_mini_change) && | |
| 675 bounds_.width() < kMiniTabRendererAsNormalTabWidth) { | |
| 676 int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth(); | |
| 677 int ideal_delta = bounds_.width() - GetMiniWidth(); | |
| 678 if (ideal_delta < mini_delta) { | |
| 679 int ideal_x = (GetMiniWidth() - kFavIconSize) / 2; | |
| 680 int x = favicon_bounds_.x() + static_cast<int>( | |
| 681 (1 - static_cast<float>(ideal_delta) / | |
| 682 static_cast<float>(mini_delta)) * | |
| 683 (ideal_x - favicon_bounds_.x())); | |
| 684 favicon_bounds_.set_x(x); | |
| 685 } | |
| 686 } | |
| 687 } else { | |
| 688 favicon_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0); | |
| 689 } | |
| 690 | |
| 691 // Size the Close button. | |
| 692 showing_close_button_ = ShouldShowCloseBox(); | |
| 693 if (showing_close_button_) { | |
| 694 int close_button_top = | |
| 695 kTopPadding + kCloseButtonVertFuzz + | |
| 696 (content_height - close_button_height_) / 2; | |
| 697 close_button_bounds_.SetRect(local_bounds.width() + kCloseButtonHorzFuzz, | |
| 698 close_button_top, close_button_width_, | |
| 699 close_button_height_); | |
| 700 | |
| 701 // If the close button color has changed, generate a new one. | |
| 702 if (theme_provider_) { | |
| 703 SkColor tab_text_color = | |
| 704 theme_provider_->GetColor(BrowserThemeProvider::COLOR_TAB_TEXT); | |
| 705 if (!close_button_color_ || tab_text_color != close_button_color_) { | |
| 706 close_button_color_ = tab_text_color; | |
| 707 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
| 708 close_button_->SetBackground(close_button_color_, | |
| 709 rb.GetBitmapNamed(IDR_TAB_CLOSE), | |
| 710 rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK)); | |
| 711 } | |
| 712 } | |
| 713 } else { | |
| 714 close_button_bounds_.SetRect(0, 0, 0, 0); | |
| 715 } | |
| 716 | |
| 717 if (!mini() || width() >= kMiniTabRendererAsNormalTabWidth) { | |
| 718 // Size the Title text to fill the remaining space. | |
| 719 int title_left = favicon_bounds_.right() + kFavIconTitleSpacing; | |
| 720 int title_top = kTopPadding; | |
| 721 | |
| 722 // If the user has big fonts, the title will appear rendered too far down | |
| 723 // on the y-axis if we use the regular top padding, so we need to adjust it | |
| 724 // so that the text appears centered. | |
| 725 gfx::Size minimum_size = GetMinimumUnselectedSize(); | |
| 726 int text_height = title_top + title_font_height_ + kBottomPadding; | |
| 727 if (text_height > minimum_size.height()) | |
| 728 title_top -= (text_height - minimum_size.height()) / 2; | |
| 729 | |
| 730 int title_width; | |
| 731 if (close_button_bounds_.width() && close_button_bounds_.height()) { | |
| 732 title_width = std::max(close_button_bounds_.x() - | |
| 733 kTitleCloseButtonSpacing - title_left, 0); | |
| 734 } else { | |
| 735 title_width = std::max(local_bounds.width() - title_left, 0); | |
| 736 } | |
| 737 title_bounds_.SetRect(title_left, title_top, title_width, content_height); | |
| 738 } | |
| 739 | |
| 740 favicon_bounds_.set_x( | |
| 741 gtk_util::MirroredLeftPointForRect(tab_.get(), favicon_bounds_)); | |
| 742 close_button_bounds_.set_x( | |
| 743 gtk_util::MirroredLeftPointForRect(tab_.get(), close_button_bounds_)); | |
| 744 title_bounds_.set_x( | |
| 745 gtk_util::MirroredLeftPointForRect(tab_.get(), title_bounds_)); | |
| 746 | |
| 747 MoveCloseButtonWidget(); | |
| 748 } | |
| 749 | |
| 750 void TabRendererGtk::MoveCloseButtonWidget() { | |
| 751 if (!close_button_bounds_.IsEmpty()) { | |
| 752 gtk_fixed_move(GTK_FIXED(tab_.get()), close_button_->widget(), | |
| 753 close_button_bounds_.x(), close_button_bounds_.y()); | |
| 754 gtk_widget_show(close_button_->widget()); | |
| 755 } else { | |
| 756 gtk_widget_hide(close_button_->widget()); | |
| 757 } | |
| 758 } | |
| 759 | |
| 760 SkBitmap* TabRendererGtk::GetMaskedBitmap(const SkBitmap* mask, | |
| 761 const SkBitmap* background, int bg_offset_x, int bg_offset_y) { | |
| 762 // We store a bitmap for each mask + background pair (4 total bitmaps). We | |
| 763 // replace the cached image if the tab has moved relative to the background. | |
| 764 BitmapCache::iterator it = cached_bitmaps_.find(std::make_pair(mask, | |
| 765 background)); | |
| 766 if (it != cached_bitmaps_.end()) { | |
| 767 if (it->second.bg_offset_x == bg_offset_x && | |
| 768 it->second.bg_offset_y == bg_offset_y) { | |
| 769 return it->second.bitmap; | |
| 770 } | |
| 771 // The background offset changed so we should re-render with the new | |
| 772 // offsets. | |
| 773 delete it->second.bitmap; | |
| 774 } | |
| 775 SkBitmap image = SkBitmapOperations::CreateTiledBitmap( | |
| 776 *background, bg_offset_x, bg_offset_y, mask->width(), | |
| 777 height() + kToolbarOverlap); | |
| 778 CachedBitmap bitmap = { | |
| 779 bg_offset_x, | |
| 780 bg_offset_y, | |
| 781 new SkBitmap(SkBitmapOperations::CreateMaskedBitmap(image, *mask)) | |
| 782 }; | |
| 783 cached_bitmaps_[std::make_pair(mask, background)] = bitmap; | |
| 784 return bitmap.bitmap; | |
| 785 } | |
| 786 | |
| 787 void TabRendererGtk::PaintTab(GdkEventExpose* event) { | |
| 788 gfx::CanvasSkiaPaint canvas(event, false); | |
| 789 if (canvas.is_empty()) | |
| 790 return; | |
| 791 | |
| 792 // The tab is rendered into a windowless widget whose offset is at the | |
| 793 // coordinate event->area. Translate by these offsets so we can render at | |
| 794 // (0,0) to match Windows' rendering metrics. | |
| 795 canvas.TranslateInt(event->area.x, event->area.y); | |
| 796 | |
| 797 // Save the original x offset so we can position background images properly. | |
| 798 background_offset_x_ = event->area.x; | |
| 799 | |
| 800 Paint(&canvas); | |
| 801 } | |
| 802 | |
| 803 void TabRendererGtk::PaintTitle(gfx::Canvas* canvas) { | |
| 804 // Paint the Title. | |
| 805 string16 title = data_.title; | |
| 806 if (title.empty()) { | |
| 807 title = data_.loading ? | |
| 808 l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) : | |
| 809 TabContents::GetDefaultTitle(); | |
| 810 } else { | |
| 811 Browser::FormatTitleForDisplay(&title); | |
| 812 } | |
| 813 | |
| 814 SkColor title_color = IsSelected() ? selected_title_color_ | |
| 815 : unselected_title_color_; | |
| 816 canvas->DrawStringInt(title, *title_font_, title_color, | |
| 817 title_bounds_.x(), title_bounds_.y(), | |
| 818 title_bounds_.width(), title_bounds_.height()); | |
| 819 } | |
| 820 | |
| 821 void TabRendererGtk::PaintIcon(gfx::Canvas* canvas) { | |
| 822 if (loading_animation_.animation_state() != ANIMATION_NONE) { | |
| 823 PaintLoadingAnimation(canvas); | |
| 824 } else { | |
| 825 canvas->Save(); | |
| 826 canvas->ClipRectInt(0, 0, width(), height() - kFavIconTitleSpacing); | |
| 827 if (should_display_crashed_favicon_) { | |
| 828 canvas->DrawBitmapInt(*crashed_fav_icon, 0, 0, | |
| 829 crashed_fav_icon->width(), | |
| 830 crashed_fav_icon->height(), | |
| 831 favicon_bounds_.x(), | |
| 832 favicon_bounds_.y() + fav_icon_hiding_offset_, | |
| 833 kFavIconSize, kFavIconSize, | |
| 834 true); | |
| 835 } else { | |
| 836 if (!data_.favicon.isNull()) { | |
| 837 if (data_.is_default_favicon && theme_provider_->UseGtkTheme()) { | |
| 838 GdkPixbuf* favicon = GtkThemeProvider::GetDefaultFavicon(true); | |
| 839 canvas->AsCanvasSkia()->DrawGdkPixbuf( | |
| 840 favicon, favicon_bounds_.x(), | |
| 841 favicon_bounds_.y() + fav_icon_hiding_offset_); | |
| 842 } else { | |
| 843 // If the favicon is an app icon, it is allowed to be drawn slightly | |
| 844 // larger than the standard favicon. | |
| 845 int favIconHeightOffset = data_.app ? -2 : 0; | |
| 846 int favIconWidthDelta = data_.app ? | |
| 847 data_.favicon.width() - kFavIconSize : 0; | |
| 848 int favIconHeightDelta = data_.app ? | |
| 849 data_.favicon.height() - kFavIconSize : 0; | |
| 850 | |
| 851 // TODO(pkasting): Use code in tab_icon_view.cc:PaintIcon() (or switch | |
| 852 // to using that class to render the favicon). | |
| 853 canvas->DrawBitmapInt(data_.favicon, 0, 0, | |
| 854 data_.favicon.width(), | |
| 855 data_.favicon.height(), | |
| 856 favicon_bounds_.x() - favIconWidthDelta/2, | |
| 857 favicon_bounds_.y() + favIconHeightOffset | |
| 858 - favIconHeightDelta/2 | |
| 859 + fav_icon_hiding_offset_, | |
| 860 kFavIconSize + favIconWidthDelta, | |
| 861 kFavIconSize + favIconHeightDelta, | |
| 862 true); | |
| 863 } | |
| 864 } | |
| 865 } | |
| 866 canvas->Restore(); | |
| 867 } | |
| 868 } | |
| 869 | |
| 870 void TabRendererGtk::PaintTabBackground(gfx::Canvas* canvas) { | |
| 871 if (IsSelected()) { | |
| 872 PaintActiveTabBackground(canvas); | |
| 873 } else { | |
| 874 PaintInactiveTabBackground(canvas); | |
| 875 | |
| 876 double throb_value = GetThrobValue(); | |
| 877 if (throb_value > 0) { | |
| 878 canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff), | |
| 879 gfx::Rect(width(), height())); | |
| 880 canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255, | |
| 881 SkXfermode::kClear_Mode); | |
| 882 PaintActiveTabBackground(canvas); | |
| 883 canvas->Restore(); | |
| 884 } | |
| 885 } | |
| 886 } | |
| 887 | |
| 888 void TabRendererGtk::PaintInactiveTabBackground(gfx::Canvas* canvas) { | |
| 889 bool is_otr = data_.off_the_record; | |
| 890 | |
| 891 // The tab image needs to be lined up with the background image | |
| 892 // so that it feels partially transparent. | |
| 893 int offset_x = background_offset_x_; | |
| 894 | |
| 895 int tab_id = is_otr ? | |
| 896 IDR_THEME_TAB_BACKGROUND_INCOGNITO : IDR_THEME_TAB_BACKGROUND; | |
| 897 | |
| 898 SkBitmap* tab_bg = theme_provider_->GetBitmapNamed(tab_id); | |
| 899 | |
| 900 // If the theme is providing a custom background image, then its top edge | |
| 901 // should be at the top of the tab. Otherwise, we assume that the background | |
| 902 // image is a composited foreground + frame image. | |
| 903 int offset_y = theme_provider_->HasCustomImage(tab_id) ? | |
| 904 0 : background_offset_y_; | |
| 905 | |
| 906 // Draw left edge. | |
| 907 SkBitmap* theme_l = GetMaskedBitmap(tab_alpha_.image_l, tab_bg, offset_x, | |
| 908 offset_y); | |
| 909 canvas->DrawBitmapInt(*theme_l, 0, 0); | |
| 910 | |
| 911 // Draw right edge. | |
| 912 SkBitmap* theme_r = GetMaskedBitmap(tab_alpha_.image_r, tab_bg, | |
| 913 offset_x + width() - tab_active_.r_width, offset_y); | |
| 914 | |
| 915 canvas->DrawBitmapInt(*theme_r, width() - theme_r->width(), 0); | |
| 916 | |
| 917 // Draw center. | |
| 918 canvas->TileImageInt(*tab_bg, | |
| 919 offset_x + tab_active_.l_width, kDropShadowOffset + offset_y, | |
| 920 tab_active_.l_width, 2, | |
| 921 width() - tab_active_.l_width - tab_active_.r_width, height() - 2); | |
| 922 | |
| 923 canvas->DrawBitmapInt(*tab_inactive_.image_l, 0, 0); | |
| 924 canvas->TileImageInt(*tab_inactive_.image_c, tab_inactive_.l_width, 0, | |
| 925 width() - tab_inactive_.l_width - tab_inactive_.r_width, height()); | |
| 926 canvas->DrawBitmapInt(*tab_inactive_.image_r, | |
| 927 width() - tab_inactive_.r_width, 0); | |
| 928 } | |
| 929 | |
| 930 void TabRendererGtk::PaintActiveTabBackground(gfx::Canvas* canvas) { | |
| 931 int offset_x = background_offset_x_; | |
| 932 | |
| 933 SkBitmap* tab_bg = theme_provider_->GetBitmapNamed(IDR_THEME_TOOLBAR); | |
| 934 | |
| 935 // Draw left edge. | |
| 936 SkBitmap* theme_l = GetMaskedBitmap(tab_alpha_.image_l, tab_bg, offset_x, 0); | |
| 937 canvas->DrawBitmapInt(*theme_l, 0, 0); | |
| 938 | |
| 939 // Draw right edge. | |
| 940 SkBitmap* theme_r = GetMaskedBitmap(tab_alpha_.image_r, tab_bg, | |
| 941 offset_x + width() - tab_active_.r_width, 0); | |
| 942 canvas->DrawBitmapInt(*theme_r, width() - tab_active_.r_width, 0); | |
| 943 | |
| 944 // Draw center. | |
| 945 canvas->TileImageInt(*tab_bg, | |
| 946 offset_x + tab_active_.l_width, kDropShadowHeight, | |
| 947 tab_active_.l_width, kDropShadowHeight, | |
| 948 width() - tab_active_.l_width - tab_active_.r_width, | |
| 949 height() - kDropShadowHeight); | |
| 950 | |
| 951 canvas->DrawBitmapInt(*tab_active_.image_l, 0, 0); | |
| 952 canvas->TileImageInt(*tab_active_.image_c, tab_active_.l_width, 0, | |
| 953 width() - tab_active_.l_width - tab_active_.r_width, height()); | |
| 954 canvas->DrawBitmapInt(*tab_active_.image_r, width() - tab_active_.r_width, 0); | |
| 955 } | |
| 956 | |
| 957 void TabRendererGtk::PaintLoadingAnimation(gfx::Canvas* canvas) { | |
| 958 const SkBitmap* frames = | |
| 959 (loading_animation_.animation_state() == ANIMATION_WAITING) ? | |
| 960 loading_animation_.waiting_animation_frames() : | |
| 961 loading_animation_.loading_animation_frames(); | |
| 962 const int image_size = frames->height(); | |
| 963 const int image_offset = loading_animation_.animation_frame() * image_size; | |
| 964 DCHECK(image_size == favicon_bounds_.height()); | |
| 965 DCHECK(image_size == favicon_bounds_.width()); | |
| 966 | |
| 967 canvas->DrawBitmapInt(*frames, image_offset, 0, image_size, image_size, | |
| 968 favicon_bounds_.x(), favicon_bounds_.y(), image_size, image_size, | |
| 969 false); | |
| 970 } | |
| 971 | |
| 972 int TabRendererGtk::IconCapacity() const { | |
| 973 if (height() < GetMinimumUnselectedSize().height()) | |
| 974 return 0; | |
| 975 return (width() - kLeftPadding - kRightPadding) / kFavIconSize; | |
| 976 } | |
| 977 | |
| 978 bool TabRendererGtk::ShouldShowCloseBox() const { | |
| 979 // The selected tab never clips close button. | |
| 980 return !mini() && (IsSelected() || IconCapacity() >= 3); | |
| 981 } | |
| 982 | |
| 983 CustomDrawButton* TabRendererGtk::MakeCloseButton() { | |
| 984 CustomDrawButton* button = new CustomDrawButton(IDR_TAB_CLOSE, | |
| 985 IDR_TAB_CLOSE_P, IDR_TAB_CLOSE_H, IDR_TAB_CLOSE); | |
| 986 | |
| 987 gtk_widget_set_tooltip_text(button->widget(), | |
| 988 l10n_util::GetStringUTF8(IDS_TOOLTIP_CLOSE_TAB).c_str()); | |
| 989 | |
| 990 g_signal_connect(button->widget(), "clicked", | |
| 991 G_CALLBACK(OnCloseButtonClickedThunk), this); | |
| 992 g_signal_connect(button->widget(), "button-release-event", | |
| 993 G_CALLBACK(OnCloseButtonMouseReleaseThunk), this); | |
| 994 g_signal_connect(button->widget(), "enter-notify-event", | |
| 995 G_CALLBACK(OnEnterNotifyEventThunk), this); | |
| 996 g_signal_connect(button->widget(), "leave-notify-event", | |
| 997 G_CALLBACK(OnLeaveNotifyEventThunk), this); | |
| 998 GTK_WIDGET_UNSET_FLAGS(button->widget(), GTK_CAN_FOCUS); | |
| 999 gtk_fixed_put(GTK_FIXED(tab_.get()), button->widget(), 0, 0); | |
| 1000 | |
| 1001 return button; | |
| 1002 } | |
| 1003 | |
| 1004 double TabRendererGtk::GetThrobValue() { | |
| 1005 if (mini_title_animation_.get() && mini_title_animation_->is_animating()) { | |
| 1006 return mini_title_animation_->GetCurrentValue() * | |
| 1007 kMiniTitleChangeThrobOpacity; | |
| 1008 } | |
| 1009 return hover_animation_.get() ? | |
| 1010 kHoverOpacity * hover_animation_->GetCurrentValue() : 0; | |
| 1011 } | |
| 1012 | |
| 1013 void TabRendererGtk::CloseButtonClicked() { | |
| 1014 // Nothing to do. | |
| 1015 } | |
| 1016 | |
| 1017 void TabRendererGtk::OnCloseButtonClicked(GtkWidget* widget) { | |
| 1018 CloseButtonClicked(); | |
| 1019 } | |
| 1020 | |
| 1021 gboolean TabRendererGtk::OnCloseButtonMouseRelease(GtkWidget* widget, | |
| 1022 GdkEventButton* event) { | |
| 1023 if (event->button == 2) { | |
| 1024 CloseButtonClicked(); | |
| 1025 return TRUE; | |
| 1026 } | |
| 1027 | |
| 1028 return FALSE; | |
| 1029 } | |
| 1030 | |
| 1031 gboolean TabRendererGtk::OnExposeEvent(GtkWidget* widget, | |
| 1032 GdkEventExpose* event) { | |
| 1033 PaintTab(event); | |
| 1034 gtk_container_propagate_expose(GTK_CONTAINER(tab_.get()), | |
| 1035 close_button_->widget(), event); | |
| 1036 return TRUE; | |
| 1037 } | |
| 1038 | |
| 1039 void TabRendererGtk::OnSizeAllocate(GtkWidget* widget, | |
| 1040 GtkAllocation* allocation) { | |
| 1041 gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y, | |
| 1042 allocation->width, allocation->height); | |
| 1043 | |
| 1044 // Nothing to do if the bounds are the same. If we don't catch this, we'll | |
| 1045 // get an infinite loop of size-allocate signals. | |
| 1046 if (bounds_ == bounds) | |
| 1047 return; | |
| 1048 | |
| 1049 bounds_ = bounds; | |
| 1050 Layout(); | |
| 1051 } | |
| 1052 | |
| 1053 gboolean TabRendererGtk::OnEnterNotifyEvent(GtkWidget* widget, | |
| 1054 GdkEventCrossing* event) { | |
| 1055 hover_animation_->SetTweenType(ui::Tween::EASE_OUT); | |
| 1056 hover_animation_->Show(); | |
| 1057 return FALSE; | |
| 1058 } | |
| 1059 | |
| 1060 gboolean TabRendererGtk::OnLeaveNotifyEvent(GtkWidget* widget, | |
| 1061 GdkEventCrossing* event) { | |
| 1062 hover_animation_->SetTweenType(ui::Tween::EASE_IN); | |
| 1063 hover_animation_->Hide(); | |
| 1064 return FALSE; | |
| 1065 } | |
| 1066 | |
| 1067 // static | |
| 1068 void TabRendererGtk::InitResources() { | |
| 1069 if (initialized_) | |
| 1070 return; | |
| 1071 | |
| 1072 LoadTabImages(); | |
| 1073 | |
| 1074 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
| 1075 const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont); | |
| 1076 // Dividing by the pango scale factor maintains an absolute pixel size across | |
| 1077 // all DPIs. | |
| 1078 int size = static_cast<int>(13 / gfx::PlatformFontGtk::GetPangoScaleFactor()); | |
| 1079 title_font_ = new gfx::Font(base_font.GetFontName(), size); | |
| 1080 title_font_height_ = title_font_->GetHeight(); | |
| 1081 | |
| 1082 crashed_fav_icon = rb.GetBitmapNamed(IDR_SAD_FAVICON); | |
| 1083 | |
| 1084 initialized_ = true; | |
| 1085 } | |
| OLD | NEW |