1 /* 2 * Copyright 2003-2010, Haiku, Inc. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Jérôme Duval 7 * François Revol 8 * Axel Dörfler, axeld@pinc-software.de. 9 */ 10 11 12 #include "VolumeControl.h" 13 14 #include <string.h> 15 #include <stdio.h> 16 17 #include <Application.h> 18 #include <Beep.h> 19 #include <Catalog.h> 20 #include <ControlLook.h> 21 #include <Dragger.h> 22 #include <MediaRoster.h> 23 #include <MessageRunner.h> 24 25 #include <AppMisc.h> 26 27 #include "desklink.h" 28 #include "MixerControl.h" 29 #include "VolumeWindow.h" 30 31 32 #undef B_TRANSLATION_CONTEXT 33 #define B_TRANSLATION_CONTEXT "VolumeControl" 34 35 36 static const uint32 kMsgReconnectVolume = 'rcms'; 37 38 39 VolumeControl::VolumeControl(int32 volumeWhich, bool beep, BMessage* message) 40 : 41 BSlider("VolumeControl", B_TRANSLATE("Volume"), 42 message, 0, 1, B_HORIZONTAL), 43 fMixerControl(new MixerControl(volumeWhich)), 44 fBeep(beep), 45 fSnapping(false), 46 fConnectRetries(0) 47 { 48 font_height fontHeight; 49 GetFontHeight(&fontHeight); 50 SetBarThickness(ceilf((fontHeight.ascent + fontHeight.descent) * 0.7)); 51 52 BRect rect(Bounds()); 53 rect.top = rect.bottom - 7; 54 rect.left = rect.right - 7; 55 BDragger* dragger = new BDragger(rect, this, 56 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 57 AddChild(dragger); 58 } 59 60 61 VolumeControl::VolumeControl(BMessage* archive) 62 : 63 BSlider(archive), 64 fMixerControl(NULL), 65 fSnapping(false), 66 fConnectRetries(0) 67 { 68 if (archive->FindBool("beep", &fBeep) != B_OK) 69 fBeep = false; 70 71 int32 volumeWhich; 72 if (archive->FindInt32("volume which", &volumeWhich) != B_OK) 73 volumeWhich = VOLUME_USE_MIXER; 74 75 fMixerControl = new MixerControl(volumeWhich); 76 77 BMessage msg(B_QUIT_REQUESTED); 78 archive->SendReply(&msg); 79 } 80 81 82 VolumeControl::~VolumeControl() 83 { 84 delete fMixerControl; 85 } 86 87 88 status_t 89 VolumeControl::Archive(BMessage* into, bool deep) const 90 { 91 status_t status; 92 93 status = BView::Archive(into, deep); 94 if (status < B_OK) 95 return status; 96 97 status = into->AddString("add_on", kAppSignature); 98 if (status < B_OK) 99 return status; 100 101 status = into->AddBool("beep", fBeep); 102 if (status != B_OK) 103 return status; 104 105 return into->AddInt32("volume which", fMixerControl->VolumeWhich()); 106 } 107 108 109 VolumeControl* 110 VolumeControl::Instantiate(BMessage* archive) 111 { 112 if (!validate_instantiation(archive, "VolumeControl")) 113 return NULL; 114 115 return new VolumeControl(archive); 116 } 117 118 119 void 120 VolumeControl::AttachedToWindow() 121 { 122 BSlider::AttachedToWindow(); 123 124 if (_IsReplicant()) 125 SetEventMask(0, 0); 126 else 127 SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 128 129 BMediaRoster* roster = BMediaRoster::Roster(); 130 roster->StartWatching(BMessenger(this), B_MEDIA_SERVER_STARTED); 131 roster->StartWatching(BMessenger(this), B_MEDIA_SERVER_QUIT); 132 133 _ConnectVolume(); 134 135 if (!fMixerControl->Connected()) { 136 // Wait a bit, and try again - the media server might not have been 137 // ready yet 138 BMessage reconnect(kMsgReconnectVolume); 139 BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1); 140 fConnectRetries = 3; 141 } 142 } 143 144 145 void 146 VolumeControl::DetachedFromWindow() 147 { 148 _DisconnectVolume(); 149 150 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 151 roster->StopWatching(BMessenger(this), B_MEDIA_SERVER_STARTED); 152 roster->StopWatching(BMessenger(this), B_MEDIA_SERVER_QUIT); 153 } 154 155 156 /*! Since we have set a mouse event mask, we don't want to forward all 157 mouse downs to the slider - instead, we only invoke it, which causes a 158 message to our target. Within the VolumeWindow, this will actually 159 cause the window to close. 160 Also, we need to mask out the dragger in this case, or else dragging 161 us will also cause a volume update. 162 */ 163 void 164 VolumeControl::MouseDown(BPoint where) 165 { 166 // Ignore clicks on the dragger 167 int32 viewToken; 168 if (Bounds().Contains(where) && Looper()->CurrentMessage() != NULL 169 && Looper()->CurrentMessage()->FindInt32("_view_token", 170 &viewToken) == B_OK 171 && viewToken != _get_object_token_(this)) 172 return; 173 174 // TODO: investigate why this does not work as expected (the dragger 175 // frame seems to be off) 176 #if 0 177 if (BView* dragger = ChildAt(0)) { 178 if (!dragger->IsHidden() && dragger->Frame().Contains(where)) 179 return; 180 } 181 #endif 182 183 if (!IsEnabled() || !Bounds().Contains(where)) { 184 Invoke(); 185 return; 186 } 187 188 BSlider::MouseDown(where); 189 } 190 191 192 void 193 VolumeControl::MouseUp(BPoint where) 194 { 195 fSnapping = false; 196 BSlider::MouseUp(where); 197 } 198 199 200 /*! Override the BSlider functionality to be able to grab the knob when 201 it's over 0 dB for some pixels. 202 */ 203 void 204 VolumeControl::MouseMoved(BPoint where, uint32 transit, 205 const BMessage* dragMessage) 206 { 207 if (!IsTracking()) { 208 BSlider::MouseMoved(where, transit, dragMessage); 209 return; 210 } 211 212 float cursorPosition = Orientation() == B_HORIZONTAL ? where.x : where.y; 213 214 if (fSnapping && cursorPosition >= fMinSnap && cursorPosition <= fMaxSnap) { 215 // Don't move the slider, keep the current value for a few 216 // more pixels 217 return; 218 } 219 220 fSnapping = false; 221 222 int32 oldValue = Value(); 223 int32 newValue = ValueForPoint(where); 224 if (oldValue == newValue) { 225 BSlider::MouseMoved(where, transit, dragMessage); 226 return; 227 } 228 229 // Check if there is a 0 dB transition at all 230 if ((oldValue < 0 && newValue >= 0) || (oldValue > 0 && newValue <= 0)) { 231 SetValue(0); 232 if (ModificationMessage() != NULL) 233 Messenger().SendMessage(ModificationMessage()); 234 235 float snapPoint = _PointForValue(0); 236 const float kMinSnapOffset = 6; 237 238 if (oldValue > newValue) { 239 // movement from right to left 240 fMinSnap = _PointForValue(-4); 241 if (fabs(snapPoint - fMinSnap) < kMinSnapOffset) 242 fMinSnap = snapPoint - kMinSnapOffset; 243 244 fMaxSnap = _PointForValue(1); 245 } else { 246 // movement from left to right 247 fMinSnap = _PointForValue(-1); 248 fMaxSnap = _PointForValue(4); 249 if (fabs(snapPoint - fMaxSnap) < kMinSnapOffset) 250 fMaxSnap = snapPoint + kMinSnapOffset; 251 } 252 253 fSnapping = true; 254 return; 255 } 256 257 BSlider::MouseMoved(where, transit, dragMessage); 258 } 259 260 261 void 262 VolumeControl::MessageReceived(BMessage* msg) 263 { 264 switch (msg->what) { 265 case B_MOUSE_WHEEL_CHANGED: 266 { 267 if (!fMixerControl->Connected()) 268 return; 269 270 // Even though the volume bar is horizontal, we use the more common 271 // vertical mouse wheel change 272 float deltaY = 0.0f; 273 274 msg->FindFloat("be:wheel_delta_y", &deltaY); 275 276 if (deltaY == 0.0f) 277 return; 278 279 int32 currentValue = Value(); 280 int32 newValue = currentValue - int32(deltaY) * 3; 281 282 if (newValue != currentValue) { 283 SetValue(newValue); 284 InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED); 285 } 286 break; 287 } 288 289 case B_MEDIA_NEW_PARAMETER_VALUE: 290 if (IsTracking()) 291 break; 292 293 SetValue((int32)fMixerControl->Volume()); 294 break; 295 296 case B_MEDIA_SERVER_STARTED: 297 { 298 BMessage reconnect(kMsgReconnectVolume); 299 BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1); 300 fConnectRetries = 3; 301 break; 302 } 303 304 case B_MEDIA_SERVER_QUIT: 305 { 306 // No media server around 307 SetLabel(B_TRANSLATE("No media server running")); 308 SetEnabled(false); 309 break; 310 } 311 312 case B_QUIT_REQUESTED: 313 Window()->MessageReceived(msg); 314 break; 315 316 case kMsgReconnectVolume: 317 _ConnectVolume(); 318 if (!fMixerControl->Connected() && --fConnectRetries > 1) { 319 BMessage reconnect(kMsgReconnectVolume); 320 BMessageRunner::StartSending(this, &reconnect, 321 6000000LL / fConnectRetries, 1); 322 } 323 break; 324 325 default: 326 return BView::MessageReceived(msg); 327 } 328 } 329 330 331 status_t 332 VolumeControl::Invoke(BMessage* message) 333 { 334 if (fBeep && fOriginalValue != Value() && message == NULL) { 335 beep(); 336 fOriginalValue = Value(); 337 } 338 339 fMixerControl->SetVolume(Value()); 340 341 return BSlider::Invoke(message); 342 } 343 344 345 void 346 VolumeControl::DrawBar() 347 { 348 BRect frame = BarFrame(); 349 BView* view = OffscreenView(); 350 351 if (be_control_look != NULL) { 352 uint32 flags = be_control_look->Flags(this); 353 rgb_color base = LowColor(); 354 rgb_color rightFillColor = (rgb_color){255, 109, 38, 255}; 355 rgb_color leftFillColor = (rgb_color){116, 224, 0, 255}; 356 357 int32 min, max; 358 GetLimits(&min, &max); 359 float position = (float)min / (min - max); 360 361 be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor, 362 rightFillColor, position, flags, Orientation()); 363 return; 364 } 365 366 BSlider::DrawBar(); 367 } 368 369 370 const char* 371 VolumeControl::UpdateText() const 372 { 373 if (!IsEnabled()) 374 return NULL; 375 376 fText.SetToFormat(B_TRANSLATE("%" B_PRId32 " dB"), Value()); 377 return fText.String(); 378 } 379 380 381 void 382 VolumeControl::_DisconnectVolume() 383 { 384 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 385 if (roster != NULL && fMixerControl->GainNode() != media_node::null) { 386 roster->StopWatching(this, fMixerControl->GainNode(), 387 B_MEDIA_NEW_PARAMETER_VALUE); 388 } 389 } 390 391 392 void 393 VolumeControl::_ConnectVolume() 394 { 395 _DisconnectVolume(); 396 397 const char* errorString = NULL; 398 float volume = 0.0; 399 fMixerControl->Connect(fMixerControl->VolumeWhich(), &volume, &errorString); 400 401 if (errorString != NULL) { 402 SetLabel(errorString); 403 SetLimits(-60, 18); 404 } else { 405 SetLabel(B_TRANSLATE("Volume")); 406 SetLimits((int32)floorf(fMixerControl->Minimum()), 407 (int32)ceilf(fMixerControl->Maximum())); 408 409 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 410 if (roster != NULL && fMixerControl->GainNode() != media_node::null) { 411 roster->StartWatching(this, fMixerControl->GainNode(), 412 B_MEDIA_NEW_PARAMETER_VALUE); 413 } 414 } 415 416 SetEnabled(errorString == NULL); 417 418 fOriginalValue = (int32)volume; 419 SetValue((int32)volume); 420 } 421 422 423 float 424 VolumeControl::_PointForValue(int32 value) const 425 { 426 int32 min, max; 427 GetLimits(&min, &max); 428 429 if (Orientation() == B_HORIZONTAL) { 430 return ceilf(1.0f * (value - min) / (max - min) 431 * (BarFrame().Width() - 2) + BarFrame().left + 1); 432 } 433 434 return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min) 435 * BarFrame().Height()); 436 } 437 438 439 bool 440 VolumeControl::_IsReplicant() const 441 { 442 return dynamic_cast<VolumeWindow*>(Window()) == NULL; 443 } 444