xref: /haiku/src/apps/terminal/AppearPrefView.cpp (revision b617a7b410c05275effb95f4b2f5608359d9b7b9)
1 /*
2  * Copyright 2001-2010, Haiku, Inc.
3  * Copyright 2003-2004 Kian Duffy, myob@users.sourceforge.net
4  * Parts Copyright 1998-1999 Kazuho Okui and Takashi Murai.
5  * All rights reserved. Distributed under the terms of the MIT license.
6  */
7 
8 
9 #include "AppearPrefView.h"
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 
14 #include <Button.h>
15 #include <Catalog.h>
16 #include <CheckBox.h>
17 #include <ColorControl.h>
18 #include <LayoutBuilder.h>
19 #include <Locale.h>
20 #include <Menu.h>
21 #include <MenuField.h>
22 #include <MenuItem.h>
23 #include <PopUpMenu.h>
24 #include <TextControl.h>
25 #include <View.h>
26 
27 #include "Colors.h"
28 #include "PrefHandler.h"
29 #include "TermConst.h"
30 
31 
32 #undef B_TRANSLATION_CONTEXT
33 #define B_TRANSLATION_CONTEXT "Terminal AppearancePrefView"
34 
35 
36 static bool
37 IsFontUsable(const BFont& font)
38 {
39 	// TODO: If BFont::IsFullAndHalfFixed() was implemented, we could
40 	// use that. But I don't think it's easily implementable using
41 	// Freetype.
42 
43 	if (font.IsFixed())
44 		return true;
45 
46 	// manually check if all applicable chars are the same width
47 	char buffer[2] = { ' ', 0 };
48 	int firstWidth = (int)ceilf(font.StringWidth(buffer));
49 
50 	// TODO: Workaround for broken fonts/font_subsystem
51 	if (firstWidth <= 0)
52 		return false;
53 
54 	for (int c = ' ' + 1; c <= 0x7e; c++) {
55 		buffer[0] = c;
56 		int width = (int)ceilf(font.StringWidth(buffer));
57 
58 		if (width != firstWidth)
59 			return false;
60 	}
61 
62 	return true;
63 }
64 
65 
66 // #pragma mark -
67 
68 
69 AppearancePrefView::AppearancePrefView(const char* name,
70 		const BMessenger& messenger)
71 	:
72 	BGroupView(name, B_VERTICAL, 5),
73 	fTerminalMessenger(messenger)
74 {
75 	const char* kColorTable[] = {
76 		B_TRANSLATE_MARK("Text"),
77 		B_TRANSLATE_MARK("Background"),
78 		B_TRANSLATE_MARK("Cursor"),
79 		B_TRANSLATE_MARK("Text under cursor"),
80 		B_TRANSLATE_MARK("Selected text"),
81 		B_TRANSLATE_MARK("Selected background"),
82 		NULL
83 	};
84 
85 	fBlinkCursor = new BCheckBox(
86 		B_TRANSLATE("Blinking cursor"),
87 			new BMessage(MSG_BLINK_CURSOR_CHANGED));
88 
89 	fWarnOnExit = new BCheckBox(
90 		B_TRANSLATE("Confirm exit if active programs exist"),
91 			new BMessage(MSG_WARN_ON_EXIT_CHANGED));
92 
93 	BMenu* fontMenu = _MakeFontMenu(MSG_HALF_FONT_CHANGED,
94 		PrefHandler::Default()->getString(PREF_HALF_FONT_FAMILY),
95 		PrefHandler::Default()->getString(PREF_HALF_FONT_STYLE));
96 	fFontField = new BMenuField(B_TRANSLATE("Font:"), fontMenu);
97 
98 	BPopUpMenu* schemesPopUp = _MakeColorSchemeMenu(MSG_COLOR_SCHEME_CHANGED,
99 		gPredefinedColorSchemes, gPredefinedColorSchemes[0]);
100 	fColorSchemeField = new BMenuField(B_TRANSLATE("Color scheme:"),
101 		schemesPopUp);
102 
103 	BPopUpMenu* colorsPopUp = _MakeMenu(MSG_COLOR_FIELD_CHANGED, kColorTable,
104 		kColorTable[0]);
105 
106 	fColorField = new BMenuField(B_TRANSLATE("Color:"), colorsPopUp);
107 
108 	fTabTitle = new BTextControl("tabTitle", B_TRANSLATE("Tab title:"), "",
109 		NULL);
110 	fTabTitle->SetModificationMessage(
111 		new BMessage(MSG_TAB_TITLE_SETTING_CHANGED));
112 	fTabTitle->SetToolTip(BString(B_TRANSLATE(
113 		"The pattern specifying the tab titles. The following placeholders\n"
114 		"can be used:\n")) << kTooTipSetTabTitlePlaceholders);
115 
116 	fWindowTitle = new BTextControl("windowTitle", B_TRANSLATE("Window title:"),
117 		"", NULL);
118 	fWindowTitle->SetModificationMessage(
119 		new BMessage(MSG_WINDOW_TITLE_SETTING_CHANGED));
120 	fWindowTitle->SetToolTip(BString(B_TRANSLATE(
121 		"The pattern specifying the window titles. The following placeholders\n"
122 		"can be used:\n")) << kTooTipSetWindowTitlePlaceholders);
123 
124 	BLayoutBuilder::Group<>(this)
125 		.SetInsets(5, 5, 5, 5)
126 		.AddGrid(5, 5)
127 			.Add(fTabTitle->CreateLabelLayoutItem(), 0, 0)
128 			.Add(fTabTitle->CreateTextViewLayoutItem(), 1, 0)
129 			.Add(fWindowTitle->CreateLabelLayoutItem(), 0, 1)
130 			.Add(fWindowTitle->CreateTextViewLayoutItem(), 1, 1)
131 			.Add(fFontField->CreateLabelLayoutItem(), 0, 2)
132 			.Add(fFontField->CreateMenuBarLayoutItem(), 1, 2)
133 			.Add(fColorSchemeField->CreateLabelLayoutItem(), 0, 3)
134 			.Add(fColorSchemeField->CreateMenuBarLayoutItem(), 1, 3)
135 			.Add(fColorField->CreateLabelLayoutItem(), 0, 4)
136 			.Add(fColorField->CreateMenuBarLayoutItem(), 1, 4)
137 			.End()
138 		.AddGlue()
139 		.Add(fColorControl = new BColorControl(BPoint(10, 10),
140 			B_CELLS_32x8, 8.0, "", new BMessage(MSG_COLOR_CHANGED)))
141 		.Add(fBlinkCursor)
142 		.Add(fWarnOnExit);
143 
144 	fTabTitle->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
145 	fWindowTitle->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
146 	fFontField->SetAlignment(B_ALIGN_RIGHT);
147 	fColorField->SetAlignment(B_ALIGN_RIGHT);
148 	fColorSchemeField->SetAlignment(B_ALIGN_RIGHT);
149 
150 	fTabTitle->SetText(PrefHandler::Default()->getString(PREF_TAB_TITLE));
151 	fWindowTitle->SetText(PrefHandler::Default()->getString(PREF_WINDOW_TITLE));
152 
153 	fColorControl->SetValue(
154 		PrefHandler::Default()->getRGB(PREF_TEXT_FORE_COLOR));
155 
156 	fBlinkCursor->SetValue(PrefHandler::Default()->getBool(PREF_BLINK_CURSOR));
157 	fWarnOnExit->SetValue(PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT));
158 
159 	BTextControl* redInput = (BTextControl*)fColorControl->ChildAt(0);
160 	BTextControl* greenInput = (BTextControl*)fColorControl->ChildAt(1);
161 	BTextControl* blueInput = (BTextControl*)fColorControl->ChildAt(2);
162 
163 	redInput->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
164 	greenInput->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
165 	blueInput->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
166 }
167 
168 
169 void
170 AppearancePrefView::GetPreferredSize(float* _width, float* _height)
171 {
172 	if (_width)
173 		*_width = Bounds().Width();
174 
175 	if (*_height)
176 		*_height = fColorControl->Frame().bottom;
177 }
178 
179 
180 void
181 AppearancePrefView::Revert()
182 {
183 	PrefHandler* pref = PrefHandler::Default();
184 
185 	fTabTitle->SetText(pref->getString(PREF_TAB_TITLE));
186 	fWindowTitle->SetText(pref->getString(PREF_WINDOW_TITLE));
187 
188 	fWarnOnExit->SetValue(pref->getBool(
189 		PREF_WARN_ON_EXIT));
190 
191 	fColorSchemeField->Menu()->ItemAt(0)->SetMarked(true);
192 	fColorControl->SetValue(pref->
193 		getRGB(PREF_TEXT_FORE_COLOR));
194 
195 	const char* family = pref->getString(PREF_HALF_FONT_FAMILY);
196 	const char* style = pref->getString(PREF_HALF_FONT_STYLE);
197 	const char* size = pref->getString(PREF_HALF_FONT_SIZE);
198 
199 	_MarkSelectedFont(family, style, size);
200 }
201 
202 
203 void
204 AppearancePrefView::AttachedToWindow()
205 {
206 	fTabTitle->SetTarget(this);
207 	fWindowTitle->SetTarget(this);
208 	fBlinkCursor->SetTarget(this);
209 	fWarnOnExit->SetTarget(this);
210 
211 	fFontField->Menu()->SetTargetForItems(this);
212 	for (int32 i = 0; i < fFontField->Menu()->CountItems(); i++) {
213 		BMenu* fontSizeMenu = fFontField->Menu()->SubmenuAt(i);
214 		if (fontSizeMenu == NULL)
215 			continue;
216 
217 		fontSizeMenu->SetTargetForItems(this);
218 	}
219 
220 	fColorControl->SetTarget(this);
221 	fColorField->Menu()->SetTargetForItems(this);
222 	fColorSchemeField->Menu()->SetTargetForItems(this);
223 
224 	_SetCurrentColorScheme();
225 }
226 
227 
228 void
229 
230 AppearancePrefView::MessageReceived(BMessage* msg)
231 {
232 	bool modified = false;
233 
234 	switch (msg->what) {
235 		case MSG_HALF_FONT_CHANGED:
236 		{
237 			const char* family = NULL;
238 			const char* style = NULL;
239 			const char* size = NULL;
240 			if (msg->FindString("font_family", &family) != B_OK
241 				|| msg->FindString("font_style", &style) != B_OK
242 				|| msg->FindString("font_size", &size) != B_OK) {
243 				break;
244 			}
245 
246 			PrefHandler* pref = PrefHandler::Default();
247 			const char* currentFamily
248 				= pref->getString(PREF_HALF_FONT_FAMILY);
249 			const char* currentStyle
250 				= pref->getString(PREF_HALF_FONT_STYLE);
251 			const char* currentSize
252 				= pref->getString(PREF_HALF_FONT_SIZE);
253 
254 			if (currentFamily == NULL || strcmp(currentFamily, family) != 0
255 				|| currentStyle == NULL || strcmp(currentStyle, style) != 0
256 				|| currentSize == NULL || strcmp(currentSize, size) != 0) {
257 				pref->setString(PREF_HALF_FONT_FAMILY, family);
258 				pref->setString(PREF_HALF_FONT_STYLE, style);
259 				pref->setString(PREF_HALF_FONT_SIZE, size);
260 				_MarkSelectedFont(family, style, size);
261 				modified = true;
262 			}
263 			break;
264 		}
265 
266 		case MSG_COLOR_CHANGED:
267 		{
268 			const BMessage* itemMessage
269 				= fColorField->Menu()->FindMarked()->Message();
270 			const char* label = NULL;
271 			if (itemMessage->FindString("label", &label) != B_OK)
272 				break;
273 			rgb_color oldColor = PrefHandler::Default()->getRGB(label);
274 			if (oldColor != fColorControl->ValueAsColor()) {
275 				BMenuItem* item = fColorSchemeField->Menu()->FindMarked();
276 				if (strcmp(item->Label(), gCustomColorScheme.name) != 0) {
277 					item->SetMarked(false);
278 					item = fColorSchemeField->Menu()->FindItem(
279 						gCustomColorScheme.name);
280 					if (item)
281 						item->SetMarked(true);
282 				}
283 
284 				PrefHandler::Default()->setRGB(label,
285 					fColorControl->ValueAsColor());
286 				modified = true;
287 			}
288 			break;
289 		}
290 
291 		case MSG_COLOR_SCHEME_CHANGED:
292 		{
293 			color_scheme* newScheme = NULL;
294 			if (msg->FindPointer("color_scheme",
295 					(void**)&newScheme) == B_OK) {
296 				_ChangeColorScheme(newScheme);
297 				const char* label = NULL;
298 				if (fColorField->Menu()->FindMarked()->Message()->FindString(
299 						"label", &label) == B_OK)
300 					fColorControl->SetValue(
301 						PrefHandler::Default()->getRGB(label));
302 				modified = true;
303 			}
304 			break;
305 		}
306 
307 		case MSG_COLOR_FIELD_CHANGED:
308 		{
309 			const char* label = NULL;
310 			if (msg->FindString("label", &label) == B_OK)
311 				fColorControl->SetValue(PrefHandler::Default()->getRGB(label));
312 			break;
313 		}
314 
315 		case MSG_BLINK_CURSOR_CHANGED:
316 			if (PrefHandler::Default()->getBool(PREF_BLINK_CURSOR)
317 				!= fBlinkCursor->Value()) {
318 					PrefHandler::Default()->setBool(PREF_BLINK_CURSOR,
319 						fBlinkCursor->Value());
320 					modified = true;
321 			}
322 			break;
323 
324 		case MSG_WARN_ON_EXIT_CHANGED:
325 			if (PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT)
326 				!= fWarnOnExit->Value()) {
327 					PrefHandler::Default()->setBool(PREF_WARN_ON_EXIT,
328 						fWarnOnExit->Value());
329 					modified = true;
330 			}
331 			break;
332 
333 		case MSG_TAB_TITLE_SETTING_CHANGED:
334 		{
335 			BString oldValue(PrefHandler::Default()->getString(PREF_TAB_TITLE));
336 			if (oldValue != fTabTitle->Text()) {
337 				PrefHandler::Default()->setString(PREF_TAB_TITLE,
338 					fTabTitle->Text());
339 				modified = true;
340 			}
341 			break;
342 		}
343 
344 		case MSG_WINDOW_TITLE_SETTING_CHANGED:
345 		{
346 			BString oldValue(PrefHandler::Default()->getString(
347 				PREF_WINDOW_TITLE));
348 			if (oldValue != fWindowTitle->Text()) {
349 				PrefHandler::Default()->setString(PREF_WINDOW_TITLE,
350 					fWindowTitle->Text());
351 				modified = true;
352 			}
353 			break;
354 		}
355 
356 		default:
357 			BView::MessageReceived(msg);
358 			return;
359 	}
360 
361 	if (modified) {
362 		fTerminalMessenger.SendMessage(msg);
363 
364 		BMessenger messenger(this);
365 		messenger.SendMessage(MSG_PREF_MODIFIED);
366 	}
367 }
368 
369 
370 void
371 AppearancePrefView::_ChangeColorScheme(color_scheme* scheme)
372 {
373 	PrefHandler* pref = PrefHandler::Default();
374 
375 	pref->setRGB(PREF_TEXT_FORE_COLOR, scheme->text_fore_color);
376 	pref->setRGB(PREF_TEXT_BACK_COLOR, scheme->text_back_color);
377 	pref->setRGB(PREF_SELECT_FORE_COLOR, scheme->select_fore_color);
378 	pref->setRGB(PREF_SELECT_BACK_COLOR, scheme->select_back_color);
379 	pref->setRGB(PREF_CURSOR_FORE_COLOR, scheme->cursor_fore_color);
380 	pref->setRGB(PREF_CURSOR_BACK_COLOR, scheme->cursor_back_color);
381 }
382 
383 
384 void
385 AppearancePrefView::_SetCurrentColorScheme()
386 {
387 	PrefHandler* pref = PrefHandler::Default();
388 
389 	gCustomColorScheme.text_fore_color = pref->getRGB(PREF_TEXT_FORE_COLOR);
390 	gCustomColorScheme.text_back_color = pref->getRGB(PREF_TEXT_BACK_COLOR);
391 	gCustomColorScheme.select_fore_color = pref->getRGB(PREF_SELECT_FORE_COLOR);
392 	gCustomColorScheme.select_back_color = pref->getRGB(PREF_SELECT_BACK_COLOR);
393 	gCustomColorScheme.cursor_fore_color = pref->getRGB(PREF_CURSOR_FORE_COLOR);
394 	gCustomColorScheme.cursor_back_color = pref->getRGB(PREF_CURSOR_BACK_COLOR);
395 
396 	const char* currentSchemeName = NULL;
397 
398 	for (const color_scheme** schemes = gPredefinedColorSchemes;
399 			*schemes != NULL; schemes++) {
400 		if (gCustomColorScheme == **schemes) {
401 			currentSchemeName = (*schemes)->name;
402 			break;
403 		}
404 	}
405 
406 	for (int32 i = 0; i < fColorSchemeField->Menu()->CountItems(); i++) {
407 		if (currentSchemeName == NULL)
408 			break;
409 		BMenuItem* item = fColorSchemeField->Menu()->ItemAt(i);
410 		if (strcmp(item->Label(), currentSchemeName) == 0) {
411 			item->SetMarked(true);
412 			break;
413 		}
414 	}
415 }
416 
417 
418 /*static*/ BMenu*
419 AppearancePrefView::_MakeFontMenu(uint32 command,
420 	const char* defaultFamily, const char* defaultStyle)
421 {
422 	BPopUpMenu* menu = new BPopUpMenu("");
423 	int32 numFamilies = count_font_families();
424 	uint32 flags;
425 
426 	for (int32 i = 0; i < numFamilies; i++) {
427 		font_family family;
428 		if (get_font_family(i, &family, &flags) == B_OK) {
429 			BFont font;
430 			font_style style;
431 			int32 numStyles = count_font_styles(family);
432 			for (int32 j = 0; j < numStyles; j++) {
433 				if (get_font_style(family, j, &style) == B_OK) {
434 					font.SetFamilyAndStyle(family, style);
435 					if (IsFontUsable(font)) {
436 						BMessage* message = new BMessage(command);
437 						const char* size
438 							= PrefHandler::Default()->getString(PREF_HALF_FONT_SIZE);
439 						message->AddString("font_family", family);
440 						message->AddString("font_style", style);
441 						message->AddString("font_size", size);
442 						char fontMenuLabel[134];
443 						snprintf(fontMenuLabel, sizeof(fontMenuLabel),
444 							"%s - %s", family, style);
445 						BMenu* fontSizeMenu = _MakeFontSizeMenu(fontMenuLabel,
446 							MSG_HALF_FONT_CHANGED, family, style, size);
447 						BMenuItem* item = new BMenuItem(fontSizeMenu, message);
448 						menu->AddItem(item);
449 						if (strcmp(defaultFamily, family) == 0
450 							&& strcmp(defaultStyle, style) == 0)
451 							item->SetMarked(true);
452 					}
453 				}
454 			}
455 		}
456 	}
457 
458 	if (menu->FindMarked() == NULL)
459 		menu->ItemAt(0)->SetMarked(true);
460 
461 	return menu;
462 }
463 
464 
465 /*static*/ BMenu*
466 AppearancePrefView::_MakeFontSizeMenu(const char* label, uint32 command,
467 	const char* family, const char* style, const char* size)
468 {
469 	BMenu* menu = new BMenu(label);
470 	menu->SetRadioMode(true);
471 	menu->SetLabelFromMarked(false);
472 
473 	int32 sizes[] = {
474 		8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, 0
475 	};
476 
477 	bool found = false;
478 
479 	for (uint32 i = 0; sizes[i]; i++) {
480 		BString fontSize;
481 		fontSize << sizes[i];
482 		BMessage* message = new BMessage(command);
483 		message->AddString("font_family", family);
484 		message->AddString("font_style", style);
485 		message->AddString("font_size", fontSize.String());
486 		BMenuItem* item = new BMenuItem(fontSize.String(), message);
487 		menu->AddItem(item);
488 		if (sizes[i] == atoi(size)) {
489 			item->SetMarked(true);
490 			found = true;
491 		}
492 	}
493 
494 	if (!found) {
495 		for (uint32 i = 0; sizes[i]; i++) {
496 			if (sizes[i] > atoi(size)) {
497 				BMessage* message = new BMessage(command);
498 				message->AddString("font_family", family);
499 				message->AddString("font_style", style);
500 				message->AddString("font_size", size);
501 				BMenuItem* item = new BMenuItem(size, message);
502 				item->SetMarked(true);
503 				menu->AddItem(item, i);
504 				break;
505 			}
506 		}
507 	}
508 
509 	return menu;
510 }
511 
512 
513 /*static*/ BPopUpMenu*
514 AppearancePrefView::_MakeMenu(uint32 msg, const char** items,
515 	const char* defaultItemName)
516 {
517 	BPopUpMenu* menu = new BPopUpMenu("");
518 
519 	while (*items) {
520 		if (strcmp((*items), "") == 0)
521 			menu->AddSeparatorItem();
522 		else {
523 			BMessage* message = new BMessage(msg);
524 			message->AddString("label", *items);
525 			BMenuItem* item = new BMenuItem(B_TRANSLATE(*items), message);
526 			menu->AddItem(item);
527 			if (strcmp(*items, defaultItemName) == 0)
528 				item->SetMarked(true);
529 		}
530 
531 		items++;
532 	}
533 
534 	return menu;
535 }
536 
537 
538 /*static*/ BPopUpMenu*
539 AppearancePrefView::_MakeColorSchemeMenu(uint32 msg, const color_scheme** items,
540 	const color_scheme* defaultItemName)
541 {
542 	BPopUpMenu* menu = new BPopUpMenu("");
543 
544 	int32 i = 0;
545 	while (*items) {
546 		if (strcmp((*items)->name, "") == 0)
547 			menu->AddSeparatorItem();
548 		else {
549 			BMessage* message = new BMessage(msg);
550 			message->AddPointer("color_scheme", (const void*)*items);
551 			menu->AddItem(new BMenuItem((*items)->name, message));
552 		}
553 
554 		items++;
555 		i++;
556 	}
557 	return menu;
558 }
559 
560 
561 void
562 AppearancePrefView::_MarkSelectedFont(const char* family, const char* style,
563 	const char* size)
564 {
565 	char fontMenuLabel[134];
566 	snprintf(fontMenuLabel, sizeof(fontMenuLabel), "%s - %s", family, style);
567 
568 	// mark the selected font
569 	BMenuItem* selectedFont = fFontField->Menu()->FindItem(fontMenuLabel);
570 	if (selectedFont != NULL)
571 		selectedFont->SetMarked(true);
572 
573 	// mark the selected font size on all font menus
574 	for (int32 i = 0; i < fFontField->Menu()->CountItems(); i++) {
575 		BMenu* fontSizeMenu = fFontField->Menu()->SubmenuAt(i);
576 		if (fontSizeMenu == NULL)
577 			continue;
578 
579 		BMenuItem* item = fontSizeMenu->FindItem(size);
580 		if (item != NULL)
581 			item->SetMarked(true);
582 	}
583 }
584