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