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