1/*
2 * Copyright 2001 Werner Freytag - please read to the LICENSE file
3 *
4 * Copyright 2002-2015, Stephan A��mus <superstippi@gmx.de>
5 * All rights reserved.
6 *
7 */
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13#include <Application.h>
14#include <Bitmap.h>
15#include <Beep.h>
16#include <ControlLook.h>
17#include <GroupLayout.h>
18#include <LayoutBuilder.h>
19#include <Message.h>
20#include <MessageRunner.h>
21#include <RadioButton.h>
22#include <StringView.h>
23#include <TextControl.h>
24#include <Window.h>
25
26#include "ColorField.h"
27#include "ColorPreview.h"
28#include "ColorSlider.h"
29#include "rgb_hsv.h"
30
31#include "ColorPickerView.h"
32
33
34#define round(x) (int)(x+.5)
35#define hexdec(str, offset) (int)(((str[offset]<60?str[offset]-48:(str[offset]|32)-87)<<4)|(str[offset+1]<60?str[offset+1]-48:(str[offset+1]|32)-87))
36
37
38ColorPickerView::ColorPickerView(const char* name, rgb_color color,
39		SelectedColorMode mode)
40	:
41	BView(name, 0),
42	h(0.0),
43	s(1.0),
44	v(1.0),
45	r((float)color.red / 255.0),
46	g((float)color.green / 255.0),
47	b((float)color.blue / 255.0),
48	fRequiresUpdate(false)
49{
50	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
51
52	RGB_to_HSV(r, g, b, h, s, v);
53
54	SetColorMode(mode, false);
55}
56
57
58ColorPickerView::~ColorPickerView()
59{
60}
61
62
63void
64ColorPickerView::AttachedToWindow()
65{
66	rgb_color color = { (uint8)(r * 255), (uint8)(g * 255), (uint8)(b * 255),
67		255 };
68	BView::AttachedToWindow();
69
70	fColorField = new ColorField(fSelectedColorMode, *p);
71	fColorField->SetMarkerToColor(color);
72	fColorField->SetTarget(this);
73
74	fColorSlider = new ColorSlider(fSelectedColorMode, *p1, *p2);
75	fColorSlider->SetMarkerToColor(color);
76	fColorSlider->SetTarget(this);
77
78	fColorPreview = new ColorPreview(color);
79	fColorPreview->SetTarget(this);
80
81	fColorField->SetExplicitMinSize(BSize(256, 256));
82	fColorField->SetExplicitMaxSize(
83		BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
84	fColorSlider->SetExplicitMaxSize(
85		BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED));
86	fColorPreview->SetExplicitMinSize(BSize(B_SIZE_UNSET, 70));
87
88	const char* title[] = { "H", "S", "V", "R", "G", "B" };
89
90	int32 selectedRadioButton = _NumForMode(fSelectedColorMode);
91
92	for (int i = 0; i < 6; i++) {
93		fRadioButton[i] = new BRadioButton(NULL, title[i],
94			new BMessage(MSG_RADIOBUTTON + i));
95		fRadioButton[i]->SetTarget(this);
96
97		if (i == selectedRadioButton)
98			fRadioButton[i]->SetValue(1);
99
100		fTextControl[i] = new BTextControl(NULL, NULL, NULL,
101			new BMessage(MSG_TEXTCONTROL + i));
102
103		fTextControl[i]->TextView()->SetMaxBytes(3);
104		for (int j = 32; j < 255; ++j) {
105			if (j < '0' || j > '9')
106				fTextControl[i]->TextView()->DisallowChar(j);
107		}
108	}
109
110	fHexTextControl = new BTextControl(NULL, "#", NULL,
111		new BMessage(MSG_HEXTEXTCONTROL));
112
113	fHexTextControl->TextView()->SetMaxBytes(6);
114	for (int j = 32; j < 255; ++j) {
115		if (!((j >= '0' && j <= '9') || (j >= 'a' && j <= 'f')
116			|| (j >= 'A' && j <= 'F'))) {
117			fHexTextControl->TextView()->DisallowChar(j);
118		}
119	}
120
121	const float inset = be_control_look->DefaultLabelSpacing();
122	BSize separatorSize(B_SIZE_UNSET, inset / 2);
123	BAlignment separatorAlignment(B_ALIGN_LEFT, B_ALIGN_TOP);
124
125	SetLayout(new BGroupLayout(B_HORIZONTAL));
126	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
127		.AddGroup(B_HORIZONTAL, 0.0f)
128			.Add(fColorField)
129		.SetInsets(3, 3, 0, 3)
130		.End()
131		.Add(fColorSlider)
132		.AddGroup(B_VERTICAL)
133			.Add(fColorPreview)
134			.AddGrid(inset / 2, inset / 2)
135				.Add(fRadioButton[0], 0, 0)
136				.Add(fTextControl[0], 1, 0)
137				.Add(new BStringView(NULL, "��"), 2, 0)
138
139				.Add(fRadioButton[1], 0, 1)
140				.Add(fTextControl[1], 1, 1)
141				.Add(new BStringView(NULL, "%"), 2, 1)
142
143				.Add(fRadioButton[2], 0, 2)
144				.Add(fTextControl[2], 1, 2)
145				.Add(new BStringView(NULL, "%"), 2, 2)
146
147				.Add(new BSpaceLayoutItem(separatorSize, separatorSize,
148					separatorSize, separatorAlignment),
149					0, 3, 3)
150
151				.Add(fRadioButton[3], 0, 4)
152				.Add(fTextControl[3], 1, 4)
153
154				.Add(fRadioButton[4], 0, 5)
155				.Add(fTextControl[4], 1, 5)
156
157				.Add(fRadioButton[5], 0, 6)
158				.Add(fTextControl[5], 1, 6)
159
160				.Add(new BSpaceLayoutItem(separatorSize, separatorSize,
161					separatorSize, separatorAlignment),
162					0, 7, 3)
163
164				.AddGroup(B_HORIZONTAL, 0.0f, 0, 8, 2)
165					.Add(fHexTextControl->CreateLabelLayoutItem())
166					.Add(fHexTextControl->CreateTextViewLayoutItem())
167				.End()
168			.End()
169		.SetInsets(0, 3, 3, 3)
170		.End()
171		.SetInsets(inset, inset, inset, inset)
172	;
173
174	// After the views are attached, configure their target
175	for (int i = 0; i < 6; i++) {
176		fRadioButton[i]->SetTarget(this);
177		fTextControl[i]->SetTarget(this);
178	}
179	fHexTextControl->SetTarget(this);
180
181	_UpdateTextControls();
182}
183
184
185void
186ColorPickerView::MessageReceived(BMessage *message)
187{
188	switch (message->what) {
189		case MSG_UPDATE_COLOR_PICKER_VIEW:
190			if (fRequiresUpdate)
191				_UpdateTextControls();
192			break;
193
194		case MSG_COLOR_FIELD:
195		{
196			float value1, value2;
197			value1 = message->FindFloat("value");
198			value2 = message->FindFloat("value", 1);
199			_UpdateColor(-1, value1, value2);
200			fRequiresUpdate = true;
201			break;
202		}
203
204		case MSG_COLOR_SLIDER:
205		{
206			float value;
207			message->FindFloat("value", &value);
208			_UpdateColor(value, -1, -1);
209			fRequiresUpdate = true;
210			break;
211		}
212
213		case MSG_COLOR_PREVIEW:
214		{
215			rgb_color* color;
216			ssize_t numBytes;
217			if (message->FindData("color", B_RGB_COLOR_TYPE,
218					(const void**)&color, &numBytes) == B_OK) {
219				color->alpha = 255;
220				SetColor(*color);
221			}
222			break;
223		}
224
225		case MSG_RADIOBUTTON:
226			SetColorMode(H_SELECTED);
227			break;
228
229		case MSG_RADIOBUTTON + 1:
230			SetColorMode(S_SELECTED);
231			break;
232
233		case MSG_RADIOBUTTON + 2:
234			SetColorMode(V_SELECTED);
235			break;
236
237		case MSG_RADIOBUTTON + 3:
238			SetColorMode(R_SELECTED);
239			break;
240
241		case MSG_RADIOBUTTON + 4:
242			SetColorMode(G_SELECTED);
243			break;
244
245		case MSG_RADIOBUTTON + 5:
246			SetColorMode(B_SELECTED);
247			break;
248
249		case MSG_TEXTCONTROL:
250		case MSG_TEXTCONTROL + 1:
251		case MSG_TEXTCONTROL + 2:
252		case MSG_TEXTCONTROL + 3:
253		case MSG_TEXTCONTROL + 4:
254		case MSG_TEXTCONTROL + 5:
255		{
256			int nr = message->what - MSG_TEXTCONTROL;
257			int value = atoi(fTextControl[nr]->Text());
258
259			switch (nr) {
260				case 0: {
261					value %= 360;
262					h = (float)value / 60;
263				} break;
264
265				case 1: {
266					value = min_c(value, 100);
267					s = (float)value / 100;
268				} break;
269
270				case 2: {
271					value = min_c(value, 100);
272					v = (float)value / 100;
273				} break;
274
275				case 3: {
276					value = min_c(value, 255);
277					r = (float)value / 255;
278				} break;
279
280				case 4: {
281					value = min_c(value, 255);
282					g = (float)value / 255;
283				} break;
284
285				case 5: {
286					value = min_c(value, 255);
287					b = (float)value / 255;
288				} break;
289			}
290
291			if (nr < 3) { // hsv-mode
292				HSV_to_RGB(h, s, v, r, g, b);
293			}
294
295			rgb_color color = { (uint8)round(r * 255), (uint8)round(g * 255),
296								(uint8)round(b * 255), 255 };
297
298			SetColor(color);
299			break;
300		}
301
302		case MSG_HEXTEXTCONTROL:
303		{
304			BString string = _HexTextControlString();
305			if (string.Length() == 6) {
306				rgb_color color = {
307					(uint8)hexdec(string, 0),
308					(uint8)hexdec(string, 2),
309					(uint8)hexdec(string, 4), 255 };
310				SetColor(color);
311			}
312			break;
313		}
314
315		default:
316			BView::MessageReceived(message);
317	}
318}
319
320
321void
322ColorPickerView::SetColorMode(SelectedColorMode mode, bool update)
323{
324	fSelectedColorMode = mode;
325	switch (mode) {
326		case R_SELECTED:
327			p = &r;
328			p1 = &g;
329			p2 = &b;
330			break;
331
332		case G_SELECTED:
333			p = &g;
334			p1 = &r;
335			p2 = &b;
336			break;
337
338		case B_SELECTED:
339			p = &b;
340			p1 = &r;
341			p2 = &g;
342			break;
343
344		case H_SELECTED:
345			p = &h;
346			p1 = &s;
347			p2 = &v;
348			break;
349
350		case S_SELECTED:
351			p = &s;
352			p1 = &h;
353			p2 = &v;
354			break;
355
356		case V_SELECTED:
357			p = &v;
358			p1 = &h;
359			p2 = &s;
360			break;
361	}
362
363	if (!update)
364		return;
365
366	fColorSlider->SetModeAndValues(fSelectedColorMode, *p1, *p2);
367	fColorField->SetModeAndValue(fSelectedColorMode, *p);
368
369}
370
371// SetColor
372void
373ColorPickerView::SetColor(rgb_color color)
374{
375	r = (float)color.red / 255;
376	g = (float)color.green / 255;
377	b = (float)color.blue / 255;
378	RGB_to_HSV(r, g, b, h, s, v);
379
380	fColorSlider->SetModeAndValues(fSelectedColorMode, *p1, *p2);
381	fColorSlider->SetMarkerToColor(color);
382
383	fColorField->SetModeAndValue(fSelectedColorMode, *p);
384	fColorField->SetMarkerToColor(color);
385
386	fColorPreview->SetColor(color);
387
388	_UpdateTextControls();
389}
390
391
392rgb_color
393ColorPickerView::Color()
394{
395	if (fSelectedColorMode & (R_SELECTED | G_SELECTED | B_SELECTED))
396		RGB_to_HSV(r, g, b, h, s, v);
397	else
398		HSV_to_RGB(h, s, v, r, g, b);
399
400	rgb_color color;
401	color.red = (uint8)round(r * 255.0);
402	color.green = (uint8)round(g * 255.0);
403	color.blue = (uint8)round(b * 255.0);
404	color.alpha = 255;
405
406	return color;
407}
408
409
410int32
411ColorPickerView::_NumForMode(SelectedColorMode mode) const
412{
413	int32 num = -1;
414	switch (mode) {
415		case H_SELECTED:
416			num = 0;
417			break;
418		case S_SELECTED:
419			num = 1;
420			break;
421		case V_SELECTED:
422			num = 2;
423			break;
424		case R_SELECTED:
425			num = 3;
426			break;
427		case G_SELECTED:
428			num = 4;
429			break;
430		case B_SELECTED:
431			num = 5;
432			break;
433	}
434	return num;
435}
436
437
438void
439ColorPickerView::_UpdateColor(float value, float value1, float value2)
440{
441	if (value != -1) {
442		fColorField->SetFixedValue(value);
443		*p = value;
444	} else if (value1 != -1 && value2 != -1) {
445		fColorSlider->SetOtherValues(value1, value2);
446		*p1 = value1; *p2 = value2;
447	}
448
449	if (fSelectedColorMode & (R_SELECTED|G_SELECTED|B_SELECTED))
450		RGB_to_HSV(r, g, b, h, s, v);
451	else
452		HSV_to_RGB(h, s, v, r, g, b);
453
454	rgb_color color = { (uint8)(r * 255), (uint8)(g * 255),
455		(uint8)(b * 255), 255 };
456	fColorPreview->SetColor(color);
457}
458
459
460void
461ColorPickerView::_UpdateTextControls()
462{
463	bool updateRequired = false;
464	updateRequired |= _SetTextControlValue(0, round(h * 60));
465	updateRequired |= _SetTextControlValue(1, round(s * 100));
466	updateRequired |= _SetTextControlValue(2, round(v * 100));
467	updateRequired |= _SetTextControlValue(3, round(r * 255));
468	updateRequired |= _SetTextControlValue(4, round(g * 255));
469	updateRequired |= _SetTextControlValue(5, round(b * 255));
470
471	BString hexString;
472	hexString.SetToFormat("%.6X",
473		(round(r * 255) << 16) | (round(g * 255) << 8) | round(b * 255));
474	updateRequired |= _SetHexTextControlString(hexString);
475
476	fRequiresUpdate = updateRequired;
477	if (fRequiresUpdate) {
478		// Couldn't set all values. Try again later.
479		BMessage message(MSG_UPDATE_COLOR_PICKER_VIEW);
480		BMessageRunner::StartSending(this, &message, 500000, 1);
481	}
482}
483
484
485int
486ColorPickerView::_TextControlValue(int32 index)
487{
488	return atoi(fTextControl[index]->Text());
489}
490
491
492// Returns whether the value needs to be set later, since it is currently
493// being edited by the user.
494bool
495ColorPickerView::_SetTextControlValue(int32 index, int value)
496{
497	BString text;
498	text << value;
499	return _SetText(fTextControl[index], text);
500}
501
502
503BString
504ColorPickerView::_HexTextControlString() const
505{
506	return fHexTextControl->TextView()->Text();
507}
508
509
510// Returns whether the value needs to be set later, since it is currently
511// being edited by the user.
512bool
513ColorPickerView::_SetHexTextControlString(const BString& text)
514{
515	return _SetText(fHexTextControl, text);
516}
517
518
519// Returns whether the value needs to be set later, since it is currently
520// being edited by the user.
521bool
522ColorPickerView::_SetText(BTextControl* control, const BString& text)
523{
524	if (text == control->Text())
525		return false;
526
527	// This textview needs updating, but don't screw with user while she is
528	// typing.
529	if (control->TextView()->IsFocus())
530		return true;
531
532	control->SetText(text.String());
533	return false;
534}
535
536