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