// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/status/clock_menu_button.h" #include "base/i18n/time_formatting.h" #include "base/string_util.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/status/status_area_host.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/pref_names.h" #include "content/common/notification_details.h" #include "content/common/notification_source.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/canvas.h" #include "ui/gfx/font.h" #include "views/widget/widget.h" #include "views/window/window.h" #include "unicode/datefmt.h" namespace { // views::MenuItemView item ids enum { CLOCK_DISPLAY_ITEM, CLOCK_OPEN_OPTIONS_ITEM }; } // namespace namespace chromeos { // Amount of slop to add into the timer to make sure we're into the next minute // when the timer goes off. const int kTimerSlopSeconds = 1; ClockMenuButton::ClockMenuButton(StatusAreaHost* host) : StatusAreaButton(host, this) { // Add as SystemAccess observer. We update the clock if timezone changes. SystemAccess::GetInstance()->AddObserver(this); CrosLibrary::Get()->GetPowerLibrary()->AddObserver(this); // Start monitoring the kUse24HourClock preference. if (host->GetProfile()) { // This can be NULL in the login screen. registrar_.Init(host->GetProfile()->GetPrefs()); registrar_.Add(prefs::kUse24HourClock, this); } UpdateTextAndSetNextTimer(); } ClockMenuButton::~ClockMenuButton() { CrosLibrary::Get()->GetPowerLibrary()->RemoveObserver(this); SystemAccess::GetInstance()->RemoveObserver(this); } void ClockMenuButton::UpdateTextAndSetNextTimer() { UpdateText(); // Try to set the timer to go off at the next change of the minute. We don't // want to have the timer go off more than necessary since that will cause // the CPU to wake up and consume power. base::Time now = base::Time::Now(); base::Time::Exploded exploded; now.LocalExplode(&exploded); // Often this will be called at minute boundaries, and we'll actually want // 60 seconds from now. int seconds_left = 60 - exploded.second; if (seconds_left == 0) seconds_left = 60; // Make sure that the timer fires on the next minute. Without this, if it is // called just a teeny bit early, then it will skip the next minute. seconds_left += kTimerSlopSeconds; timer_.Start(base::TimeDelta::FromSeconds(seconds_left), this, &ClockMenuButton::UpdateTextAndSetNextTimer); } void ClockMenuButton::UpdateText() { base::Time time(base::Time::Now()); // If the profie is present, check the use 24-hour clock preference. const bool use_24hour_clock = host_->GetProfile() && host_->GetProfile()->GetPrefs()->GetBoolean(prefs::kUse24HourClock); SetText(UTF16ToWide(base::TimeFormatTimeOfDayWithHourClockType( time, use_24hour_clock ? base::k24HourClock : base::k12HourClock, base::kDropAmPm))); SetTooltipText(UTF16ToWide(base::TimeFormatShortDate(time))); SchedulePaint(); } // ClockMenuButton, NotificationObserver implementation: void ClockMenuButton::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::PREF_CHANGED) { std::string* pref_name = Details<std::string>(details).ptr(); if (*pref_name == prefs::kUse24HourClock) { UpdateText(); } } } // ClockMenuButton, views::MenuDelegate implementation: std::wstring ClockMenuButton::GetLabel(int id) const { DCHECK_EQ(CLOCK_DISPLAY_ITEM, id); const string16 label = base::TimeFormatFriendlyDate(base::Time::Now()); return UTF16ToWide(label); } bool ClockMenuButton::IsCommandEnabled(int id) const { DCHECK(id == CLOCK_DISPLAY_ITEM || id == CLOCK_OPEN_OPTIONS_ITEM); return id == CLOCK_OPEN_OPTIONS_ITEM; } void ClockMenuButton::ExecuteCommand(int id) { DCHECK_EQ(CLOCK_OPEN_OPTIONS_ITEM, id); host_->OpenButtonOptions(this); } // ClockMenuButton, PowerLibrary::Observer implementation: void ClockMenuButton::SystemResumed() { UpdateText(); } // ClockMenuButton, SystemLibrary::Observer implementation: void ClockMenuButton::TimezoneChanged(const icu::TimeZone& timezone) { UpdateText(); } int ClockMenuButton::horizontal_padding() { return 3; } // ClockMenuButton, views::ViewMenuDelegate implementation: void ClockMenuButton::RunMenu(views::View* source, const gfx::Point& pt) { // View passed in must be a views::MenuButton, i.e. the ClockMenuButton. DCHECK_EQ(source, this); EnsureMenu(); // TODO(rhashimoto): Remove this workaround when WebUI provides a // top-level widget on the ChromeOS login screen that is a window. // The current BackgroundView class for the ChromeOS login screen // creates a owning Widget that has a native GtkWindow but is not a // Window. This makes it impossible to get the NativeWindow via // the views API. This workaround casts the top-level NativeWidget // to a NativeWindow that we can pass to MenuItemView::RunMenuAt(). gfx::NativeWindow window = GTK_WINDOW(source->GetWidget()->GetNativeView()); gfx::Point screen_loc; views::View::ConvertPointToScreen(source, &screen_loc); gfx::Rect bounds(screen_loc, source->size()); menu_->RunMenuAt( window, this, bounds, views::MenuItemView::TOPRIGHT, true); } // ClockMenuButton, views::View implementation: void ClockMenuButton::OnLocaleChanged() { UpdateText(); } void ClockMenuButton::EnsureMenu() { if (!menu_.get()) { menu_.reset(new views::MenuItemView(this)); // Text for this item will be set by GetLabel(). menu_->AppendDelegateMenuItem(CLOCK_DISPLAY_ITEM); // If options dialog is unavailable, don't show a separator and configure // menu item. if (host_->ShouldOpenButtonOptions(this)) { menu_->AppendSeparator(); const string16 clock_open_options_label = l10n_util::GetStringUTF16(IDS_STATUSBAR_CLOCK_OPEN_OPTIONS_DIALOG); menu_->AppendMenuItemWithLabel( CLOCK_OPEN_OPTIONS_ITEM, UTF16ToWide(clock_open_options_label)); } } } } // namespace chromeos