1/*
2 * Copyright 2022 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "ThemeView.h"
8
9#include <stdio.h>
10
11#include <Alert.h>
12#include <Catalog.h>
13#include <Directory.h>
14#include <Entry.h>
15#include <File.h>
16#include <Font.h>
17#include <LayoutBuilder.h>
18#include <Locale.h>
19#include <Messenger.h>
20#include <Path.h>
21#include <PopUpMenu.h>
22#include <SpaceLayoutItem.h>
23
24#include "ThemeWindow.h"
25#include "Colors.h"
26#include "ColorPreview.h"
27#include "ColorListView.h"
28#include "ColorItem.h"
29#include "TermConst.h"
30#include "PrefHandler.h"
31
32#undef B_TRANSLATION_CONTEXT
33#define B_TRANSLATION_CONTEXT "Terminal ThemeView"
34
35#define COLOR_DROPPED 'cldp'
36#define DECORATOR_CHANGED 'dcch'
37
38
39/* static */ const char*
40ThemeView::kColorTable[] = {
41	B_TRANSLATE_MARK("Text"),
42	B_TRANSLATE_MARK("Background"),
43	B_TRANSLATE_MARK("Cursor"),
44	B_TRANSLATE_MARK("Text under cursor"),
45	B_TRANSLATE_MARK("Selected text"),
46	B_TRANSLATE_MARK("Selected background"),
47	B_TRANSLATE_MARK("ANSI black color"),
48	B_TRANSLATE_MARK("ANSI red color"),
49	B_TRANSLATE_MARK("ANSI green color"),
50	B_TRANSLATE_MARK("ANSI yellow color"),
51	B_TRANSLATE_MARK("ANSI blue color"),
52	B_TRANSLATE_MARK("ANSI magenta color"),
53	B_TRANSLATE_MARK("ANSI cyan color"),
54	B_TRANSLATE_MARK("ANSI white color"),
55	B_TRANSLATE_MARK("ANSI bright black color"),
56	B_TRANSLATE_MARK("ANSI bright red color"),
57	B_TRANSLATE_MARK("ANSI bright green color"),
58	B_TRANSLATE_MARK("ANSI bright yellow color"),
59	B_TRANSLATE_MARK("ANSI bright blue color"),
60	B_TRANSLATE_MARK("ANSI bright magenta color"),
61	B_TRANSLATE_MARK("ANSI bright cyan color"),
62	B_TRANSLATE_MARK("ANSI bright white color"),
63	NULL
64};
65
66
67ThemeView::ThemeView(const char* name, const BMessenger& messenger)
68	:
69	BGroupView(name, B_VERTICAL, 5),
70	fTerminalMessenger(messenger)
71{
72	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
73
74	// Set up list of color attributes
75	fAttrList = new ColorListView("AttributeList");
76
77	fScrollView = new BScrollView("ScrollView", fAttrList, 0, false, true);
78	fScrollView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
79
80	PrefHandler* prefHandler = PrefHandler::Default();
81
82	for (const char** table = kColorTable; *table != NULL; ++table) {
83		fAttrList->AddItem(new ColorItem(B_TRANSLATE_NOCOLLECT(*table),
84			prefHandler->getRGB(*table)));
85	}
86
87	fColorSchemeMenu = new BPopUpMenu("");
88	fColorSchemeField = new BMenuField(B_TRANSLATE("Color scheme:"),
89		fColorSchemeMenu);
90
91	fColorPreview = new ColorPreview(new BMessage(COLOR_DROPPED), 0);
92	fColorPreview->SetExplicitAlignment(BAlignment(B_ALIGN_HORIZONTAL_CENTER,
93		B_ALIGN_VERTICAL_CENTER));
94
95	fPicker = new BColorControl(B_ORIGIN, B_CELLS_32x8, 8.0,
96		"picker", new BMessage(MSG_UPDATE_COLOR));
97
98	fPreview = new BTextView("preview");
99
100	BLayoutBuilder::Group<>(this)
101		.AddGrid()
102			.Add(fColorSchemeField->CreateLabelLayoutItem(), 0, 5)
103			.Add(fColorSchemeField->CreateMenuBarLayoutItem(), 1, 5)
104			.End()
105		.Add(fScrollView, 10.0)
106		.Add(fPreview)
107		.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
108			.Add(fColorPreview)
109			.AddGlue()
110			.Add(fPicker);
111
112	fColorPreview->Parent()->SetExplicitMaxSize(
113		BSize(B_SIZE_UNSET, fPicker->Bounds().Height()));
114	fAttrList->SetSelectionMessage(new BMessage(MSG_COLOR_ATTRIBUTE_CHOSEN));
115	fScrollView->SetExplicitMinSize(BSize(B_SIZE_UNSET, 22 * 16));
116	fColorSchemeField->SetAlignment(B_ALIGN_RIGHT);
117
118	_MakeColorSchemeMenu();
119
120	_UpdateStyle();
121
122	InvalidateLayout(true);
123}
124
125
126ThemeView::~ThemeView()
127{
128}
129
130
131void
132ThemeView::_UpdateStyle()
133{
134	PrefHandler* prefHandler = PrefHandler::Default();
135
136	const char* colours[] = {
137		B_TRANSLATE("black"),
138		B_TRANSLATE("red"),
139		B_TRANSLATE("green"),
140		B_TRANSLATE("yellow"),
141		B_TRANSLATE("blue"),
142		B_TRANSLATE("magenta"),
143		B_TRANSLATE("cyan"),
144		B_TRANSLATE("white"),
145	};
146
147	text_run_array *array = (text_run_array*)malloc(sizeof(text_run_array)
148			+ sizeof(text_run) * 16);
149	int offset = 1;
150	int index = 0;
151	array->count = 16;
152	array->runs[0].offset = offset;
153	array->runs[0].font = *be_fixed_font;
154	array->runs[0].color = prefHandler->getRGB(PREF_ANSI_BLACK_COLOR);
155	offset += strlen(colours[index++]) + 1;
156	array->runs[1].offset = offset;
157	array->runs[1].font = *be_fixed_font;
158	array->runs[1].color = prefHandler->getRGB(PREF_ANSI_RED_COLOR);
159	offset += strlen(colours[index++]) + 1;
160	array->runs[2].offset = offset;
161	array->runs[2].font = *be_fixed_font;
162	array->runs[2].color = prefHandler->getRGB(PREF_ANSI_GREEN_COLOR);
163	offset += strlen(colours[index++]) + 1;
164	array->runs[3].offset = offset;
165	array->runs[3].font = *be_fixed_font;
166	array->runs[3].color = prefHandler->getRGB(PREF_ANSI_YELLOW_COLOR);
167	offset += strlen(colours[index++]) + 1;
168	array->runs[4].offset = offset;
169	array->runs[4].font = *be_fixed_font;
170	array->runs[4].color = prefHandler->getRGB(PREF_ANSI_BLUE_COLOR);
171	offset += strlen(colours[index++]) + 1;
172	array->runs[5].offset = offset;
173	array->runs[5].font = *be_fixed_font;
174	array->runs[5].color = prefHandler->getRGB(PREF_ANSI_MAGENTA_COLOR);
175	offset += strlen(colours[index++]) + 1;
176	array->runs[6].offset = offset;
177	array->runs[6].font = *be_fixed_font;
178	array->runs[6].color = prefHandler->getRGB(PREF_ANSI_CYAN_COLOR);
179	offset += strlen(colours[index++]) + 1;
180	array->runs[7].offset = offset;
181	array->runs[7].font = *be_fixed_font;
182	array->runs[7].color = prefHandler->getRGB(PREF_ANSI_WHITE_COLOR);
183	offset += strlen(colours[index++]) + 1;
184	index = 0;
185	offset++;
186	array->runs[8].offset = offset;
187	array->runs[8].font = *be_fixed_font;
188	array->runs[8].color = prefHandler->getRGB(PREF_ANSI_BLACK_HCOLOR);
189	offset += strlen(colours[index++]) + 1;
190	array->runs[9].offset = offset;
191	array->runs[9].font = *be_fixed_font;
192	array->runs[9].color = prefHandler->getRGB(PREF_ANSI_RED_HCOLOR);
193	offset += strlen(colours[index++]) + 1;
194	array->runs[10].offset = offset;
195	array->runs[10].font = *be_fixed_font;
196	array->runs[10].color = prefHandler->getRGB(PREF_ANSI_GREEN_HCOLOR);
197	offset += strlen(colours[index++]) + 1;
198	array->runs[11].offset = offset;
199	array->runs[11].font = *be_fixed_font;
200	array->runs[11].color = prefHandler->getRGB(PREF_ANSI_YELLOW_HCOLOR);
201	offset += strlen(colours[index++]) + 1;
202	array->runs[12].offset = offset;
203	array->runs[12].font = *be_fixed_font;
204	array->runs[12].color = prefHandler->getRGB(PREF_ANSI_BLUE_HCOLOR);
205	offset += strlen(colours[index++]) + 1;
206	array->runs[13].offset = offset;
207	array->runs[13].font = *be_fixed_font;
208	array->runs[13].color = prefHandler->getRGB(PREF_ANSI_MAGENTA_HCOLOR);
209	offset += strlen(colours[index++]) + 1;
210	array->runs[14].offset = offset;
211	array->runs[14].font = *be_fixed_font;
212	array->runs[14].color = prefHandler->getRGB(PREF_ANSI_CYAN_HCOLOR);
213	offset += strlen(colours[index++]) + 1;
214	array->runs[15].offset = offset;
215	array->runs[15].font = *be_fixed_font;
216	array->runs[15].color = prefHandler->getRGB(PREF_ANSI_WHITE_HCOLOR);
217
218	fPreview->SetStylable(true);
219	fPreview->MakeEditable(false);
220	fPreview->MakeSelectable(false);
221	fPreview->SetViewColor(prefHandler->getRGB(PREF_TEXT_BACK_COLOR));
222
223	BString previewText;
224	previewText << '\n';
225	for (int ix = 0; ix < 8; ++ix) {
226		previewText << ' ' << colours[ix];
227	}
228	previewText << '\n';
229	for (int ix = 0; ix < 8; ++ix) {
230		previewText << ' ' << colours[ix];
231	}
232	previewText << '\n';
233
234	fPreview->SetAlignment(B_ALIGN_CENTER);
235	fPreview->SetText(previewText.String(), array);
236	font_height height;
237	be_fixed_font->GetHeight(&height);
238	fPreview->SetExplicitMinSize(BSize(B_SIZE_UNSET, (height.ascent + height.descent) * 5));
239}
240
241
242void
243ThemeView::AttachedToWindow()
244{
245	fPicker->SetTarget(this);
246	fAttrList->SetTarget(this);
247	fColorPreview->SetTarget(this);
248	fColorSchemeField->Menu()->SetTargetForItems(this);
249
250	fAttrList->Select(0);
251	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
252
253	_SetCurrentColorScheme();
254}
255
256
257void
258ThemeView::WindowActivated(bool active)
259{
260	if (!active)
261		return;
262
263	UpdateMenu();
264}
265
266
267void
268ThemeView::UpdateMenu()
269{
270	PrefHandler::Default()->LoadThemes();
271
272	_MakeColorSchemeMenu();
273	_SetCurrentColorScheme();
274
275	fColorSchemeField->Menu()->SetTargetForItems(this);
276}
277
278
279void
280ThemeView::MessageReceived(BMessage *msg)
281{
282	bool modified = false;
283
284	switch (msg->what) {
285		case MSG_COLOR_SCHEME_CHANGED:
286		{
287			color_scheme* newScheme = NULL;
288			if (msg->FindPointer("color_scheme",
289					(void**)&newScheme) == B_OK) {
290				_ChangeColorScheme(newScheme);
291				modified = true;
292			}
293			break;
294		}
295
296		case MSG_SET_COLOR:
297		{
298			rgb_color* color;
299			ssize_t size;
300			const char* name;
301
302			if (msg->FindData(kRGBColor, B_RGB_COLOR_TYPE,
303					(const void**)&color, &size) == B_OK
304				&& msg->FindString(kName, &name) == B_OK) {
305				_SetColor(name, *color);
306				modified = true;
307			}
308			break;
309		}
310
311		case MSG_SET_CURRENT_COLOR:
312		{
313			rgb_color* color;
314			ssize_t size;
315
316			if (msg->FindData(kRGBColor, B_RGB_COLOR_TYPE,
317					(const void**)&color, &size) == B_OK) {
318				_SetCurrentColor(*color);
319				modified = true;
320			}
321			break;
322		}
323
324		case MSG_UPDATE_COLOR:
325		{
326			// Received from the color fPicker when its color changes
327			rgb_color color = fPicker->ValueAsColor();
328			_SetCurrentColor(color);
329			modified = true;
330
331			break;
332		}
333
334		case MSG_COLOR_ATTRIBUTE_CHOSEN:
335		{
336			// Received when the user chooses a GUI fAttribute from the list
337			const int32 currentIndex = fAttrList->CurrentSelection();
338			if (currentIndex < 0)
339				break;
340
341			rgb_color color = PrefHandler::Default()->getRGB(kColorTable[currentIndex]);
342			_SetCurrentColor(color);
343			break;
344		}
345
346		case MSG_THEME_MODIFIED:
347			_SetCurrentColorScheme();
348			BView::MessageReceived(msg);
349			return;
350
351		default:
352			BView::MessageReceived(msg);
353			break;
354	}
355
356	if (modified) {
357		_UpdateStyle();
358		fTerminalMessenger.SendMessage(msg);
359
360		BMessenger messenger(this);
361		messenger.SendMessage(MSG_THEME_MODIFIED);
362	}
363}
364
365
366void
367ThemeView::SetDefaults()
368{
369	PrefHandler* prefHandler = PrefHandler::Default();
370
371	int32 count = fAttrList->CountItems();
372	for (int32 index = 0; index < count; ++index) {
373		ColorItem* item = (ColorItem*)fAttrList->ItemAt(index);
374		item->SetColor(prefHandler->getRGB(kColorTable[index]));
375		fAttrList->InvalidateItem(index);
376	}
377
378	int32 currentIndex = fAttrList->CurrentSelection();
379	ColorItem* item = (ColorItem*)fAttrList->ItemAt(currentIndex);
380	if (item != NULL) {
381		rgb_color color = item->Color();
382		fPicker->SetValue(color);
383		fColorPreview->SetColor(color);
384		fColorPreview->Invalidate();
385	}
386
387	_UpdateStyle();
388
389	BMessage message(MSG_COLOR_SCHEME_CHANGED);
390	fTerminalMessenger.SendMessage(&message);
391}
392
393
394void
395ThemeView::Revert()
396{
397	_SetCurrentColorScheme();
398
399	SetDefaults();
400}
401
402
403void
404ThemeView::_ChangeColorScheme(color_scheme* scheme)
405{
406	PrefHandler* pref = PrefHandler::Default();
407
408	pref->setRGB(PREF_TEXT_FORE_COLOR, scheme->text_fore_color);
409	pref->setRGB(PREF_TEXT_BACK_COLOR, scheme->text_back_color);
410	pref->setRGB(PREF_SELECT_FORE_COLOR, scheme->select_fore_color);
411	pref->setRGB(PREF_SELECT_BACK_COLOR, scheme->select_back_color);
412	pref->setRGB(PREF_CURSOR_FORE_COLOR, scheme->cursor_fore_color);
413	pref->setRGB(PREF_CURSOR_BACK_COLOR, scheme->cursor_back_color);
414	pref->setRGB(PREF_ANSI_BLACK_COLOR, scheme->ansi_colors.black);
415	pref->setRGB(PREF_ANSI_RED_COLOR, scheme->ansi_colors.red);
416	pref->setRGB(PREF_ANSI_GREEN_COLOR, scheme->ansi_colors.green);
417	pref->setRGB(PREF_ANSI_YELLOW_COLOR, scheme->ansi_colors.yellow);
418	pref->setRGB(PREF_ANSI_BLUE_COLOR, scheme->ansi_colors.blue);
419	pref->setRGB(PREF_ANSI_MAGENTA_COLOR, scheme->ansi_colors.magenta);
420	pref->setRGB(PREF_ANSI_CYAN_COLOR, scheme->ansi_colors.cyan);
421	pref->setRGB(PREF_ANSI_WHITE_COLOR, scheme->ansi_colors.white);
422	pref->setRGB(PREF_ANSI_BLACK_HCOLOR, scheme->ansi_colors_h.black);
423	pref->setRGB(PREF_ANSI_RED_HCOLOR, scheme->ansi_colors_h.red);
424	pref->setRGB(PREF_ANSI_GREEN_HCOLOR, scheme->ansi_colors_h.green);
425	pref->setRGB(PREF_ANSI_YELLOW_HCOLOR, scheme->ansi_colors_h.yellow);
426	pref->setRGB(PREF_ANSI_BLUE_HCOLOR, scheme->ansi_colors_h.blue);
427	pref->setRGB(PREF_ANSI_MAGENTA_HCOLOR, scheme->ansi_colors_h.magenta);
428	pref->setRGB(PREF_ANSI_CYAN_HCOLOR, scheme->ansi_colors_h.cyan);
429	pref->setRGB(PREF_ANSI_WHITE_HCOLOR, scheme->ansi_colors_h.white);
430
431	int32 count = fAttrList->CountItems();
432	for (int32 index = 0; index < count; ++index) {
433		ColorItem* item = static_cast<ColorItem*>(fAttrList->ItemAt(index));
434		rgb_color color = pref->getRGB(kColorTable[index]);
435		item->SetColor(color);
436
437		if (item->IsSelected()) {
438			fPicker->SetValue(color);
439			fColorPreview->SetColor(color);
440			fColorPreview->Invalidate();
441		}
442	}
443
444	fAttrList->Invalidate();
445}
446
447
448void
449ThemeView::_SetCurrentColorScheme()
450{
451	PrefHandler* pref = PrefHandler::Default();
452
453	pref->LoadColorScheme(&gCustomColorScheme);
454
455	const char* currentSchemeName = NULL;
456
457	int32 i = 0;
458	while (i < gColorSchemes->CountItems()) {
459		const color_scheme *item = gColorSchemes->ItemAt(i);
460		i++;
461
462		if (gCustomColorScheme == *item) {
463			currentSchemeName = item->name;
464			break;
465		}
466	}
467
468	// If the scheme is not one of the known ones, assume a custom one.
469	if (currentSchemeName == NULL)
470		currentSchemeName = "Custom";
471
472	for (int32 i = 0; i < fColorSchemeField->Menu()->CountItems(); i++) {
473		BMenuItem* item = fColorSchemeField->Menu()->ItemAt(i);
474		if (strcmp(item->Label(), currentSchemeName) == 0) {
475			item->SetMarked(true);
476			break;
477		}
478	}
479}
480
481
482void
483ThemeView::_MakeColorSchemeMenuItem(const color_scheme *item)
484{
485	if (item == NULL)
486		return;
487
488	BMessage* message = new BMessage(MSG_COLOR_SCHEME_CHANGED);
489	message->AddPointer("color_scheme", (const void*)item);
490	fColorSchemeMenu->AddItem(new BMenuItem(item->name, message));
491}
492
493
494void
495ThemeView::_MakeColorSchemeMenu()
496{
497	while (fColorSchemeMenu->CountItems() > 0)
498		delete(fColorSchemeMenu->RemoveItem((int32)0));
499
500	FindColorSchemeByName comparator("Default");
501
502	const color_scheme* defaultItem = gColorSchemes->FindIf(comparator);
503
504	_MakeColorSchemeMenuItem(defaultItem);
505
506	int32 index = 0;
507	while (index < gColorSchemes->CountItems()) {
508		const color_scheme *item = gColorSchemes->ItemAt(index);
509		index++;
510
511		if (strcmp(item->name, "") == 0)
512			fColorSchemeMenu->AddSeparatorItem();
513		else if (item == defaultItem)
514			continue;
515		else
516			_MakeColorSchemeMenuItem(item);
517	}
518
519	// Add the custom item at the very end
520	fColorSchemeMenu->AddSeparatorItem();
521	_MakeColorSchemeMenuItem(&gCustomColorScheme);
522}
523
524
525void
526ThemeView::_SetCurrentColor(rgb_color color)
527{
528	int32 currentIndex = fAttrList->CurrentSelection();
529	ColorItem* item = (ColorItem*)fAttrList->ItemAt(currentIndex);
530	if (item != NULL) {
531		item->SetColor(color);
532		fAttrList->InvalidateItem(currentIndex);
533
534		PrefHandler::Default()->setRGB(kColorTable[currentIndex], color);
535	}
536
537	fPicker->SetValue(color);
538	fColorPreview->SetColor(color);
539	fColorPreview->Invalidate();
540}
541
542
543void
544ThemeView::_SetColor(const char* name, rgb_color color)
545{
546	PrefHandler::Default()->setRGB(name, color);
547
548	_UpdateStyle();
549}
550