xref: /haiku/src/kits/interface/ColorControl.cpp (revision 9ecf9d1c1d4888d341a6eac72112c72d1ae3a4cb)
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::SetLayout(BLayout* layout)
205 {
206 	// We need to implement this method, since we have another SetLayout()
207 	// method and C++ has this special method hiding "feature".
208 	BControl::SetLayout(layout);
209 }
210 
211 
212 void
213 BColorControl::SetValue(int32 value)
214 {
215 	if (Value() == value)
216 		return;
217 
218 	rgb_color c1 = ValueAsColor();
219 	rgb_color c2;
220 	c2.red = (value & 0xFF000000) >> 24;
221 	c2.green = (value & 0x00FF0000) >> 16;
222 	c2.blue = (value & 0x0000FF00) >> 8;
223 	char string[4];
224 
225 	if (c1.red != c2.red) {
226 		sprintf(string, "%d", c2.red);
227 		fRedText->SetText(string);
228 	}
229 
230 	if (c1.green != c2.green) {
231 		sprintf(string, "%d", c2.green);
232 		fGreenText->SetText(string);
233 	}
234 
235 	if (c1.blue != c2.blue) {
236 		sprintf(string, "%d", c2.blue);
237 		fBlueText->SetText(string);
238 	}
239 
240 	BControl::SetValueNoUpdate(value);
241 
242 	// TODO: This causes lot of flickering
243 	Invalidate();
244 
245 	if (LockLooper()) {
246 		Window()->UpdateIfNeeded();
247 		UnlockLooper();
248 	}
249 }
250 
251 
252 rgb_color
253 BColorControl::ValueAsColor()
254 {
255 	int32 value = Value();
256 	rgb_color color;
257 
258 	color.red = (value & 0xFF000000) >> 24;
259 	color.green = (value & 0x00FF0000) >> 16;
260 	color.blue = (value & 0x0000FF00) >> 8;
261 	color.alpha = 255;
262 
263 	return color;
264 }
265 
266 
267 void
268 BColorControl::SetEnabled(bool enabled)
269 {
270 	BControl::SetEnabled(enabled);
271 
272 	fRedText->SetEnabled(enabled);
273 	fGreenText->SetEnabled(enabled);
274 	fBlueText->SetEnabled(enabled);
275 }
276 
277 
278 void
279 BColorControl::AttachedToWindow()
280 {
281 	if (Parent())
282 		SetViewColor(Parent()->ViewColor());
283 	else
284 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
285 
286 	BControl::AttachedToWindow();
287 
288 	fRedText->SetTarget(this);
289 	fGreenText->SetTarget(this);
290 	fBlueText->SetTarget(this);
291 }
292 
293 
294 void
295 BColorControl::MessageReceived(BMessage *message)
296 {
297 	switch (message->what) {
298 		case kMsgColorEntered:
299 		{
300 			rgb_color color;
301 			color.red = strtol(fRedText->Text(), NULL, 10);
302 			color.green = strtol(fGreenText->Text(), NULL, 10);
303 			color.blue = strtol(fBlueText->Text(), NULL, 10);
304 			color.alpha = 255;
305 
306 			SetValue(color);
307 			Invoke();
308 			break;
309 		}
310 		default:
311 			BControl::MessageReceived(message);
312 	}
313 }
314 
315 
316 void
317 BColorControl::Draw(BRect updateRect)
318 {
319 	if (fBitmap) {
320 		if (!fBitmap->Lock())
321 			return;
322 
323 		if (fOffscreenView->Bounds().Intersects(updateRect)) {
324 			_UpdateOffscreen(updateRect);
325 			BRegion region(updateRect);
326 			ConstrainClippingRegion(&region);
327 			DrawBitmap(fBitmap, B_ORIGIN);
328 			ConstrainClippingRegion(NULL);
329 		}
330 
331 		fBitmap->Unlock();
332 	} else
333 		_DrawColorArea(this, updateRect);
334 }
335 
336 
337 void
338 BColorControl::_DrawColorArea(BView* target, BRect update)
339 {
340 	BRegion region(update);
341 	target->ConstrainClippingRegion(&region);
342 
343 	BRect rect(0.0f, 0.0f, ceil(fColumns * fCellSize), Bounds().bottom);
344 
345 	rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR),
346 	lightenmax = tint_color(noTint, B_LIGHTEN_MAX_TINT),
347 	darken1 = tint_color(noTint, B_DARKEN_1_TINT),
348 	darken4 = tint_color(noTint, B_DARKEN_4_TINT);
349 
350 	// First bevel
351 	target->SetHighColor(darken1);
352 	target->StrokeLine(rect.LeftBottom(), rect.LeftTop());
353 	target->StrokeLine(rect.LeftTop(), rect.RightTop());
354 	target->SetHighColor(lightenmax);
355 	target->StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
356 	target->StrokeLine(rect.RightBottom(), BPoint(rect.right, rect.top + 1.0f));
357 
358 	rect.InsetBy(1.0f, 1.0f);
359 
360 	// Second bevel
361 	target->SetHighColor(darken4);
362 	target->StrokeLine(rect.LeftBottom(), rect.LeftTop());
363 	target->StrokeLine(rect.LeftTop(), rect.RightTop());
364 	target->SetHighColor(noTint);
365 	target->StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
366 	target->StrokeLine(rect.RightBottom(), BPoint(rect.right, rect.top + 1.0f));
367 
368 	// Ramps
369 	rgb_color white = {255, 255, 255, 255};
370 	rgb_color red = {255, 0, 0, 255};
371 	rgb_color green = {0, 255, 0, 255};
372 	rgb_color blue = {0, 0, 255, 255};
373 
374 	rect.InsetBy(1.0f, 1.0f);
375 
376 	BRect rampRect(rect);
377 	float rampSize = rampRect.Height() / 4.0;
378 
379 	rampRect.bottom = rampRect.top + rampSize;
380 
381 	_ColorRamp(rampRect, target, white, 0, false);
382 
383 	rampRect.OffsetBy(0, rampSize);
384 	_ColorRamp(rampRect, target, red, 0, false);
385 
386 	rampRect.OffsetBy(0,rampSize);
387 	_ColorRamp(rampRect, target, green, 0, false);
388 
389 	rampRect.OffsetBy(0, rampSize);
390 	_ColorRamp(rampRect, target, blue, 0, false);
391 
392 	// Selectors
393 	rgb_color color = ValueAsColor();
394 	float x, y = rampSize * 1.5;
395 
396 	target->SetDrawingMode(B_OP_OVER);
397 	target->SetPenSize(2.0f);
398 
399 	x = rect.left + color.red * (rect.Width() - 7) / 255;
400 	target->SetHighColor(255, 255, 255);
401 	target->StrokeEllipse(BRect(x, y, x + 4.0f, y + 4.0f));
402 
403 	y += rampSize;
404 
405 	x = rect.left + color.green * (rect.Width() - 7) / 255;
406 	target->SetHighColor(255, 255, 255);
407 	target->StrokeEllipse(BRect(x, y, x + 4.0f, y + 4.0f));
408 
409 	y += rampSize;
410 
411 	x = rect.left + color.blue * (rect.Width() - 7) / 255;
412 	target->SetHighColor(255, 255, 255);
413 	target->StrokeEllipse(BRect(x, y, x + 4.0f, y + 4.0f));
414 
415 	target->SetPenSize(1.0f);
416 	target->SetDrawingMode(B_OP_COPY);
417 
418 	target->ConstrainClippingRegion(NULL);
419 }
420 
421 
422 void
423 BColorControl::_ColorRamp(BRect rect, BView* target,
424 	rgb_color baseColor, int16 flag, bool focused)
425 {
426 	float width = rect.Width()+1;
427 	rgb_color color;
428 	color.alpha = 255;
429 
430 	target->BeginLineArray((int32)width);
431 
432 	for (float i = 0; i <= width; i++) {
433 		color.red = (uint8)(i * baseColor.red / width);
434 		color.green = (uint8)(i * baseColor.green / width);
435 		color.blue = (uint8)(i * baseColor.blue / width);
436 
437 		target->AddLine(BPoint(rect.left + i, rect.top),
438 			BPoint(rect.left + i, rect.bottom), color);
439 	}
440 
441 	target->EndLineArray();
442 }
443 
444 
445 void
446 BColorControl::_UpdateOffscreen(BRect update)
447 {
448 	if (fBitmap->Lock()) {
449 		update = update & fOffscreenView->Bounds();
450 		fOffscreenView->FillRect(update);
451 		_DrawColorArea(fOffscreenView, update);
452 		fOffscreenView->Sync();
453 		fBitmap->Unlock();
454 	}
455 }
456 
457 
458 void
459 BColorControl::SetCellSize(float cellSide)
460 {
461 	fCellSize = max_c(kMinCellSize, cellSide);
462 
463 	ResizeToPreferred();
464 }
465 
466 
467 float
468 BColorControl::CellSize() const
469 {
470 	return fCellSize;
471 }
472 
473 
474 void
475 BColorControl::SetLayout(color_control_layout layout)
476 {
477 	switch (layout) {
478 		case B_CELLS_4x64:
479 			fColumns = 4;
480 			fRows = 64;
481 			break;
482 		case B_CELLS_8x32:
483 			fColumns = 8;
484 			fRows = 32;
485 			break;
486 		case B_CELLS_16x16:
487 			fColumns = 16;
488 			fRows = 16;
489 			break;
490 		case B_CELLS_32x8:
491 			fColumns = 32;
492 			fRows = 8;
493 			break;
494 		case B_CELLS_64x4:
495 			fColumns = 64;
496 			fRows = 4;
497 			break;
498 	}
499 
500 	ResizeToPreferred();
501 	Invalidate();
502 }
503 
504 
505 color_control_layout
506 BColorControl::Layout() const
507 {
508 	if (fColumns == 4 && fRows == 64)
509 		return B_CELLS_4x64;
510 	if (fColumns == 8 && fRows == 32)
511 		return B_CELLS_8x32;
512 	if (fColumns == 16 && fRows == 16)
513 		return B_CELLS_16x16;
514 	if (fColumns == 32 && fRows == 8)
515 		return B_CELLS_32x8;
516 	if (fColumns == 64 && fRows == 4)
517 		return B_CELLS_64x4;
518 
519 	return B_CELLS_32x8;
520 }
521 
522 
523 void
524 BColorControl::WindowActivated(bool state)
525 {
526 	BControl::WindowActivated(state);
527 }
528 
529 
530 void
531 BColorControl::KeyDown(const char* bytes, int32 numBytes)
532 {
533 	// TODO: make this keyboard navigable!
534 	BControl::KeyDown(bytes, numBytes);
535 }
536 
537 
538 void
539 BColorControl::MouseUp(BPoint point)
540 {
541 	fFocusedComponent = 0;
542 	SetTracking(false);
543 }
544 
545 
546 void
547 BColorControl::MouseDown(BPoint point)
548 {
549 	BRect rect(0.0f, 0.0f, fColumns * fCellSize, Bounds().bottom);
550 	if (!rect.Contains(point))
551 		return;
552 
553 	rgb_color color = ValueAsColor();
554 
555 	float rampsize = rect.bottom / 4;
556 
557 	uint8 shade = (unsigned char)max_c(0,
558 		min_c((point.x - 2) * 255 / (rect.Width() - 4.0f), 255));
559 
560 	if (point.y - 2 < rampsize) {
561 		color.red = color.green = color.blue = shade;
562 		fFocusedComponent = 1;
563 	} else if (point.y - 2 < (rampsize * 2)) {
564 		color.red = shade;
565 		fFocusedComponent = 2;
566 	} else if (point.y - 2 < (rampsize * 3)) {
567 		color.green = shade;
568 		fFocusedComponent = 3;
569 	} else {
570 		color.blue = shade;
571 		fFocusedComponent = 4;
572 	}
573 
574 	SetValue(color);
575 	Invoke();
576 
577 	SetTracking(true);
578 	MakeFocus();
579 	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
580 }
581 
582 
583 void
584 BColorControl::MouseMoved(BPoint point, uint32 transit,
585 	const BMessage *message)
586 {
587 	if (fFocusedComponent == 0 || !IsTracking())
588 		return;
589 
590 	rgb_color color = ValueAsColor();
591 
592 	BRect rect(0.0f, 0.0f, fColumns * fCellSize, Bounds().bottom);
593 
594 	uint8 shade = (unsigned char)max_c(0,
595 		min_c((point.x - 2) * 255 / (rect.Width() - 4.0f), 255));
596 
597 	switch (fFocusedComponent) {
598 		case 1:
599 			color.red = color.green = color.blue = shade;
600 			break;
601 		case 2:
602 			color.red = shade;
603 			break;
604 		case 3:
605 			color.green = shade;
606 			break;
607 		case 4:
608 			color.blue = shade;
609 			break;
610 		default:
611 			break;
612 	}
613 
614 	SetValue(color);
615 	Invoke();
616 }
617 
618 
619 void
620 BColorControl::DetachedFromWindow()
621 {
622 	BControl::DetachedFromWindow();
623 }
624 
625 
626 void
627 BColorControl::GetPreferredSize(float *_width, float *_height)
628 {
629 	if (_width)
630 		*_width = fColumns * fCellSize + 4.0f + fRedText->Bounds().Width();
631 
632 	if (_height)
633 		*_height = fBlueText->Frame().bottom;
634 }
635 
636 
637 void
638 BColorControl::ResizeToPreferred()
639 {
640 	BControl::ResizeToPreferred();
641 
642 	_LayoutView();
643 }
644 
645 
646 status_t
647 BColorControl::Invoke(BMessage *msg)
648 {
649 	return BControl::Invoke(msg);
650 }
651 
652 
653 void
654 BColorControl::FrameMoved(BPoint new_position)
655 {
656 	BControl::FrameMoved(new_position);
657 }
658 
659 
660 void
661 BColorControl::FrameResized(float new_width, float new_height)
662 {
663 	BControl::FrameResized(new_width, new_height);
664 }
665 
666 
667 BHandler *
668 BColorControl::ResolveSpecifier(BMessage *msg, int32 index,
669 	BMessage *specifier, int32 form, const char *property)
670 {
671 	return BControl::ResolveSpecifier(msg, index, specifier, form, property);
672 }
673 
674 
675 status_t
676 BColorControl::GetSupportedSuites(BMessage *data)
677 {
678 	return BControl::GetSupportedSuites(data);
679 }
680 
681 
682 void
683 BColorControl::MakeFocus(bool state)
684 {
685 	BControl::MakeFocus(state);
686 }
687 
688 
689 void
690 BColorControl::AllAttached()
691 {
692 	BControl::AllAttached();
693 }
694 
695 
696 void
697 BColorControl::AllDetached()
698 {
699 	BControl::AllDetached();
700 }
701 
702 
703 status_t
704 BColorControl::Perform(perform_code d, void *arg)
705 {
706 	return BControl::Perform(d, arg);
707 }
708 
709 
710 void BColorControl::_ReservedColorControl1() {}
711 void BColorControl::_ReservedColorControl2() {}
712 void BColorControl::_ReservedColorControl3() {}
713 void BColorControl::_ReservedColorControl4() {}
714 
715 
716 BColorControl &
717 BColorControl::operator=(const BColorControl &)
718 {
719 	return *this;
720 }
721