Chromium Code Reviews| Index: chrome/browser/chromeos/status/power_menu_button.cc |
| diff --git a/chrome/browser/chromeos/status/power_menu_button.cc b/chrome/browser/chromeos/status/power_menu_button.cc |
| index eb1fcf4210f71dc570a8c6a01301f328b0eabdec..2a7ed0733dee087a2a9fc77e2b96d60a118f11b9 100644 |
| --- a/chrome/browser/chromeos/status/power_menu_button.cc |
| +++ b/chrome/browser/chromeos/status/power_menu_button.cc |
| @@ -15,6 +15,8 @@ |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/canvas.h" |
| +#include "ui/gfx/canvas_skia.h" |
| +#include "ui/gfx/font.h" |
| #include "views/controls/menu/menu_item_view.h" |
| #include "views/controls/menu/submenu_view.h" |
| #include "views/widget/widget.h" |
| @@ -28,25 +30,219 @@ enum { |
| POWER_NO_BATTERY, |
| }; |
| +enum ImageType { |
| + DISCHARGING, |
| + CHARGING, |
| + BOLT |
| +}; |
| + |
| +enum ImageSize { |
| + SMALL, |
| + LARGE |
| +}; |
| + |
| +// Initialize time deltas to large values so they will be replaced when set |
| +// to small values. |
| +const int64 kInitialMS = 0x7fffffff; |
| +// Width and height of small images. |
| +const int kSmallImageWidth = 26, kSmallImageHeight = 24; |
| +// Width and height of large images. |
| +const int kLargeImageWidth = 57, kLargeImageHeight = 35; |
| +// Number of different power states. |
| +const int kNumPowerImages = 20; |
| + |
| +// Constants for status displayed when user clicks button. |
| +// Padding around status. |
| +const int kPadLeftX = 10, kPadRightX = 10, kPadY = 5; |
| +// Padding between battery and text. |
| +const int kBatteryPadX = 10; |
| +// Spacing between lines of text. |
| +const int kEstimateSpacing = 3; |
| +// Color of text embedded within battery. |
| +const SkColor kPercentageColor = 0xFF333333; |
| +// Used for embossing text. |
| +const SkColor kPercentageShadowColor = 0x80ffffff; |
| +// Status text/ |
| +const SkColor kEstimateColor = SK_ColorBLACK; |
| +// Size of percentage w/in battery. |
| +const int kBatteryFontSizeDelta = 3; |
| + |
| +// Battery images come from two collections (small and large). In each there |
| +// are |kNumPowerImages| battery states for both on and off charge, followed |
| +// by the missing battery image and the unknown image. |
| +// They are layed out like this: |
| +// Discharging Charging Bolt |
| +// | | + |
| +// || || + |
| +// ... |
| +// ||||| ||||| + |
| +// ||X|| ||?|| |
| +SkBitmap GetImage(ImageSize size, ImageType type, int index) { |
| + int image_width, image_height, image_index; |
| + |
| + if (size == SMALL) { |
| + image_index = IDR_STATUSBAR_BATTERY_SMALL_ALL; |
| + image_width = kSmallImageWidth; |
| + image_height = kSmallImageHeight; |
| + } else { |
| + image_index = IDR_STATUSBAR_BATTERY_LARGE_ALL; |
| + image_width = kLargeImageWidth; |
| + image_height = kLargeImageHeight; |
| + } |
| + SkBitmap* all_images = |
| + ResourceBundle::GetSharedInstance().GetBitmapNamed(image_index); |
| + SkBitmap image; |
|
achuithb
2011/08/09 18:58:07
Maybe move this to right before extractSubset?
DaveMoore
2011/08/12 21:03:11
Done.
|
| + SkIRect subset = |
| + SkIRect::MakeXYWH( |
| + static_cast<int>(type) * image_width, |
| + index * image_height, |
| + image_width, |
| + image_height); |
| + |
| + all_images->extractSubset(&image, subset); |
| + return image; |
| +} |
| + |
| +SkBitmap GetMissingImage(ImageSize size) { |
| + return GetImage(size, DISCHARGING, kNumPowerImages); |
| +} |
| + |
| +SkBitmap GetUnknownImage(ImageSize size) { |
| + return GetImage(size, CHARGING, kNumPowerImages); |
| +} |
| + |
| } // namespace |
| namespace chromeos { |
| using base::TimeDelta; |
| +class PowerMenuButton::StatusView : public View { |
| + public: |
| + explicit StatusView(PowerMenuButton* menu_button) |
| + : menu_button_(menu_button) { |
| + estimate_font_ = |
| + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont); |
| + percentage_font_ = |
| + estimate_font_.DeriveFont(kBatteryFontSizeDelta, gfx::Font::BOLD); |
| + } |
| + |
| + gfx::Size GetPreferredSize() { |
| + int estimate_w, estimate_h, charging_w, charging_h; |
| + string16 estimate_text = menu_button_->GetBatteryIsChargedText(); |
| + string16 charging_text = l10n_util::GetStringUTF16( |
| + menu_button_->line_power_on_ ? |
| + IDS_STATUSBAR_BATTERY_CHARGING : |
| + IDS_STATUSBAR_BATTERY_DISCHARGING); |
| + gfx::CanvasSkia::SizeStringInt( |
| + estimate_text, estimate_font_, &estimate_w, &estimate_h, 0); |
| + gfx::CanvasSkia::SizeStringInt( |
| + charging_text, estimate_font_, &charging_w, &charging_h, 0); |
| + gfx::Size size = gfx::Size( |
| + kPadLeftX + kLargeImageWidth + kBatteryPadX + kPadRightX + |
| + std::max(charging_w, estimate_w), |
| + (2 * kPadY) + |
| + std::max(kLargeImageHeight, |
| + kEstimateSpacing + (2 * estimate_font_.GetHeight()))); |
| + return size; |
| + } |
| + |
| + void Update() { |
| + PreferredSizeChanged(); |
| + // Force a paint even if the size didn't change. |
| + SchedulePaint(); |
| + } |
| + |
| + protected: |
| + void OnPaint(gfx::Canvas* canvas) { |
| + SkBitmap image; |
| + |
| + bool draw_percentage_text = false; |
| + if (!CrosLibrary::Get()->EnsureLoaded()) { |
| + image = GetUnknownImage(LARGE); |
| + } else if (!menu_button_->battery_is_present_) { |
| + image = GetMissingImage(LARGE); |
| + } else { |
| + image = GetImage( |
| + LARGE, |
| + menu_button_->line_power_on_ ? CHARGING : DISCHARGING, |
| + menu_button_->battery_index_); |
| + if (menu_button_->battery_percentage_ < 100 || |
| + !menu_button_->line_power_on_) { |
| + draw_percentage_text = true; |
| + } |
| + } |
| + int image_x = kPadLeftX, image_y = (height() - image.height()) / 2; |
| + |
|
achuithb
2011/08/09 18:58:07
Shouldn't this newline be after DrawBitmapInt?
DaveMoore
2011/08/12 21:03:11
Done.
|
| + canvas->DrawBitmapInt(image, image_x, image_y); |
| + if (draw_percentage_text) { |
| + string16 text = UTF8ToUTF16(StringPrintf( |
| + "%d%%", |
| + static_cast<int>(menu_button_->battery_percentage_))); |
| + int text_h = percentage_font_.GetHeight(); |
| + int text_y = ((height() - text_h) / 2); |
| + canvas->DrawStringInt( |
| + text, percentage_font_, kPercentageShadowColor, |
| + image_x, text_y + 1, image.width(), text_h, |
| + gfx::Canvas::TEXT_ALIGN_CENTER | gfx::Canvas::NO_ELLIPSIS); |
| + canvas->DrawStringInt( |
| + text, percentage_font_, kPercentageColor, |
| + image_x, text_y, image.width(), text_h, |
| + gfx::Canvas::TEXT_ALIGN_CENTER | gfx::Canvas::NO_ELLIPSIS); |
| + if (menu_button_->line_power_on_) { |
| + image = GetImage(LARGE, BOLT, menu_button_->battery_index_); |
| + canvas->DrawBitmapInt(image, image_x, image_y); |
| + } |
| + } |
| + string16 charging_text = l10n_util::GetStringUTF16( |
| + menu_button_->line_power_on_ ? |
| + IDS_STATUSBAR_BATTERY_CHARGING : |
| + IDS_STATUSBAR_BATTERY_DISCHARGING); |
| + string16 estimate_text = menu_button_->GetBatteryIsChargedText(); |
| + int text_h = estimate_font_.GetHeight(); |
| + int text_x = image_x + kLargeImageWidth + kBatteryPadX; |
| + int charging_y = (height() - (kEstimateSpacing + (2 * text_h))) / 2; |
| + int estimate_y = charging_y + text_h + kEstimateSpacing; |
| + canvas->DrawStringInt( |
| + charging_text, estimate_font_, kEstimateColor, |
| + text_x, charging_y, width() - text_x, text_h, |
| + gfx::Canvas::TEXT_ALIGN_LEFT); |
| + canvas->DrawStringInt( |
| + estimate_text, estimate_font_, kEstimateColor, |
| + text_x, estimate_y, width() - text_x, text_h, |
| + gfx::Canvas::TEXT_ALIGN_LEFT); |
| + } |
| + |
| + bool OnMousePressed(const views::MouseEvent& event) { |
| + return true; |
| + } |
| + |
| + void OnMouseReleased(const views::MouseEvent& event) { |
| + if (event.IsLeftMouseButton()) { |
| + DCHECK(menu_button_->menu_); |
| + menu_button_->menu_->Cancel(); |
| + } |
| + } |
| + |
| + private: |
| + PowerMenuButton* menu_button_; |
| + gfx::Font percentage_font_; |
| + gfx::Font estimate_font_; |
| +}; |
| + |
| //////////////////////////////////////////////////////////////////////////////// |
| // PowerMenuButton |
| -// static |
| -const int PowerMenuButton::kNumPowerImages = 19; |
| - |
| PowerMenuButton::PowerMenuButton(StatusAreaHost* host) |
| : StatusAreaButton(host, this), |
| battery_is_present_(false), |
| line_power_on_(false), |
| - battery_fully_charged_(false), |
| battery_percentage_(0.0), |
| - icon_id_(-1) { |
| + battery_index_(-1), |
| + battery_time_to_full_(TimeDelta::FromMicroseconds(kInitialMS)), |
| + battery_time_to_empty_(TimeDelta::FromMicroseconds(kInitialMS)), |
| + status_(NULL), |
| + menu_(NULL) { |
| UpdateIconAndLabelInfo(); |
| CrosLibrary::Get()->GetPowerLibrary()->AddObserver(this); |
| } |
| @@ -58,19 +254,7 @@ PowerMenuButton::~PowerMenuButton() { |
| // PowerMenuButton, views::MenuDelegate implementation: |
| std::wstring PowerMenuButton::GetLabel(int id) const { |
| - string16 label; |
| - switch (id) { |
| - case POWER_BATTERY_PERCENTAGE_ITEM: |
| - label = GetBatteryPercentageText(); |
| - break; |
| - case POWER_BATTERY_IS_CHARGED_ITEM: |
| - label = GetBatteryIsChargedText(); |
| - break; |
| - default: |
| - NOTREACHED(); |
| - } |
| - |
| - return UTF16ToWide(label); |
| + return std::wstring(); |
|
achuithb
2011/08/09 18:58:07
What is GetLabel used for?
|
| } |
| bool PowerMenuButton::IsCommandEnabled(int id) const { |
| @@ -85,7 +269,7 @@ string16 PowerMenuButton::GetBatteryPercentageText() const { |
| string16 PowerMenuButton::GetBatteryIsChargedText() const { |
| // The second item shows the battery is charged if it is. |
| - if (battery_fully_charged_) |
| + if (battery_percentage_ >= 100 && line_power_on_) |
| return l10n_util::GetStringUTF16(IDS_STATUSBAR_BATTERY_IS_CHARGED); |
| // If battery is in an intermediate charge state, show how much time left. |
| @@ -130,7 +314,18 @@ void PowerMenuButton::OnLocaleChanged() { |
| // PowerMenuButton, views::ViewMenuDelegate implementation: |
| void PowerMenuButton::RunMenu(views::View* source, const gfx::Point& pt) { |
| - UpdateMenu(); |
| + menu_ = new views::MenuItemView(this); |
| + views::MenuItemView* submenu = |
| + menu_->AppendMenuItem( |
| + POWER_BATTERY_PERCENTAGE_ITEM, |
| + std::wstring(), |
| + views::MenuItemView::NORMAL); |
| + status_ = new StatusView(this); |
| + submenu->AddChildView(status_); |
| + menu_->CreateSubmenu()->set_resize_open_menu(true); |
| + menu_->SetMargins(0, 0); |
| + submenu->SetMargins(0, 0); |
| + menu_->ChildrenChanged(); |
| gfx::Point screen_location; |
| views::View::ConvertPointToScreen(source, &screen_location); |
| @@ -141,6 +336,9 @@ void PowerMenuButton::RunMenu(views::View* source, const gfx::Point& pt) { |
| bounds, |
| views::MenuItemView::TOPRIGHT, |
| true); |
| + delete menu_; |
| + status_ = NULL; |
|
achuithb
2011/08/09 18:58:07
Is status_ leaked or does AddChildView transfer ow
DaveMoore
2011/08/12 21:03:11
AddChildView transfers ownership.
That aren't stac
|
| + menu_ = NULL; |
| } |
| //////////////////////////////////////////////////////////////////////////////// |
| @@ -162,87 +360,47 @@ void PowerMenuButton::UpdateIconAndLabelInfo() { |
| if (cros_loaded) { |
| battery_is_present_ = cros->battery_is_present(); |
| line_power_on_ = cros->line_power_on(); |
| - battery_fully_charged_ = cros->battery_fully_charged(); |
| - battery_percentage_ = cros->battery_percentage(); |
| + |
| // If fully charged, always show 100% even if internal number is a bit less. |
| - // Note: we always call cros->battery_percentage() for test predictability. |
| - if (battery_fully_charged_) |
| + if (cros->battery_fully_charged()) { |
| + // We always call cros->battery_percentage() for test predictability. |
| + cros->battery_percentage(); |
| battery_percentage_ = 100.0; |
| - // Map 1-100 to 0-100, so 1% shows up as 0%, and 100% remains 100%. |
| - static const double k = 100.0/99; |
| - battery_percentage_ = (battery_percentage_ > 1.0 ? |
| - battery_percentage_ : 1.0) * k - k; |
| + } else { |
| + battery_percentage_ = cros->battery_percentage(); |
| + } |
| UpdateBatteryTime(&battery_time_to_full_, cros->battery_time_to_full()); |
| UpdateBatteryTime(&battery_time_to_empty_, cros->battery_time_to_empty()); |
| } |
| if (!cros_loaded) { |
| - icon_id_ = IDR_STATUSBAR_BATTERY_UNKNOWN; |
| + battery_index_ = -1; |
| + SetIcon(GetUnknownImage(SMALL)); |
| } else if (!battery_is_present_) { |
| - icon_id_ = IDR_STATUSBAR_BATTERY_MISSING; |
| - } else if (line_power_on_ && battery_fully_charged_) { |
| - icon_id_ = IDR_STATUSBAR_BATTERY_CHARGED; |
| + battery_index_ = -1; |
| + SetIcon(GetMissingImage(SMALL)); |
| } else { |
| - // Get the power image depending on battery percentage. Percentage is |
| - // from 0 to 100, so we need to convert that to 0 to kNumPowerImages - 1. |
| - // NOTE: Use an array rather than just calculating a resource number to |
| - // avoid creating implicit ordering dependencies on the resource values. |
| - static const int kChargingImages[kNumPowerImages] = { |
| - IDR_STATUSBAR_BATTERY_CHARGING_1, |
| - IDR_STATUSBAR_BATTERY_CHARGING_2, |
| - IDR_STATUSBAR_BATTERY_CHARGING_3, |
| - IDR_STATUSBAR_BATTERY_CHARGING_4, |
| - IDR_STATUSBAR_BATTERY_CHARGING_5, |
| - IDR_STATUSBAR_BATTERY_CHARGING_6, |
| - IDR_STATUSBAR_BATTERY_CHARGING_7, |
| - IDR_STATUSBAR_BATTERY_CHARGING_8, |
| - IDR_STATUSBAR_BATTERY_CHARGING_9, |
| - IDR_STATUSBAR_BATTERY_CHARGING_10, |
| - IDR_STATUSBAR_BATTERY_CHARGING_11, |
| - IDR_STATUSBAR_BATTERY_CHARGING_12, |
| - IDR_STATUSBAR_BATTERY_CHARGING_13, |
| - IDR_STATUSBAR_BATTERY_CHARGING_14, |
| - IDR_STATUSBAR_BATTERY_CHARGING_15, |
| - IDR_STATUSBAR_BATTERY_CHARGING_16, |
| - IDR_STATUSBAR_BATTERY_CHARGING_17, |
| - IDR_STATUSBAR_BATTERY_CHARGING_18, |
| - IDR_STATUSBAR_BATTERY_CHARGING_19, |
| - }; |
| - static const int kDischargingImages[kNumPowerImages] = { |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_1, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_2, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_3, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_4, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_5, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_6, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_7, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_8, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_9, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_10, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_11, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_12, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_13, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_14, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_15, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_16, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_17, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_18, |
| - IDR_STATUSBAR_BATTERY_DISCHARGING_19, |
| - }; |
| - |
| - int index = static_cast<int>(battery_percentage_ / 100.0 * |
| - nextafter(static_cast<float>(kNumPowerImages), 0)); |
| - index = std::max(std::min(index, kNumPowerImages - 1), 0); |
| - icon_id_ = line_power_on_ ? |
| - kChargingImages[index] : kDischargingImages[index]; |
| + // Preserve the fully charged icon for 100% only. |
| + if (battery_percentage_ >= 100) { |
| + battery_index_ = kNumPowerImages - 1; |
| + } else { |
| + battery_index_ = |
| + static_cast<int>(battery_percentage_ / 100.0 * |
| + nextafter(static_cast<float>(kNumPowerImages - 1), 0)); |
| + battery_index_ = |
| + std::max(std::min(battery_index_, kNumPowerImages - 2), 0); |
| + } |
| + SetIcon(GetImage( |
| + SMALL, line_power_on_ ? CHARGING : DISCHARGING, battery_index_)); |
| } |
| - SetIcon(*ResourceBundle::GetSharedInstance().GetBitmapNamed(icon_id_)); |
| - SetTooltipText(GetLabel(POWER_BATTERY_PERCENTAGE_ITEM)); |
| - SetAccessibleName(GetBatteryPercentageText()); |
| - UpdateMenu(); |
| + percentage_text_ = GetBatteryPercentageText(); |
| + SetTooltipText(UTF16ToWide(percentage_text_)); |
| + SetAccessibleName(percentage_text_); |
| SchedulePaint(); |
| + if (status_) |
| + status_->Update(); |
| } |
| void PowerMenuButton::UpdateBatteryTime(TimeDelta* previous, |
| @@ -252,29 +410,13 @@ void PowerMenuButton::UpdateBatteryTime(TimeDelta* previous, |
| const TimeDelta diff = current - *previous; |
| // If the diff is small and positive, ignore it in favor of |
| // keeping time monotonically decreasing. |
| - if (diff < kMinDiff || diff > kMaxDiff) |
| + // If previous is 0, then it either was never set (initial condition) |
| + // or got down to 0 |
|
achuithb
2011/08/09 18:58:07
period at end of comment.
DaveMoore
2011/08/12 21:03:11
Done.
|
| + if (*previous == TimeDelta::FromMicroseconds(kInitialMS) || |
| + diff < kMinDiff || |
| + diff > kMaxDiff) { |
| *previous = current; |
| -} |
| - |
| -void PowerMenuButton::UpdateMenu() { |
| - if (!menu_.get()) |
| - menu_.reset(new views::MenuItemView(this)); |
| - |
| - // Remove existing items. |
| - const int old_count = menu_->CreateSubmenu()->child_count(); |
| - for (int i = 0; i < old_count; ++i) |
| - menu_->RemoveMenuItemAt(0); |
| - |
| - if (battery_is_present_) { |
| - // Create menu items whose text will be supplied by GetLabel(). |
| - menu_->AppendDelegateMenuItem(POWER_BATTERY_PERCENTAGE_ITEM); |
| - menu_->AppendDelegateMenuItem(POWER_BATTERY_IS_CHARGED_ITEM); |
| - } else { |
| - menu_->AppendMenuItemWithLabel( |
| - POWER_NO_BATTERY, |
| - UTF16ToWide(l10n_util::GetStringUTF16(IDS_STATUSBAR_NO_BATTERY))); |
| } |
| - menu_->ChildrenChanged(); |
| } |
| } // namespace chromeos |