xref: /haiku/src/apps/terminal/ThemeView.cpp (revision 3634f142352af2428aed187781fc9d75075e9140)
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 		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 
242 void
243 ThemeView::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 
257 void
258 ThemeView::WindowActivated(bool active)
259 {
260 	if (!active)
261 		return;
262 
263 	UpdateMenu();
264 }
265 
266 
267 void
268 ThemeView::UpdateMenu()
269 {
270 	PrefHandler::Default()->LoadThemes();
271 
272 	_MakeColorSchemeMenu();
273 	_SetCurrentColorScheme();
274 
275 	fColorSchemeField->Menu()->SetTargetForItems(this);
276 }
277 
278 
279 void
280 ThemeView::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 
366 void
367 ThemeView::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 
394 void
395 ThemeView::Revert()
396 {
397 	_SetCurrentColorScheme();
398 
399 	SetDefaults();
400 }
401 
402 
403 void
404 ThemeView::_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 
448 void
449 ThemeView::_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 
482 void
483 ThemeView::_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 
494 void
495 ThemeView::_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 
525 void
526 ThemeView::_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 
543 void
544 ThemeView::_SetColor(const char* name, rgb_color color)
545 {
546 	PrefHandler::Default()->setRGB(name, color);
547 
548 	_UpdateStyle();
549 }
550