xref: /haiku/src/apps/terminal/ThemeView.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
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*
40 ThemeView::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 
67 ThemeView::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 
126 ThemeView::~ThemeView()
127 {
128 }
129 
130 
131 void
132 ThemeView::_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 	{
227 		previewText << ' ' << colours[ix];
228 	}
229 	previewText << '\n';
230 	for (int ix = 0; ix < 8; ++ix)
231 	{
232 		previewText << ' ' << colours[ix];
233 	}
234 	previewText << '\n';
235 
236 	fPreview->SetAlignment(B_ALIGN_CENTER);
237 	fPreview->SetText(previewText.String(), array);
238 	font_height height;
239 	be_fixed_font->GetHeight(&height);
240 	fPreview->SetExplicitMinSize(BSize(B_SIZE_UNSET, (height.ascent + height.descent) * 5));
241 }
242 
243 
244 void
245 ThemeView::AttachedToWindow()
246 {
247 	fPicker->SetTarget(this);
248 	fAttrList->SetTarget(this);
249 	fColorPreview->SetTarget(this);
250 	fColorSchemeField->Menu()->SetTargetForItems(this);
251 
252 	fAttrList->Select(0);
253 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
254 
255 	_SetCurrentColorScheme();
256 }
257 
258 
259 void
260 ThemeView::WindowActivated(bool active)
261 {
262 	if (!active)
263 		return;
264 
265 	UpdateMenu();
266 }
267 
268 
269 void
270 ThemeView::UpdateMenu()
271 {
272 	PrefHandler::Default()->LoadThemes();
273 
274 	_MakeColorSchemeMenu();
275 	_SetCurrentColorScheme();
276 
277 	fColorSchemeField->Menu()->SetTargetForItems(this);
278 }
279 
280 
281 void
282 ThemeView::MessageReceived(BMessage *msg)
283 {
284 	bool modified = false;
285 
286 	switch (msg->what) {
287 		case MSG_COLOR_SCHEME_CHANGED:
288 		{
289 			color_scheme* newScheme = NULL;
290 			if (msg->FindPointer("color_scheme",
291 					(void**)&newScheme) == B_OK) {
292 				_ChangeColorScheme(newScheme);
293 				modified = true;
294 			}
295 			break;
296 		}
297 
298 		case MSG_SET_COLOR:
299 		{
300 			rgb_color* color;
301 			ssize_t size;
302 			const char* name;
303 
304 			if (msg->FindData(kRGBColor, B_RGB_COLOR_TYPE,
305 					(const void**)&color, &size) == B_OK
306 				&& msg->FindString(kName, &name) == B_OK) {
307 				_SetColor(name, *color);
308 				modified = true;
309 			}
310 			break;
311 		}
312 
313 		case MSG_SET_CURRENT_COLOR:
314 		{
315 			rgb_color* color;
316 			ssize_t size;
317 
318 			if (msg->FindData(kRGBColor, B_RGB_COLOR_TYPE,
319 					(const void**)&color, &size) == B_OK) {
320 				_SetCurrentColor(*color);
321 				modified = true;
322 			}
323 			break;
324 		}
325 
326 		case MSG_UPDATE_COLOR:
327 		{
328 			// Received from the color fPicker when its color changes
329 			rgb_color color = fPicker->ValueAsColor();
330 			_SetCurrentColor(color);
331 			modified = true;
332 
333 			break;
334 		}
335 
336 		case MSG_COLOR_ATTRIBUTE_CHOSEN:
337 		{
338 			// Received when the user chooses a GUI fAttribute from the list
339 			ColorItem* item = (ColorItem*)
340 				fAttrList->ItemAt(fAttrList->CurrentSelection());
341 			if (item == NULL)
342 				break;
343 
344 			const char* label = item->Text();
345 			rgb_color color = PrefHandler::Default()->getRGB(label);
346 			_SetCurrentColor(color);
347 			break;
348 		}
349 
350 		case MSG_THEME_MODIFIED:
351 			_SetCurrentColorScheme();
352 			BView::MessageReceived(msg);
353 			return;
354 
355 		default:
356 			BView::MessageReceived(msg);
357 			break;
358 	}
359 
360 	if (modified) {
361 		_UpdateStyle();
362 		fTerminalMessenger.SendMessage(msg);
363 
364 		BMessenger messenger(this);
365 		messenger.SendMessage(MSG_THEME_MODIFIED);
366 	}
367 }
368 
369 
370 void
371 ThemeView::SetDefaults()
372 {
373 	PrefHandler* prefHandler = PrefHandler::Default();
374 
375 	int32 count = fAttrList->CountItems();
376 	for (int32 index = 0; index < count; ++index) {
377 		ColorItem* item = (ColorItem*)fAttrList->ItemAt(index);
378 		item->SetColor(prefHandler->getRGB(item->Text()));
379 		fAttrList->InvalidateItem(index);
380 	}
381 
382 	int32 currentIndex = fAttrList->CurrentSelection();
383 	ColorItem* item = (ColorItem*)fAttrList->ItemAt(currentIndex);
384 	if (item != NULL) {
385 		rgb_color color = item->Color();
386 		fPicker->SetValue(color);
387 		fColorPreview->SetColor(color);
388 		fColorPreview->Invalidate();
389 	}
390 
391 	_UpdateStyle();
392 
393 	BMessage message(MSG_COLOR_SCHEME_CHANGED);
394 	fTerminalMessenger.SendMessage(&message);
395 }
396 
397 
398 void
399 ThemeView::Revert()
400 {
401 	_SetCurrentColorScheme();
402 
403 	SetDefaults();
404 }
405 
406 
407 void
408 ThemeView::_ChangeColorScheme(color_scheme* scheme)
409 {
410 	PrefHandler* pref = PrefHandler::Default();
411 
412 	pref->setRGB(PREF_TEXT_FORE_COLOR, scheme->text_fore_color);
413 	pref->setRGB(PREF_TEXT_BACK_COLOR, scheme->text_back_color);
414 	pref->setRGB(PREF_SELECT_FORE_COLOR, scheme->select_fore_color);
415 	pref->setRGB(PREF_SELECT_BACK_COLOR, scheme->select_back_color);
416 	pref->setRGB(PREF_CURSOR_FORE_COLOR, scheme->cursor_fore_color);
417 	pref->setRGB(PREF_CURSOR_BACK_COLOR, scheme->cursor_back_color);
418 	pref->setRGB(PREF_ANSI_BLACK_COLOR, scheme->ansi_colors.black);
419 	pref->setRGB(PREF_ANSI_RED_COLOR, scheme->ansi_colors.red);
420 	pref->setRGB(PREF_ANSI_GREEN_COLOR, scheme->ansi_colors.green);
421 	pref->setRGB(PREF_ANSI_YELLOW_COLOR, scheme->ansi_colors.yellow);
422 	pref->setRGB(PREF_ANSI_BLUE_COLOR, scheme->ansi_colors.blue);
423 	pref->setRGB(PREF_ANSI_MAGENTA_COLOR, scheme->ansi_colors.magenta);
424 	pref->setRGB(PREF_ANSI_CYAN_COLOR, scheme->ansi_colors.cyan);
425 	pref->setRGB(PREF_ANSI_WHITE_COLOR, scheme->ansi_colors.white);
426 	pref->setRGB(PREF_ANSI_BLACK_HCOLOR, scheme->ansi_colors_h.black);
427 	pref->setRGB(PREF_ANSI_RED_HCOLOR, scheme->ansi_colors_h.red);
428 	pref->setRGB(PREF_ANSI_GREEN_HCOLOR, scheme->ansi_colors_h.green);
429 	pref->setRGB(PREF_ANSI_YELLOW_HCOLOR, scheme->ansi_colors_h.yellow);
430 	pref->setRGB(PREF_ANSI_BLUE_HCOLOR, scheme->ansi_colors_h.blue);
431 	pref->setRGB(PREF_ANSI_MAGENTA_HCOLOR, scheme->ansi_colors_h.magenta);
432 	pref->setRGB(PREF_ANSI_CYAN_HCOLOR, scheme->ansi_colors_h.cyan);
433 	pref->setRGB(PREF_ANSI_WHITE_HCOLOR, scheme->ansi_colors_h.white);
434 
435 	int32 count = fAttrList->CountItems();
436 	for (int32 index = 0; index < count; ++index)
437 	{
438 		ColorItem* item = static_cast<ColorItem*>(fAttrList->ItemAt(index));
439 		rgb_color color = pref->getRGB(item->Text());
440 		item->SetColor(color);
441 
442 		if (item->IsSelected()) {
443 			fPicker->SetValue(color);
444 			fColorPreview->SetColor(color);
445 			fColorPreview->Invalidate();
446 		}
447 	}
448 
449 	fAttrList->Invalidate();
450 }
451 
452 
453 void
454 ThemeView::_SetCurrentColorScheme()
455 {
456 	PrefHandler* pref = PrefHandler::Default();
457 
458 	pref->LoadColorScheme(&gCustomColorScheme);
459 
460 	const char* currentSchemeName = NULL;
461 
462 	int32 i = 0;
463 	while (i < gColorSchemes->CountItems()) {
464 		const color_scheme *item = gColorSchemes->ItemAt(i);
465 		i++;
466 
467 		if (gCustomColorScheme == *item) {
468 			currentSchemeName = item->name;
469 			break;
470 		}
471 	}
472 
473 	// If the scheme is not one of the known ones, assume a custom one.
474 	if (currentSchemeName == NULL)
475 		currentSchemeName = "Custom";
476 
477 	for (int32 i = 0; i < fColorSchemeField->Menu()->CountItems(); i++) {
478 		BMenuItem* item = fColorSchemeField->Menu()->ItemAt(i);
479 		if (strcmp(item->Label(), currentSchemeName) == 0) {
480 			item->SetMarked(true);
481 			break;
482 		}
483 	}
484 }
485 
486 
487 void
488 ThemeView::_MakeColorSchemeMenuItem(const color_scheme *item)
489 {
490 	if (item == NULL)
491 		return;
492 
493 	BMessage* message = new BMessage(MSG_COLOR_SCHEME_CHANGED);
494 	message->AddPointer("color_scheme", (const void*)item);
495 	fColorSchemeMenu->AddItem(new BMenuItem(item->name, message));
496 }
497 
498 
499 void
500 ThemeView::_MakeColorSchemeMenu()
501 {
502 	while (fColorSchemeMenu->CountItems() > 0)
503 		delete(fColorSchemeMenu->RemoveItem((int32)0));
504 
505 	FindColorSchemeByName comparator("Default");
506 
507 	const color_scheme* defaultItem = gColorSchemes->FindIf(comparator);
508 
509 	_MakeColorSchemeMenuItem(defaultItem);
510 
511 	int32 index = 0;
512 	while (index < gColorSchemes->CountItems()) {
513 		const color_scheme *item = gColorSchemes->ItemAt(index);
514 		index++;
515 
516 		if (strcmp(item->name, "") == 0)
517 			fColorSchemeMenu->AddSeparatorItem();
518 		else if (item == defaultItem)
519 			continue;
520 		else
521 			_MakeColorSchemeMenuItem(item);
522 	}
523 
524 	// Add the custom item at the very end
525 	fColorSchemeMenu->AddSeparatorItem();
526 	_MakeColorSchemeMenuItem(&gCustomColorScheme);
527 }
528 
529 
530 void
531 ThemeView::_SetCurrentColor(rgb_color color)
532 {
533 	int32 currentIndex = fAttrList->CurrentSelection();
534 	ColorItem* item = (ColorItem*)fAttrList->ItemAt(currentIndex);
535 	if (item != NULL) {
536 		item->SetColor(color);
537 		fAttrList->InvalidateItem(currentIndex);
538 
539 		PrefHandler::Default()->setRGB(item->Text(), color);
540 	}
541 
542 	fPicker->SetValue(color);
543 	fColorPreview->SetColor(color);
544 	fColorPreview->Invalidate();
545 }
546 
547 
548 void
549 ThemeView::_SetColor(const char* name, rgb_color color)
550 {
551 	PrefHandler::Default()->setRGB(name, color);
552 
553 	_UpdateStyle();
554 }
555