| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/chromeos/status/power_menu_button.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/auto_reset.h" | |
| 10 #include "base/string_number_conversions.h" | |
| 11 #include "base/stringprintf.h" | |
| 12 #include "base/time.h" | |
| 13 #include "base/utf_string_conversions.h" | |
| 14 #include "chrome/browser/chromeos/status/status_area_bubble.h" | |
| 15 #include "chrome/browser/chromeos/view_ids.h" | |
| 16 #include "chromeos/dbus/dbus_thread_manager.h" | |
| 17 #include "grit/generated_resources.h" | |
| 18 #include "grit/theme_resources.h" | |
| 19 #include "ui/base/l10n/l10n_util.h" | |
| 20 #include "ui/base/resource/resource_bundle.h" | |
| 21 #include "ui/gfx/canvas.h" | |
| 22 #include "ui/gfx/font.h" | |
| 23 #include "ui/gfx/image/image.h" | |
| 24 #include "ui/views/controls/menu/menu_item_view.h" | |
| 25 #include "ui/views/controls/menu/menu_runner.h" | |
| 26 #include "ui/views/controls/menu/submenu_view.h" | |
| 27 #include "ui/views/widget/widget.h" | |
| 28 | |
| 29 namespace { | |
| 30 | |
| 31 // Menu item ids. | |
| 32 enum { | |
| 33 POWER_BATTERY_PERCENTAGE_ITEM = 1000, | |
| 34 POWER_BATTERY_IS_CHARGED_ITEM, | |
| 35 POWER_NO_BATTERY, | |
| 36 }; | |
| 37 | |
| 38 enum ImageType { | |
| 39 DISCHARGING, | |
| 40 CHARGING, | |
| 41 BOLT | |
| 42 }; | |
| 43 | |
| 44 enum ImageSize { | |
| 45 SMALL, | |
| 46 LARGE | |
| 47 }; | |
| 48 | |
| 49 // Initialize time deltas to large values so they will be replaced when set | |
| 50 // to small values. | |
| 51 const int64 kInitialMS = 0x7fffffff; | |
| 52 // Width and height of small images. | |
| 53 const int kSmallImageWidth = 26, kSmallImageHeight = 24; | |
| 54 // Width and height of large images. | |
| 55 const int kLargeImageWidth = 57, kLargeImageHeight = 35; | |
| 56 // Number of different power states. | |
| 57 const int kNumPowerImages = 20; | |
| 58 | |
| 59 // Color of text embedded within battery. | |
| 60 const SkColor kPercentageColor = 0xFF333333; | |
| 61 // Used for embossing text. | |
| 62 const SkColor kPercentageShadowColor = 0x80ffffff; | |
| 63 // Size of percentage w/in battery. | |
| 64 const int kBatteryFontSizeDelta = 3; | |
| 65 | |
| 66 // Battery images come from two collections (small and large). In each there | |
| 67 // are |kNumPowerImages| battery states for both on and off charge, followed | |
| 68 // by the missing battery image and the unknown image. | |
| 69 // They are layed out like this: | |
| 70 // Discharging Charging Bolt | |
| 71 // | | + | |
| 72 // || || + | |
| 73 // ... | |
| 74 // ||||| ||||| + | |
| 75 // ||X|| ||?|| | |
| 76 SkBitmap GetImage(ImageSize size, ImageType type, int index) { | |
| 77 int image_width, image_height, image_index; | |
| 78 | |
| 79 if (size == SMALL) { | |
| 80 image_index = IDR_STATUSBAR_BATTERY_SMALL_ALL; | |
| 81 image_width = kSmallImageWidth; | |
| 82 image_height = kSmallImageHeight; | |
| 83 } else { | |
| 84 image_index = IDR_STATUSBAR_BATTERY_LARGE_ALL; | |
| 85 image_width = kLargeImageWidth; | |
| 86 image_height = kLargeImageHeight; | |
| 87 } | |
| 88 const SkBitmap* all_images = ResourceBundle::GetSharedInstance(). | |
| 89 GetImageNamed(image_index).ToSkBitmap(); | |
| 90 SkIRect subset = | |
| 91 SkIRect::MakeXYWH( | |
| 92 static_cast<int>(type) * image_width, | |
| 93 index * image_height, | |
| 94 image_width, | |
| 95 image_height); | |
| 96 | |
| 97 SkBitmap image; | |
| 98 all_images->extractSubset(&image, subset); | |
| 99 return image; | |
| 100 } | |
| 101 | |
| 102 SkBitmap GetImageWithPercentage(ImageSize size, ImageType type, | |
| 103 double battery_percentage) { | |
| 104 // Preserve the fully charged icon for 100% only. | |
| 105 int battery_index = 0; | |
| 106 if (battery_percentage >= 100) { | |
| 107 battery_index = kNumPowerImages - 1; | |
| 108 } else { | |
| 109 battery_index = static_cast<int> ( | |
| 110 battery_percentage / 100.0 * | |
| 111 nextafter(static_cast<double>(kNumPowerImages - 1), 0)); | |
| 112 battery_index = | |
| 113 std::max(std::min(battery_index, kNumPowerImages - 2), 0); | |
| 114 } | |
| 115 return GetImage(size, type, battery_index); | |
| 116 } | |
| 117 | |
| 118 SkBitmap GetUnknownImage(ImageSize size) { | |
| 119 return GetImage(size, CHARGING, kNumPowerImages); | |
| 120 } | |
| 121 | |
| 122 class BatteryIconView : public views::View { | |
| 123 public: | |
| 124 BatteryIconView() | |
| 125 : battery_percentage_(0), | |
| 126 battery_is_present_(false), | |
| 127 line_power_on_(false), | |
| 128 percentage_font_(ResourceBundle::GetSharedInstance(). | |
| 129 GetFont(ResourceBundle::BaseFont). | |
| 130 DeriveFont(kBatteryFontSizeDelta, gfx::Font::BOLD)) { | |
| 131 } | |
| 132 | |
| 133 virtual gfx::Size GetPreferredSize() OVERRIDE { | |
| 134 return gfx::Size(kLargeImageWidth, kLargeImageHeight); | |
| 135 } | |
| 136 | |
| 137 void set_battery_percentage(double battery_percentage) { | |
| 138 battery_percentage_ = battery_percentage; | |
| 139 SchedulePaint(); | |
| 140 } | |
| 141 | |
| 142 void set_battery_is_present(bool battery_is_present) { | |
| 143 battery_is_present_ = battery_is_present; | |
| 144 SchedulePaint(); | |
| 145 } | |
| 146 | |
| 147 void set_line_power_on(bool line_power_on) { | |
| 148 line_power_on_ = line_power_on; | |
| 149 SchedulePaint(); | |
| 150 } | |
| 151 | |
| 152 protected: | |
| 153 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { | |
| 154 SkBitmap image; | |
| 155 if (battery_is_present_) { | |
| 156 image = GetImageWithPercentage(LARGE, | |
| 157 line_power_on_ ? CHARGING : DISCHARGING, | |
| 158 battery_percentage_); | |
| 159 } else { | |
| 160 NOTREACHED(); | |
| 161 return; | |
| 162 } | |
| 163 const int image_x = 0; | |
| 164 const int image_y = (height() - image.height()) / 2; | |
| 165 canvas->DrawBitmapInt(image, image_x, image_y); | |
| 166 | |
| 167 if (battery_is_present_ && (battery_percentage_ < 100 || !line_power_on_)) { | |
| 168 const string16 text = UTF8ToUTF16(base::StringPrintf( | |
| 169 "%d%%", static_cast<int>(battery_percentage_))); | |
| 170 const int text_h = percentage_font_.GetHeight(); | |
| 171 const int text_y = ((height() - text_h) / 2); | |
| 172 canvas->DrawStringInt( | |
| 173 text, percentage_font_, kPercentageShadowColor, | |
| 174 image_x, text_y + 1, image.width(), text_h, | |
| 175 gfx::Canvas::TEXT_ALIGN_CENTER | gfx::Canvas::NO_ELLIPSIS); | |
| 176 canvas->DrawStringInt( | |
| 177 text, percentage_font_, kPercentageColor, | |
| 178 image_x, text_y, image.width(), text_h, | |
| 179 gfx::Canvas::TEXT_ALIGN_CENTER | gfx::Canvas::NO_ELLIPSIS); | |
| 180 if (line_power_on_) | |
| 181 canvas->DrawBitmapInt( | |
| 182 GetImageWithPercentage(LARGE, BOLT, battery_percentage_), | |
| 183 image_x, image_y); | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 private: | |
| 188 double battery_percentage_; | |
| 189 bool battery_is_present_; | |
| 190 bool line_power_on_; | |
| 191 gfx::Font percentage_font_; | |
| 192 | |
| 193 DISALLOW_COPY_AND_ASSIGN(BatteryIconView); | |
| 194 }; | |
| 195 | |
| 196 } // namespace | |
| 197 | |
| 198 namespace chromeos { | |
| 199 | |
| 200 using base::TimeDelta; | |
| 201 | |
| 202 //////////////////////////////////////////////////////////////////////////////// | |
| 203 // PowerMenuButton | |
| 204 | |
| 205 PowerMenuButton::PowerMenuButton(StatusAreaButton::Delegate* delegate) | |
| 206 : StatusAreaButton(delegate, this), | |
| 207 battery_is_present_(false), | |
| 208 line_power_on_(false), | |
| 209 battery_percentage_(0.0), | |
| 210 battery_time_to_full_(TimeDelta::FromMicroseconds(kInitialMS)), | |
| 211 battery_time_to_empty_(TimeDelta::FromMicroseconds(kInitialMS)), | |
| 212 status_(NULL) { | |
| 213 set_id(VIEW_ID_STATUS_BUTTON_POWER); | |
| 214 UpdateIconAndLabelInfo(); | |
| 215 DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); | |
| 216 DBusThreadManager::Get()->GetPowerManagerClient()->RequestStatusUpdate( | |
| 217 PowerManagerClient::UPDATE_INITIAL); | |
| 218 } | |
| 219 | |
| 220 PowerMenuButton::~PowerMenuButton() { | |
| 221 DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); | |
| 222 } | |
| 223 | |
| 224 string16 PowerMenuButton::GetBatteryIsChargedText() const { | |
| 225 // The second item shows the battery is charged if it is. | |
| 226 if (battery_percentage_ >= 100 && line_power_on_) | |
| 227 return l10n_util::GetStringUTF16(IDS_STATUSBAR_BATTERY_IS_CHARGED); | |
| 228 | |
| 229 // If battery is in an intermediate charge state, show how much time left. | |
| 230 TimeDelta time = line_power_on_ ? battery_time_to_full_ : | |
| 231 battery_time_to_empty_; | |
| 232 if (time.InSeconds() == 0) { | |
| 233 // If time is 0, then that means we are still calculating how much time. | |
| 234 // Depending if line power is on, we either show a message saying that we | |
| 235 // are calculating time until full or calculating remaining time. | |
| 236 int msg = line_power_on_ ? | |
| 237 IDS_STATUSBAR_BATTERY_CALCULATING_TIME_UNTIL_FULL : | |
| 238 IDS_STATUSBAR_BATTERY_CALCULATING_TIME_UNTIL_EMPTY; | |
| 239 return l10n_util::GetStringUTF16(msg); | |
| 240 } else { | |
| 241 // Depending if line power is on, we either show a message saying XX:YY | |
| 242 // until full or XX:YY remaining where XX is number of hours and YY is | |
| 243 // number of minutes. | |
| 244 int msg = line_power_on_ ? IDS_STATUSBAR_BATTERY_TIME_UNTIL_FULL : | |
| 245 IDS_STATUSBAR_BATTERY_TIME_UNTIL_EMPTY; | |
| 246 int hour = time.InHours(); | |
| 247 int min = (time - TimeDelta::FromHours(hour)).InMinutes(); | |
| 248 string16 hour_str = base::IntToString16(hour); | |
| 249 string16 min_str = base::IntToString16(min); | |
| 250 // Append a "0" before the minute if it's only a single digit. | |
| 251 if (min < 10) | |
| 252 min_str = ASCIIToUTF16("0") + min_str; | |
| 253 return l10n_util::GetStringFUTF16(msg, hour_str, min_str); | |
| 254 } | |
| 255 } | |
| 256 | |
| 257 int PowerMenuButton::icon_width() { | |
| 258 return 26; | |
| 259 } | |
| 260 | |
| 261 //////////////////////////////////////////////////////////////////////////////// | |
| 262 // PowerMenuButton, views::View implementation: | |
| 263 void PowerMenuButton::OnLocaleChanged() { | |
| 264 UpdateIconAndLabelInfo(); | |
| 265 } | |
| 266 | |
| 267 //////////////////////////////////////////////////////////////////////////////// | |
| 268 // PowerMenuButton, views::MenuButtonListener implementation: | |
| 269 | |
| 270 void PowerMenuButton::OnMenuButtonClicked(views::View* source, | |
| 271 const gfx::Point& point) { | |
| 272 // Explicitly query the power status. | |
| 273 DBusThreadManager::Get()->GetPowerManagerClient()->RequestStatusUpdate( | |
| 274 PowerManagerClient::UPDATE_USER); | |
| 275 | |
| 276 views::MenuItemView* menu = new views::MenuItemView(this); | |
| 277 // MenuRunner takes ownership of |menu|. | |
| 278 menu_runner_.reset(new views::MenuRunner(menu)); | |
| 279 views::MenuItemView* submenu = menu->AppendMenuItem( | |
| 280 POWER_BATTERY_PERCENTAGE_ITEM, | |
| 281 string16(), | |
| 282 views::MenuItemView::NORMAL); | |
| 283 status_ = new StatusAreaBubbleContentView(new BatteryIconView, string16()); | |
| 284 UpdateStatusView(); | |
| 285 submenu->AddChildView(status_); | |
| 286 menu->CreateSubmenu()->set_resize_open_menu(true); | |
| 287 menu->SetMargins(0, 0); | |
| 288 submenu->SetMargins(0, 0); | |
| 289 menu->ChildrenChanged(); | |
| 290 | |
| 291 gfx::Point screen_location; | |
| 292 views::View::ConvertPointToScreen(source, &screen_location); | |
| 293 gfx::Rect bounds(screen_location, source->size()); | |
| 294 if (menu_runner_->RunMenuAt( | |
| 295 source->GetWidget()->GetTopLevelWidget(), this, bounds, | |
| 296 views::MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) == | |
| 297 views::MenuRunner::MENU_DELETED) | |
| 298 return; | |
| 299 status_ = NULL; | |
| 300 menu_runner_.reset(NULL); | |
| 301 } | |
| 302 | |
| 303 //////////////////////////////////////////////////////////////////////////////// | |
| 304 // PowerMenuButton, PowerManagerClient::Observer implementation: | |
| 305 | |
| 306 void PowerMenuButton::PowerChanged(const PowerSupplyStatus& power_status) { | |
| 307 power_status_ = power_status; | |
| 308 UpdateIconAndLabelInfo(); | |
| 309 } | |
| 310 | |
| 311 //////////////////////////////////////////////////////////////////////////////// | |
| 312 // PowerMenuButton, StatusAreaButton implementation: | |
| 313 | |
| 314 void PowerMenuButton::UpdateIconAndLabelInfo() { | |
| 315 battery_is_present_ = power_status_.battery_is_present; | |
| 316 line_power_on_ = power_status_.line_power_on; | |
| 317 | |
| 318 bool should_be_visible = battery_is_present_; | |
| 319 if (should_be_visible != visible()) | |
| 320 SetVisible(should_be_visible); | |
| 321 | |
| 322 if (!should_be_visible) | |
| 323 return; | |
| 324 | |
| 325 // If fully charged, always show 100% even if internal number is a bit less. | |
| 326 if (power_status_.battery_is_full) | |
| 327 battery_percentage_ = 100.0; | |
| 328 else | |
| 329 battery_percentage_ = power_status_.battery_percentage; | |
| 330 | |
| 331 UpdateBatteryTime(&battery_time_to_full_, | |
| 332 TimeDelta::FromSeconds( | |
| 333 power_status_.battery_seconds_to_full)); | |
| 334 UpdateBatteryTime(&battery_time_to_empty_, | |
| 335 TimeDelta::FromSeconds( | |
| 336 power_status_.battery_seconds_to_empty)); | |
| 337 | |
| 338 SetIcon(GetImageWithPercentage( | |
| 339 SMALL, line_power_on_ ? CHARGING : DISCHARGING, battery_percentage_)); | |
| 340 const int message_id = line_power_on_ ? | |
| 341 IDS_STATUSBAR_BATTERY_CHARGING_PERCENTAGE : | |
| 342 IDS_STATUSBAR_BATTERY_USING_PERCENTAGE; | |
| 343 string16 tooltip_text = l10n_util::GetStringFUTF16( | |
| 344 message_id, base::IntToString16(static_cast<int>(battery_percentage_))); | |
| 345 SetTooltipText(tooltip_text); | |
| 346 SetAccessibleName(tooltip_text); | |
| 347 SchedulePaint(); | |
| 348 UpdateStatusView(); | |
| 349 } | |
| 350 | |
| 351 void PowerMenuButton::UpdateStatusView() { | |
| 352 if (status_) { | |
| 353 string16 charging_text; | |
| 354 if (battery_is_present_) { | |
| 355 charging_text = GetBatteryIsChargedText(); | |
| 356 } else { | |
| 357 charging_text = l10n_util::GetStringUTF16(IDS_STATUSBAR_NO_BATTERY); | |
| 358 } | |
| 359 status_->SetMessage(charging_text); | |
| 360 BatteryIconView* battery_icon_view = | |
| 361 static_cast<BatteryIconView*>(status_->icon_view()); | |
| 362 battery_icon_view->set_battery_percentage(battery_percentage_); | |
| 363 battery_icon_view->set_battery_is_present(battery_is_present_); | |
| 364 battery_icon_view->set_line_power_on(line_power_on_); | |
| 365 } | |
| 366 } | |
| 367 | |
| 368 void PowerMenuButton::UpdateBatteryTime(TimeDelta* previous, | |
| 369 const TimeDelta& current) { | |
| 370 static const TimeDelta kMaxDiff(TimeDelta::FromMinutes(10)); | |
| 371 static const TimeDelta kMinDiff(TimeDelta::FromMinutes(0)); | |
| 372 const TimeDelta diff = current - *previous; | |
| 373 // If the diff is small and positive, ignore it in favor of | |
| 374 // keeping time monotonically decreasing. | |
| 375 // If previous is 0, then it either was never set (initial condition) | |
| 376 // or got down to 0. | |
| 377 if (*previous == TimeDelta::FromMicroseconds(kInitialMS) || | |
| 378 diff < kMinDiff || | |
| 379 diff > kMaxDiff) { | |
| 380 *previous = current; | |
| 381 } | |
| 382 } | |
| 383 | |
| 384 } // namespace chromeos | |
| OLD | NEW |