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