⛏️ index : haiku.git

/*
 * Copyright 2001-2025 Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Alexandre Deckner, alex@zappotek.com
 *		Axel Dörfler, axeld@pinc-software.de
 *		Jérôme Duval
 *		Marc Flerackers, mflerackers@androme.be
 *		John Scipione, jscipione@gmail.com
 */

/**	BColorControl displays a palette of selectable colors. */

#include <ColorControl.h>

#include <algorithm>

#include <stdio.h>
#include <stdlib.h>

#include <ControlLook.h>
#include <Bitmap.h>
#include <TextControl.h>
#include <Region.h>
#include <Screen.h>
#include <SystemCatalog.h>
#include <Window.h>

using BPrivate::gSystemCatalog;

#include <binary_compatibility/Interface.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ColorControl"

static const uint32 kMsgColorEntered = 'ccol';
static const float kMinCellSize = 6.0f;
static const float kSelectorPenSize = 2.0f;
static const float kSelectorSize = 4.0f;
static const float kSelectorHSpacing = 2.0f;
static const float kTextFieldsHSpacing = 6.0f;
static const float kDefaultFontSize = 12.0f;
static const float kBevelSpacing = 2.0f;
static const uint32 kRampCount = 4;


BColorControl::BColorControl(BPoint leftTop, color_control_layout layout,
	float cellSize, const char* name, BMessage* message, bool useOffscreen)
	:
	BControl(BRect(leftTop, leftTop), name, NULL, message,
		B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_NAVIGABLE),
	fRedText(NULL),
	fGreenText(NULL),
	fBlueText(NULL),
	fOffscreenBitmap(NULL)
{
	_InitData(layout, cellSize, useOffscreen, NULL);
}


BColorControl::BColorControl(BMessage* data)
	:
	BControl(data),
	fRedText(NULL),
	fGreenText(NULL),
	fBlueText(NULL),
	fOffscreenBitmap(NULL)
{
	int32 layout;
	float cellSize;
	bool useOffscreen;

	data->FindInt32("_layout", &layout);
	data->FindFloat("_csize", &cellSize);
	data->FindBool("_use_off", &useOffscreen);

	_InitData((color_control_layout)layout, cellSize, useOffscreen, data);
}


BColorControl::~BColorControl()
{
	delete fOffscreenBitmap;
}


void
BColorControl::_InitData(color_control_layout layout, float size,
	bool useOffscreen, BMessage* data)
{
	fPaletteMode = BScreen(B_MAIN_SCREEN_ID).ColorSpace() == B_CMAP8;
		//TODO: we don't support workspace and colorspace changing for now
		//		so we take the main_screen colorspace at startup
	fColumns = layout;
	fRows = 256 / fColumns;

	_SetCellSize(size);

	fSelectedPaletteColorIndex = -1;
	fPreviousSelectedPaletteColorIndex = -1;
	fFocusedRamp = !fPaletteMode && IsFocus() ? 1 : -1;
	fClickedRamp = -1;

	const char* red = B_TRANSLATE_MARK("Red:");
	const char* green = B_TRANSLATE_MARK("Green:");
	const char* blue = B_TRANSLATE_MARK("Blue:");
	red = gSystemCatalog.GetString(red, "ColorControl");
	green = gSystemCatalog.GetString(green, "ColorControl");
	blue = gSystemCatalog.GetString(blue, "ColorControl");

	if (data != NULL) {
		fRedText = (BTextControl*)FindView("_red");
		fGreenText = (BTextControl*)FindView("_green");
		fBlueText = (BTextControl*)FindView("_blue");

		int32 value = 0;
		data->FindInt32("_val", &value);

		SetValue(value);
	} else {
		BRect textRect(0.0f, 0.0f, 0.0f, 0.0f);
		float labelWidth = std::max(StringWidth(red),
			std::max(StringWidth(green), StringWidth(blue)))
				+ kTextFieldsHSpacing;
		textRect.right = labelWidth + StringWidth("999999");
			// enough room for 3 digits plus 3 digits of padding
		font_height fontHeight;
		GetFontHeight(&fontHeight);
		float labelHeight = fontHeight.ascent + fontHeight.descent;
		textRect.bottom = labelHeight;

		// red

		fRedText = new BTextControl(textRect, "_red", red, "0",
			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
			B_WILL_DRAW | B_NAVIGABLE);
		fRedText->SetDivider(labelWidth);

		for (int32 i = 0; i < 256; i++)
			fRedText->TextView()->DisallowChar(i);
		for (int32 i = '0'; i <= '9'; i++)
			fRedText->TextView()->AllowChar(i);
		fRedText->TextView()->SetMaxBytes(3);

		// green

		textRect.OffsetBy(0, _TextRectOffset());
		fGreenText = new BTextControl(textRect, "_green", green, "0",
			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
			B_WILL_DRAW | B_NAVIGABLE);
		fGreenText->SetDivider(labelWidth);

		for (int32 i = 0; i < 256; i++)
			fGreenText->TextView()->DisallowChar(i);
		for (int32 i = '0'; i <= '9'; i++)
			fGreenText->TextView()->AllowChar(i);
		fGreenText->TextView()->SetMaxBytes(3);

		// blue

		textRect.OffsetBy(0, _TextRectOffset());
		fBlueText = new BTextControl(textRect, "_blue", blue, "0",
			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
			B_WILL_DRAW | B_NAVIGABLE);
		fBlueText->SetDivider(labelWidth);

		for (int32 i = 0; i < 256; i++)
			fBlueText->TextView()->DisallowChar(i);
		for (int32 i = '0'; i <= '9'; i++)
			fBlueText->TextView()->AllowChar(i);
		fBlueText->TextView()->SetMaxBytes(3);

		AddChild(fRedText);
		AddChild(fGreenText);
		AddChild(fBlueText);
	}

	fRedText->SetHighUIColor(B_PANEL_TEXT_COLOR);
	fBlueText->SetHighUIColor(B_PANEL_TEXT_COLOR);
	fGreenText->SetHighUIColor(B_PANEL_TEXT_COLOR);

	// right align rgb values so that they line up
	fRedText->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT);
	fGreenText->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT);
	fBlueText->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT);

	ResizeToPreferred();

	if (useOffscreen) {
		if (fOffscreenBitmap != NULL) {
			BRect bounds = _PaletteFrame();
			fOffscreenBitmap = new BBitmap(bounds, B_RGB32, true, false);
			BView* offscreenView = new BView(bounds, "off_view", 0, 0);

			fOffscreenBitmap->Lock();
			fOffscreenBitmap->AddChild(offscreenView);
			fOffscreenBitmap->Unlock();
		}
	} else {
		delete fOffscreenBitmap;
		fOffscreenBitmap = NULL;
	}
}


void
BColorControl::_LayoutView()
{
	fPaletteFrame.Set(0, 0, fColumns * fCellSize, fRows * fCellSize);
	fPaletteFrame.OffsetBy(kBevelSpacing, kBevelSpacing);
	if (!fPaletteMode) {
		// Reduce the inner space by 1 pixel so that the frame
		// is exactly rows * cellsize pixels in height
		fPaletteFrame.bottom -= 1;
	}

	float rampHeight = (float)(fRows * fCellSize / kRampCount);
	float offset = _TextRectOffset();
	float y = 0;
	if (rampHeight > fRedText->Frame().Height()) {
		// there is enough room to fit kRampCount labels,
		// shift text controls down by one ramp
		offset = rampHeight;
		y = floorf(offset + (offset - fRedText->Frame().Height()) / 2);
	}

	BRect rect = _PaletteFrame();
	fRedText->MoveTo(rect.right + kTextFieldsHSpacing, y);

	y += offset;
	fGreenText->MoveTo(rect.right + kTextFieldsHSpacing, y);

	y += offset;
	fBlueText->MoveTo(rect.right + kTextFieldsHSpacing, y);
}


BArchivable*
BColorControl::Instantiate(BMessage* data)
{
	if (validate_instantiation(data, "BColorControl"))
		return new BColorControl(data);

	return NULL;
}


status_t
BColorControl::Archive(BMessage* data, bool deep) const
{
	status_t status = BControl::Archive(data, deep);

	if (status == B_OK)
		status = data->AddInt32("_layout", Layout());

	if (status == B_OK)
		status = data->AddFloat("_csize", fCellSize);

	if (status == B_OK)
		status = data->AddBool("_use_off", fOffscreenBitmap != NULL);

	return status;
}


void
BColorControl::SetLayout(BLayout* layout)
{
	// We need to implement this method, since we have another SetLayout()
	// method and C++ has this special method hiding "feature".
	BControl::SetLayout(layout);
}


void
BColorControl::SetValue(int32 value)
{
	rgb_color c1 = ValueAsColor();
	rgb_color c2;
	c2.red = (value & 0xFF000000) >> 24;
	c2.green = (value & 0x00FF0000) >> 16;
	c2.blue = (value & 0x0000FF00) >> 8;
	c2.alpha = 255;

	if (fPaletteMode) {
		//workaround when two indexes have the same color
		rgb_color c
			= BScreen(Window()).ColorForIndex(fSelectedPaletteColorIndex);
		c.alpha = 255;
		if (fSelectedPaletteColorIndex == -1 || c != c2) {
				//here SetValue hasn't been called by mouse tracking
			fSelectedPaletteColorIndex = BScreen(Window()).IndexForColor(c2);
		}

		c2 = BScreen(Window()).ColorForIndex(fSelectedPaletteColorIndex);

		Invalidate(_PaletteSelectorFrame(fPreviousSelectedPaletteColorIndex));
		Invalidate(_PaletteSelectorFrame(fSelectedPaletteColorIndex));

		fPreviousSelectedPaletteColorIndex = fSelectedPaletteColorIndex;
	} else if (c1 != c2)
		Invalidate();

	// Set the value here, since BTextControl will trigger
	// Window()->UpdateIfNeeded() which will cause us to draw the indicators
	// at the old offset.
	if (Value() != value)
		BControl::SetValueNoUpdate(value);

	// the textcontrols have to be updated even when the color
	// hasn't changed since the value is clamped upstream
	// and the textcontrols would still show the unclamped value
	char string[4];
	sprintf(string, "%d", c2.red);
	fRedText->SetText(string);
	sprintf(string, "%d", c2.green);
	fGreenText->SetText(string);
	sprintf(string, "%d", c2.blue);
	fBlueText->SetText(string);
}


rgb_color
BColorControl::ValueAsColor()
{
	int32 value = Value();
	rgb_color color;

	color.red = (value & 0xFF000000) >> 24;
	color.green = (value & 0x00FF0000) >> 16;
	color.blue = (value & 0x0000FF00) >> 8;
	color.alpha = 255;

	return color;
}


void
BColorControl::SetEnabled(bool enabled)
{
	BControl::SetEnabled(enabled);

	fRedText->SetEnabled(enabled);
	fGreenText->SetEnabled(enabled);
	fBlueText->SetEnabled(enabled);
}


void
BColorControl::AttachedToWindow()
{
	BControl::AttachedToWindow();

	AdoptParentColors();

	fRedText->SetTarget(this);
	fGreenText->SetTarget(this);
	fBlueText->SetTarget(this);

	if (fOffscreenBitmap != NULL)
		_InitOffscreen();
}


void
BColorControl::MessageReceived(BMessage* message)
{
	if (message->WasDropped() && IsEnabled()) {
		char* name;
		type_code type;
		rgb_color* color;
		ssize_t size;
		if (message->GetInfo(B_RGB_COLOR_TYPE, 0, &name, &type) == B_OK
			&& message->FindData(name, type, (const void**)&color, &size) == B_OK) {
			SetValue(*color);
			Invoke(message);
		}
	}

	switch (message->what) {
		case kMsgColorEntered:
		{
			rgb_color color;
			color.red = min_c(strtol(fRedText->Text(), NULL, 10), 255);
			color.green = min_c(strtol(fGreenText->Text(), NULL, 10), 255);
			color.blue = min_c(strtol(fBlueText->Text(), NULL, 10), 255);
			color.alpha = 255;

			SetValue(color);
			Invoke();
			break;
		}

		case B_SCREEN_CHANGED:
		{
			BRect frame;
			uint32 mode;
			if (message->FindRect("frame", &frame) == B_OK
				&& message->FindInt32("mode", (int32*)&mode) == B_OK) {
				if ((fPaletteMode && mode == B_CMAP8)
					|| (!fPaletteMode && mode != B_CMAP8)) {
					// not switching to or from B_CMAP8, break
					break;
				}

				// fake an archive message (so we don't rebuild views)
				BMessage* data = new BMessage();
				data->AddInt32("_val", Value());

				// reinititialize
				bool useOffscreen = fOffscreenBitmap != NULL;
				_InitData((color_control_layout)fColumns, fCellSize,
					useOffscreen, data);
				if (useOffscreen)
					_InitOffscreen();

				// cleanup
				delete data;
			}
			break;
		}

		default:
			BControl::MessageReceived(message);
			break;
	}
}


void
BColorControl::Draw(BRect updateRect)
{
	if (fOffscreenBitmap != NULL)
		DrawBitmap(fOffscreenBitmap, B_ORIGIN);
	else
		_DrawColorArea(this, updateRect);

	_DrawSelectors(this);
}


void
BColorControl::_DrawColorArea(BView* target, BRect updateRect)
{
	BRect rect = _PaletteFrame();
	bool enabled = IsEnabled();

	rgb_color base = ViewColor();
	rgb_color darken1 = tint_color(base, B_DARKEN_1_TINT);

	uint32 flags = be_control_look->Flags(this);
	be_control_look->DrawTextControlBorder(target, rect, updateRect,
		base, flags);

	if (fPaletteMode) {
		int colBegin = max_c(0, -1 + int(updateRect.left) / int(fCellSize));
		int colEnd = min_c(fColumns,
			2 + int(updateRect.right) / int(fCellSize));
		int rowBegin = max_c(0, -1 + int(updateRect.top) / int(fCellSize));
		int rowEnd = min_c(fRows, 2 + int(updateRect.bottom)
			/ int(fCellSize));

		// grid
		target->SetHighColor(enabled ? darken1 : base);

		for (int xi = 0; xi < fColumns + 1; xi++) {
			float x = fPaletteFrame.left + float(xi) * fCellSize;
			target->StrokeLine(BPoint(x, fPaletteFrame.top),
				BPoint(x, fPaletteFrame.bottom));
		}
		for (int yi = 0; yi < fRows + 1; yi++) {
			float y = fPaletteFrame.top + float(yi) * fCellSize;
			target->StrokeLine(BPoint(fPaletteFrame.left, y),
				BPoint(fPaletteFrame.right, y));
		}

		// colors
		for (int col = colBegin; col < colEnd; col++) {
			for (int row = rowBegin; row < rowEnd; row++) {
				uint8 colorIndex = row * fColumns + col;
				float x = fPaletteFrame.left + col * fCellSize;
				float y = fPaletteFrame.top + row * fCellSize;

				target->SetHighColor(system_colors()->color_list[colorIndex]);
				target->FillRect(BRect(x + 1, y + 1,
					x + fCellSize - 1, y + fCellSize - 1));
			}
		}
	} else {
		rgb_color white = { 255, 255, 255, 255 };
		rgb_color red   = { 255, 0, 0, 255 };
		rgb_color green = { 0, 255, 0, 255 };
		rgb_color blue  = { 0, 0, 255, 255 };

		rgb_color compColor = { 0, 0, 0, 255 };
		if (!enabled) {
			compColor.red = compColor.green = compColor.blue = 156;
			red.red = green.green = blue.blue = 70;
			white.red = white.green = white.blue = 70;
		}
		_DrawColorRamp(_RampFrame(0), target, white, compColor, 0, false,
			updateRect);
		_DrawColorRamp(_RampFrame(1), target, red, compColor, 0, false,
			updateRect);
		_DrawColorRamp(_RampFrame(2), target, green, compColor, 0, false,
			updateRect);
		_DrawColorRamp(_RampFrame(3), target, blue, compColor, 0, false,
			updateRect);
	}
}


void
BColorControl::_DrawSelectors(BView* target)
{
	rgb_color base = ViewColor();
	rgb_color lightenmax = tint_color(base, B_LIGHTEN_MAX_TINT);

	if (fPaletteMode) {
		if (fSelectedPaletteColorIndex != -1) {
			target->SetHighColor(lightenmax);
			target->StrokeRect(
				_PaletteSelectorFrame(fSelectedPaletteColorIndex));
		}
	} else {
		rgb_color color = ValueAsColor();
		target->SetHighColor(255, 255, 255);
		target->SetLowColor(0, 0, 0);

		int components[4] = { color.alpha, color.red, color.green, color.blue };

		for (int i = 1; i < 4; i++) {
			BPoint center = _SelectorPosition(_RampFrame(i), components[i]);

			target->SetPenSize(kSelectorPenSize);
			target->StrokeEllipse(center, kSelectorSize / 2, kSelectorSize / 2);
			target->SetPenSize(kSelectorPenSize / 2);
			target->StrokeEllipse(center, kSelectorSize, kSelectorSize,
				B_SOLID_LOW);
			if (i == fFocusedRamp) {
				target->StrokeEllipse(center,
					kSelectorSize / 2, kSelectorSize / 2, B_SOLID_LOW);
			}
		}

		target->SetPenSize(1.0f);
	}
}


void
BColorControl::_DrawColorRamp(BRect rect, BView* target,
	rgb_color baseColor, rgb_color compColor, int16 flag, bool focused,
	BRect updateRect)
{
	float width = rect.Width() + 1;
	rgb_color color = ValueAsColor();
	color.alpha = 255;

	updateRect = updateRect & rect;

	if (updateRect.IsValid() && updateRect.Width() >= 0) {
		target->BeginLineArray((int32)updateRect.Width() + 1);

		for (float i = (updateRect.left - rect.left);
				i <= (updateRect.right - rect.left) + 1; i++) {
			if (baseColor.red == 255)
				color.red = (uint8)(i * 255 / width) + compColor.red;
			if (baseColor.green == 255)
				color.green = (uint8)(i * 255 / width) + compColor.green;
			if (baseColor.blue == 255)
				color.blue = (uint8)(i * 255 / width) + compColor.blue;

			target->AddLine(BPoint(rect.left + i, rect.top),
				BPoint(rect.left + i, rect.bottom - 1), color);
		}

		target->EndLineArray();
	}
}


BPoint
BColorControl::_SelectorPosition(const BRect& rampRect, uint8 shade) const
{
	float radius = kSelectorSize / 2 + kSelectorPenSize / 2;

	return BPoint(rampRect.left + kSelectorHSpacing + radius +
		shade * (rampRect.Width() - 2 * (kSelectorHSpacing + radius)) / 255,
		rampRect.top + rampRect.Height() / 2);
}


BRect
BColorControl::_PaletteFrame() const
{
	return fPaletteFrame.InsetByCopy(-kBevelSpacing, -kBevelSpacing);
}


BRect
BColorControl::_RampFrame(uint8 rampIndex) const
{
	float rampHeight = (float)(fRows * fCellSize / kRampCount);

	return BRect(fPaletteFrame.left,
		fPaletteFrame.top + float(rampIndex) * rampHeight,
		fPaletteFrame.right,
		fPaletteFrame.top + float(rampIndex + 1) * rampHeight);
}


void
BColorControl::_SetCellSize(float size)
{
	BFont font;
	GetFont(&font);
	fCellSize = std::max(kMinCellSize,
		ceilf(size * font.Size() / kDefaultFontSize));
}


float
BColorControl::_TextRectOffset()
{
	return std::max(fRedText->Bounds().Height(),
		ceilf(_PaletteFrame().Height() / 3));
}


BRect
BColorControl::_PaletteSelectorFrame(uint8 colorIndex) const
{
	uint32 row = colorIndex / fColumns;
	uint32 column = colorIndex % fColumns;
	float x = fPaletteFrame.left + column * fCellSize;
	float y = fPaletteFrame.top + row * fCellSize;
	return BRect(x, y, x + fCellSize, y + fCellSize);
}


void
BColorControl::_InitOffscreen()
{
	if (fOffscreenBitmap->Lock()) {
		BView* offscreenView = fOffscreenBitmap->ChildAt((int32)0);
		if (offscreenView != NULL) {
			_DrawColorArea(offscreenView, _PaletteFrame());
			offscreenView->Sync();
		}
		fOffscreenBitmap->Unlock();
	}
}


void
BColorControl::_InvalidateSelector(int16 ramp, rgb_color color, bool focused)
{
	if (fPaletteMode)
		return;

	if (ramp < 1 || ramp > 3)
		return;

	float invalidateRadius = focused
		? kSelectorSize + kSelectorPenSize / 2
		: kSelectorSize / 2 + kSelectorPenSize;

	uint8 colorValue = ramp == 1 ? color.red : ramp == 2 ? color.green
		: color.blue;

	BPoint pos = _SelectorPosition(_RampFrame(ramp), colorValue);
	Invalidate(BRect(pos.x - invalidateRadius, pos.y - invalidateRadius,
		pos.x + invalidateRadius, pos.y + invalidateRadius));
}


void
BColorControl::SetCellSize(float size)
{
	_SetCellSize(size);
	ResizeToPreferred();
}


float
BColorControl::CellSize() const
{
	return fCellSize;
}


void
BColorControl::SetLayout(color_control_layout layout)
{
	switch (layout) {
		case B_CELLS_4x64:
			fColumns = 4;
			fRows = 64;
			break;

		case B_CELLS_8x32:
			fColumns = 8;
			fRows = 32;
			break;

		case B_CELLS_16x16:
			fColumns = 16;
			fRows = 16;
			break;

		case B_CELLS_32x8:
			fColumns = 32;
			fRows = 8;
			break;

		case B_CELLS_64x4:
			fColumns = 64;
			fRows = 4;
			break;
	}

	ResizeToPreferred();
	Invalidate();
}


color_control_layout
BColorControl::Layout() const
{
	if (fColumns == 4 && fRows == 64)
		return B_CELLS_4x64;

	if (fColumns == 8 && fRows == 32)
		return B_CELLS_8x32;

	if (fColumns == 16 && fRows == 16)
		return B_CELLS_16x16;

	if (fColumns == 32 && fRows == 8)
		return B_CELLS_32x8;

	if (fColumns == 64 && fRows == 4)
		return B_CELLS_64x4;

	return B_CELLS_32x8;
}


void
BColorControl::WindowActivated(bool state)
{
	BControl::WindowActivated(state);
}


void
BColorControl::KeyDown(const char* bytes, int32 numBytes)
{
	if (IsFocus() && !fPaletteMode && numBytes == 1) {
		rgb_color color = ValueAsColor();

		switch (bytes[0]) {
			case B_UP_ARROW:
			{
				int16 oldFocus = fFocusedRamp;
				fFocusedRamp--;
				if (fFocusedRamp < 1)
					fFocusedRamp = 3;

				_InvalidateSelector(oldFocus, color, true);
				_InvalidateSelector(fFocusedRamp, color, true);
				break;
			}

			case B_DOWN_ARROW:
			{
				int16 oldFocus = fFocusedRamp;
				fFocusedRamp++;
				if (fFocusedRamp > 3)
					fFocusedRamp = 1;

				_InvalidateSelector(oldFocus, color, true);
				_InvalidateSelector(fFocusedRamp, color, true);
				break;
			}

			case B_LEFT_ARROW:
			{
				bool goFaster = false;
				if (Window() != NULL) {
					BMessage* message = Window()->CurrentMessage();
					if (message != NULL && message->what == B_KEY_DOWN) {
						int32 repeats = 0;
						if (message->FindInt32("be:key_repeat", &repeats)
								== B_OK && repeats > 4) {
							goFaster = true;
						}
					}
				}

				if (fFocusedRamp == 1) {
					if (goFaster && color.red >= 5)
						color.red -= 5;
					else if (color.red > 0)
						color.red--;
				} else if (fFocusedRamp == 2) {
					if (goFaster && color.green >= 5)
						color.green -= 5;
					else if (color.green > 0)
						color.green--;
				} else if (fFocusedRamp == 3) {
				 	if (goFaster && color.blue >= 5)
						color.blue -= 5;
					else if (color.blue > 0)
						color.blue--;
				}

				SetValue(color);
				Invoke();
				break;
			}

			case B_RIGHT_ARROW:
			{
				bool goFaster = false;
				if (Window() != NULL) {
					BMessage* message = Window()->CurrentMessage();
					if (message != NULL && message->what == B_KEY_DOWN) {
						int32 repeats = 0;
						if (message->FindInt32("be:key_repeat", &repeats)
								== B_OK && repeats > 4) {
							goFaster = true;
						}
					}
				}

				if (fFocusedRamp == 1) {
					if (goFaster && color.red <= 250)
						color.red += 5;
					else if (color.red < 255)
						color.red++;
				} else if (fFocusedRamp == 2) {
					if (goFaster && color.green <= 250)
						color.green += 5;
					else if (color.green < 255)
						color.green++;
				} else if (fFocusedRamp == 3) {
				 	if (goFaster && color.blue <= 250)
						color.blue += 5;
					else if (color.blue < 255)
						color.blue++;
				}

				SetValue(color);
				Invoke();
				break;
			}
		}
	}

	BControl::KeyDown(bytes, numBytes);
}


void
BColorControl::MouseUp(BPoint point)
{
	fClickedRamp = -1;
	SetTracking(false);
}


void
BColorControl::MouseDown(BPoint point)
{
	if (!IsEnabled())
		return;
	if (!fPaletteFrame.Contains(point))
		return;

	if (fPaletteMode) {
		int col = (int)((point.x - fPaletteFrame.left) / fCellSize);
		int row = (int)((point.y - fPaletteFrame.top) / fCellSize);
		int colorIndex = row * fColumns + col;
		if (colorIndex >= 0 && colorIndex < 256) {
			fSelectedPaletteColorIndex = colorIndex;
			SetValue(system_colors()->color_list[colorIndex]);
		}
	} else {
		rgb_color color = ValueAsColor();

		uint8 shade = (unsigned char)max_c(0,
			min_c((point.x - _RampFrame(0).left) * 255
				/ _RampFrame(0).Width(), 255));

		if (_RampFrame(0).Contains(point)) {
			color.red = color.green = color.blue = shade;
			fClickedRamp = 0;
		} else if (_RampFrame(1).Contains(point)) {
			color.red = shade;
			fClickedRamp = 1;
		} else if (_RampFrame(2).Contains(point)) {
			color.green = shade;
			fClickedRamp = 2;
		} else if (_RampFrame(3).Contains(point)) {
			color.blue = shade;
			fClickedRamp = 3;
		}

		SetValue(color);
	}

	Invoke();

	SetTracking(true);
	SetMouseEventMask(B_POINTER_EVENTS,
		B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
}


void
BColorControl::MouseMoved(BPoint point, uint32 transit,
	const BMessage* message)
{
	if (!IsTracking())
		return;

	if (fPaletteMode && fPaletteFrame.Contains(point)) {
		int col = (int)((point.x - fPaletteFrame.left) / fCellSize);
		int row = (int)((point.y - fPaletteFrame.top) / fCellSize);
		int colorIndex = row * fColumns + col;
		if (colorIndex >= 0 && colorIndex < 256) {
			fSelectedPaletteColorIndex = colorIndex;
			SetValue(system_colors()->color_list[colorIndex]);
		}
	} else {
		if (fClickedRamp < 0 || fClickedRamp > 3)
			return;

		rgb_color color = ValueAsColor();

		uint8 shade = (unsigned char)max_c(0,
			min_c((point.x - _RampFrame(0).left) * 255
				/ _RampFrame(0).Width(), 255));

		if (fClickedRamp == 0)
			color.red = color.green = color.blue = shade;
		else if (fClickedRamp == 1)
			color.red = shade;
		else if (fClickedRamp == 2)
			color.green = shade;
		else if (fClickedRamp == 3)
			color.blue = shade;

		SetValue(color);
	}

	Invoke();
}


void
BColorControl::DetachedFromWindow()
{
	BControl::DetachedFromWindow();
}


void
BColorControl::GetPreferredSize(float* _width, float* _height)
{
	BRect rect = _PaletteFrame();

	if (rect.Height() < fBlueText->Frame().bottom) {
		// adjust the height to fit
		rect.bottom = fBlueText->Frame().bottom;
	}

	if (_width) {
		*_width = rect.Width() + kTextFieldsHSpacing
			+ fRedText->Bounds().Width();
	}

	if (_height)
		*_height = rect.Height();
}


void
BColorControl::ResizeToPreferred()
{
	_LayoutView();
	BControl::ResizeToPreferred();
}


status_t
BColorControl::Invoke(BMessage* message)
{
	return BControl::Invoke(message);
}


void
BColorControl::FrameMoved(BPoint newPosition)
{
	BControl::FrameMoved(newPosition);
}


void
BColorControl::FrameResized(float newWidth, float newHeight)
{
	BControl::FrameResized(newWidth, newHeight);
}


BHandler*
BColorControl::ResolveSpecifier(BMessage* message, int32 index,
	BMessage* specifier, int32 form, const char* property)
{
	return BControl::ResolveSpecifier(message, index, specifier, form,
		property);
}


status_t
BColorControl::GetSupportedSuites(BMessage* data)
{
	return BControl::GetSupportedSuites(data);
}


void
BColorControl::MakeFocus(bool focused)
{
	fFocusedRamp = !fPaletteMode && focused ? 1 : -1;
	BControl::MakeFocus(focused);
}


void
BColorControl::AllAttached()
{
	BControl::AllAttached();
}


void
BColorControl::AllDetached()
{
	BControl::AllDetached();
}


status_t
BColorControl::SetIcon(const BBitmap* icon, uint32 flags)
{
	return BControl::SetIcon(icon, flags);
}


status_t
BColorControl::Perform(perform_code code, void* _data)
{
	switch (code) {
		case PERFORM_CODE_MIN_SIZE:
			((perform_data_min_size*)_data)->return_value
				= BColorControl::MinSize();
			return B_OK;

		case PERFORM_CODE_MAX_SIZE:
			((perform_data_max_size*)_data)->return_value
				= BColorControl::MaxSize();
			return B_OK;

		case PERFORM_CODE_PREFERRED_SIZE:
			((perform_data_preferred_size*)_data)->return_value
				= BColorControl::PreferredSize();
			return B_OK;

		case PERFORM_CODE_LAYOUT_ALIGNMENT:
			((perform_data_layout_alignment*)_data)->return_value
				= BColorControl::LayoutAlignment();
			return B_OK;

		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
			((perform_data_has_height_for_width*)_data)->return_value
				= BColorControl::HasHeightForWidth();
			return B_OK;

		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
		{
			perform_data_get_height_for_width* data
				= (perform_data_get_height_for_width*)_data;
			BColorControl::GetHeightForWidth(data->width, &data->min,
				&data->max, &data->preferred);
			return B_OK;
		}

		case PERFORM_CODE_SET_LAYOUT:
		{
			perform_data_set_layout* data = (perform_data_set_layout*)_data;
			BColorControl::SetLayout(data->layout);
			return B_OK;
		}

		case PERFORM_CODE_LAYOUT_INVALIDATED:
		{
			perform_data_layout_invalidated* data
				= (perform_data_layout_invalidated*)_data;
			BColorControl::LayoutInvalidated(data->descendants);
			return B_OK;
		}

		case PERFORM_CODE_DO_LAYOUT:
		{
			BColorControl::DoLayout();
			return B_OK;
		}

		case PERFORM_CODE_SET_ICON:
		{
			perform_data_set_icon* data = (perform_data_set_icon*)_data;
			return BColorControl::SetIcon(data->icon, data->flags);
		}
	}

	return BControl::Perform(code, _data);
}


void BColorControl::_ReservedColorControl1() {}
void BColorControl::_ReservedColorControl2() {}
void BColorControl::_ReservedColorControl3() {}
void BColorControl::_ReservedColorControl4() {}


BColorControl &
BColorControl::operator=(const BColorControl &)
{
	return *this;
}