1/*
2 * Copyright 2005-2015, Haiku Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stefano Ceccherini (stefano.ceccherini@gmail.com)
7 *		Stephan A��mus <superstippi@gmx.de>
8 */
9
10#include <ChannelSlider.h>
11
12#include <new>
13
14#include <Bitmap.h>
15#include <ControlLook.h>
16#include <Debug.h>
17#include <PropertyInfo.h>
18#include <Screen.h>
19#include <Window.h>
20
21
22const static unsigned char
23kVerticalKnobData[] = {
24	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
25	0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
26	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0xff,
27	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0xff,
28	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12,
29	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12,
30	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12,
31	0xff, 0x00, 0x3f, 0x3f, 0xcb, 0xcb, 0xcb, 0xcb, 0x3f, 0x3f, 0x00, 0x12,
32	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12,
33	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12,
34	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12,
35	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12,
36	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12,
37	0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x12,
38	0xff, 0xff, 0xff, 0xff, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0xff
39};
40
41
42const static unsigned char
43kHorizontalKnobData[] = {
44	0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
45	0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
46	0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x3f,
47	0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0xff, 0xff,
48	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xcb, 0x3f, 0x3f, 0x3f, 0x3f,
49	0x3f, 0x00, 0x12, 0xff, 0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xcb,
50	0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12, 0xff, 0xff, 0x00, 0x3f, 0x3f,
51	0x3f, 0x3f, 0x3f, 0xcb, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12, 0xff,
52	0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xcb, 0x3f, 0x3f, 0x3f, 0x3f,
53	0x3f, 0x00, 0x12, 0xff, 0xff, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
54	0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12, 0xff, 0xff, 0x00, 0x3f, 0x3f,
55	0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x00, 0x12, 0xff,
56	0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
57	0x00, 0x0c, 0x12, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0x12, 0x12, 0x12,
58	0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
59	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
60};
61
62
63static property_info
64sPropertyInfo[] = {
65	{ "Orientation",
66		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
67		{ B_DIRECT_SPECIFIER, 0 }, NULL, 0, { B_INT32_TYPE }
68	},
69
70	{ 0 }
71};
72
73
74const static float kPadding = 3.0;
75
76
77BChannelSlider::BChannelSlider(BRect area, const char* name, const char* label,
78	BMessage* model, int32 channels, uint32 resizeMode, uint32 flags)
79	: BChannelControl(area, name, label, model, channels, resizeMode, flags)
80{
81	_InitData();
82}
83
84
85BChannelSlider::BChannelSlider(BRect area, const char* name, const char* label,
86	BMessage* model, orientation orientation, int32 channels,
87		uint32 resizeMode, uint32 flags)
88	: BChannelControl(area, name, label, model, channels, resizeMode, flags)
89
90{
91	_InitData();
92	SetOrientation(orientation);
93}
94
95
96BChannelSlider::BChannelSlider(const char* name, const char* label,
97	BMessage* model, orientation orientation, int32 channels,
98		uint32 flags)
99	: BChannelControl(name, label, model, channels, flags)
100
101{
102	_InitData();
103	SetOrientation(orientation);
104}
105
106
107BChannelSlider::BChannelSlider(BMessage* archive)
108	: BChannelControl(archive)
109{
110	_InitData();
111
112	orientation orient;
113	if (archive->FindInt32("_orient", (int32*)&orient) == B_OK)
114		SetOrientation(orient);
115}
116
117
118BChannelSlider::~BChannelSlider()
119{
120	delete fBacking;
121	delete fLeftKnob;
122	delete fMidKnob;
123	delete fRightKnob;
124	delete[] fInitialValues;
125}
126
127
128BArchivable*
129BChannelSlider::Instantiate(BMessage* archive)
130{
131	if (validate_instantiation(archive, "BChannelSlider"))
132		return new (std::nothrow) BChannelSlider(archive);
133
134	return NULL;
135}
136
137
138status_t
139BChannelSlider::Archive(BMessage* into, bool deep) const
140{
141	status_t status = BChannelControl::Archive(into, deep);
142	if (status == B_OK)
143		status = into->AddInt32("_orient", (int32)Orientation());
144
145	return status;
146}
147
148
149void
150BChannelSlider::AttachedToWindow()
151{
152	AdoptParentColors();
153	BChannelControl::AttachedToWindow();
154}
155
156
157void
158BChannelSlider::AllAttached()
159{
160	BChannelControl::AllAttached();
161}
162
163
164void
165BChannelSlider::DetachedFromWindow()
166{
167	BChannelControl::DetachedFromWindow();
168}
169
170
171void
172BChannelSlider::AllDetached()
173{
174	BChannelControl::AllDetached();
175}
176
177
178void
179BChannelSlider::MessageReceived(BMessage* message)
180{
181	if (message->what == B_COLORS_UPDATED
182		&& fBacking != NULL && fBackingView != NULL) {
183		rgb_color color;
184		if (message->FindColor(ui_color_name(B_PANEL_BACKGROUND_COLOR), &color)
185				== B_OK
186			&& fBacking->Lock()) {
187
188			if (fBackingView->LockLooper()) {
189				fBackingView->SetLowColor(color);
190				fBackingView->UnlockLooper();
191			}
192			fBacking->Unlock();
193		}
194	}
195
196	switch (message->what) {
197		case B_SET_PROPERTY: {
198		case B_GET_PROPERTY:
199			BMessage reply(B_REPLY);
200			int32 index = 0;
201			BMessage specifier;
202			int32 what = 0;
203			const char* property = NULL;
204			bool handled = false;
205			status_t status = message->GetCurrentSpecifier(&index, &specifier,
206				&what, &property);
207			BPropertyInfo propInfo(sPropertyInfo);
208			if (status == B_OK
209				&& propInfo.FindMatch(message, index, &specifier, what,
210					property) >= 0) {
211				handled = true;
212				if (message->what == B_SET_PROPERTY) {
213					orientation orient;
214					if (specifier.FindInt32("data", (int32*)&orient) == B_OK) {
215						SetOrientation(orient);
216						Invalidate(Bounds());
217					}
218				} else if (message->what == B_GET_PROPERTY)
219					reply.AddInt32("result", (int32)Orientation());
220				else
221					status = B_BAD_SCRIPT_SYNTAX;
222			}
223
224			if (handled) {
225				reply.AddInt32("error", status);
226				message->SendReply(&reply);
227			} else {
228				BChannelControl::MessageReceived(message);
229			}
230		}	break;
231
232		default:
233			BChannelControl::MessageReceived(message);
234			break;
235	}
236}
237
238
239void
240BChannelSlider::Draw(BRect updateRect)
241{
242	_UpdateFontDimens();
243	_DrawThumbs();
244
245	SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
246	BRect bounds(Bounds());
247	if (Label()) {
248		float labelWidth = StringWidth(Label());
249		DrawString(Label(), BPoint((bounds.Width() - labelWidth) / 2.0,
250			fBaseLine));
251	}
252
253	if (MinLimitLabel()) {
254		if (fIsVertical) {
255			if (MinLimitLabel()) {
256				float x = (bounds.Width() - StringWidth(MinLimitLabel()))
257					/ 2.0;
258				DrawString(MinLimitLabel(), BPoint(x, bounds.bottom
259					- kPadding));
260			}
261		} else {
262			if (MinLimitLabel()) {
263				DrawString(MinLimitLabel(), BPoint(kPadding, bounds.bottom
264					- kPadding));
265			}
266		}
267	}
268
269	if (MaxLimitLabel()) {
270		if (fIsVertical) {
271			if (MaxLimitLabel()) {
272				float x = (bounds.Width() - StringWidth(MaxLimitLabel()))
273					/ 2.0;
274				DrawString(MaxLimitLabel(), BPoint(x, 2 * fLineFeed));
275			}
276		} else {
277			if (MaxLimitLabel()) {
278				DrawString(MaxLimitLabel(), BPoint(bounds.right - kPadding
279					- StringWidth(MaxLimitLabel()), bounds.bottom - kPadding));
280			}
281		}
282	}
283}
284
285
286void
287BChannelSlider::MouseDown(BPoint where)
288{
289	if (!IsEnabled())
290		BControl::MouseDown(where);
291	else {
292		fCurrentChannel = -1;
293		fMinPoint = 0;
294
295		// Search the channel on which the mouse was over
296		int32 numChannels = CountChannels();
297		for (int32 channel = 0; channel < numChannels; channel++) {
298			BRect frame = ThumbFrameFor(channel);
299			frame.OffsetBy(fClickDelta);
300
301			float range = ThumbRangeFor(channel);
302			if (fIsVertical) {
303				fMinPoint = frame.top + frame.Height() / 2;
304				frame.bottom += range;
305			} else {
306				// TODO: Fix this, the clickzone isn't perfect
307				frame.right += range;
308				fMinPoint = frame.Width();
309			}
310
311			// Click was on a slider.
312			if (frame.Contains(where)) {
313				fCurrentChannel = channel;
314				SetCurrentChannel(channel);
315				break;
316			}
317		}
318
319		// Click wasn't on a slider. Bail out.
320		if (fCurrentChannel == -1)
321			return;
322
323		uint32 buttons = 0;
324		BMessage* currentMessage = Window()->CurrentMessage();
325		if (currentMessage != NULL)
326			currentMessage->FindInt32("buttons", (int32*)&buttons);
327
328		fAllChannels = (buttons & B_SECONDARY_MOUSE_BUTTON) == 0;
329
330		if (fInitialValues != NULL && fAllChannels) {
331			delete[] fInitialValues;
332			fInitialValues = NULL;
333		}
334
335		if (fInitialValues == NULL)
336			fInitialValues = new (std::nothrow) int32[numChannels];
337
338		if (fInitialValues) {
339			if (fAllChannels) {
340				for (int32 i = 0; i < numChannels; i++)
341					fInitialValues[i] = ValueFor(i);
342			} else {
343				fInitialValues[fCurrentChannel] = ValueFor(fCurrentChannel);
344			}
345		}
346
347		if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) {
348			if (!IsTracking()) {
349				SetTracking(true);
350				_DrawThumbs();
351				Flush();
352			}
353
354			_MouseMovedCommon(where, B_ORIGIN);
355			SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
356				B_NO_POINTER_HISTORY);
357		} else {
358			do {
359				snooze(30000);
360				GetMouse(&where, &buttons);
361				_MouseMovedCommon(where, B_ORIGIN);
362			} while (buttons != 0);
363			_FinishChange();
364			fCurrentChannel = -1;
365			fAllChannels = false;
366		}
367	}
368}
369
370
371void
372BChannelSlider::MouseUp(BPoint where)
373{
374	if (IsEnabled() && IsTracking()) {
375		_FinishChange();
376		SetTracking(false);
377		fAllChannels = false;
378		fCurrentChannel = -1;
379		fMinPoint = 0;
380	} else {
381		BChannelControl::MouseUp(where);
382	}
383}
384
385
386void
387BChannelSlider::MouseMoved(BPoint where, uint32 code, const BMessage* message)
388{
389	if (IsEnabled() && IsTracking())
390		_MouseMovedCommon(where, B_ORIGIN);
391	else
392		BChannelControl::MouseMoved(where, code, message);
393}
394
395
396void
397BChannelSlider::WindowActivated(bool state)
398{
399	BChannelControl::WindowActivated(state);
400}
401
402
403void
404BChannelSlider::KeyDown(const char* bytes, int32 numBytes)
405{
406	BControl::KeyDown(bytes, numBytes);
407}
408
409
410void
411BChannelSlider::KeyUp(const char* bytes, int32 numBytes)
412{
413	BView::KeyUp(bytes, numBytes);
414}
415
416
417void
418BChannelSlider::FrameResized(float newWidth, float newHeight)
419{
420	BChannelControl::FrameResized(newWidth, newHeight);
421
422	delete fBacking;
423	fBacking = NULL;
424
425	Invalidate(Bounds());
426}
427
428
429void
430BChannelSlider::SetFont(const BFont* font, uint32 mask)
431{
432	BChannelControl::SetFont(font, mask);
433}
434
435
436void
437BChannelSlider::MakeFocus(bool focusState)
438{
439	if (focusState && !IsFocus())
440		fFocusChannel = -1;
441	BChannelControl::MakeFocus(focusState);
442}
443
444
445void
446BChannelSlider::GetPreferredSize(float* width, float* height)
447{
448	_UpdateFontDimens();
449
450	if (fIsVertical) {
451		*width = 11.0 * CountChannels();
452		*width = max_c(*width, ceilf(StringWidth(Label())));
453		*width = max_c(*width, ceilf(StringWidth(MinLimitLabel())));
454		*width = max_c(*width, ceilf(StringWidth(MaxLimitLabel())));
455		*width += kPadding * 2.0;
456
457		*height = (fLineFeed * 3.0) + (kPadding * 2.0) + 147.0;
458	} else {
459		*width = max_c(64.0, ceilf(StringWidth(Label())));
460		*width = max_c(*width, ceilf(StringWidth(MinLimitLabel()))
461			+ ceilf(StringWidth(MaxLimitLabel())) + 10.0);
462		*width += kPadding * 2.0;
463
464		*height = 11.0 * CountChannels() + (fLineFeed * 2.0)
465			+ (kPadding * 2.0);
466	}
467}
468
469
470BHandler*
471BChannelSlider::ResolveSpecifier(BMessage* message, int32 index,
472	BMessage* specifier, int32 form, const char* property)
473{
474	BHandler* target = this;
475	BPropertyInfo propertyInfo(sPropertyInfo);
476	if (propertyInfo.FindMatch(message, index, specifier, form,
477		property) != B_OK) {
478		target = BChannelControl::ResolveSpecifier(message, index, specifier,
479			form, property);
480	}
481	return target;
482}
483
484
485status_t
486BChannelSlider::GetSupportedSuites(BMessage* data)
487{
488	if (data == NULL)
489		return B_BAD_VALUE;
490
491	status_t err = data->AddString("suites", "suite/vnd.Be-channel-slider");
492
493	BPropertyInfo propInfo(sPropertyInfo);
494	if (err == B_OK)
495		err = data->AddFlat("messages", &propInfo);
496
497	if (err == B_OK)
498		return BChannelControl::GetSupportedSuites(data);
499	return err;
500}
501
502
503void
504BChannelSlider::SetEnabled(bool on)
505{
506	BChannelControl::SetEnabled(on);
507}
508
509
510orientation
511BChannelSlider::Orientation() const
512{
513	return fIsVertical ? B_VERTICAL : B_HORIZONTAL;
514}
515
516
517void
518BChannelSlider::SetOrientation(orientation orientation)
519{
520	bool isVertical = orientation == B_VERTICAL;
521	if (isVertical != fIsVertical) {
522		fIsVertical = isVertical;
523		InvalidateLayout();
524		Invalidate(Bounds());
525	}
526}
527
528
529int32
530BChannelSlider::MaxChannelCount() const
531{
532	return 32;
533}
534
535
536bool
537BChannelSlider::SupportsIndividualLimits() const
538{
539	return false;
540}
541
542
543void
544BChannelSlider::DrawChannel(BView* into, int32 channel, BRect area,
545	bool pressed)
546{
547	float hCenter = area.Width() / 2;
548	float vCenter = area.Height() / 2;
549
550	BPoint leftTop;
551	BPoint bottomRight;
552	if (fIsVertical) {
553		leftTop.Set(area.left + hCenter, area.top + vCenter);
554		bottomRight.Set(leftTop.x, leftTop.y + ThumbRangeFor(channel));
555	} else {
556		leftTop.Set(area.left, area.top + vCenter);
557		bottomRight.Set(area.left + ThumbRangeFor(channel), leftTop.y);
558	}
559
560	DrawGroove(into, channel, leftTop, bottomRight);
561
562	BPoint thumbLocation = leftTop;
563	if (fIsVertical)
564		thumbLocation.y += ThumbDeltaFor(channel);
565	else
566		thumbLocation.x += ThumbDeltaFor(channel);
567
568	DrawThumb(into, channel, thumbLocation, pressed);
569}
570
571
572void
573BChannelSlider::DrawGroove(BView* into, int32 channel, BPoint leftTop,
574	BPoint bottomRight)
575{
576	ASSERT(into != NULL);
577	BRect rect(leftTop, bottomRight);
578
579	rect.InsetBy(-2.5, -2.5);
580	rect.left = floorf(rect.left);
581	rect.top = floorf(rect.top);
582	rect.right = floorf(rect.right);
583	rect.bottom = floorf(rect.bottom);
584	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
585	rgb_color barColor = be_control_look->SliderBarColor(base);
586	uint32 flags = 0;
587	be_control_look->DrawSliderBar(into, rect, rect, base,
588		barColor, flags, Orientation());
589}
590
591
592void
593BChannelSlider::DrawThumb(BView* into, int32 channel, BPoint where,
594	bool pressed)
595{
596	ASSERT(into != NULL);
597
598	const BBitmap* thumb = ThumbFor(channel, pressed);
599	if (thumb == NULL)
600		return;
601
602	BRect bitmapBounds(thumb->Bounds());
603	where.x -= bitmapBounds.right / 2.0;
604	where.y -= bitmapBounds.bottom / 2.0;
605
606	BRect rect(bitmapBounds.OffsetToCopy(where));
607	rect.InsetBy(1, 1);
608	rect.left = floorf(rect.left);
609	rect.top = floorf(rect.top);
610	rect.right = ceilf(rect.right + 0.5);
611	rect.bottom = ceilf(rect.bottom + 0.5);
612	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
613	uint32 flags = 0;
614	be_control_look->DrawSliderThumb(into, rect, rect, base,
615		flags, Orientation());
616}
617
618
619const BBitmap*
620BChannelSlider::ThumbFor(int32 channel, bool pressed)
621{
622	if (fLeftKnob != NULL)
623		return fLeftKnob;
624
625	if (fIsVertical) {
626		fLeftKnob = new (std::nothrow) BBitmap(BRect(0, 0, 11, 14),
627			B_CMAP8);
628		if (fLeftKnob != NULL) {
629			fLeftKnob->SetBits(kVerticalKnobData,
630					sizeof(kVerticalKnobData), 0, B_CMAP8);
631		}
632	} else {
633		fLeftKnob = new (std::nothrow) BBitmap(BRect(0, 0, 14, 11),
634			B_CMAP8);
635		if (fLeftKnob != NULL) {
636			fLeftKnob->SetBits(kHorizontalKnobData,
637					sizeof(kHorizontalKnobData), 0, B_CMAP8);
638		}
639	}
640
641	return fLeftKnob;
642}
643
644
645BRect
646BChannelSlider::ThumbFrameFor(int32 channel)
647{
648	_UpdateFontDimens();
649
650	BRect frame(0.0, 0.0, 0.0, 0.0);
651	const BBitmap* thumb = ThumbFor(channel, false);
652	if (thumb != NULL) {
653		frame = thumb->Bounds();
654		if (fIsVertical) {
655			frame.OffsetBy(channel * frame.Width(), (frame.Height() / 2.0) -
656				(kPadding * 2.0) - 1.0);
657		} else {
658			frame.OffsetBy(frame.Width() / 2.0, channel * frame.Height()
659				+ 1.0);
660		}
661	}
662	return frame;
663}
664
665
666float
667BChannelSlider::ThumbDeltaFor(int32 channel)
668{
669	float delta = 0.0;
670	if (channel >= 0 && channel < MaxChannelCount()) {
671		float range = ThumbRangeFor(channel);
672		int32 limitRange = MaxLimitList()[channel] - MinLimitList()[channel];
673		delta = (ValueList()[channel] - MinLimitList()[channel]) * range
674			/ limitRange;
675
676		if (fIsVertical)
677			delta = range - delta;
678	}
679
680	return delta;
681}
682
683
684float
685BChannelSlider::ThumbRangeFor(int32 channel)
686{
687	_UpdateFontDimens();
688
689	float range = 0;
690	BRect bounds = Bounds();
691	BRect frame = ThumbFrameFor(channel);
692	if (fIsVertical) {
693		// *height = (fLineFeed * 3.0) + (kPadding * 2.0) + 100.0;
694		range = bounds.Height() - frame.Height() - (fLineFeed * 3.0) -
695			(kPadding * 2.0);
696	} else {
697		// *width = some width + kPadding * 2.0;
698		range = bounds.Width() - frame.Width() - (kPadding * 2.0);
699	}
700	return range;
701}
702
703
704// #pragma mark -
705
706
707void
708BChannelSlider::_InitData()
709{
710	_UpdateFontDimens();
711
712	fLeftKnob = NULL;
713	fMidKnob = NULL;
714	fRightKnob = NULL;
715	fBacking = NULL;
716	fBackingView = NULL;
717	fIsVertical = Bounds().Width() / Bounds().Height() < 1;
718	fClickDelta = B_ORIGIN;
719
720	fCurrentChannel = -1;
721	fAllChannels = false;
722	fInitialValues = NULL;
723	fMinPoint = 0;
724	fFocusChannel = -1;
725}
726
727
728void
729BChannelSlider::_FinishChange(bool update)
730{
731	if (fInitialValues != NULL) {
732		bool* inMask = NULL;
733		int32 numChannels = CountChannels();
734		if (!fAllChannels) {
735			inMask = new (std::nothrow) bool[CountChannels()];
736			if (inMask) {
737				for (int i = 0; i < numChannels; i++)
738					inMask[i] = false;
739				inMask[fCurrentChannel] = true;
740			}
741		}
742		InvokeChannel(update ? ModificationMessage() : NULL, 0, numChannels,
743			inMask);
744
745		delete[] inMask;
746	}
747
748	if (!update) {
749		SetTracking(false);
750		Invalidate();
751	}
752}
753
754
755void
756BChannelSlider::_UpdateFontDimens()
757{
758	font_height height;
759	GetFontHeight(&height);
760	fBaseLine = height.ascent + height.leading;
761	fLineFeed = fBaseLine + height.descent;
762}
763
764
765void
766BChannelSlider::_DrawThumbs()
767{
768	if (fBacking == NULL) {
769		// This is the idea: we build a bitmap by taking the coordinates
770		// of the first and last thumb frames (top/left and bottom/right)
771		BRect first = ThumbFrameFor(0);
772		BRect last = ThumbFrameFor(CountChannels() - 1);
773		BRect rect(first.LeftTop(), last.RightBottom());
774
775		if (fIsVertical)
776			rect.top -= ThumbRangeFor(0);
777		else
778			rect.right += ThumbRangeFor(0);
779
780		rect.OffsetTo(B_ORIGIN);
781		fBacking = new (std::nothrow) BBitmap(rect, B_RGB32, true);
782		if (fBacking) {
783			fBackingView = new (std::nothrow) BView(rect, "", 0, B_WILL_DRAW);
784			if (fBackingView) {
785				if (fBacking->Lock()) {
786					fBacking->AddChild(fBackingView);
787					fBackingView->SetFontSize(10.0);
788					fBackingView->SetLowColor(
789						ui_color(B_PANEL_BACKGROUND_COLOR));
790					fBackingView->SetViewColor(
791						ui_color(B_PANEL_BACKGROUND_COLOR));
792					fBacking->Unlock();
793				}
794			} else {
795				delete fBacking;
796				fBacking = NULL;
797			}
798		}
799	}
800
801	if (fBacking && fBackingView) {
802		BPoint drawHere;
803
804		BRect bounds(fBacking->Bounds());
805		drawHere.x = (Bounds().Width() - bounds.Width()) / 2.0;
806		drawHere.y = (Bounds().Height() - bounds.Height()) - kPadding
807			- fLineFeed;
808
809		if (fBacking->Lock()) {
810			// Clear the view's background
811			fBackingView->FillRect(fBackingView->Bounds(), B_SOLID_LOW);
812
813			BRect channelArea;
814			// draw the entire control
815			for (int32 channel = 0; channel < CountChannels(); channel++) {
816				channelArea = ThumbFrameFor(channel);
817				bool pressed = IsTracking()
818					&& (channel == fCurrentChannel || fAllChannels);
819				DrawChannel(fBackingView, channel, channelArea, pressed);
820			}
821
822			// draw some kind of current value tool tip
823			if (fCurrentChannel != -1 && fMinPoint != 0) {
824				char valueString[32];
825				snprintf(valueString, 32, "%" B_PRId32,
826					ValueFor(fCurrentChannel));
827				SetToolTip(valueString);
828				ShowToolTip(ToolTip());
829			} else {
830				HideToolTip();
831			}
832
833			fBackingView->Sync();
834			fBacking->Unlock();
835		}
836
837		DrawBitmapAsync(fBacking, drawHere);
838
839		// fClickDelta is used in MouseMoved()
840		fClickDelta = drawHere;
841	}
842}
843
844
845void
846BChannelSlider::_DrawGrooveFrame(BView* into, const BRect& area)
847{
848	if (into) {
849		rgb_color oldColor = into->HighColor();
850
851		into->SetHighColor(255, 255, 255);
852		into->StrokeRect(area);
853		into->SetHighColor(tint_color(into->ViewColor(), B_DARKEN_1_TINT));
854		into->StrokeLine(area.LeftTop(), BPoint(area.right, area.top));
855		into->StrokeLine(area.LeftTop(), BPoint(area.left, area.bottom - 1));
856		into->SetHighColor(tint_color(into->ViewColor(), B_DARKEN_2_TINT));
857		into->StrokeLine(BPoint(area.left + 1, area.top + 1),
858			BPoint(area.right - 1, area.top + 1));
859		into->StrokeLine(BPoint(area.left + 1, area.top + 1),
860			BPoint(area.left + 1, area.bottom - 2));
861
862		into->SetHighColor(oldColor);
863	}
864}
865
866
867void
868BChannelSlider::_MouseMovedCommon(BPoint point, BPoint point2)
869{
870	float floatValue = 0;
871	int32 limitRange = MaxLimitList()[fCurrentChannel] -
872			MinLimitList()[fCurrentChannel];
873	float range = ThumbRangeFor(fCurrentChannel);
874	if (fIsVertical)
875		floatValue = range - (point.y - fMinPoint);
876	else
877		floatValue = range + (point.x - fMinPoint);
878
879	int32 value = (int32)(floatValue / range * limitRange) +
880		MinLimitList()[fCurrentChannel];
881	if (fAllChannels)
882		SetAllValue(value);
883	else
884		SetValueFor(fCurrentChannel, value);
885
886	if (ModificationMessage())
887		_FinishChange(true);
888
889	_DrawThumbs();
890}
891
892
893// #pragma mark - FBC padding
894
895
896void BChannelSlider::_Reserved_BChannelSlider_0(void*, ...) {}
897void BChannelSlider::_Reserved_BChannelSlider_1(void*, ...) {}
898void BChannelSlider::_Reserved_BChannelSlider_2(void*, ...) {}
899void BChannelSlider::_Reserved_BChannelSlider_3(void*, ...) {}
900void BChannelSlider::_Reserved_BChannelSlider_4(void*, ...) {}
901void BChannelSlider::_Reserved_BChannelSlider_5(void*, ...) {}
902void BChannelSlider::_Reserved_BChannelSlider_6(void*, ...) {}
903void BChannelSlider::_Reserved_BChannelSlider_7(void*, ...) {}
904