1 /* 2 * Copyright 2003-2009, 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 if (oldValue > newValue) { 227 // movement from right to left 228 fMinSnap = _PointForValue(-4); 229 fMaxSnap = _PointForValue(1); 230 } else { 231 // movement from left to right 232 fMinSnap = _PointForValue(-1); 233 fMaxSnap = _PointForValue(4); 234 } 235 236 fSnapping = true; 237 return; 238 } 239 240 BSlider::MouseMoved(where, transit, dragMessage); 241 } 242 243 244 void 245 VolumeControl::MessageReceived(BMessage* msg) 246 { 247 switch (msg->what) { 248 case B_MOUSE_WHEEL_CHANGED: 249 { 250 if (!fMixerControl->Connected()) 251 return; 252 253 // Even though the volume bar is horizontal, we use the more common 254 // vertical mouse wheel change 255 float deltaY = 0.0f; 256 257 msg->FindFloat("be:wheel_delta_y", &deltaY); 258 259 if (deltaY == 0.0f) 260 return; 261 262 int32 currentValue = Value(); 263 int32 newValue = currentValue - int32(deltaY) * 3; 264 265 if (newValue != currentValue) { 266 SetValue(newValue); 267 InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED); 268 } 269 break; 270 } 271 272 case B_MEDIA_NEW_PARAMETER_VALUE: 273 if (IsTracking()) 274 break; 275 276 SetValue((int32)fMixerControl->Volume()); 277 break; 278 279 case B_ABOUT_REQUESTED: 280 (new BAlert("About Volume Control", "Volume Control\n" 281 " Written by Jérôme DUVAL, and Axel Dörfler.\n\n" 282 "Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku", 283 "OK"))->Go(NULL); 284 break; 285 286 case B_SOME_APP_LAUNCHED: 287 case B_SOME_APP_QUIT: 288 { 289 const char* signature; 290 if (msg->FindString("be:signature", &signature) != B_OK) 291 break; 292 293 bool isMediaServer = !strcmp(signature, kMediaServerSignature); 294 bool isAddOnServer = !strcmp(signature, kAddOnServerSignature); 295 if (isMediaServer) 296 fMediaServerRunning = msg->what == B_SOME_APP_LAUNCHED; 297 if (isAddOnServer) 298 fAddOnServerRunning = msg->what == B_SOME_APP_LAUNCHED; 299 300 if (isMediaServer || isAddOnServer) { 301 if (!fMediaServerRunning && !fAddOnServerRunning) { 302 // No media server around 303 SetLabel("No media server running"); 304 SetEnabled(false); 305 } else if (fMediaServerRunning && fAddOnServerRunning) { 306 // HACK! 307 // quit our now invalid instance of the media roster 308 // so that before new nodes are created, 309 // we get a new roster 310 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 311 if (roster != NULL) { 312 roster->Lock(); 313 roster->Quit(); 314 } 315 316 BMessage reconnect(kMsgReconnectVolume); 317 BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1); 318 fConnectRetries = 3; 319 } 320 } 321 break; 322 } 323 324 case kMsgReconnectVolume: 325 _ConnectVolume(); 326 if (!fMixerControl->Connected() && --fConnectRetries > 1) { 327 BMessage reconnect(kMsgReconnectVolume); 328 BMessageRunner::StartSending(this, &reconnect, 329 6000000LL / fConnectRetries, 1); 330 } 331 break; 332 333 default: 334 return BView::MessageReceived(msg); 335 } 336 } 337 338 339 status_t 340 VolumeControl::Invoke(BMessage* message) 341 { 342 if (fBeep && fOriginalValue != Value() && message == NULL) { 343 beep(); 344 fOriginalValue = Value(); 345 } 346 347 fMixerControl->SetVolume(Value()); 348 349 return BSlider::Invoke(message); 350 } 351 352 353 void 354 VolumeControl::DrawBar() 355 { 356 BRect frame = BarFrame(); 357 BView* view = OffscreenView(); 358 359 if (be_control_look != NULL) { 360 uint32 flags = be_control_look->Flags(this); 361 rgb_color base = LowColor(); 362 rgb_color rightFillColor = (rgb_color){255, 109, 38, 255}; 363 rgb_color leftFillColor = (rgb_color){116, 224, 0, 255}; 364 365 int32 min, max; 366 GetLimits(&min, &max); 367 float position = (float)min / (min - max); 368 369 be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor, 370 rightFillColor, position, flags, Orientation()); 371 return; 372 } 373 374 BSlider::DrawBar(); 375 } 376 377 378 const char* 379 VolumeControl::UpdateText() const 380 { 381 if (!IsEnabled()) 382 return NULL; 383 384 snprintf(fText, sizeof(fText), "%ld dB", Value()); 385 return fText; 386 } 387 388 389 void 390 VolumeControl::_DisconnectVolume() 391 { 392 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 393 if (roster != NULL && fMixerControl->GainNode() != media_node::null) { 394 roster->StopWatching(this, fMixerControl->GainNode(), 395 B_MEDIA_NEW_PARAMETER_VALUE); 396 } 397 } 398 399 400 void 401 VolumeControl::_ConnectVolume() 402 { 403 _DisconnectVolume(); 404 405 const char* errorString = NULL; 406 float volume = 0.0; 407 fMixerControl->Connect(fMixerControl->VolumeWhich(), &volume, &errorString); 408 409 if (errorString != NULL) { 410 SetLabel(errorString); 411 SetLimits(-60, 18); 412 } else { 413 SetLabel("Volume"); 414 SetLimits((int32)floorf(fMixerControl->Minimum()), 415 (int32)ceilf(fMixerControl->Maximum())); 416 417 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 418 if (roster != NULL && fMixerControl->GainNode() != media_node::null) { 419 roster->StartWatching(this, fMixerControl->GainNode(), 420 B_MEDIA_NEW_PARAMETER_VALUE); 421 } 422 } 423 424 SetEnabled(errorString == NULL); 425 426 fOriginalValue = (int32)volume; 427 SetValue((int32)volume); 428 } 429 430 431 float 432 VolumeControl::_PointForValue(int32 value) const 433 { 434 int32 min, max; 435 GetLimits(&min, &max); 436 437 if (Orientation() == B_HORIZONTAL) { 438 return ceilf(1.0f * (value - min) / (max - min) 439 * (BarFrame().Width() - 2) + BarFrame().left + 1); 440 } 441 442 return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min) 443 * BarFrame().Height()); 444 } 445 446 447 bool 448 VolumeControl::_IsReplicant() const 449 { 450 return dynamic_cast<VolumeWindow*>(Window()) == NULL; 451 } 452