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_TRANSLATION_CONTEXT 35 #define B_TRANSLATION_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 archive->SendReply(new BMessage(B_QUIT_REQUESTED)); 83 } 84 85 86 VolumeControl::~VolumeControl() 87 { 88 delete fMixerControl; 89 } 90 91 92 status_t 93 VolumeControl::Archive(BMessage* into, bool deep) const 94 { 95 status_t status; 96 97 status = BView::Archive(into, deep); 98 if (status < B_OK) 99 return status; 100 101 status = into->AddString("add_on", kAppSignature); 102 if (status < B_OK) 103 return status; 104 105 status = into->AddBool("beep", fBeep); 106 if (status != B_OK) 107 return status; 108 109 return into->AddInt32("volume which", fMixerControl->VolumeWhich()); 110 } 111 112 113 VolumeControl* 114 VolumeControl::Instantiate(BMessage* archive) 115 { 116 if (!validate_instantiation(archive, "VolumeControl")) 117 return NULL; 118 119 return new VolumeControl(archive); 120 } 121 122 123 void 124 VolumeControl::AttachedToWindow() 125 { 126 BSlider::AttachedToWindow(); 127 128 if (_IsReplicant()) 129 SetEventMask(0, 0); 130 else 131 SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 132 133 be_roster->StartWatching(this, B_REQUEST_LAUNCHED | B_REQUEST_QUIT); 134 135 _ConnectVolume(); 136 137 if (!fMixerControl->Connected()) { 138 // Wait a bit, and try again - the media server might not have been 139 // ready yet 140 BMessage reconnect(kMsgReconnectVolume); 141 BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1); 142 fConnectRetries = 3; 143 } 144 } 145 146 147 void 148 VolumeControl::DetachedFromWindow() 149 { 150 _DisconnectVolume(); 151 152 be_roster->StopWatching(this); 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_SOME_APP_LAUNCHED: 297 case B_SOME_APP_QUIT: 298 { 299 const char* signature; 300 if (msg->FindString("be:signature", &signature) != B_OK) 301 break; 302 303 bool isMediaServer = !strcmp(signature, kMediaServerSignature); 304 bool isAddOnServer = !strcmp(signature, kAddOnServerSignature); 305 if (isMediaServer) 306 fMediaServerRunning = msg->what == B_SOME_APP_LAUNCHED; 307 if (isAddOnServer) 308 fAddOnServerRunning = msg->what == B_SOME_APP_LAUNCHED; 309 310 if (isMediaServer || isAddOnServer) { 311 if (!fMediaServerRunning && !fAddOnServerRunning) { 312 // No media server around 313 SetLabel(B_TRANSLATE("No media server running")); 314 SetEnabled(false); 315 } else if (fMediaServerRunning && fAddOnServerRunning) { 316 // HACK! 317 // quit our now invalid instance of the media roster 318 // so that before new nodes are created, 319 // we get a new roster 320 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 321 if (roster != NULL) { 322 roster->Lock(); 323 roster->Quit(); 324 } 325 326 BMessage reconnect(kMsgReconnectVolume); 327 BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1); 328 fConnectRetries = 3; 329 } 330 } 331 break; 332 } 333 334 case B_QUIT_REQUESTED: 335 Window()->MessageReceived(msg); 336 break; 337 338 case kMsgReconnectVolume: 339 _ConnectVolume(); 340 if (!fMixerControl->Connected() && --fConnectRetries > 1) { 341 BMessage reconnect(kMsgReconnectVolume); 342 BMessageRunner::StartSending(this, &reconnect, 343 6000000LL / fConnectRetries, 1); 344 } 345 break; 346 347 default: 348 return BView::MessageReceived(msg); 349 } 350 } 351 352 353 status_t 354 VolumeControl::Invoke(BMessage* message) 355 { 356 if (fBeep && fOriginalValue != Value() && message == NULL) { 357 beep(); 358 fOriginalValue = Value(); 359 } 360 361 fMixerControl->SetVolume(Value()); 362 363 return BSlider::Invoke(message); 364 } 365 366 367 void 368 VolumeControl::DrawBar() 369 { 370 BRect frame = BarFrame(); 371 BView* view = OffscreenView(); 372 373 if (be_control_look != NULL) { 374 uint32 flags = be_control_look->Flags(this); 375 rgb_color base = LowColor(); 376 rgb_color rightFillColor = (rgb_color){255, 109, 38, 255}; 377 rgb_color leftFillColor = (rgb_color){116, 224, 0, 255}; 378 379 int32 min, max; 380 GetLimits(&min, &max); 381 float position = (float)min / (min - max); 382 383 be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor, 384 rightFillColor, position, flags, Orientation()); 385 return; 386 } 387 388 BSlider::DrawBar(); 389 } 390 391 392 const char* 393 VolumeControl::UpdateText() const 394 { 395 if (!IsEnabled()) 396 return NULL; 397 398 fText.SetToFormat(B_TRANSLATE("%" B_PRId32 " dB"), Value()); 399 return fText.String(); 400 } 401 402 403 void 404 VolumeControl::_DisconnectVolume() 405 { 406 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 407 if (roster != NULL && fMixerControl->GainNode() != media_node::null) { 408 roster->StopWatching(this, fMixerControl->GainNode(), 409 B_MEDIA_NEW_PARAMETER_VALUE); 410 } 411 } 412 413 414 void 415 VolumeControl::_ConnectVolume() 416 { 417 _DisconnectVolume(); 418 419 const char* errorString = NULL; 420 float volume = 0.0; 421 fMixerControl->Connect(fMixerControl->VolumeWhich(), &volume, &errorString); 422 423 if (errorString != NULL) { 424 SetLabel(errorString); 425 SetLimits(-60, 18); 426 } else { 427 SetLabel(B_TRANSLATE("Volume")); 428 SetLimits((int32)floorf(fMixerControl->Minimum()), 429 (int32)ceilf(fMixerControl->Maximum())); 430 431 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 432 if (roster != NULL && fMixerControl->GainNode() != media_node::null) { 433 roster->StartWatching(this, fMixerControl->GainNode(), 434 B_MEDIA_NEW_PARAMETER_VALUE); 435 } 436 } 437 438 SetEnabled(errorString == NULL); 439 440 fOriginalValue = (int32)volume; 441 SetValue((int32)volume); 442 } 443 444 445 float 446 VolumeControl::_PointForValue(int32 value) const 447 { 448 int32 min, max; 449 GetLimits(&min, &max); 450 451 if (Orientation() == B_HORIZONTAL) { 452 return ceilf(1.0f * (value - min) / (max - min) 453 * (BarFrame().Width() - 2) + BarFrame().left + 1); 454 } 455 456 return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min) 457 * BarFrame().Height()); 458 } 459 460 461 bool 462 VolumeControl::_IsReplicant() const 463 { 464 return dynamic_cast<VolumeWindow*>(Window()) == NULL; 465 } 466