xref: /haiku/src/kits/interface/ColorControl.cpp (revision 1d9d47fc72028bb71b5f232a877231e59cfe2438)
1 /*
2  * Copyright 2001-2006, Haiku Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Axel Dörfler, axeld@pinc-software.de
8  */
9 
10 /**	BColorControl displays a palette of selectable colors. */
11 
12 
13 #include <stdio.h>
14 #include <stdlib.h>
15 
16 #include <ColorControl.h>
17 #include <Bitmap.h>
18 #include <TextControl.h>
19 #include <Region.h>
20 #include <Screen.h>
21 #include <Window.h>
22 
23 
24 static const uint32 kMsgColorEntered = 'ccol';
25 static const uint32 kMinCellSize = 6;
26 
27 
28 BColorControl::BColorControl(BPoint leftTop, color_control_layout layout,
29 	float cellSize, const char *name, BMessage *message,
30 	bool bufferedDrawing)
31 	: BControl(BRect(leftTop, leftTop), name, NULL, message,
32 			B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_NAVIGABLE)
33 {
34 	_InitData(layout, cellSize, bufferedDrawing, NULL);
35 }
36 
37 
38 BColorControl::BColorControl(BMessage* archive)
39 	: BControl(archive)
40 {
41 	int32 layout;
42 	float cellSize;
43 	bool useOffscreen;
44 
45 	archive->FindInt32("_layout", &layout);
46 	archive->FindFloat("_csize", &cellSize);
47 	archive->FindBool("_use_off", &useOffscreen);
48 
49 	_InitData((color_control_layout)layout, cellSize, useOffscreen, archive);
50 }
51 
52 
53 BColorControl::~BColorControl()
54 {
55 }
56 
57 
58 void
59 BColorControl::_InitData(color_control_layout layout, float size,
60 	bool useOffscreen, BMessage* archive)
61 {
62 	fColumns = layout;
63 	fRows = 256 / fColumns;
64 	fCellSize = max_c(kMinCellSize, size);
65 	fFocusedComponent = 0;
66 
67 	if (archive) {
68 		int32 value = 0;
69 		archive->FindInt32("_val", &value);
70 
71 		SetValue(value);
72 
73 		fRedText = (BTextControl*)FindView("_red");
74 		fGreenText = (BTextControl*)FindView("_green");
75 		fBlueText = (BTextControl*)FindView("_blue");
76 	} else {
77 		BRect rect(0.0f, 0.0f, 70.0f, 15.0f);
78 		float labelWidth = StringWidth("Green:") + 5;
79 		rect.right = labelWidth + StringWidth("999") + 20;
80 
81 		// red
82 
83 		fRedText = new BTextControl(rect, "_red", "Red:", "0",
84 			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
85 			B_WILL_DRAW | B_NAVIGABLE);
86 		fRedText->SetDivider(labelWidth);
87 
88 		float offset = fRedText->Bounds().Height() + 2;
89 
90 		for (int32 i = 0; i < 256; i++)
91 			fRedText->TextView()->DisallowChar(i);
92 		for (int32 i = '0'; i <= '9'; i++)
93 			fRedText->TextView()->AllowChar(i);
94 		fRedText->TextView()->SetMaxBytes(3);
95 
96 		// green
97 
98 		rect.OffsetBy(0.0f, offset);
99 		fGreenText = new BTextControl(rect, "_green", "Green:", "0",
100 			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
101 			B_WILL_DRAW | B_NAVIGABLE);
102 		fGreenText->SetDivider(labelWidth);
103 
104 		for (int32 i = 0; i < 256; i++)
105 			fGreenText->TextView()->DisallowChar(i);
106 		for (int32 i = '0'; i <= '9'; i++)
107 			fGreenText->TextView()->AllowChar(i);
108 		fGreenText->TextView()->SetMaxBytes(3);
109 
110 		// blue
111 
112 		rect.OffsetBy(0.0f, offset);
113 		fBlueText = new BTextControl(rect, "_blue", "Blue:", "0",
114 			new BMessage(kMsgColorEntered), B_FOLLOW_LEFT | B_FOLLOW_TOP,
115 			B_WILL_DRAW | B_NAVIGABLE);
116 		fBlueText->SetDivider(labelWidth);
117 
118 		for (int32 i = 0; i < 256; i++)
119 			fBlueText->TextView()->DisallowChar(i);
120 		for (int32 i = '0'; i <= '9'; i++)
121 			fBlueText->TextView()->AllowChar(i);
122 		fBlueText->TextView()->SetMaxBytes(3);
123 
124 		_LayoutView();
125 
126 		AddChild(fRedText);
127 		AddChild(fGreenText);
128 		AddChild(fBlueText);
129 	}
130 
131 	if (useOffscreen) {
132 		BRect bounds(Bounds());
133 		bounds.right = floorf(fBlueText->Frame().left) - 1;
134 
135 		fBitmap = new BBitmap(bounds, /*BScreen(Window()).ColorSpace()*/B_RGB32, true, false);
136 		fOffscreenView = new BView(bounds, "off_view", 0, 0);
137 
138 		fBitmap->Lock();
139 		fBitmap->AddChild(fOffscreenView);
140 		fBitmap->Unlock();
141 	} else {
142 		fBitmap = NULL;
143 		fOffscreenView = NULL;
144 	}
145 }
146 
147 
148 void
149 BColorControl::_LayoutView()
150 {
151 	BRect rect(0.0f, 0.0f, ceil(fColumns * fCellSize), ceil(fRows * fCellSize + 2));
152 
153 	if (rect.Height() < fBlueText->Frame().bottom) {
154 		// adjust the height to fit
155 		rect.bottom = fBlueText->Frame().bottom;
156 	}
157 
158 	ResizeTo(rect.Width() + fRedText->Bounds().Width(), rect.Height());
159 
160 	float offset = floor(rect.bottom / 4);
161 	float y = offset;
162 	if (offset < fRedText->Bounds().Height() + 2) {
163 		offset = fRedText->Bounds().Height() + 2;
164 		y = 0;
165 	}
166 
167 	fRedText->MoveTo(rect.right + 1, y);
168 
169 	y += offset;
170 	fGreenText->MoveTo(rect.right + 1, y);
171 
172 	y += offset;
173 	fBlueText->MoveTo(rect.right + 1, y);
174 }
175 
176 
177 BArchivable *
178 BColorControl::Instantiate(BMessage *archive)
179 {
180 	if ( validate_instantiation(archive, "BColorControl"))
181 		return new BColorControl(archive);
182 
183 	return NULL;
184 }
185 
186 
187 status_t
188 BColorControl::Archive(BMessage *archive, bool deep) const
189 {
190 	status_t status = BControl::Archive(archive, deep);
191 
192 	if (status == B_OK)
193 		status = archive->AddInt32("_layout", Layout());
194 	if (status == B_OK)
195 		status = archive->AddFloat("_csize", fCellSize);
196 	if (status == B_OK)
197 		status = archive->AddBool("_use_off", fOffscreenView != NULL);
198 
199 	return status;
200 }
201 
202 
203 void
204 BColorControl::SetValue(int32 value)
205 {
206 	if (Value() == value)
207 		return;
208 
209 	rgb_color c1 = ValueAsColor();
210 	rgb_color c2;
211 	c2.red = (value & 0xFF000000) >> 24;
212 	c2.green = (value & 0x00FF0000) >> 16;
213 	c2.blue = (value & 0x0000FF00) >> 8;
214 	char string[4];
215 
216 	if (c1.red != c2.red) {
217 		sprintf(string, "%d", c2.red);
218 		fRedText->SetText(string);
219 	}
220 
221 	if (c1.green != c2.green) {
222 		sprintf(string, "%d", c2.green);
223 		fGreenText->SetText(string);
224 	}
225 
226 	if (c1.blue != c2.blue) {
227 		sprintf(string, "%d", c2.blue);
228 		fBlueText->SetText(string);
229 	}
230 
231 	BControl::SetValueNoUpdate(value);
232 
233 	// TODO: This causes lot of flickering
234 	Invalidate();
235 
236 	if (LockLooper()) {
237 		Window()->UpdateIfNeeded();
238 		UnlockLooper();
239 	}
240 }
241 
242 
243 rgb_color
244 BColorControl::ValueAsColor()
245 {
246 	int32 value = Value();
247 	rgb_color color;
248 
249 	color.red = (value & 0xFF000000) >> 24;
250 	color.green = (value & 0x00FF0000) >> 16;
251 	color.blue = (value & 0x0000FF00) >> 8;
252 	color.alpha = 255;
253 
254 	return color;
255 }
256 
257 
258 void
259 BColorControl::SetEnabled(bool enabled)
260 {
261 	BControl::SetEnabled(enabled);
262 
263 	fRedText->SetEnabled(enabled);
264 	fGreenText->SetEnabled(enabled);
265 	fBlueText->SetEnabled(enabled);
266 }
267 
268 
269 void
270 BColorControl::AttachedToWindow()
271 {
272 	if (Parent())
273 		SetViewColor(Parent()->ViewColor());
274 	else
275 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
276 
277 	BControl::AttachedToWindow();
278 
279 	fRedText->SetTarget(this);
280 	fGreenText->SetTarget(this);
281 	fBlueText->SetTarget(this);
282 }
283 
284 
285 void
286 BColorControl::MessageReceived(BMessage *message)
287 {
288 	switch (message->what) {
289 		case kMsgColorEntered:
290 		{
291 			rgb_color color;
292 			color.red = strtol(fRedText->Text(), NULL, 10);
293 			color.green = strtol(fGreenText->Text(), NULL, 10);
294 			color.blue = strtol(fBlueText->Text(), NULL, 10);
295 			color.alpha = 255;
296 
297 			SetValue(color);
298 			Invoke();
299 			break;
300 		}
301 		default:
302 			BControl::MessageReceived(message);
303 	}
304 }
305 
306 
307 void
308 BColorControl::Draw(BRect updateRect)
309 {
310 	if (fBitmap) {
311 		if (!fBitmap->Lock())
312 			return;
313 
314 		if (fOffscreenView->Bounds().Intersects(updateRect)) {
315 			_UpdateOffscreen(updateRect);
316 			BRegion region(updateRect);
317 			ConstrainClippingRegion(&region);
318 			DrawBitmap(fBitmap, B_ORIGIN);
319 			ConstrainClippingRegion(NULL);
320 		}
321 
322 		fBitmap->Unlock();
323 	} else
324 		_DrawColorArea(this, updateRect);
325 }
326 
327 
328 void
329 BColorControl::_DrawColorArea(BView* target, BRect update)
330 {
331 	BRegion region(update);
332 	target->ConstrainClippingRegion(&region);
333 
334 	BRect rect(0.0f, 0.0f, ceil(fColumns * fCellSize), Bounds().bottom);
335 
336 	rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR),
337 	lightenmax = tint_color(noTint, B_LIGHTEN_MAX_TINT),
338 	darken1 = tint_color(noTint, B_DARKEN_1_TINT),
339 	darken4 = tint_color(noTint, B_DARKEN_4_TINT);
340 
341 	// First bevel
342 	target->SetHighColor(darken1);
343 	target->StrokeLine(rect.LeftBottom(), rect.LeftTop());
344 	target->StrokeLine(rect.LeftTop(), rect.RightTop());
345 	target->SetHighColor(lightenmax);
346 	target->StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
347 	target->StrokeLine(rect.RightBottom(), BPoint(rect.right, rect.top + 1.0f));
348 
349 	rect.InsetBy(1.0f, 1.0f);
350 
351 	// Second bevel
352 	target->SetHighColor(darken4);
353 	target->StrokeLine(rect.LeftBottom(), rect.LeftTop());
354 	target->StrokeLine(rect.LeftTop(), rect.RightTop());
355 	target->SetHighColor(noTint);
356 	target->StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
357 	target->StrokeLine(rect.RightBottom(), BPoint(rect.right, rect.top + 1.0f));
358 
359 	// Ramps
360 	rgb_color white = {255, 255, 255, 255};
361 	rgb_color red = {255, 0, 0, 255};
362 	rgb_color green = {0, 255, 0, 255};
363 	rgb_color blue = {0, 0, 255, 255};
364 
365 	rect.InsetBy(1.0f, 1.0f);
366 
367 	BRect rampRect(rect);
368 	float rampSize = rampRect.Height() / 4.0;
369 
370 	rampRect.bottom = rampRect.top + rampSize;
371 
372 	_ColorRamp(rampRect, target, white, 0, false);
373 
374 	rampRect.OffsetBy(0, rampSize);
375 	_ColorRamp(rampRect, target, red, 0, false);
376 
377 	rampRect.OffsetBy(0,rampSize);
378 	_ColorRamp(rampRect, target, green, 0, false);
379 
380 	rampRect.OffsetBy(0, rampSize);
381 	_ColorRamp(rampRect, target, blue, 0, false);
382 
383 	// Selectors
384 	rgb_color color = ValueAsColor();
385 	float x, y = rampSize * 1.5;
386 
387 	target->SetDrawingMode(B_OP_OVER);
388 	target->SetPenSize(2.0f);
389 
390 	x = rect.left + color.red * (rect.Width() - 7) / 255;
391 	target->SetHighColor(255, 255, 255);
392 	target->StrokeEllipse(BRect(x, y, x + 4.0f, y + 4.0f));
393 
394 	y += rampSize;
395 
396 	x = rect.left + color.green * (rect.Width() - 7) / 255;
397 	target->SetHighColor(255, 255, 255);
398 	target->StrokeEllipse(BRect(x, y, x + 4.0f, y + 4.0f));
399 
400 	y += rampSize;
401 
402 	x = rect.left + color.blue * (rect.Width() - 7) / 255;
403 	target->SetHighColor(255, 255, 255);
404 	target->StrokeEllipse(BRect(x, y, x + 4.0f, y + 4.0f));
405 
406 	target->SetPenSize(1.0f);
407 	target->SetDrawingMode(B_OP_COPY);
408 
409 	target->ConstrainClippingRegion(NULL);
410 }
411 
412 
413 void
414 BColorControl::_ColorRamp(BRect rect, BView* target,
415 	rgb_color baseColor, int16 flag, bool focused)
416 {
417 	float width = rect.Width()+1;
418 	rgb_color color;
419 	color.alpha = 255;
420 
421 	target->BeginLineArray((int32)width);
422 
423 	for (float i = 0; i <= width; i++) {
424 		color.red = (uint8)(i * baseColor.red / width);
425 		color.green = (uint8)(i * baseColor.green / width);
426 		color.blue = (uint8)(i * baseColor.blue / width);
427 
428 		target->AddLine(BPoint(rect.left + i, rect.top),
429 			BPoint(rect.left + i, rect.bottom), color);
430 	}
431 
432 	target->EndLineArray();
433 }
434 
435 
436 void
437 BColorControl::_UpdateOffscreen(BRect update)
438 {
439 	if (fBitmap->Lock()) {
440 		update = update & fOffscreenView->Bounds();
441 		fOffscreenView->FillRect(update);
442 		_DrawColorArea(fOffscreenView, update);
443 		fOffscreenView->Sync();
444 		fBitmap->Unlock();
445 	}
446 }
447 
448 
449 void
450 BColorControl::SetCellSize(float cellSide)
451 {
452 	fCellSize = max_c(kMinCellSize, cellSide);
453 
454 	ResizeToPreferred();
455 }
456 
457 
458 float
459 BColorControl::CellSize() const
460 {
461 	return fCellSize;
462 }
463 
464 
465 void
466 BColorControl::SetLayout(color_control_layout layout)
467 {
468 	switch (layout) {
469 		case B_CELLS_4x64:
470 			fColumns = 4;
471 			fRows = 64;
472 			break;
473 		case B_CELLS_8x32:
474 			fColumns = 8;
475 			fRows = 32;
476 			break;
477 		case B_CELLS_16x16:
478 			fColumns = 16;
479 			fRows = 16;
480 			break;
481 		case B_CELLS_32x8:
482 			fColumns = 32;
483 			fRows = 8;
484 			break;
485 		case B_CELLS_64x4:
486 			fColumns = 64;
487 			fRows = 4;
488 			break;
489 	}
490 
491 	ResizeToPreferred();
492 	Invalidate();
493 }
494 
495 
496 color_control_layout
497 BColorControl::Layout() const
498 {
499 	if (fColumns == 4 && fRows == 64)
500 		return B_CELLS_4x64;
501 	if (fColumns == 8 && fRows == 32)
502 		return B_CELLS_8x32;
503 	if (fColumns == 16 && fRows == 16)
504 		return B_CELLS_16x16;
505 	if (fColumns == 32 && fRows == 8)
506 		return B_CELLS_32x8;
507 	if (fColumns == 64 && fRows == 4)
508 		return B_CELLS_64x4;
509 
510 	return B_CELLS_32x8;
511 }
512 
513 
514 void
515 BColorControl::WindowActivated(bool state)
516 {
517 	BControl::WindowActivated(state);
518 }
519 
520 
521 void
522 BColorControl::KeyDown(const char* bytes, int32 numBytes)
523 {
524 	// TODO: make this keyboard navigable!
525 	BControl::KeyDown(bytes, numBytes);
526 }
527 
528 
529 void
530 BColorControl::MouseUp(BPoint point)
531 {
532 	fFocusedComponent = 0;
533 	SetTracking(false);
534 }
535 
536 
537 void
538 BColorControl::MouseDown(BPoint point)
539 {
540 	BRect rect(0.0f, 0.0f, fColumns * fCellSize, Bounds().bottom);
541 	if (!rect.Contains(point))
542 		return;
543 
544 	rgb_color color = ValueAsColor();
545 
546 	float rampsize = rect.bottom / 4;
547 
548 	uint8 shade = (unsigned char)max_c(0,
549 		min_c((point.x - 2) * 255 / (rect.Width() - 4.0f), 255));
550 
551 	if (point.y - 2 < rampsize) {
552 		color.red = color.green = color.blue = shade;
553 		fFocusedComponent = 1;
554 	} else if (point.y - 2 < (rampsize * 2)) {
555 		color.red = shade;
556 		fFocusedComponent = 2;
557 	} else if (point.y - 2 < (rampsize * 3)) {
558 		color.green = shade;
559 		fFocusedComponent = 3;
560 	} else {
561 		color.blue = shade;
562 		fFocusedComponent = 4;
563 	}
564 
565 	SetValue(color);
566 	Invoke();
567 
568 	SetTracking(true);
569 	MakeFocus();
570 	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
571 }
572 
573 
574 void
575 BColorControl::MouseMoved(BPoint point, uint32 transit,
576 	const BMessage *message)
577 {
578 	if (fFocusedComponent == 0 || !IsTracking())
579 		return;
580 
581 	rgb_color color = ValueAsColor();
582 
583 	BRect rect(0.0f, 0.0f, fColumns * fCellSize, Bounds().bottom);
584 
585 	uint8 shade = (unsigned char)max_c(0,
586 		min_c((point.x - 2) * 255 / (rect.Width() - 4.0f), 255));
587 
588 	switch (fFocusedComponent) {
589 		case 1:
590 			color.red = color.green = color.blue = shade;
591 			break;
592 		case 2:
593 			color.red = shade;
594 			break;
595 		case 3:
596 			color.green = shade;
597 			break;
598 		case 4:
599 			color.blue = shade;
600 			break;
601 		default:
602 			break;
603 	}
604 
605 	SetValue(color);
606 	Invoke();
607 }
608 
609 
610 void
611 BColorControl::DetachedFromWindow()
612 {
613 	BControl::DetachedFromWindow();
614 }
615 
616 
617 void
618 BColorControl::GetPreferredSize(float *_width, float *_height)
619 {
620 	if (_width)
621 		*_width = fColumns * fCellSize + 4.0f + fRedText->Bounds().Width();
622 
623 	if (_height)
624 		*_height = fBlueText->Frame().bottom;
625 }
626 
627 
628 void
629 BColorControl::ResizeToPreferred()
630 {
631 	BControl::ResizeToPreferred();
632 
633 	_LayoutView();
634 }
635 
636 
637 status_t
638 BColorControl::Invoke(BMessage *msg)
639 {
640 	return BControl::Invoke(msg);
641 }
642 
643 
644 void
645 BColorControl::FrameMoved(BPoint new_position)
646 {
647 	BControl::FrameMoved(new_position);
648 }
649 
650 
651 void
652 BColorControl::FrameResized(float new_width, float new_height)
653 {
654 	BControl::FrameResized(new_width, new_height);
655 }
656 
657 
658 BHandler *
659 BColorControl::ResolveSpecifier(BMessage *msg, int32 index,
660 	BMessage *specifier, int32 form, const char *property)
661 {
662 	return BControl::ResolveSpecifier(msg, index, specifier, form, property);
663 }
664 
665 
666 status_t
667 BColorControl::GetSupportedSuites(BMessage *data)
668 {
669 	return BControl::GetSupportedSuites(data);
670 }
671 
672 
673 void
674 BColorControl::MakeFocus(bool state)
675 {
676 	BControl::MakeFocus(state);
677 }
678 
679 
680 void
681 BColorControl::AllAttached()
682 {
683 	BControl::AllAttached();
684 }
685 
686 
687 void
688 BColorControl::AllDetached()
689 {
690 	BControl::AllDetached();
691 }
692 
693 
694 status_t
695 BColorControl::Perform(perform_code d, void *arg)
696 {
697 	return BControl::Perform(d, arg);
698 }
699 
700 
701 void BColorControl::_ReservedColorControl1() {}
702 void BColorControl::_ReservedColorControl2() {}
703 void BColorControl::_ReservedColorControl3() {}
704 void BColorControl::_ReservedColorControl4() {}
705 
706 
707 BColorControl &
708 BColorControl::operator=(const BColorControl &)
709 {
710 	return *this;
711 }
712