18ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// Copyright (c) 2010 The Chromium Authors. All rights reserved.
28ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// Use of this source code is governed by a BSD-style license that can be
38ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// found in the LICENSE file.
48ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
58ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#include "chrome/common/extensions/extension_action.h"
68ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
78ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#include <algorithm>
88ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
98ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#include "base/logging.h"
10201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch#include "chrome/common/badge_util.h"
118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#include "googleurl/src/gurl.h"
128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#include "grit/app_resources.h"
138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#include "third_party/skia/include/core/SkBitmap.h"
148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#include "third_party/skia/include/effects/SkGradientShader.h"
1572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/base/resource/resource_bundle.h"
1672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/gfx/canvas_skia.h"
1772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/gfx/rect.h"
188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsennamespace {
208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// Different platforms need slightly different constants to look good.
228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#if defined(OS_LINUX) && !defined(TOOLKIT_VIEWS)
238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst float kTextSize = 9.0;
248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kBottomMargin = 0;
258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kPadding = 2;
268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kTopTextPadding = 0;
278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#elif defined(OS_LINUX) && defined(TOOLKIT_VIEWS)
288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst float kTextSize = 8.0;
298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kBottomMargin = 5;
308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kPadding = 2;
318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kTopTextPadding = 1;
328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#elif defined(OS_MACOSX)
338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst float kTextSize = 9.0;
348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kBottomMargin = 5;
358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kPadding = 2;
368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kTopTextPadding = 0;
378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#else
388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst float kTextSize = 10;
398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kBottomMargin = 5;
408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kPadding = 2;
418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// The padding between the top of the badge and the top of the text.
428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kTopTextPadding = -1;
438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#endif
448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kBadgeHeight = 11;
468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kMaxTextWidth = 23;
478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// The minimum width for center-aligning the badge.
488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int kCenterAlignThreshold = 20;
498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}  // namespace
528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenconst int ExtensionAction::kDefaultTabId = -1;
548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
558ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenExtensionAction::ExtensionAction() {
568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
588ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenExtensionAction::~ExtensionAction() {
598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvoid ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) {
628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // We store |url| even if it is empty, rather than removing a URL from the
638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // map.  If an extension has a default popup, and removes it for a tab via
648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // the API, we must remember that there is no popup for that specific tab.
658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // If we removed the tab's URL, GetPopupURL would incorrectly return the
668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // default URL.
678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SetValue(&popup_url_, tab_id, url);
688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenbool ExtensionAction::HasPopup(int tab_id) {
718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return !GetPopupUrl(tab_id).is_empty();
728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
748ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenGURL ExtensionAction::GetPopupUrl(int tab_id) {
758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return GetValue(&popup_url_, tab_id);
768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvoid ExtensionAction::SetIcon(int tab_id, const SkBitmap& bitmap) {
798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SetValue(&icon_, tab_id, bitmap);
808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
828ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenSkBitmap ExtensionAction::GetIcon(int tab_id) {
838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return GetValue(&icon_, tab_id);
848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvoid ExtensionAction::SetIconIndex(int tab_id, int index) {
878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (static_cast<size_t>(index) >= icon_paths_.size()) {
888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    NOTREACHED();
898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return;
908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SetValue(&icon_index_, tab_id, index);
928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvoid ExtensionAction::ClearAllValuesForTab(int tab_id) {
958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  title_.erase(tab_id);
968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  icon_.erase(tab_id);
978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  icon_index_.erase(tab_id);
988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  badge_text_.erase(tab_id);
998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  badge_text_color_.erase(tab_id);
1008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  badge_background_color_.erase(tab_id);
1018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  visible_.erase(tab_id);
1028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  popup_url_.erase(tab_id);
1038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
1048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvoid ExtensionAction::PaintBadge(gfx::Canvas* canvas,
1068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                 const gfx::Rect& bounds,
1078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                 int tab_id) {
1088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  std::string text = GetBadgeText(tab_id);
1098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (text.empty())
1108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return;
1118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SkColor text_color = GetBadgeTextColor(tab_id);
1138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SkColor background_color = GetBadgeBackgroundColor(tab_id);
1148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (SkColorGetA(text_color) == 0x00)
1168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    text_color = SK_ColorWHITE;
1178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (SkColorGetA(background_color) == 0x00)
1198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    background_color = SkColorSetARGB(255, 218, 0, 24);  // Default badge color.
1208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  canvas->Save();
1228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
123201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  SkPaint* text_paint = badge_util::GetBadgeTextPaintSingleton();
124201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  text_paint->setTextSize(SkFloatToScalar(kTextSize));
1258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  text_paint->setColor(text_color);
1268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // Calculate text width. We clamp it to a max size.
1288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SkScalar text_width = text_paint->measureText(text.c_str(), text.size());
1298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  text_width = SkIntToScalar(
1308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      std::min(kMaxTextWidth, SkScalarFloor(text_width)));
1318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // Calculate badge size. It is clamped to a min width just because it looks
1338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // silly if it is too skinny.
1348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  int badge_width = SkScalarFloor(text_width) + kPadding * 2;
1358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  int icon_width = GetIcon(tab_id).width();
1368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // Force the pixel width of badge to be either odd (if the icon width is odd)
1378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // or even otherwise. If there is a mismatch you get http://crbug.com/26400.
1388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (icon_width != 0 && (badge_width % 2 != GetIcon(tab_id).width() % 2))
1398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    badge_width += 1;
1408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  badge_width = std::max(kBadgeHeight, badge_width);
1418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // Paint the badge background color in the right location. It is usually
1438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // right-aligned, but it can also be center-aligned if it is large.
1448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SkRect rect;
1458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  rect.fBottom = SkIntToScalar(bounds.bottom() - kBottomMargin);
1468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  rect.fTop = rect.fBottom - SkIntToScalar(kBadgeHeight);
1478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (badge_width >= kCenterAlignThreshold) {
1488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    rect.fLeft = SkIntToScalar(
1498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                     SkScalarFloor(SkIntToScalar(bounds.x()) +
1508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                   SkIntToScalar(bounds.width() / 2.0) -
1518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                   SkIntToScalar(badge_width / 2.0)));
1528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    rect.fRight = rect.fLeft + SkIntToScalar(badge_width);
1538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  } else {
1548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    rect.fRight = SkIntToScalar(bounds.right());
1558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    rect.fLeft = rect.fRight - badge_width;
1568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
1578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SkPaint rect_paint;
1598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  rect_paint.setStyle(SkPaint::kFill_Style);
1608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  rect_paint.setAntiAlias(true);
1618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  rect_paint.setColor(background_color);
1628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  canvas->AsCanvasSkia()->drawRoundRect(rect, SkIntToScalar(2),
1638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                        SkIntToScalar(2), rect_paint);
1648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // Overlay the gradient. It is stretchy, so we do this in three parts.
1668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
1678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SkBitmap* gradient_left = resource_bundle.GetBitmapNamed(
1688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      IDR_BROWSER_ACTION_BADGE_LEFT);
1698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SkBitmap* gradient_right = resource_bundle.GetBitmapNamed(
1708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      IDR_BROWSER_ACTION_BADGE_RIGHT);
1718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  SkBitmap* gradient_center = resource_bundle.GetBitmapNamed(
1728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      IDR_BROWSER_ACTION_BADGE_CENTER);
1738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  canvas->AsCanvasSkia()->drawBitmap(*gradient_left, rect.fLeft, rect.fTop);
1758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  canvas->TileImageInt(*gradient_center,
1768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      SkScalarFloor(rect.fLeft) + gradient_left->width(),
1778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      SkScalarFloor(rect.fTop),
1788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      SkScalarFloor(rect.width()) - gradient_left->width() -
1798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                    gradient_right->width(),
1808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      SkScalarFloor(rect.height()));
1818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  canvas->AsCanvasSkia()->drawBitmap(*gradient_right,
1828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      rect.fRight - SkIntToScalar(gradient_right->width()), rect.fTop);
1838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // Finally, draw the text centered within the badge. We set a clip in case the
1858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // text was too large.
1868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  rect.fLeft += kPadding;
1878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  rect.fRight -= kPadding;
1888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  canvas->AsCanvasSkia()->clipRect(rect);
1898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  canvas->AsCanvasSkia()->drawText(text.c_str(), text.size(),
1908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                   rect.fLeft + (rect.width() - text_width) / 2,
1918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                   rect.fTop + kTextSize + kTopTextPadding,
1928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                   *text_paint);
1938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  canvas->Restore();
1948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
195