1/*
2 * Copyright 2001-2013 Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Alexandre Deckner, alex@zappotek.com
7 *		Axel D��rfler, axeld@pinc-software.de
8 *		J��r��me Duval
9 *		Marc Flerackers, mflerackers@androme.be
10 *		John Scipione, jscipione@gmail.com
11 */
12
13/**	BColorControl displays a palette of selectable colors. */
14
15#include <ColorControl.h>
16
17#include <algorithm>
18
19#include <stdio.h>
20#include <stdlib.h>
21
22#include <ControlLook.h>
23#include <Bitmap.h>
24#include <TextControl.h>
25#include <Region.h>
26#include <Screen.h>
27#include <SystemCatalog.h>
28#include <Window.h>
29
30using BPrivate::gSystemCatalog;
31
32#include <binary_compatibility/Interface.h>
33
34
35#undef B_TRANSLATION_CONTEXT
36#define B_TRANSLATION_CONTEXT "ColorControl"
37
38static const uint32 kMsgColorEntered = 'ccol';
39static const float kMinCellSize = 6.0f;
40static const float kSelectorPenSize = 2.0f;
41static const float kSelectorSize = 4.0f;
42static const float kSelectorHSpacing = 2.0f;
43static const float kTextFieldsHSpacing = 6.0f;
44static const float kDefaultFontSize = 12.0f;
45static const float kBevelSpacing = 2.0f;
46static const uint32 kRampCount = 4;
47
48
49BColorControl::BColorControl(BPoint leftTop, color_control_layout layout,
50	float cellSize, const char* name, BMessage* message, bool useOffscreen)
51	:
52	BControl(BRect(leftTop, leftTop), name, NULL, message,
53		B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_NAVIGABLE),
54	fRedText(NULL),
55	fGreenText(NULL),
56	fBlueText(NULL),
57	fOffscreenBitmap(NULL)
58{
59	_InitData(layout, cellSize, useOffscreen, NULL);
60}
61
62
63BColorControl::BColorControl(BMessage* data)
64	:
65	BControl(data),
66	fRedText(NULL),
67	fGreenText(NULL),
68	fBlueText(NULL),
69	fOffscreenBitmap(NULL)
70{
71	int32 layout;
72	float cellSize;
73	bool useOffscreen;
74
75	data->FindInt32("_layout", &layout);
76	data->FindFloat("_csize", &cellSize);
77	data->FindBool("_use_off", &useOffscreen);
78
79	_InitData((color_control_layout)layout, cellSize, useOffscreen, data);
80}
81
82
83BColorControl::~BColorControl()
84{
85	delete fOffscreenBitmap;
86}
87
88
89void
90BColorControl::_InitData(color_control_layout layout, float size,
91	bool useOffscreen, BMessage* data)
92{
93	fPaletteMode = BScreen(B_MAIN_SCREEN_ID).ColorSpace() == B_CMAP8;
94		//TODO: we don't support workspace and colorspace changing for now
95		//		so we take the main_screen colorspace at startup
96	fColumns = layout;
97	fRows = 256 / fColumns;
98
99	_SetCellSize(size);
100
101	fSelectedPaletteColorIndex = -1;
102	fPreviousSelectedPaletteColorIndex = -1;
103	fFocusedRamp = !fPaletteMode && IsFocus() ? 1 : -1;
104	fClickedRamp = -1;
105
106	const char* red = B_TRANSLATE_MARK("Red:");
107	const char* green = B_TRANSLATE_MARK("Green:");
108	const char* blue = B_TRANSLATE_MARK("Blue:");
109	red = gSystemCatalog.GetString(red, "ColorControl");
110	green = gSystemCatalog.GetString(green, "ColorControl");
111	blue = gSystemCatalog.GetString(blue, "ColorControl");
112
113	if (data != NULL) {
114		fRedText = (BTextControl*)FindView("_red");
115		fGreenText = (BTextControl*)FindView("_green");
116		fBlueText = (BTextControl*)FindView("_blue");
117
118		int32 value = 0;
119		data->FindInt32("_val", &value);
120
121		SetValue(value);
122	} else {
123		BRect textRect(0.0f, 0.0f, 0.0f, 0.0f);
124		float labelWidth = std::max(StringWidth(red),
125			std::max(StringWidth(green), StringWidth(blue)))
126				+ kTextFieldsHSpacing;
127		textRect.right = labelWidth + StringWidth("999999");
128			// enough room for 3 digits plus 3 digits of padding
129		font_height fontHeight;
130		GetFontHeight(&fontHeight);
131		float labelHeight = fontHeight.ascent + fontHeight.descent;
132		textRect.bottom = labelHeight;
133
134		// red
135
136		fRedText = new BTextControl(textRect, "_red", red, "0",
137			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
138			B_WILL_DRAW | B_NAVIGABLE);
139		fRedText->SetDivider(labelWidth);
140
141		for (int32 i = 0; i < 256; i++)
142			fRedText->TextView()->DisallowChar(i);
143		for (int32 i = '0'; i <= '9'; i++)
144			fRedText->TextView()->AllowChar(i);
145		fRedText->TextView()->SetMaxBytes(3);
146
147		// green
148
149		textRect.OffsetBy(0, _TextRectOffset());
150		fGreenText = new BTextControl(textRect, "_green", green, "0",
151			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
152			B_WILL_DRAW | B_NAVIGABLE);
153		fGreenText->SetDivider(labelWidth);
154
155		for (int32 i = 0; i < 256; i++)
156			fGreenText->TextView()->DisallowChar(i);
157		for (int32 i = '0'; i <= '9'; i++)
158			fGreenText->TextView()->AllowChar(i);
159		fGreenText->TextView()->SetMaxBytes(3);
160
161		// blue
162
163		textRect.OffsetBy(0, _TextRectOffset());
164		fBlueText = new BTextControl(textRect, "_blue", blue, "0",
165			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
166			B_WILL_DRAW | B_NAVIGABLE);
167		fBlueText->SetDivider(labelWidth);
168
169		for (int32 i = 0; i < 256; i++)
170			fBlueText->TextView()->DisallowChar(i);
171		for (int32 i = '0'; i <= '9'; i++)
172			fBlueText->TextView()->AllowChar(i);
173		fBlueText->TextView()->SetMaxBytes(3);
174
175		AddChild(fRedText);
176		AddChild(fGreenText);
177		AddChild(fBlueText);
178	}
179
180	// right align rgb values so that they line up
181	fRedText->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT);
182	fGreenText->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT);
183	fBlueText->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT);
184
185	ResizeToPreferred();
186
187	if (useOffscreen) {
188		if (fOffscreenBitmap != NULL) {
189			BRect bounds = _PaletteFrame();
190			fOffscreenBitmap = new BBitmap(bounds, B_RGB32, true, false);
191			BView* offscreenView = new BView(bounds, "off_view", 0, 0);
192
193			fOffscreenBitmap->Lock();
194			fOffscreenBitmap->AddChild(offscreenView);
195			fOffscreenBitmap->Unlock();
196		}
197	} else {
198		delete fOffscreenBitmap;
199		fOffscreenBitmap = NULL;
200	}
201}
202
203
204void
205BColorControl::_LayoutView()
206{
207	fPaletteFrame.Set(0, 0, fColumns * fCellSize, fRows * fCellSize);
208	fPaletteFrame.OffsetBy(kBevelSpacing, kBevelSpacing);
209	if (!fPaletteMode) {
210		// Reduce the inner space by 1 pixel so that the frame
211		// is exactly rows * cellsize pixels in height
212		fPaletteFrame.bottom -= 1;
213	}
214
215	float rampHeight = (float)(fRows * fCellSize / kRampCount);
216	float offset = _TextRectOffset();
217	float y = 0;
218	if (rampHeight > fRedText->Frame().Height()) {
219		// there is enough room to fit kRampCount labels,
220		// shift text controls down by one ramp
221		offset = rampHeight;
222		y = floorf(offset + (offset - fRedText->Frame().Height()) / 2);
223	}
224
225	BRect rect = _PaletteFrame();
226	fRedText->MoveTo(rect.right + kTextFieldsHSpacing, y);
227
228	y += offset;
229	fGreenText->MoveTo(rect.right + kTextFieldsHSpacing, y);
230
231	y += offset;
232	fBlueText->MoveTo(rect.right + kTextFieldsHSpacing, y);
233}
234
235
236BArchivable*
237BColorControl::Instantiate(BMessage* data)
238{
239	if (validate_instantiation(data, "BColorControl"))
240		return new BColorControl(data);
241
242	return NULL;
243}
244
245
246status_t
247BColorControl::Archive(BMessage* data, bool deep) const
248{
249	status_t status = BControl::Archive(data, deep);
250
251	if (status == B_OK)
252		status = data->AddInt32("_layout", Layout());
253
254	if (status == B_OK)
255		status = data->AddFloat("_csize", fCellSize);
256
257	if (status == B_OK)
258		status = data->AddBool("_use_off", fOffscreenBitmap != NULL);
259
260	return status;
261}
262
263
264void
265BColorControl::SetLayout(BLayout* layout)
266{
267	// We need to implement this method, since we have another SetLayout()
268	// method and C++ has this special method hiding "feature".
269	BControl::SetLayout(layout);
270}
271
272
273void
274BColorControl::SetValue(int32 value)
275{
276	rgb_color c1 = ValueAsColor();
277	rgb_color c2;
278	c2.red = (value & 0xFF000000) >> 24;
279	c2.green = (value & 0x00FF0000) >> 16;
280	c2.blue = (value & 0x0000FF00) >> 8;
281	c2.alpha = 255;
282
283	if (fPaletteMode) {
284		//workaround when two indexes have the same color
285		rgb_color c
286			= BScreen(Window()).ColorForIndex(fSelectedPaletteColorIndex);
287		c.alpha = 255;
288		if (fSelectedPaletteColorIndex == -1 || c != c2) {
289				//here SetValue hasn't been called by mouse tracking
290			fSelectedPaletteColorIndex = BScreen(Window()).IndexForColor(c2);
291		}
292
293		c2 = BScreen(Window()).ColorForIndex(fSelectedPaletteColorIndex);
294
295		Invalidate(_PaletteSelectorFrame(fPreviousSelectedPaletteColorIndex));
296		Invalidate(_PaletteSelectorFrame(fSelectedPaletteColorIndex));
297
298		fPreviousSelectedPaletteColorIndex = fSelectedPaletteColorIndex;
299	} else if (c1 != c2)
300		Invalidate();
301
302	// Set the value here, since BTextControl will trigger
303	// Window()->UpdateIfNeeded() which will cause us to draw the indicators
304	// at the old offset.
305	if (Value() != value)
306		BControl::SetValueNoUpdate(value);
307
308	// the textcontrols have to be updated even when the color
309	// hasn't changed since the value is clamped upstream
310	// and the textcontrols would still show the unclamped value
311	char string[4];
312	sprintf(string, "%d", c2.red);
313	fRedText->SetText(string);
314	sprintf(string, "%d", c2.green);
315	fGreenText->SetText(string);
316	sprintf(string, "%d", c2.blue);
317	fBlueText->SetText(string);
318}
319
320
321rgb_color
322BColorControl::ValueAsColor()
323{
324	int32 value = Value();
325	rgb_color color;
326
327	color.red = (value & 0xFF000000) >> 24;
328	color.green = (value & 0x00FF0000) >> 16;
329	color.blue = (value & 0x0000FF00) >> 8;
330	color.alpha = 255;
331
332	return color;
333}
334
335
336void
337BColorControl::SetEnabled(bool enabled)
338{
339	BControl::SetEnabled(enabled);
340
341	fRedText->SetEnabled(enabled);
342	fGreenText->SetEnabled(enabled);
343	fBlueText->SetEnabled(enabled);
344}
345
346
347void
348BColorControl::AttachedToWindow()
349{
350	BControl::AttachedToWindow();
351
352	AdoptParentColors();
353
354	fRedText->SetTarget(this);
355	fGreenText->SetTarget(this);
356	fBlueText->SetTarget(this);
357
358	if (fOffscreenBitmap != NULL)
359		_InitOffscreen();
360}
361
362
363void
364BColorControl::MessageReceived(BMessage* message)
365{
366	switch (message->what) {
367		case kMsgColorEntered:
368		{
369			rgb_color color;
370			color.red = min_c(strtol(fRedText->Text(), NULL, 10), 255);
371			color.green = min_c(strtol(fGreenText->Text(), NULL, 10), 255);
372			color.blue = min_c(strtol(fBlueText->Text(), NULL, 10), 255);
373			color.alpha = 255;
374
375			SetValue(color);
376			Invoke();
377			break;
378		}
379
380		case B_SCREEN_CHANGED:
381		{
382			BRect frame;
383			uint32 mode;
384			if (message->FindRect("frame", &frame) == B_OK
385				&& message->FindInt32("mode", (int32*)&mode) == B_OK) {
386				if ((fPaletteMode && mode == B_CMAP8)
387					|| (!fPaletteMode && mode != B_CMAP8)) {
388					// not switching to or from B_CMAP8, break
389					break;
390				}
391
392				// fake an archive message (so we don't rebuild views)
393				BMessage* data = new BMessage();
394				data->AddInt32("_val", Value());
395
396				// reinititialize
397				bool useOffscreen = fOffscreenBitmap != NULL;
398				_InitData((color_control_layout)fColumns, fCellSize,
399					useOffscreen, data);
400				if (useOffscreen)
401					_InitOffscreen();
402
403				// cleanup
404				delete data;
405			}
406			break;
407		}
408
409		default:
410			BControl::MessageReceived(message);
411	}
412}
413
414
415void
416BColorControl::Draw(BRect updateRect)
417{
418	if (fOffscreenBitmap != NULL)
419		DrawBitmap(fOffscreenBitmap, B_ORIGIN);
420	else
421		_DrawColorArea(this, updateRect);
422
423	_DrawSelectors(this);
424}
425
426
427void
428BColorControl::_DrawColorArea(BView* target, BRect updateRect)
429{
430	BRect rect = _PaletteFrame();
431	bool enabled = IsEnabled();
432
433	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
434	rgb_color darken1 = tint_color(base, B_DARKEN_1_TINT);
435
436	uint32 flags = be_control_look->Flags(this);
437	be_control_look->DrawTextControlBorder(target, rect, updateRect,
438		base, flags);
439
440	if (fPaletteMode) {
441		int colBegin = max_c(0, -1 + int(updateRect.left) / int(fCellSize));
442		int colEnd = min_c(fColumns,
443			2 + int(updateRect.right) / int(fCellSize));
444		int rowBegin = max_c(0, -1 + int(updateRect.top) / int(fCellSize));
445		int rowEnd = min_c(fRows, 2 + int(updateRect.bottom)
446			/ int(fCellSize));
447
448		// grid
449		target->SetHighColor(enabled ? darken1 : base);
450
451		for (int xi = 0; xi < fColumns + 1; xi++) {
452			float x = fPaletteFrame.left + float(xi) * fCellSize;
453			target->StrokeLine(BPoint(x, fPaletteFrame.top),
454				BPoint(x, fPaletteFrame.bottom));
455		}
456		for (int yi = 0; yi < fRows + 1; yi++) {
457			float y = fPaletteFrame.top + float(yi) * fCellSize;
458			target->StrokeLine(BPoint(fPaletteFrame.left, y),
459				BPoint(fPaletteFrame.right, y));
460		}
461
462		// colors
463		for (int col = colBegin; col < colEnd; col++) {
464			for (int row = rowBegin; row < rowEnd; row++) {
465				uint8 colorIndex = row * fColumns + col;
466				float x = fPaletteFrame.left + col * fCellSize;
467				float y = fPaletteFrame.top + row * fCellSize;
468
469				target->SetHighColor(system_colors()->color_list[colorIndex]);
470				target->FillRect(BRect(x + 1, y + 1,
471					x + fCellSize - 1, y + fCellSize - 1));
472			}
473		}
474	} else {
475		rgb_color white = { 255, 255, 255, 255 };
476		rgb_color red   = { 255, 0, 0, 255 };
477		rgb_color green = { 0, 255, 0, 255 };
478		rgb_color blue  = { 0, 0, 255, 255 };
479
480		rgb_color compColor = { 0, 0, 0, 255 };
481		if (!enabled) {
482			compColor.red = compColor.green = compColor.blue = 156;
483			red.red = green.green = blue.blue = 70;
484			white.red = white.green = white.blue = 70;
485		}
486		_DrawColorRamp(_RampFrame(0), target, white, compColor, 0, false,
487			updateRect);
488		_DrawColorRamp(_RampFrame(1), target, red, compColor, 0, false,
489			updateRect);
490		_DrawColorRamp(_RampFrame(2), target, green, compColor, 0, false,
491			updateRect);
492		_DrawColorRamp(_RampFrame(3), target, blue, compColor, 0, false,
493			updateRect);
494	}
495}
496
497
498void
499BColorControl::_DrawSelectors(BView* target)
500{
501	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
502	rgb_color lightenmax = tint_color(base, B_LIGHTEN_MAX_TINT);
503
504	if (fPaletteMode) {
505		if (fSelectedPaletteColorIndex != -1) {
506			target->SetHighColor(lightenmax);
507			target->StrokeRect(
508				_PaletteSelectorFrame(fSelectedPaletteColorIndex));
509		}
510	} else {
511		rgb_color color = ValueAsColor();
512		target->SetHighColor(255, 255, 255);
513		target->SetLowColor(0, 0, 0);
514
515		int components[4] = { color.alpha, color.red, color.green, color.blue };
516
517		for (int i = 1; i < 4; i++) {
518			BPoint center = _SelectorPosition(_RampFrame(i), components[i]);
519
520			target->SetPenSize(kSelectorPenSize);
521			target->StrokeEllipse(center, kSelectorSize / 2, kSelectorSize / 2);
522			target->SetPenSize(kSelectorPenSize / 2);
523			target->StrokeEllipse(center, kSelectorSize, kSelectorSize,
524				B_SOLID_LOW);
525			if (i == fFocusedRamp) {
526				target->StrokeEllipse(center,
527					kSelectorSize / 2, kSelectorSize / 2, B_SOLID_LOW);
528			}
529		}
530
531		target->SetPenSize(1.0f);
532	}
533}
534
535
536void
537BColorControl::_DrawColorRamp(BRect rect, BView* target,
538	rgb_color baseColor, rgb_color compColor, int16 flag, bool focused,
539	BRect updateRect)
540{
541	float width = rect.Width() + 1;
542	rgb_color color = ValueAsColor();
543	color.alpha = 255;
544
545	updateRect = updateRect & rect;
546
547	if (updateRect.IsValid() && updateRect.Width() >= 0) {
548		target->BeginLineArray((int32)updateRect.Width() + 1);
549
550		for (float i = (updateRect.left - rect.left);
551				i <= (updateRect.right - rect.left) + 1; i++) {
552			if (baseColor.red == 255)
553				color.red = (uint8)(i * 255 / width) + compColor.red;
554			if (baseColor.green == 255)
555				color.green = (uint8)(i * 255 / width) + compColor.green;
556			if (baseColor.blue == 255)
557				color.blue = (uint8)(i * 255 / width) + compColor.blue;
558
559			target->AddLine(BPoint(rect.left + i, rect.top),
560				BPoint(rect.left + i, rect.bottom - 1), color);
561		}
562
563		target->EndLineArray();
564	}
565}
566
567
568BPoint
569BColorControl::_SelectorPosition(const BRect& rampRect, uint8 shade) const
570{
571	float radius = kSelectorSize / 2 + kSelectorPenSize / 2;
572
573	return BPoint(rampRect.left + kSelectorHSpacing + radius +
574		shade * (rampRect.Width() - 2 * (kSelectorHSpacing + radius)) / 255,
575		rampRect.top + rampRect.Height() / 2);
576}
577
578
579BRect
580BColorControl::_PaletteFrame() const
581{
582	return fPaletteFrame.InsetByCopy(-kBevelSpacing, -kBevelSpacing);
583}
584
585
586BRect
587BColorControl::_RampFrame(uint8 rampIndex) const
588{
589	float rampHeight = (float)(fRows * fCellSize / kRampCount);
590
591	return BRect(fPaletteFrame.left,
592		fPaletteFrame.top + float(rampIndex) * rampHeight,
593		fPaletteFrame.right,
594		fPaletteFrame.top + float(rampIndex + 1) * rampHeight);
595}
596
597
598void
599BColorControl::_SetCellSize(float size)
600{
601	BFont font;
602	GetFont(&font);
603	fCellSize = std::max(kMinCellSize,
604		ceilf(size * font.Size() / kDefaultFontSize));
605}
606
607
608float
609BColorControl::_TextRectOffset()
610{
611	return std::max(fRedText->Bounds().Height(),
612		ceilf(_PaletteFrame().Height() / 3));
613}
614
615
616BRect
617BColorControl::_PaletteSelectorFrame(uint8 colorIndex) const
618{
619	uint32 row = colorIndex / fColumns;
620	uint32 column = colorIndex % fColumns;
621	float x = fPaletteFrame.left + column * fCellSize;
622	float y = fPaletteFrame.top + row * fCellSize;
623	return BRect(x, y, x + fCellSize, y + fCellSize);
624}
625
626
627void
628BColorControl::_InitOffscreen()
629{
630	if (fOffscreenBitmap->Lock()) {
631		BView* offscreenView = fOffscreenBitmap->ChildAt((int32)0);
632		if (offscreenView != NULL) {
633			_DrawColorArea(offscreenView, _PaletteFrame());
634			offscreenView->Sync();
635		}
636		fOffscreenBitmap->Unlock();
637	}
638}
639
640
641void
642BColorControl::_InvalidateSelector(int16 ramp, rgb_color color, bool focused)
643{
644	if (fPaletteMode)
645		return;
646
647	if (ramp < 1 || ramp > 3)
648		return;
649
650	float invalidateRadius = focused
651		? kSelectorSize + kSelectorPenSize / 2
652		: kSelectorSize / 2 + kSelectorPenSize;
653
654	uint8 colorValue = ramp == 1 ? color.red : ramp == 2 ? color.green
655		: color.blue;
656
657	BPoint pos = _SelectorPosition(_RampFrame(ramp), colorValue);
658	Invalidate(BRect(pos.x - invalidateRadius, pos.y - invalidateRadius,
659		pos.x + invalidateRadius, pos.y + invalidateRadius));
660}
661
662
663void
664BColorControl::SetCellSize(float size)
665{
666	_SetCellSize(size);
667	ResizeToPreferred();
668}
669
670
671float
672BColorControl::CellSize() const
673{
674	return fCellSize;
675}
676
677
678void
679BColorControl::SetLayout(color_control_layout layout)
680{
681	switch (layout) {
682		case B_CELLS_4x64:
683			fColumns = 4;
684			fRows = 64;
685			break;
686
687		case B_CELLS_8x32:
688			fColumns = 8;
689			fRows = 32;
690			break;
691
692		case B_CELLS_16x16:
693			fColumns = 16;
694			fRows = 16;
695			break;
696
697		case B_CELLS_32x8:
698			fColumns = 32;
699			fRows = 8;
700			break;
701
702		case B_CELLS_64x4:
703			fColumns = 64;
704			fRows = 4;
705			break;
706	}
707
708	ResizeToPreferred();
709	Invalidate();
710}
711
712
713color_control_layout
714BColorControl::Layout() const
715{
716	if (fColumns == 4 && fRows == 64)
717		return B_CELLS_4x64;
718
719	if (fColumns == 8 && fRows == 32)
720		return B_CELLS_8x32;
721
722	if (fColumns == 16 && fRows == 16)
723		return B_CELLS_16x16;
724
725	if (fColumns == 32 && fRows == 8)
726		return B_CELLS_32x8;
727
728	if (fColumns == 64 && fRows == 4)
729		return B_CELLS_64x4;
730
731	return B_CELLS_32x8;
732}
733
734
735void
736BColorControl::WindowActivated(bool state)
737{
738	BControl::WindowActivated(state);
739}
740
741
742void
743BColorControl::KeyDown(const char* bytes, int32 numBytes)
744{
745	if (IsFocus() && !fPaletteMode && numBytes == 1) {
746		rgb_color color = ValueAsColor();
747
748		switch (bytes[0]) {
749			case B_UP_ARROW:
750			{
751				int16 oldFocus = fFocusedRamp;
752				fFocusedRamp--;
753				if (fFocusedRamp < 1)
754					fFocusedRamp = 3;
755
756				_InvalidateSelector(oldFocus, color, true);
757				_InvalidateSelector(fFocusedRamp, color, true);
758				break;
759			}
760
761			case B_DOWN_ARROW:
762			{
763				int16 oldFocus = fFocusedRamp;
764				fFocusedRamp++;
765				if (fFocusedRamp > 3)
766					fFocusedRamp = 1;
767
768				_InvalidateSelector(oldFocus, color, true);
769				_InvalidateSelector(fFocusedRamp, color, true);
770				break;
771			}
772
773			case B_LEFT_ARROW:
774			{
775				bool goFaster = false;
776				if (Window() != NULL) {
777					BMessage* message = Window()->CurrentMessage();
778					if (message != NULL && message->what == B_KEY_DOWN) {
779						int32 repeats = 0;
780						if (message->FindInt32("be:key_repeat", &repeats)
781								== B_OK && repeats > 4) {
782							goFaster = true;
783						}
784					}
785				}
786
787				if (fFocusedRamp == 1) {
788					if (goFaster && color.red >= 5)
789						color.red -= 5;
790					else if (color.red > 0)
791						color.red--;
792				} else if (fFocusedRamp == 2) {
793					if (goFaster && color.green >= 5)
794						color.green -= 5;
795					else if (color.green > 0)
796						color.green--;
797				} else if (fFocusedRamp == 3) {
798				 	if (goFaster && color.blue >= 5)
799						color.blue -= 5;
800					else if (color.blue > 0)
801						color.blue--;
802				}
803
804				SetValue(color);
805				Invoke();
806				break;
807			}
808
809			case B_RIGHT_ARROW:
810			{
811				bool goFaster = false;
812				if (Window() != NULL) {
813					BMessage* message = Window()->CurrentMessage();
814					if (message != NULL && message->what == B_KEY_DOWN) {
815						int32 repeats = 0;
816						if (message->FindInt32("be:key_repeat", &repeats)
817								== B_OK && repeats > 4) {
818							goFaster = true;
819						}
820					}
821				}
822
823				if (fFocusedRamp == 1) {
824					if (goFaster && color.red <= 250)
825						color.red += 5;
826					else if (color.red < 255)
827						color.red++;
828				} else if (fFocusedRamp == 2) {
829					if (goFaster && color.green <= 250)
830						color.green += 5;
831					else if (color.green < 255)
832						color.green++;
833				} else if (fFocusedRamp == 3) {
834				 	if (goFaster && color.blue <= 250)
835						color.blue += 5;
836					else if (color.blue < 255)
837						color.blue++;
838				}
839
840				SetValue(color);
841				Invoke();
842				break;
843			}
844		}
845	}
846
847	BControl::KeyDown(bytes, numBytes);
848}
849
850
851void
852BColorControl::MouseUp(BPoint point)
853{
854	fClickedRamp = -1;
855	SetTracking(false);
856}
857
858
859void
860BColorControl::MouseDown(BPoint point)
861{
862	if (!IsEnabled())
863		return;
864	if (!fPaletteFrame.Contains(point))
865		return;
866
867	if (fPaletteMode) {
868		int col = (int)((point.x - fPaletteFrame.left) / fCellSize);
869		int row = (int)((point.y - fPaletteFrame.top) / fCellSize);
870		int colorIndex = row * fColumns + col;
871		if (colorIndex >= 0 && colorIndex < 256) {
872			fSelectedPaletteColorIndex = colorIndex;
873			SetValue(system_colors()->color_list[colorIndex]);
874		}
875	} else {
876		rgb_color color = ValueAsColor();
877
878		uint8 shade = (unsigned char)max_c(0,
879			min_c((point.x - _RampFrame(0).left) * 255
880				/ _RampFrame(0).Width(), 255));
881
882		if (_RampFrame(0).Contains(point)) {
883			color.red = color.green = color.blue = shade;
884			fClickedRamp = 0;
885		} else if (_RampFrame(1).Contains(point)) {
886			color.red = shade;
887			fClickedRamp = 1;
888		} else if (_RampFrame(2).Contains(point)) {
889			color.green = shade;
890			fClickedRamp = 2;
891		} else if (_RampFrame(3).Contains(point)) {
892			color.blue = shade;
893			fClickedRamp = 3;
894		}
895
896		SetValue(color);
897	}
898
899	Invoke();
900
901	SetTracking(true);
902	SetMouseEventMask(B_POINTER_EVENTS,
903		B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
904}
905
906
907void
908BColorControl::MouseMoved(BPoint point, uint32 transit,
909	const BMessage* message)
910{
911	if (!IsTracking())
912		return;
913
914	if (fPaletteMode && fPaletteFrame.Contains(point)) {
915		int col = (int)((point.x - fPaletteFrame.left) / fCellSize);
916		int row = (int)((point.y - fPaletteFrame.top) / fCellSize);
917		int colorIndex = row * fColumns + col;
918		if (colorIndex >= 0 && colorIndex < 256) {
919			fSelectedPaletteColorIndex = colorIndex;
920			SetValue(system_colors()->color_list[colorIndex]);
921		}
922	} else {
923		if (fClickedRamp < 0 || fClickedRamp > 3)
924			return;
925
926		rgb_color color = ValueAsColor();
927
928		uint8 shade = (unsigned char)max_c(0,
929			min_c((point.x - _RampFrame(0).left) * 255
930				/ _RampFrame(0).Width(), 255));
931
932		if (fClickedRamp == 0)
933			color.red = color.green = color.blue = shade;
934		else if (fClickedRamp == 1)
935			color.red = shade;
936		else if (fClickedRamp == 2)
937			color.green = shade;
938		else if (fClickedRamp == 3)
939			color.blue = shade;
940
941		SetValue(color);
942	}
943
944	Invoke();
945}
946
947
948void
949BColorControl::DetachedFromWindow()
950{
951	BControl::DetachedFromWindow();
952}
953
954
955void
956BColorControl::GetPreferredSize(float* _width, float* _height)
957{
958	BRect rect = _PaletteFrame();
959
960	if (rect.Height() < fBlueText->Frame().bottom) {
961		// adjust the height to fit
962		rect.bottom = fBlueText->Frame().bottom;
963	}
964
965	if (_width) {
966		*_width = rect.Width() + kTextFieldsHSpacing
967			+ fRedText->Bounds().Width();
968	}
969
970	if (_height)
971		*_height = rect.Height();
972}
973
974
975void
976BColorControl::ResizeToPreferred()
977{
978	_LayoutView();
979	BControl::ResizeToPreferred();
980}
981
982
983status_t
984BColorControl::Invoke(BMessage* message)
985{
986	return BControl::Invoke(message);
987}
988
989
990void
991BColorControl::FrameMoved(BPoint newPosition)
992{
993	BControl::FrameMoved(newPosition);
994}
995
996
997void
998BColorControl::FrameResized(float newWidth, float newHeight)
999{
1000	BControl::FrameResized(newWidth, newHeight);
1001}
1002
1003
1004BHandler*
1005BColorControl::ResolveSpecifier(BMessage* message, int32 index,
1006	BMessage* specifier, int32 form, const char* property)
1007{
1008	return BControl::ResolveSpecifier(message, index, specifier, form,
1009		property);
1010}
1011
1012
1013status_t
1014BColorControl::GetSupportedSuites(BMessage* data)
1015{
1016	return BControl::GetSupportedSuites(data);
1017}
1018
1019
1020void
1021BColorControl::MakeFocus(bool focused)
1022{
1023	fFocusedRamp = !fPaletteMode && focused ? 1 : -1;
1024	BControl::MakeFocus(focused);
1025}
1026
1027
1028void
1029BColorControl::AllAttached()
1030{
1031	BControl::AllAttached();
1032}
1033
1034
1035void
1036BColorControl::AllDetached()
1037{
1038	BControl::AllDetached();
1039}
1040
1041
1042status_t
1043BColorControl::SetIcon(const BBitmap* icon, uint32 flags)
1044{
1045	return BControl::SetIcon(icon, flags);
1046}
1047
1048
1049status_t
1050BColorControl::Perform(perform_code code, void* _data)
1051{
1052	switch (code) {
1053		case PERFORM_CODE_MIN_SIZE:
1054			((perform_data_min_size*)_data)->return_value
1055				= BColorControl::MinSize();
1056			return B_OK;
1057
1058		case PERFORM_CODE_MAX_SIZE:
1059			((perform_data_max_size*)_data)->return_value
1060				= BColorControl::MaxSize();
1061			return B_OK;
1062
1063		case PERFORM_CODE_PREFERRED_SIZE:
1064			((perform_data_preferred_size*)_data)->return_value
1065				= BColorControl::PreferredSize();
1066			return B_OK;
1067
1068		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1069			((perform_data_layout_alignment*)_data)->return_value
1070				= BColorControl::LayoutAlignment();
1071			return B_OK;
1072
1073		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1074			((perform_data_has_height_for_width*)_data)->return_value
1075				= BColorControl::HasHeightForWidth();
1076			return B_OK;
1077
1078		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1079		{
1080			perform_data_get_height_for_width* data
1081				= (perform_data_get_height_for_width*)_data;
1082			BColorControl::GetHeightForWidth(data->width, &data->min,
1083				&data->max, &data->preferred);
1084			return B_OK;
1085		}
1086
1087		case PERFORM_CODE_SET_LAYOUT:
1088		{
1089			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1090			BColorControl::SetLayout(data->layout);
1091			return B_OK;
1092		}
1093
1094		case PERFORM_CODE_LAYOUT_INVALIDATED:
1095		{
1096			perform_data_layout_invalidated* data
1097				= (perform_data_layout_invalidated*)_data;
1098			BColorControl::LayoutInvalidated(data->descendants);
1099			return B_OK;
1100		}
1101
1102		case PERFORM_CODE_DO_LAYOUT:
1103		{
1104			BColorControl::DoLayout();
1105			return B_OK;
1106		}
1107
1108		case PERFORM_CODE_SET_ICON:
1109		{
1110			perform_data_set_icon* data = (perform_data_set_icon*)_data;
1111			return BColorControl::SetIcon(data->icon, data->flags);
1112		}
1113	}
1114
1115	return BControl::Perform(code, _data);
1116}
1117
1118
1119void BColorControl::_ReservedColorControl1() {}
1120void BColorControl::_ReservedColorControl2() {}
1121void BColorControl::_ReservedColorControl3() {}
1122void BColorControl::_ReservedColorControl4() {}
1123
1124
1125BColorControl &
1126BColorControl::operator=(const BColorControl &)
1127{
1128	return *this;
1129}
1130