1// Copyright (c) 2011 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/clock_menu_button.h"
6
7#include "base/i18n/time_formatting.h"
8#include "base/string_util.h"
9#include "base/time.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/chromeos/cros/cros_library.h"
12#include "chrome/browser/chromeos/status/status_area_host.h"
13#include "chrome/browser/prefs/pref_service.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/common/pref_names.h"
16#include "content/common/notification_details.h"
17#include "content/common/notification_source.h"
18#include "grit/generated_resources.h"
19#include "ui/base/l10n/l10n_util.h"
20#include "ui/gfx/canvas.h"
21#include "ui/gfx/font.h"
22#include "unicode/datefmt.h"
23
24namespace chromeos {
25
26// Amount of slop to add into the timer to make sure we're into the next minute
27// when the timer goes off.
28const int kTimerSlopSeconds = 1;
29
30ClockMenuButton::ClockMenuButton(StatusAreaHost* host)
31    : StatusAreaButton(host, this) {
32  // Add as SystemAccess observer. We update the clock if timezone changes.
33  SystemAccess::GetInstance()->AddObserver(this);
34  CrosLibrary::Get()->GetPowerLibrary()->AddObserver(this);
35  // Start monitoring the kUse24HourClock preference.
36  if (host->GetProfile()) {  // This can be NULL in the login screen.
37    registrar_.Init(host->GetProfile()->GetPrefs());
38    registrar_.Add(prefs::kUse24HourClock, this);
39  }
40
41  UpdateTextAndSetNextTimer();
42}
43
44ClockMenuButton::~ClockMenuButton() {
45  CrosLibrary::Get()->GetPowerLibrary()->RemoveObserver(this);
46  SystemAccess::GetInstance()->RemoveObserver(this);
47}
48
49void ClockMenuButton::UpdateTextAndSetNextTimer() {
50  UpdateText();
51
52  // Try to set the timer to go off at the next change of the minute. We don't
53  // want to have the timer go off more than necessary since that will cause
54  // the CPU to wake up and consume power.
55  base::Time now = base::Time::Now();
56  base::Time::Exploded exploded;
57  now.LocalExplode(&exploded);
58
59  // Often this will be called at minute boundaries, and we'll actually want
60  // 60 seconds from now.
61  int seconds_left = 60 - exploded.second;
62  if (seconds_left == 0)
63    seconds_left = 60;
64
65  // Make sure that the timer fires on the next minute. Without this, if it is
66  // called just a teeny bit early, then it will skip the next minute.
67  seconds_left += kTimerSlopSeconds;
68
69  timer_.Start(base::TimeDelta::FromSeconds(seconds_left), this,
70               &ClockMenuButton::UpdateTextAndSetNextTimer);
71}
72
73void ClockMenuButton::UpdateText() {
74  base::Time time(base::Time::Now());
75  // If the profie is present, check the use 24-hour clock preference.
76  const bool use_24hour_clock =
77      host_->GetProfile() &&
78      host_->GetProfile()->GetPrefs()->GetBoolean(prefs::kUse24HourClock);
79  if (use_24hour_clock) {
80    SetText(UTF16ToWide(base::TimeFormatTimeOfDayWithHourClockType(
81        time, base::k24HourClock)));
82  } else {
83    // Remove the am/pm field if it's present.
84    scoped_ptr<icu::DateFormat> formatter(
85        icu::DateFormat::createTimeInstance(icu::DateFormat::kShort));
86    icu::UnicodeString time_string;
87    icu::FieldPosition ampm_field(icu::DateFormat::kAmPmField);
88    formatter->format(
89        static_cast<UDate>(time.ToDoubleT() * 1000), time_string, ampm_field);
90    int ampm_length = ampm_field.getEndIndex() - ampm_field.getBeginIndex();
91    if (ampm_length) {
92      int begin = ampm_field.getBeginIndex();
93      // Doesn't include any spacing before the field.
94      if (begin)
95        begin--;
96      time_string.removeBetween(begin, ampm_field.getEndIndex());
97    }
98    string16 time_string16 =
99        string16(time_string.getBuffer(),
100                 static_cast<size_t>(time_string.length()));
101    SetText(UTF16ToWide(time_string16));
102  }
103  SetTooltipText(UTF16ToWide(base::TimeFormatShortDate(time)));
104  SchedulePaint();
105}
106
107////////////////////////////////////////////////////////////////////////////////
108// ClockMenuButton, NotificationObserver implementation:
109
110void ClockMenuButton::Observe(NotificationType type,
111                              const NotificationSource& source,
112                              const NotificationDetails& details) {
113  if (type == NotificationType::PREF_CHANGED) {
114    std::string* pref_name = Details<std::string>(details).ptr();
115    if (*pref_name == prefs::kUse24HourClock) {
116      UpdateText();
117    }
118  }
119}
120
121
122////////////////////////////////////////////////////////////////////////////////
123// ClockMenuButton, ui::MenuModel implementation:
124
125int ClockMenuButton::GetItemCount() const {
126  // If options dialog is unavailable, don't count a separator and configure
127  // menu item.
128  return host_->ShouldOpenButtonOptions(this) ? 3 : 1;
129}
130
131ui::MenuModel::ItemType ClockMenuButton::GetTypeAt(int index) const {
132  // There's a separator between the current date and the menu item to open
133  // the options menu.
134  return index == 1 ? ui::MenuModel::TYPE_SEPARATOR:
135                      ui::MenuModel::TYPE_COMMAND;
136}
137
138string16 ClockMenuButton::GetLabelAt(int index) const {
139  if (index == 0)
140    return base::TimeFormatFriendlyDate(base::Time::Now());
141  return l10n_util::GetStringUTF16(IDS_STATUSBAR_CLOCK_OPEN_OPTIONS_DIALOG);
142}
143
144bool ClockMenuButton::IsEnabledAt(int index) const {
145  // The 1st item is the current date, which is disabled.
146  return index != 0;
147}
148
149void ClockMenuButton::ActivatedAt(int index) {
150  host_->OpenButtonOptions(this);
151}
152
153///////////////////////////////////////////////////////////////////////////////
154// ClockMenuButton, PowerLibrary::Observer implementation:
155
156void ClockMenuButton::SystemResumed() {
157  UpdateText();
158}
159
160///////////////////////////////////////////////////////////////////////////////
161// ClockMenuButton, SystemAccess::Observer implementation:
162
163void ClockMenuButton::TimezoneChanged(const icu::TimeZone& timezone) {
164  UpdateText();
165}
166
167////////////////////////////////////////////////////////////////////////////////
168// ClockMenuButton, views::ViewMenuDelegate implementation:
169
170void ClockMenuButton::RunMenu(views::View* source, const gfx::Point& pt) {
171  if (!clock_menu_.get())
172    clock_menu_.reset(new views::Menu2(this));
173  else
174    clock_menu_->Rebuild();
175  clock_menu_->UpdateStates();
176  clock_menu_->RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT);
177}
178
179////////////////////////////////////////////////////////////////////////////////
180// ClockMenuButton, views::View implementation:
181
182void ClockMenuButton::OnLocaleChanged() {
183  UpdateText();
184}
185
186}  // namespace chromeos
187