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