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 <Alert.h> 18 #include <Application.h> 19 #include <Beep.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 static const char* kMediaServerSignature = "application/x-vnd.Be.media-server"; 33 static const char* kAddOnServerSignature = "application/x-vnd.Be.addon-host"; 34 35 static const uint32 kMsgReconnectVolume = 'rcms'; 36 37 38 VolumeControl::VolumeControl(int32 volumeWhich, bool beep, BMessage* message) 39 : 40 BSlider("VolumeControl", "Volume", message, 0, 1, B_HORIZONTAL), 41 fMixerControl(new MixerControl(volumeWhich)), 42 fBeep(beep), 43 fSnapping(false), 44 fConnectRetries(0) 45 { 46 font_height fontHeight; 47 GetFontHeight(&fontHeight); 48 SetBarThickness(ceilf((fontHeight.ascent + fontHeight.descent) * 0.7)); 49 50 BRect rect(Bounds()); 51 rect.top = rect.bottom - 7; 52 rect.left = rect.right - 7; 53 BDragger* dragger = new BDragger(rect, this, 54 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 55 AddChild(dragger); 56 } 57 58 59 VolumeControl::VolumeControl(BMessage* archive) 60 : 61 BSlider(archive), 62 fMixerControl(NULL), 63 fSnapping(false), 64 fConnectRetries(0) 65 { 66 if (archive->FindBool("beep", &fBeep) != B_OK) 67 fBeep = false; 68 69 int32 volumeWhich; 70 if (archive->FindInt32("volume which", &volumeWhich) != B_OK) 71 volumeWhich = VOLUME_USE_MIXER; 72 73 fMixerControl = new MixerControl(volumeWhich); 74 } 75 76 77 VolumeControl::~VolumeControl() 78 { 79 delete fMixerControl; 80 } 81 82 83 status_t 84 VolumeControl::Archive(BMessage* into, bool deep) const 85 { 86 status_t status; 87 88 status = BView::Archive(into, deep); 89 if (status < B_OK) 90 return status; 91 92 status = into->AddString("add_on", kAppSignature); 93 if (status < B_OK) 94 return status; 95 96 status = into->AddBool("beep", fBeep); 97 if (status != B_OK) 98 return status; 99 100 return into->AddInt32("volume which", fMixerControl->VolumeWhich()); 101 } 102 103 104 VolumeControl* 105 VolumeControl::Instantiate(BMessage* archive) 106 { 107 if (!validate_instantiation(archive, "VolumeControl")) 108 return NULL; 109 110 return new VolumeControl(archive); 111 } 112 113 114 void 115 VolumeControl::AttachedToWindow() 116 { 117 BSlider::AttachedToWindow(); 118 119 if (_IsReplicant()) 120 SetEventMask(0, 0); 121 else 122 SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 123 124 be_roster->StartWatching(this, B_REQUEST_LAUNCHED | B_REQUEST_QUIT); 125 126 _ConnectVolume(); 127 128 if (!fMixerControl->Connected()) { 129 // Wait a bit, and try again - the media server might not have been 130 // ready yet 131 BMessage reconnect(kMsgReconnectVolume); 132 BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1); 133 fConnectRetries = 3; 134 } 135 } 136 137 138 void 139 VolumeControl::DetachedFromWindow() 140 { 141 _DisconnectVolume(); 142 143 be_roster->StopWatching(this); 144 } 145 146 147 /*! Since we have set a mouse event mask, we don't want to forward all 148 mouse downs to the slider - instead, we only invoke it, which causes a 149 message to our target. Within the VolumeWindow, this will actually 150 cause the window to close. 151 Also, we need to mask out the dragger in this case, or else dragging 152 us will also cause a volume update. 153 */ 154 void 155 VolumeControl::MouseDown(BPoint where) 156 { 157 // Ignore clicks on the dragger 158 int32 viewToken; 159 if (Bounds().Contains(where) && Looper()->CurrentMessage() != NULL 160 && Looper()->CurrentMessage()->FindInt32("_view_token", 161 &viewToken) == B_OK 162 && viewToken != _get_object_token_(this)) 163 return; 164 165 // TODO: investigate why this does not work as expected (the dragger 166 // frame seems to be off) 167 #if 0 168 if (BView* dragger = ChildAt(0)) { 169 if (!dragger->IsHidden() && dragger->Frame().Contains(where)) 170 return; 171 } 172 #endif 173 174 if (!IsEnabled() || !Bounds().Contains(where)) { 175 Invoke(); 176 return; 177 } 178 179 BSlider::MouseDown(where); 180 } 181 182 183 void 184 VolumeControl::MouseUp(BPoint where) 185 { 186 fSnapping = false; 187 BSlider::MouseUp(where); 188 } 189 190 191 /*! Override the BSlider functionality to be able to grab the knob when 192 it's over 0 dB for some pixels. 193 */ 194 void 195 VolumeControl::MouseMoved(BPoint where, uint32 transit, 196 const BMessage* dragMessage) 197 { 198 if (!IsTracking()) { 199 BSlider::MouseMoved(where, transit, dragMessage); 200 return; 201 } 202 203 float cursorPosition = Orientation() == B_HORIZONTAL ? where.x : where.y; 204 205 if (fSnapping && cursorPosition >= fMinSnap && cursorPosition <= fMaxSnap) { 206 // Don't move the slider, keep the current value for a few 207 // more pixels 208 return; 209 } 210 211 fSnapping = false; 212 213 int32 oldValue = Value(); 214 int32 newValue = ValueForPoint(where); 215 if (oldValue == newValue) { 216 BSlider::MouseMoved(where, transit, dragMessage); 217 return; 218 } 219 220 // Check if there is a 0 dB transition at all 221 if ((oldValue < 0 && newValue >= 0) || (oldValue > 0 && newValue <= 0)) { 222 SetValue(0); 223 if (ModificationMessage() != NULL) 224 Messenger().SendMessage(ModificationMessage()); 225 226 float snapPoint = _PointForValue(0); 227 const float kMinSnapOffset = 6; 228 229 if (oldValue > newValue) { 230 // movement from right to left 231 fMinSnap = _PointForValue(-4); 232 if (fabs(snapPoint - fMinSnap) < kMinSnapOffset) 233 fMinSnap = snapPoint - kMinSnapOffset; 234 235 fMaxSnap = _PointForValue(1); 236 } else { 237 // movement from left to right 238 fMinSnap = _PointForValue(-1); 239 fMaxSnap = _PointForValue(4); 240 if (fabs(snapPoint - fMaxSnap) < kMinSnapOffset) 241 fMaxSnap = snapPoint + kMinSnapOffset; 242 } 243 244 fSnapping = true; 245 return; 246 } 247 248 BSlider::MouseMoved(where, transit, dragMessage); 249 } 250 251 252 void 253 VolumeControl::MessageReceived(BMessage* msg) 254 { 255 switch (msg->what) { 256 case B_MOUSE_WHEEL_CHANGED: 257 { 258 if (!fMixerControl->Connected()) 259 return; 260 261 // Even though the volume bar is horizontal, we use the more common 262 // vertical mouse wheel change 263 float deltaY = 0.0f; 264 265 msg->FindFloat("be:wheel_delta_y", &deltaY); 266 267 if (deltaY == 0.0f) 268 return; 269 270 int32 currentValue = Value(); 271 int32 newValue = currentValue - int32(deltaY) * 3; 272 273 if (newValue != currentValue) { 274 SetValue(newValue); 275 InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED); 276 } 277 break; 278 } 279 280 case B_MEDIA_NEW_PARAMETER_VALUE: 281 if (IsTracking()) 282 break; 283 284 SetValue((int32)fMixerControl->Volume()); 285 break; 286 287 case B_ABOUT_REQUESTED: 288 (new BAlert("About Volume Control", "Volume Control\n" 289 " Written by Jérôme DUVAL, and Axel Dörfler.\n\n" 290 "Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku", 291 "OK"))->Go(NULL); 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("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 snprintf(fText, sizeof(fText), "%ld dB", Value()); 393 return fText; 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("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