Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 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 "ash/system/chromeos/audio/tray_audio.h" |
| 6 | |
| 7 | #include <cmath> |
| 8 | |
| 9 | #include "ash/ash_constants.h" |
| 10 | #include "ash/ash_switches.h" |
| 11 | #include "ash/shell.h" |
| 12 | #include "ash/system/tray/actionable_view.h" |
| 13 | #include "ash/system/tray/fixed_sized_scroll_view.h" |
| 14 | #include "ash/system/tray/hover_highlight_view.h" |
| 15 | #include "ash/system/tray/system_tray.h" |
| 16 | #include "ash/system/tray/system_tray_delegate.h" |
| 17 | #include "ash/system/tray/system_tray_notifier.h" |
| 18 | #include "ash/system/tray/tray_constants.h" |
| 19 | #include "ash/volume_control_delegate.h" |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame] | 20 | #include "base/strings/utf_string_conversions.h" |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 21 | #include "chromeos/audio/cras_audio_handler.h" |
| 22 | #include "grit/ash_resources.h" |
| 23 | #include "grit/ash_strings.h" |
| 24 | #include "third_party/skia/include/core/SkCanvas.h" |
| 25 | #include "third_party/skia/include/core/SkPaint.h" |
| 26 | #include "third_party/skia/include/core/SkRect.h" |
| 27 | #include "third_party/skia/include/effects/SkGradientShader.h" |
| 28 | #include "ui/base/l10n/l10n_util.h" |
| 29 | #include "ui/base/resource/resource_bundle.h" |
| 30 | #include "ui/gfx/canvas.h" |
| 31 | #include "ui/gfx/image/image.h" |
| 32 | #include "ui/gfx/image/image_skia_operations.h" |
| 33 | #include "ui/views/controls/button/image_button.h" |
| 34 | #include "ui/views/controls/image_view.h" |
| 35 | #include "ui/views/controls/label.h" |
| 36 | #include "ui/views/controls/slider.h" |
| 37 | #include "ui/views/layout/box_layout.h" |
| 38 | #include "ui/views/view.h" |
| 39 | |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 40 | using chromeos::CrasAudioHandler; |
| 41 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 42 | namespace ash { |
| 43 | namespace internal { |
| 44 | |
| 45 | namespace { |
| 46 | const int kVolumeImageWidth = 25; |
| 47 | const int kVolumeImageHeight = 25; |
| 48 | const int kBarSeparatorWidth = 25; |
| 49 | const int kBarSeparatorHeight = 30; |
| 50 | const int kSliderRightPaddingToVolumeViewEdge = 17; |
| 51 | const int kExtraPaddingBetweenBarAndMore = 10; |
| 52 | |
| 53 | const int kNoAudioDeviceIcon = -1; |
| 54 | |
| 55 | // IDR_AURA_UBER_TRAY_VOLUME_LEVELS contains 5 images, |
| 56 | // The one for mute is at the 0 index and the other |
| 57 | // four are used for ascending volume levels. |
| 58 | const int kVolumeLevels = 4; |
| 59 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 60 | bool IsAudioMuted() { |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 61 | return CrasAudioHandler::Get()->IsOutputMuted(); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | float GetVolumeLevel() { |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 65 | return CrasAudioHandler::Get()->GetOutputVolumePercent() / 100.0f; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 66 | } |
| 67 | |
| 68 | int GetAudioDeviceIconId(chromeos::AudioDeviceType type) { |
| 69 | if (type == chromeos::AUDIO_TYPE_HEADPHONE) |
| 70 | return IDR_AURA_UBER_TRAY_AUDIO_HEADPHONE; |
| 71 | else if (type == chromeos::AUDIO_TYPE_USB) |
| 72 | return IDR_AURA_UBER_TRAY_AUDIO_USB; |
| 73 | else if (type == chromeos::AUDIO_TYPE_BLUETOOTH) |
| 74 | return IDR_AURA_UBER_TRAY_AUDIO_BLUETOOTH; |
| 75 | else if (type == chromeos::AUDIO_TYPE_HDMI) |
| 76 | return IDR_AURA_UBER_TRAY_AUDIO_HDMI; |
| 77 | else |
| 78 | return kNoAudioDeviceIcon; |
| 79 | } |
| 80 | |
| 81 | } // namespace |
| 82 | |
| 83 | namespace tray { |
| 84 | |
| 85 | class VolumeButton : public views::ToggleImageButton { |
| 86 | public: |
| 87 | explicit VolumeButton(views::ButtonListener* listener) |
| 88 | : views::ToggleImageButton(listener), |
| 89 | image_index_(-1) { |
| 90 | SetImageAlignment(ALIGN_CENTER, ALIGN_MIDDLE); |
| 91 | image_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| 92 | IDR_AURA_UBER_TRAY_VOLUME_LEVELS); |
| 93 | SetPreferredSize(gfx::Size(kTrayPopupItemHeight, kTrayPopupItemHeight)); |
| 94 | Update(); |
| 95 | } |
| 96 | |
| 97 | virtual ~VolumeButton() {} |
| 98 | |
| 99 | void Update() { |
| 100 | float level = GetVolumeLevel(); |
| 101 | int image_index = IsAudioMuted() ? |
| 102 | 0 : (level == 1.0 ? |
| 103 | kVolumeLevels : |
| 104 | std::max(1, int(std::ceil(level * (kVolumeLevels - 1))))); |
| 105 | if (image_index != image_index_) { |
| 106 | gfx::Rect region(0, image_index * kVolumeImageHeight, |
| 107 | kVolumeImageWidth, kVolumeImageHeight); |
| 108 | gfx::ImageSkia image_skia = gfx::ImageSkiaOperations::ExtractSubset( |
| 109 | *(image_.ToImageSkia()), region); |
| 110 | SetImage(views::CustomButton::STATE_NORMAL, &image_skia); |
| 111 | image_index_ = image_index; |
| 112 | } |
| 113 | SchedulePaint(); |
| 114 | } |
| 115 | |
| 116 | private: |
| 117 | // Overridden from views::View. |
| 118 | virtual gfx::Size GetPreferredSize() OVERRIDE { |
| 119 | gfx::Size size = views::ToggleImageButton::GetPreferredSize(); |
| 120 | size.set_height(kTrayPopupItemHeight); |
| 121 | return size; |
| 122 | } |
| 123 | |
| 124 | gfx::Image image_; |
| 125 | int image_index_; |
| 126 | |
| 127 | DISALLOW_COPY_AND_ASSIGN(VolumeButton); |
| 128 | }; |
| 129 | |
| 130 | class VolumeSlider : public views::Slider { |
| 131 | public: |
| 132 | explicit VolumeSlider(views::SliderListener* listener) |
| 133 | : views::Slider(listener, views::Slider::HORIZONTAL) { |
| 134 | set_focus_border_color(kFocusBorderColor); |
| 135 | SetValue(GetVolumeLevel()); |
| 136 | SetAccessibleName( |
| 137 | ui::ResourceBundle::GetSharedInstance().GetLocalizedString( |
| 138 | IDS_ASH_STATUS_TRAY_VOLUME)); |
| 139 | Update(); |
| 140 | } |
| 141 | virtual ~VolumeSlider() {} |
| 142 | |
| 143 | void Update() { |
| 144 | UpdateState(!IsAudioMuted()); |
| 145 | } |
| 146 | |
| 147 | DISALLOW_COPY_AND_ASSIGN(VolumeSlider); |
| 148 | }; |
| 149 | |
| 150 | // Vertical bar separator that can be placed on the VolumeView. |
| 151 | class BarSeparator : public views::View { |
| 152 | public: |
| 153 | BarSeparator() {} |
| 154 | virtual ~BarSeparator() {} |
| 155 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 156 | // Overriden from views::View. |
| 157 | virtual gfx::Size GetPreferredSize() OVERRIDE { |
| 158 | return gfx::Size(kBarSeparatorWidth, kBarSeparatorHeight); |
| 159 | } |
| 160 | |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 161 | private: |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 162 | virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { |
| 163 | canvas->FillRect(gfx::Rect(width() / 2, 0, 1, height()), |
| 164 | kButtonStrokeColor); |
| 165 | } |
| 166 | |
| 167 | DISALLOW_COPY_AND_ASSIGN(BarSeparator); |
| 168 | }; |
| 169 | |
| 170 | class VolumeView : public ActionableView, |
| 171 | public views::ButtonListener, |
| 172 | public views::SliderListener { |
| 173 | public: |
| 174 | VolumeView(SystemTrayItem* owner, bool is_default_view) |
| 175 | : owner_(owner), |
| 176 | icon_(NULL), |
| 177 | slider_(NULL), |
| 178 | bar_(NULL), |
| 179 | device_type_(NULL), |
| 180 | more_(NULL), |
| 181 | is_default_view_(is_default_view) { |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 182 | set_focusable(false); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 183 | SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, |
| 184 | kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems)); |
| 185 | |
| 186 | icon_ = new VolumeButton(this); |
| 187 | AddChildView(icon_); |
| 188 | |
| 189 | slider_ = new VolumeSlider(this); |
| 190 | AddChildView(slider_); |
| 191 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 192 | bar_ = new BarSeparator; |
| 193 | AddChildView(bar_); |
| 194 | |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 195 | device_type_ = new views::ImageView; |
| 196 | AddChildView(device_type_); |
| 197 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 198 | more_ = new views::ImageView; |
| 199 | more_->EnableCanvasFlippingForRTLUI(true); |
| 200 | more_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| 201 | IDR_AURA_UBER_TRAY_MORE).ToImageSkia()); |
| 202 | AddChildView(more_); |
| 203 | |
| 204 | Update(); |
| 205 | } |
| 206 | |
| 207 | virtual ~VolumeView() {} |
| 208 | |
| 209 | void Update() { |
| 210 | icon_->Update(); |
| 211 | slider_->Update(); |
| 212 | UpdateDeviceTypeAndMore(); |
| 213 | Layout(); |
| 214 | } |
| 215 | |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 216 | // Sets volume level on slider_, |percent| is ranged from [0.00] to [1.00]. |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 217 | void SetVolumeLevel(float percent) { |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 218 | // Slider's value is in finer granularity than audio volume level(0.01), |
| 219 | // there will be a small discrepancy between slider's value and volume level |
| 220 | // on audio side. To avoid the jittering in slider UI, do not set change |
| 221 | // slider value if the change is less than 1%. |
| 222 | if (std::abs(percent-slider_->value()) < 0.01) |
| 223 | return; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 224 | // The change in volume will be reflected via accessibility system events, |
| 225 | // so we prevent the UI event from being sent here. |
| 226 | slider_->set_enable_accessibility_events(false); |
| 227 | slider_->SetValue(percent); |
| 228 | // It is possible that the volume was (un)muted, but the actual volume level |
| 229 | // did not change. In that case, setting the value of the slider won't |
| 230 | // trigger an update. So explicitly trigger an update. |
| 231 | Update(); |
| 232 | slider_->set_enable_accessibility_events(true); |
| 233 | } |
| 234 | |
| 235 | private: |
| 236 | // Updates bar_, device_type_ icon, and more_ buttons. |
| 237 | void UpdateDeviceTypeAndMore() { |
Torne (Richard Coles) | a93a17c | 2013-05-15 11:34:50 +0100 | [diff] [blame] | 238 | if (!ash::switches::ShowAudioDeviceMenu() || !is_default_view_) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 239 | more_->SetVisible(false); |
| 240 | bar_->SetVisible(false); |
| 241 | device_type_->SetVisible(false); |
| 242 | return; |
| 243 | } |
| 244 | |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 245 | CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 246 | bool show_more = audio_handler->has_alternative_output() || |
| 247 | audio_handler->has_alternative_input(); |
| 248 | more_->SetVisible(show_more); |
| 249 | |
| 250 | // Show output device icon if necessary. |
| 251 | chromeos::AudioDevice device; |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 252 | if (!audio_handler->GetActiveOutputDevice(&device)) |
| 253 | return; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 254 | int device_icon = GetAudioDeviceIconId(device.type); |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 255 | bar_->SetVisible(show_more); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 256 | if (device_icon != kNoAudioDeviceIcon) { |
| 257 | device_type_->SetVisible(true); |
| 258 | device_type_->SetImage( |
| 259 | ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| 260 | device_icon).ToImageSkia()); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 261 | } else { |
| 262 | device_type_->SetVisible(false); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 263 | } |
| 264 | } |
| 265 | |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 266 | void HandleVolumeUp(int volume) { |
| 267 | CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| 268 | audio_handler->SetOutputVolumePercent(volume); |
| 269 | if (audio_handler->IsOutputMuted() && |
| 270 | !audio_handler->IsOutputVolumeBelowDefaultMuteLvel()) |
| 271 | audio_handler->SetOutputMute(false); |
| 272 | } |
| 273 | |
| 274 | void HandleVolumeDown(int volume) { |
| 275 | CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| 276 | audio_handler->SetOutputVolumePercent(volume); |
| 277 | if (audio_handler->IsOutputVolumeBelowDefaultMuteLvel() && |
| 278 | !audio_handler->IsOutputMuted()) { |
| 279 | audio_handler->SetOutputMute(true); |
| 280 | } else if (!audio_handler->IsOutputVolumeBelowDefaultMuteLvel() && |
| 281 | audio_handler->IsOutputMuted()) { |
| 282 | audio_handler->SetOutputMute(false); |
| 283 | } |
| 284 | } |
| 285 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 286 | // Overridden from views::View. |
| 287 | virtual void Layout() OVERRIDE { |
| 288 | views::View::Layout(); |
| 289 | |
| 290 | if (!more_->visible()) { |
| 291 | int w = width() - slider_->bounds().x() - |
| 292 | kSliderRightPaddingToVolumeViewEdge; |
| 293 | slider_->SetSize(gfx::Size(w, slider_->height())); |
| 294 | return; |
| 295 | } |
| 296 | |
| 297 | // Make sure the chevron always has the full size. |
| 298 | gfx::Size size = more_->GetPreferredSize(); |
| 299 | gfx::Rect bounds(size); |
| 300 | bounds.set_x(width() - size.width() - kTrayPopupPaddingBetweenItems); |
| 301 | bounds.set_y((height() - size.height()) / 2); |
| 302 | more_->SetBoundsRect(bounds); |
| 303 | |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 304 | // Layout either bar_ or device_type_ at the left of the more_ button. |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 305 | views::View* view_left_to_more; |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 306 | if (device_type_->visible()) |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 307 | view_left_to_more = device_type_; |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 308 | else |
| 309 | view_left_to_more = bar_; |
| 310 | gfx::Size view_size = view_left_to_more->GetPreferredSize(); |
| 311 | gfx::Rect view_bounds(view_size); |
| 312 | view_bounds.set_x(more_->bounds().x() - view_size.width() - |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 313 | kExtraPaddingBetweenBarAndMore); |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 314 | view_bounds.set_y((height() - view_size.height()) / 2); |
| 315 | view_left_to_more->SetBoundsRect(view_bounds); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 316 | |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 317 | // Layout vertical bar next to view_left_to_more if device_type_ is visible. |
| 318 | if (device_type_->visible()) { |
| 319 | gfx::Size bar_size = bar_->GetPreferredSize(); |
| 320 | gfx::Rect bar_bounds(bar_size); |
| 321 | bar_bounds.set_x(view_left_to_more->bounds().x() - bar_size.width()); |
| 322 | bar_bounds.set_y((height() - bar_size.height()) / 2); |
| 323 | bar_->SetBoundsRect(bar_bounds); |
| 324 | } |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 325 | |
| 326 | // Layout slider, calculate slider width. |
| 327 | gfx::Rect slider_bounds = slider_->bounds(); |
| 328 | slider_bounds.set_width( |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 329 | bar_->bounds().x() |
| 330 | - (device_type_->visible() ? 0 : kTrayPopupPaddingBetweenItems) |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 331 | - slider_bounds.x()); |
| 332 | slider_->SetBoundsRect(slider_bounds); |
| 333 | } |
| 334 | |
| 335 | // Overridden from views::ButtonListener. |
| 336 | virtual void ButtonPressed(views::Button* sender, |
| 337 | const ui::Event& event) OVERRIDE { |
| 338 | CHECK(sender == icon_); |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 339 | bool mute_on = !IsAudioMuted(); |
| 340 | CrasAudioHandler::Get()->SetOutputMute(mute_on); |
| 341 | if (!mute_on) |
| 342 | CrasAudioHandler::Get()->AdjustOutputVolumeToAudibleLevel(); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 343 | } |
| 344 | |
| 345 | // Overridden from views:SliderListener. |
| 346 | virtual void SliderValueChanged(views::Slider* sender, |
| 347 | float value, |
| 348 | float old_value, |
| 349 | views::SliderChangeReason reason) OVERRIDE { |
| 350 | if (reason == views::VALUE_CHANGED_BY_USER) { |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 351 | int volume = value * 100.0f; |
| 352 | int old_volume = CrasAudioHandler::Get()->GetOutputVolumePercent(); |
| 353 | // Do not call change audio volume if the difference is less than |
| 354 | // 1%, which is beyond cras audio api's granularity for output volume. |
| 355 | if (std::abs(volume - old_volume) < 1) |
| 356 | return; |
| 357 | if (volume > old_volume) |
| 358 | HandleVolumeUp(volume); |
| 359 | else |
| 360 | HandleVolumeDown(volume); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 361 | } |
| 362 | icon_->Update(); |
| 363 | } |
| 364 | |
| 365 | // Overriden from ActinableView. |
| 366 | virtual bool PerformAction(const ui::Event& event) OVERRIDE { |
| 367 | if (!more_->visible()) |
| 368 | return false; |
| 369 | owner_->TransitionDetailedView(); |
| 370 | return true; |
| 371 | } |
| 372 | |
| 373 | SystemTrayItem* owner_; |
| 374 | VolumeButton* icon_; |
| 375 | VolumeSlider* slider_; |
| 376 | BarSeparator* bar_; |
| 377 | views::ImageView* device_type_; |
| 378 | views::ImageView* more_; |
| 379 | bool is_default_view_; |
| 380 | |
| 381 | DISALLOW_COPY_AND_ASSIGN(VolumeView); |
| 382 | }; |
| 383 | |
| 384 | class AudioDetailedView : public TrayDetailsView, |
| 385 | public ViewClickListener { |
| 386 | public: |
| 387 | AudioDetailedView(SystemTrayItem* owner, user::LoginStatus login) |
| 388 | : TrayDetailsView(owner), |
| 389 | login_(login) { |
| 390 | CreateItems(); |
| 391 | Update(); |
| 392 | } |
| 393 | |
| 394 | virtual ~AudioDetailedView() { |
| 395 | } |
| 396 | |
| 397 | void Update() { |
| 398 | UpdateAudioDevices(); |
| 399 | Layout(); |
| 400 | } |
| 401 | |
| 402 | private: |
| 403 | void CreateItems() { |
| 404 | CreateScrollableList(); |
| 405 | CreateHeaderEntry(); |
| 406 | } |
| 407 | |
| 408 | void CreateHeaderEntry() { |
| 409 | CreateSpecialRow(IDS_ASH_STATUS_TRAY_AUDIO, this); |
| 410 | } |
| 411 | |
| 412 | void UpdateAudioDevices() { |
| 413 | output_devices_.clear(); |
| 414 | input_devices_.clear(); |
| 415 | chromeos::AudioDeviceList devices; |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 416 | CrasAudioHandler::Get()->GetAudioDevices(&devices); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 417 | for (size_t i = 0; i < devices.size(); ++i) { |
| 418 | if (devices[i].is_input) |
| 419 | input_devices_.push_back(devices[i]); |
| 420 | else |
| 421 | output_devices_.push_back(devices[i]); |
| 422 | } |
| 423 | UpdateScrollableList(); |
| 424 | } |
| 425 | |
| 426 | void UpdateScrollableList() { |
| 427 | scroll_content()->RemoveAllChildViews(true); |
| 428 | device_map_.clear(); |
| 429 | |
| 430 | // Add audio output devices. |
| 431 | AddScrollListItem( |
| 432 | l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_OUTPUT), |
| 433 | gfx::Font::BOLD, |
| 434 | false); /* no checkmark */ |
| 435 | for (size_t i = 0; i < output_devices_.size(); ++i) { |
| 436 | HoverHighlightView* container = AddScrollListItem( |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 437 | UTF8ToUTF16(output_devices_[i].display_name), |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 438 | gfx::Font::NORMAL, |
| 439 | output_devices_[i].active); /* checkmark if active */ |
| 440 | device_map_[container] = output_devices_[i]; |
| 441 | } |
| 442 | |
| 443 | AddScrollSeparator(); |
| 444 | |
| 445 | // Add audio input devices. |
| 446 | AddScrollListItem( |
| 447 | l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_INPUT), |
| 448 | gfx::Font::BOLD, |
| 449 | false); /* no checkmark */ |
| 450 | for (size_t i = 0; i < input_devices_.size(); ++i) { |
| 451 | HoverHighlightView* container = AddScrollListItem( |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 452 | UTF8ToUTF16(input_devices_[i].display_name), |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 453 | gfx::Font::NORMAL, |
| 454 | input_devices_[i].active); /* checkmark if active */ |
| 455 | device_map_[container] = input_devices_[i]; |
| 456 | } |
| 457 | |
| 458 | scroll_content()->SizeToPreferredSize(); |
| 459 | scroller()->Layout(); |
| 460 | } |
| 461 | |
| 462 | HoverHighlightView* AddScrollListItem(const string16& text, |
| 463 | gfx::Font::FontStyle style, |
| 464 | bool checked) { |
| 465 | HoverHighlightView* container = new HoverHighlightView(this); |
| 466 | container->AddCheckableLabel(text, style, checked); |
| 467 | scroll_content()->AddChildView(container); |
| 468 | return container; |
| 469 | } |
| 470 | |
| 471 | // Overridden from ViewClickListener. |
| 472 | virtual void OnViewClicked(views::View* sender) OVERRIDE { |
| 473 | if (sender == footer()->content()) { |
| 474 | owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING); |
| 475 | } else { |
| 476 | AudioDeviceMap::iterator iter = device_map_.find(sender); |
| 477 | if (iter == device_map_.end()) |
| 478 | return; |
| 479 | chromeos::AudioDevice& device = iter->second; |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 480 | CrasAudioHandler::Get()->SwitchToDevice(device); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 481 | } |
| 482 | } |
| 483 | |
| 484 | typedef std::map<views::View*, chromeos::AudioDevice> AudioDeviceMap; |
| 485 | |
| 486 | user::LoginStatus login_; |
| 487 | chromeos::AudioDeviceList output_devices_; |
| 488 | chromeos::AudioDeviceList input_devices_; |
| 489 | AudioDeviceMap device_map_; |
| 490 | |
| 491 | DISALLOW_COPY_AND_ASSIGN(AudioDetailedView); |
| 492 | }; |
| 493 | |
| 494 | } // namespace tray |
| 495 | |
| 496 | TrayAudio::TrayAudio(SystemTray* system_tray) |
| 497 | : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_VOLUME_MUTE), |
| 498 | volume_view_(NULL), |
| 499 | audio_detail_(NULL), |
| 500 | pop_up_volume_view_(false) { |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 501 | CrasAudioHandler::Get()->AddAudioObserver(this); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 502 | } |
| 503 | |
| 504 | TrayAudio::~TrayAudio() { |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 505 | if (CrasAudioHandler::IsInitialized()) |
| 506 | CrasAudioHandler::Get()->RemoveAudioObserver(this); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 507 | } |
| 508 | |
| 509 | bool TrayAudio::GetInitialVisibility() { |
| 510 | return IsAudioMuted(); |
| 511 | } |
| 512 | |
| 513 | views::View* TrayAudio::CreateDefaultView(user::LoginStatus status) { |
| 514 | volume_view_ = new tray::VolumeView(this, true); |
| 515 | return volume_view_; |
| 516 | } |
| 517 | |
| 518 | views::View* TrayAudio::CreateDetailedView(user::LoginStatus status) { |
Torne (Richard Coles) | a93a17c | 2013-05-15 11:34:50 +0100 | [diff] [blame] | 519 | if (!ash::switches::ShowAudioDeviceMenu() || pop_up_volume_view_) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 520 | volume_view_ = new tray::VolumeView(this, false); |
| 521 | return volume_view_; |
| 522 | } else { |
| 523 | audio_detail_ = new tray::AudioDetailedView(this, status); |
| 524 | return audio_detail_; |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | void TrayAudio::DestroyDefaultView() { |
| 529 | volume_view_ = NULL; |
| 530 | } |
| 531 | |
| 532 | void TrayAudio::DestroyDetailedView() { |
| 533 | if (audio_detail_) { |
| 534 | audio_detail_ = NULL; |
| 535 | } else if (volume_view_) { |
| 536 | volume_view_ = NULL; |
| 537 | pop_up_volume_view_ = false; |
| 538 | } |
| 539 | } |
| 540 | |
| 541 | bool TrayAudio::ShouldHideArrow() const { |
| 542 | return true; |
| 543 | } |
| 544 | |
| 545 | bool TrayAudio::ShouldShowLauncher() const { |
| 546 | return false; |
| 547 | } |
| 548 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 549 | void TrayAudio::OnOutputVolumeChanged() { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 550 | float percent = GetVolumeLevel(); |
| 551 | if (tray_view()) |
| 552 | tray_view()->SetVisible(GetInitialVisibility()); |
| 553 | |
| 554 | if (volume_view_) { |
| 555 | volume_view_->SetVolumeLevel(percent); |
| 556 | SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds); |
| 557 | return; |
| 558 | } |
| 559 | pop_up_volume_view_ = true; |
| 560 | PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); |
| 561 | } |
| 562 | |
| 563 | void TrayAudio::OnOutputMuteChanged() { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 564 | if (tray_view()) |
| 565 | tray_view()->SetVisible(GetInitialVisibility()); |
| 566 | |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 567 | if (volume_view_) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 568 | volume_view_->Update(); |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 569 | SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds); |
| 570 | } else { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 571 | pop_up_volume_view_ = true; |
| 572 | PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false); |
| 573 | } |
| 574 | } |
| 575 | |
Torne (Richard Coles) | b2df76e | 2013-05-13 16:52:09 +0100 | [diff] [blame] | 576 | void TrayAudio::OnInputGainChanged() { |
| 577 | } |
| 578 | |
| 579 | void TrayAudio::OnInputMuteChanged() { |
| 580 | } |
| 581 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 582 | void TrayAudio::OnAudioNodesChanged() { |
| 583 | Update(); |
| 584 | } |
| 585 | |
| 586 | void TrayAudio::OnActiveOutputNodeChanged() { |
| 587 | Update(); |
| 588 | } |
| 589 | |
| 590 | void TrayAudio::OnActiveInputNodeChanged() { |
| 591 | Update(); |
| 592 | } |
| 593 | |
| 594 | void TrayAudio::Update() { |
Torne (Richard Coles) | b2df76e | 2013-05-13 16:52:09 +0100 | [diff] [blame] | 595 | if (tray_view()) |
| 596 | tray_view()->SetVisible(GetInitialVisibility()); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 597 | if (audio_detail_) |
| 598 | audio_detail_->Update(); |
Torne (Richard Coles) | b2df76e | 2013-05-13 16:52:09 +0100 | [diff] [blame] | 599 | if (volume_view_) { |
| 600 | volume_view_->SetVolumeLevel(GetVolumeLevel()); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 601 | volume_view_->Update(); |
Torne (Richard Coles) | b2df76e | 2013-05-13 16:52:09 +0100 | [diff] [blame] | 602 | } |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 603 | } |
| 604 | |
| 605 | } // namespace internal |
| 606 | } // namespace ash |