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 InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED); 249 } 250 break; 251 } 252 253 case B_MEDIA_NEW_PARAMETER_VALUE: 254 if (IsTracking()) 255 break; 256 257 SetValue((int32)fMixerControl->Volume()); 258 break; 259 260 case B_ABOUT_REQUESTED: 261 (new BAlert("About Volume Control", "Volume Control\n" 262 " Written by Jérôme DUVAL, and Axel Dörfler.\n\n" 263 "Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku", 264 "OK"))->Go(NULL); 265 break; 266 267 default: 268 return BView::MessageReceived(msg); 269 } 270 } 271 272 273 status_t 274 VolumeControl::Invoke(BMessage* message) 275 { 276 if (fBeep && fOriginalValue != Value() && message == NULL) { 277 beep(); 278 fOriginalValue = Value(); 279 } 280 281 fMixerControl->SetVolume(Value()); 282 283 return BSlider::Invoke(message); 284 } 285 286 287 void 288 VolumeControl::DrawBar() 289 { 290 BRect frame = BarFrame(); 291 BView* view = OffscreenView(); 292 293 if (be_control_look != NULL) { 294 uint32 flags = be_control_look->Flags(this); 295 rgb_color base = LowColor(); 296 rgb_color rightFillColor = (rgb_color){255, 109, 38, 255}; 297 rgb_color leftFillColor = (rgb_color){116, 224, 0, 255}; 298 299 int32 min, max; 300 GetLimits(&min, &max); 301 float position = 1.0f * (max - min - max) / (max - min); 302 303 be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor, 304 rightFillColor, position, flags, Orientation()); 305 return; 306 } 307 308 BSlider::DrawBar(); 309 } 310 311 312 const char* 313 VolumeControl::UpdateText() const 314 { 315 if (!IsEnabled()) 316 return NULL; 317 318 snprintf(fText, sizeof(fText), "%ld dB", Value()); 319 return fText; 320 } 321 322 323 void 324 VolumeControl::_InitVolume(int32 volumeWhich) 325 { 326 const char* errorString = NULL; 327 float volume = 0.0; 328 fMixerControl = new MixerControl(volumeWhich, &volume, &errorString); 329 330 if (errorString != NULL) { 331 SetLabel(errorString); 332 SetLimits(-60, 18); 333 } else { 334 SetLabel("Volume"); 335 SetLimits((int32)floorf(fMixerControl->Minimum()), 336 (int32)ceilf(fMixerControl->Maximum())); 337 } 338 339 SetEnabled(errorString == NULL); 340 341 fOriginalValue = (int32)volume; 342 SetValue((int32)volume); 343 } 344 345 346 float 347 VolumeControl::_PointForValue(int32 value) const 348 { 349 int32 min, max; 350 GetLimits(&min, &max); 351 352 if (Orientation() == B_HORIZONTAL) { 353 return ceilf(1.0f * (value - min) / (max - min) 354 * (BarFrame().Width() - 2) + BarFrame().left + 1); 355 } else { 356 return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min) 357 * BarFrame().Height()); 358 } 359 } 360 361 362 bool 363 VolumeControl::_IsReplicant() const 364 { 365 return dynamic_cast<VolumeWindow*>(Window()) == NULL; 366 } 367