• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 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 "ash/system/audio/volume_view.h"
6 
7 #include "ash/ash_constants.h"
8 #include "ash/shell.h"
9 #include "ash/system/audio/tray_audio.h"
10 #include "ash/system/audio/tray_audio_delegate.h"
11 #include "ash/system/tray/system_tray_item.h"
12 #include "ash/system/tray/tray_constants.h"
13 #include "grit/ash_resources.h"
14 #include "grit/ash_strings.h"
15 #include "ui/base/resource/resource_bundle.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/image/image_skia_operations.h"
18 #include "ui/views/controls/button/image_button.h"
19 #include "ui/views/controls/image_view.h"
20 #include "ui/views/layout/box_layout.h"
21 
22 namespace {
23 const int kVolumeImageWidth = 25;
24 const int kVolumeImageHeight = 25;
25 const int kBarSeparatorWidth = 25;
26 const int kBarSeparatorHeight = 30;
27 const int kSliderRightPaddingToVolumeViewEdge = 17;
28 const int kExtraPaddingBetweenBarAndMore = 10;
29 
30 // IDR_AURA_UBER_TRAY_VOLUME_LEVELS contains 5 images,
31 // The one for mute is at the 0 index and the other
32 // four are used for ascending volume levels.
33 const int kVolumeLevels = 4;
34 
35 }  // namespace
36 
37 namespace ash {
38 namespace tray {
39 
40 class VolumeButton : public views::ToggleImageButton {
41  public:
VolumeButton(views::ButtonListener * listener,system::TrayAudioDelegate * audio_delegate)42    VolumeButton(views::ButtonListener* listener,
43                 system::TrayAudioDelegate* audio_delegate)
44       : views::ToggleImageButton(listener),
45         audio_delegate_(audio_delegate),
46         image_index_(-1) {
47     SetImageAlignment(ALIGN_CENTER, ALIGN_MIDDLE);
48     image_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
49         IDR_AURA_UBER_TRAY_VOLUME_LEVELS);
50     Update();
51   }
52 
~VolumeButton()53   virtual ~VolumeButton() {}
54 
Update()55   void Update() {
56     float level =
57         static_cast<float>(audio_delegate_->GetOutputVolumeLevel()) / 100.0f;
58     int image_index = audio_delegate_->IsOutputAudioMuted() ?
59         0 : (level == 1.0 ?
60              kVolumeLevels :
61              std::max(1, int(std::ceil(level * (kVolumeLevels - 1)))));
62     if (image_index != image_index_) {
63       gfx::Rect region(0, image_index * kVolumeImageHeight,
64                        kVolumeImageWidth, kVolumeImageHeight);
65       gfx::ImageSkia image_skia = gfx::ImageSkiaOperations::ExtractSubset(
66           *(image_.ToImageSkia()), region);
67       SetImage(views::CustomButton::STATE_NORMAL, &image_skia);
68       image_index_ = image_index;
69     }
70     SchedulePaint();
71   }
72 
73  private:
74   // Overridden from views::View.
GetPreferredSize() const75   virtual gfx::Size GetPreferredSize() const OVERRIDE {
76     gfx::Size size = views::ToggleImageButton::GetPreferredSize();
77     size.set_height(kTrayPopupItemHeight);
78     return size;
79   }
80 
81   system::TrayAudioDelegate* audio_delegate_;
82   gfx::Image image_;
83   int image_index_;
84 
85   DISALLOW_COPY_AND_ASSIGN(VolumeButton);
86 };
87 
88 class VolumeSlider : public views::Slider {
89  public:
VolumeSlider(views::SliderListener * listener,system::TrayAudioDelegate * audio_delegate)90   VolumeSlider(views::SliderListener* listener,
91                system::TrayAudioDelegate* audio_delegate)
92       : views::Slider(listener, views::Slider::HORIZONTAL),
93         audio_delegate_(audio_delegate) {
94     set_focus_border_color(kFocusBorderColor);
95     SetValue(
96         static_cast<float>(audio_delegate_->GetOutputVolumeLevel()) / 100.0f);
97     SetAccessibleName(
98             ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
99                 IDS_ASH_STATUS_TRAY_VOLUME));
100     Update();
101   }
~VolumeSlider()102   virtual ~VolumeSlider() {}
103 
Update()104   void Update() {
105     UpdateState(!audio_delegate_->IsOutputAudioMuted());
106   }
107 
108  private:
109   system::TrayAudioDelegate* audio_delegate_;
110 
111   DISALLOW_COPY_AND_ASSIGN(VolumeSlider);
112 };
113 
114 // Vertical bar separator that can be placed on the VolumeView.
115 class BarSeparator : public views::View {
116  public:
BarSeparator()117   BarSeparator() {}
~BarSeparator()118   virtual ~BarSeparator() {}
119 
120   // Overriden from views::View.
GetPreferredSize() const121   virtual gfx::Size GetPreferredSize() const OVERRIDE {
122     return gfx::Size(kBarSeparatorWidth, kBarSeparatorHeight);
123   }
124 
125  private:
OnPaint(gfx::Canvas * canvas)126   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
127     canvas->FillRect(gfx::Rect(width() / 2, 0, 1, height()),
128                      kButtonStrokeColor);
129   }
130 
131   DISALLOW_COPY_AND_ASSIGN(BarSeparator);
132 };
133 
VolumeView(SystemTrayItem * owner,system::TrayAudioDelegate * audio_delegate,bool is_default_view)134 VolumeView::VolumeView(SystemTrayItem* owner,
135                        system::TrayAudioDelegate* audio_delegate,
136                        bool is_default_view)
137     : owner_(owner),
138       audio_delegate_(audio_delegate),
139       icon_(NULL),
140       slider_(NULL),
141       bar_(NULL),
142       device_type_(NULL),
143       more_(NULL),
144       is_default_view_(is_default_view) {
145   SetFocusable(false);
146   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
147         kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems));
148 
149   icon_ = new VolumeButton(this, audio_delegate_);
150   AddChildView(icon_);
151 
152   slider_ = new VolumeSlider(this, audio_delegate_);
153   AddChildView(slider_);
154 
155   bar_ = new BarSeparator;
156   AddChildView(bar_);
157 
158   device_type_ = new views::ImageView;
159   AddChildView(device_type_);
160 
161   more_ = new views::ImageView;
162   more_->EnableCanvasFlippingForRTLUI(true);
163   more_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
164       IDR_AURA_UBER_TRAY_MORE).ToImageSkia());
165   AddChildView(more_);
166 
167   Update();
168 }
169 
~VolumeView()170 VolumeView::~VolumeView() {
171 }
172 
Update()173 void VolumeView::Update() {
174   icon_->Update();
175   slider_->Update();
176   UpdateDeviceTypeAndMore();
177   Layout();
178 }
179 
SetVolumeLevel(float percent)180 void VolumeView::SetVolumeLevel(float percent) {
181   // Slider's value is in finer granularity than audio volume level(0.01),
182   // there will be a small discrepancy between slider's value and volume level
183   // on audio side. To avoid the jittering in slider UI, do not set change
184   // slider value if the change is less than 1%.
185   if (std::abs(percent-slider_->value()) < 0.01)
186     return;
187   // The change in volume will be reflected via accessibility system events,
188   // so we prevent the UI event from being sent here.
189   slider_->set_enable_accessibility_events(false);
190   slider_->SetValue(percent);
191   // It is possible that the volume was (un)muted, but the actual volume level
192   // did not change. In that case, setting the value of the slider won't
193   // trigger an update. So explicitly trigger an update.
194   Update();
195   slider_->set_enable_accessibility_events(true);
196 }
197 
UpdateDeviceTypeAndMore()198 void VolumeView::UpdateDeviceTypeAndMore() {
199   if (!TrayAudio::ShowAudioDeviceMenu() || !is_default_view_) {
200     more_->SetVisible(false);
201     bar_->SetVisible(false);
202     device_type_->SetVisible(false);
203     return;
204   }
205 
206   bool show_more = audio_delegate_->HasAlternativeSources();
207   more_->SetVisible(show_more);
208   bar_->SetVisible(show_more);
209 
210   // Show output device icon if necessary.
211   int device_icon = audio_delegate_->GetActiveOutputDeviceIconId();
212   if (device_icon != system::TrayAudioDelegate::kNoAudioDeviceIcon) {
213     device_type_->SetVisible(true);
214     device_type_->SetImage(
215         ui::ResourceBundle::GetSharedInstance().GetImageNamed(
216             device_icon).ToImageSkia());
217   } else {
218     device_type_->SetVisible(false);
219   }
220 }
221 
HandleVolumeUp(float level)222 void VolumeView::HandleVolumeUp(float level) {
223   audio_delegate_->SetOutputVolumeLevel(level);
224   if (audio_delegate_->IsOutputAudioMuted() &&
225       level > audio_delegate_->GetOutputDefaultVolumeMuteLevel()) {
226     audio_delegate_->SetOutputAudioIsMuted(false);
227   }
228 }
229 
HandleVolumeDown(float level)230 void VolumeView::HandleVolumeDown(float level) {
231   audio_delegate_->SetOutputVolumeLevel(level);
232   if (!audio_delegate_->IsOutputAudioMuted() &&
233       level <= audio_delegate_->GetOutputDefaultVolumeMuteLevel()) {
234     audio_delegate_->SetOutputAudioIsMuted(true);
235   } else if (audio_delegate_->IsOutputAudioMuted() &&
236              level > audio_delegate_->GetOutputDefaultVolumeMuteLevel()) {
237     audio_delegate_->SetOutputAudioIsMuted(false);
238   }
239 }
240 
Layout()241 void VolumeView::Layout() {
242   views::View::Layout();
243 
244   if (!more_->visible()) {
245     int w = width() - slider_->bounds().x() -
246             kSliderRightPaddingToVolumeViewEdge;
247     slider_->SetSize(gfx::Size(w, slider_->height()));
248     return;
249   }
250 
251   // Make sure the chevron always has the full size.
252   gfx::Size size = more_->GetPreferredSize();
253   gfx::Rect bounds(size);
254   bounds.set_x(width() - size.width() - kTrayPopupPaddingBetweenItems);
255   bounds.set_y((height() - size.height()) / 2);
256   more_->SetBoundsRect(bounds);
257 
258   // Layout either bar_ or device_type_ at the left of the more_ button.
259   views::View* view_left_to_more;
260   if (device_type_->visible())
261     view_left_to_more = device_type_;
262   else
263     view_left_to_more = bar_;
264   gfx::Size view_size = view_left_to_more->GetPreferredSize();
265   gfx::Rect view_bounds(view_size);
266   view_bounds.set_x(more_->bounds().x() - view_size.width() -
267                     kExtraPaddingBetweenBarAndMore);
268   view_bounds.set_y((height() - view_size.height()) / 2);
269   view_left_to_more->SetBoundsRect(view_bounds);
270 
271   // Layout vertical bar next to view_left_to_more if device_type_ is visible.
272   if (device_type_->visible()) {
273     gfx::Size bar_size = bar_->GetPreferredSize();
274     gfx::Rect bar_bounds(bar_size);
275     bar_bounds.set_x(view_left_to_more->bounds().x() - bar_size.width());
276     bar_bounds.set_y((height() - bar_size.height()) / 2);
277     bar_->SetBoundsRect(bar_bounds);
278   }
279 
280   // Layout slider, calculate slider width.
281   gfx::Rect slider_bounds = slider_->bounds();
282   slider_bounds.set_width(
283       bar_->bounds().x()
284       - (device_type_->visible() ? 0 : kTrayPopupPaddingBetweenItems)
285       - slider_bounds.x());
286   slider_->SetBoundsRect(slider_bounds);
287 }
288 
ButtonPressed(views::Button * sender,const ui::Event & event)289 void VolumeView::ButtonPressed(views::Button* sender, const ui::Event& event) {
290   CHECK(sender == icon_);
291   bool mute_on = !audio_delegate_->IsOutputAudioMuted();
292   audio_delegate_->SetOutputAudioIsMuted(mute_on);
293   if (!mute_on)
294     audio_delegate_->AdjustOutputVolumeToAudibleLevel();
295   icon_->Update();
296 }
297 
SliderValueChanged(views::Slider * sender,float value,float old_value,views::SliderChangeReason reason)298 void VolumeView::SliderValueChanged(views::Slider* sender,
299                                     float value,
300                                     float old_value,
301                                     views::SliderChangeReason reason) {
302   if (reason == views::VALUE_CHANGED_BY_USER) {
303     float new_volume = value * 100.0f;
304     float current_volume = audio_delegate_->GetOutputVolumeLevel();
305     // Do not call change audio volume if the difference is less than
306     // 1%, which is beyond cras audio api's granularity for output volume.
307     if (std::abs(new_volume - current_volume) < 1.0f)
308       return;
309     Shell::GetInstance()->metrics()->RecordUserMetricsAction(
310         is_default_view_ ?
311         ash::UMA_STATUS_AREA_CHANGED_VOLUME_MENU :
312         ash::UMA_STATUS_AREA_CHANGED_VOLUME_POPUP);
313     if (new_volume > current_volume)
314       HandleVolumeUp(new_volume);
315     else
316       HandleVolumeDown(new_volume);
317   }
318   icon_->Update();
319 }
320 
PerformAction(const ui::Event & event)321 bool VolumeView::PerformAction(const ui::Event& event) {
322   if (!more_->visible())
323     return false;
324   owner_->TransitionDetailedView();
325   return true;
326 }
327 
328 }  // namespace tray
329 }  // namespace ash
330