xref: /haiku/src/apps/terminal/AppearPrefView.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
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("Text"),
77 		B_TRANSLATE("Background"),
78 		B_TRANSLATE("Cursor"),
79 		B_TRANSLATE("Text under cursor"),
80 		B_TRANSLATE("Selected text"),
81 		B_TRANSLATE("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 	fColorField->SetEnabled(false);
108 
109 	fTabTitle = new BTextControl("tabTitle", B_TRANSLATE("Tab title:"), "",
110 		NULL);
111 	fTabTitle->SetModificationMessage(
112 		new BMessage(MSG_TAB_TITLE_SETTING_CHANGED));
113 	fTabTitle->SetToolTip(BString(B_TRANSLATE(
114 		"The pattern specifying the tab titles. The following placeholders\n"
115 		"can be used:\n")) << kTooTipSetTabTitlePlaceholders);
116 
117 	fWindowTitle = new BTextControl("windowTitle", B_TRANSLATE("Window title:"),
118 		"", NULL);
119 	fWindowTitle->SetModificationMessage(
120 		new BMessage(MSG_WINDOW_TITLE_SETTING_CHANGED));
121 	fWindowTitle->SetToolTip(BString(B_TRANSLATE(
122 		"The pattern specifying the window titles. The following placeholders\n"
123 		"can be used:\n")) << kTooTipSetWindowTitlePlaceholders);
124 
125 	BLayoutBuilder::Group<>(this)
126 		.SetInsets(5, 5, 5, 5)
127 		.AddGrid(5, 5)
128 			.Add(fTabTitle->CreateLabelLayoutItem(), 0, 0)
129 			.Add(fTabTitle->CreateTextViewLayoutItem(), 1, 0)
130 			.Add(fWindowTitle->CreateLabelLayoutItem(), 0, 1)
131 			.Add(fWindowTitle->CreateTextViewLayoutItem(), 1, 1)
132 			.Add(fFontField->CreateLabelLayoutItem(), 0, 2)
133 			.Add(fFontField->CreateMenuBarLayoutItem(), 1, 2)
134 			.Add(fColorSchemeField->CreateLabelLayoutItem(), 0, 3)
135 			.Add(fColorSchemeField->CreateMenuBarLayoutItem(), 1, 3)
136 			.Add(fColorField->CreateLabelLayoutItem(), 0, 4)
137 			.Add(fColorField->CreateMenuBarLayoutItem(), 1, 4)
138 			.End()
139 		.AddGlue()
140 		.Add(fColorControl = new BColorControl(BPoint(10, 10),
141 			B_CELLS_32x8, 8.0, "", new BMessage(MSG_COLOR_CHANGED)))
142 		.Add(fBlinkCursor)
143 		.Add(fWarnOnExit);
144 
145 	fTabTitle->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
146 	fWindowTitle->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
147 	fFontField->SetAlignment(B_ALIGN_RIGHT);
148 	fColorField->SetAlignment(B_ALIGN_RIGHT);
149 	fColorSchemeField->SetAlignment(B_ALIGN_RIGHT);
150 
151 	fTabTitle->SetText(PrefHandler::Default()->getString(PREF_TAB_TITLE));
152 	fWindowTitle->SetText(PrefHandler::Default()->getString(PREF_WINDOW_TITLE));
153 
154 	fColorControl->SetEnabled(false);
155 	fColorControl->SetValue(
156 		PrefHandler::Default()->getRGB(PREF_TEXT_FORE_COLOR));
157 
158 	fBlinkCursor->SetValue(PrefHandler::Default()->getBool(PREF_BLINK_CURSOR));
159 	fWarnOnExit->SetValue(PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT));
160 
161 	BTextControl* redInput = (BTextControl*)fColorControl->ChildAt(0);
162 	BTextControl* greenInput = (BTextControl*)fColorControl->ChildAt(1);
163 	BTextControl* blueInput = (BTextControl*)fColorControl->ChildAt(2);
164 
165 	redInput->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
166 	greenInput->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
167 	blueInput->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
168 }
169 
170 
171 void
172 AppearancePrefView::GetPreferredSize(float* _width, float* _height)
173 {
174 	if (_width)
175 		*_width = Bounds().Width();
176 
177 	if (*_height)
178 		*_height = fColorControl->Frame().bottom;
179 }
180 
181 
182 void
183 AppearancePrefView::Revert()
184 {
185 	PrefHandler* pref = PrefHandler::Default();
186 
187 	fTabTitle->SetText(pref->getString(PREF_TAB_TITLE));
188 	fWindowTitle->SetText(pref->getString(PREF_WINDOW_TITLE));
189 
190 	fWarnOnExit->SetValue(pref->getBool(
191 		PREF_WARN_ON_EXIT));
192 
193 	fColorSchemeField->Menu()->ItemAt(0)->SetMarked(true);
194 	fColorControl->SetValue(pref->
195 		getRGB(PREF_TEXT_FORE_COLOR));
196 
197 	const char* family = pref->getString(PREF_HALF_FONT_FAMILY);
198 	const char* style = pref->getString(PREF_HALF_FONT_STYLE);
199 	const char* size = pref->getString(PREF_HALF_FONT_SIZE);
200 
201 	_MarkSelectedFont(family, style, size);
202 }
203 
204 
205 void
206 AppearancePrefView::AttachedToWindow()
207 {
208 	fTabTitle->SetTarget(this);
209 	fWindowTitle->SetTarget(this);
210 	fBlinkCursor->SetTarget(this);
211 	fWarnOnExit->SetTarget(this);
212 
213 	fFontField->Menu()->SetTargetForItems(this);
214 	for (int32 i = 0; i < fFontField->Menu()->CountItems(); i++) {
215 		BMenu* fontSizeMenu = fFontField->Menu()->SubmenuAt(i);
216 		if (fontSizeMenu == NULL)
217 			continue;
218 
219 		fontSizeMenu->SetTargetForItems(this);
220 	}
221 
222   	fColorControl->SetTarget(this);
223   	fColorField->Menu()->SetTargetForItems(this);
224   	fColorSchemeField->Menu()->SetTargetForItems(this);
225 
226   	_SetCurrentColorScheme(fColorSchemeField);
227   	bool enableCustomColors =
228 		strcmp(fColorSchemeField->Menu()->FindMarked()->Label(),
229 			gCustomColorScheme.name) == 0;
230 
231   	_EnableCustomColors(enableCustomColors);
232 }
233 
234 
235 void
236 
237 AppearancePrefView::MessageReceived(BMessage* msg)
238 {
239 	bool modified = false;
240 
241 	switch (msg->what) {
242 		case MSG_HALF_FONT_CHANGED:
243 		{
244 			const char* family = NULL;
245 			const char* style = NULL;
246 			const char* size = NULL;
247 			if (msg->FindString("font_family", &family) != B_OK
248 				|| msg->FindString("font_style", &style) != B_OK
249 				|| msg->FindString("font_size", &size) != B_OK) {
250 				break;
251 			}
252 
253 			PrefHandler* pref = PrefHandler::Default();
254 			const char* currentFamily
255 				= pref->getString(PREF_HALF_FONT_FAMILY);
256 			const char* currentStyle
257 				= pref->getString(PREF_HALF_FONT_STYLE);
258 			const char* currentSize
259 				= pref->getString(PREF_HALF_FONT_SIZE);
260 
261 			if (currentFamily == NULL || strcmp(currentFamily, family) != 0
262 				|| currentStyle == NULL || strcmp(currentStyle, style) != 0
263 				|| currentSize == NULL || strcmp(currentSize, size) != 0) {
264 				pref->setString(PREF_HALF_FONT_FAMILY, family);
265 				pref->setString(PREF_HALF_FONT_STYLE, style);
266 				pref->setString(PREF_HALF_FONT_SIZE, size);
267 				_MarkSelectedFont(family, style, size);
268 				modified = true;
269 			}
270 			break;
271 		}
272 
273 		case MSG_COLOR_CHANGED:
274 		{
275 			rgb_color oldColor = PrefHandler::Default()->getRGB(
276 				fColorField->Menu()->FindMarked()->Label());
277 			if (oldColor != fColorControl->ValueAsColor()) {
278 				PrefHandler::Default()->setRGB(
279 					fColorField->Menu()->FindMarked()->Label(),
280 					fColorControl->ValueAsColor());
281 				modified = true;
282 			}
283 			break;
284 		}
285 
286 		case MSG_COLOR_SCHEME_CHANGED:
287 		{
288 			color_scheme* newScheme = NULL;
289 			if (msg->FindPointer("color_scheme",
290 					(void**)&newScheme) == B_OK) {
291 				if (newScheme == &gCustomColorScheme)
292 					_EnableCustomColors(true);
293 				else
294 					_EnableCustomColors(false);
295 
296 				_ChangeColorScheme(newScheme);
297 				modified = true;
298 			}
299 			break;
300 		}
301 
302 		case MSG_COLOR_FIELD_CHANGED:
303 			fColorControl->SetValue(PrefHandler::Default()->getRGB(
304 				fColorField->Menu()->FindMarked()->Label()));
305 			break;
306 
307 		case MSG_BLINK_CURSOR_CHANGED:
308 			if (PrefHandler::Default()->getBool(PREF_BLINK_CURSOR)
309 				!= fBlinkCursor->Value()) {
310 					PrefHandler::Default()->setBool(PREF_BLINK_CURSOR,
311 						fBlinkCursor->Value());
312 					modified = true;
313 			}
314 			break;
315 
316 		case MSG_WARN_ON_EXIT_CHANGED:
317 			if (PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT)
318 				!= fWarnOnExit->Value()) {
319 					PrefHandler::Default()->setBool(PREF_WARN_ON_EXIT,
320 						fWarnOnExit->Value());
321 					modified = true;
322 			}
323 			break;
324 
325 		case MSG_TAB_TITLE_SETTING_CHANGED:
326 		{
327 			BString oldValue(PrefHandler::Default()->getString(PREF_TAB_TITLE));
328 			if (oldValue != fTabTitle->Text()) {
329 				PrefHandler::Default()->setString(PREF_TAB_TITLE,
330 					fTabTitle->Text());
331 				modified = true;
332 			}
333 			break;
334 		}
335 
336 		case MSG_WINDOW_TITLE_SETTING_CHANGED:
337 		{
338 			BString oldValue(PrefHandler::Default()->getString(
339 				PREF_WINDOW_TITLE));
340 			if (oldValue != fWindowTitle->Text()) {
341 				PrefHandler::Default()->setString(PREF_WINDOW_TITLE,
342 					fWindowTitle->Text());
343 				modified = true;
344 			}
345 			break;
346 		}
347 
348 		default:
349 			BView::MessageReceived(msg);
350 			return;
351 	}
352 
353 	if (modified) {
354 		fTerminalMessenger.SendMessage(msg);
355 
356 		BMessenger messenger(this);
357 		messenger.SendMessage(MSG_PREF_MODIFIED);
358 	}
359 }
360 
361 
362 void
363 AppearancePrefView::_EnableCustomColors(bool enable)
364 {
365 	fColorField->SetEnabled(enable);
366 	fColorControl->SetEnabled(enable);
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(BMenuField* field)
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 		BMenuItem* item = fColorSchemeField->Menu()->ItemAt(i);
408 		if (strcmp(item->Label(), currentSchemeName) == 0) {
409 			item->SetMarked(true);
410 			break;
411 		}
412 	}
413 }
414 
415 
416 /*static*/ BMenu*
417 AppearancePrefView::_MakeFontMenu(uint32 command,
418 	const char* defaultFamily, const char* defaultStyle)
419 {
420 	BPopUpMenu* menu = new BPopUpMenu("");
421 	int32 numFamilies = count_font_families();
422 	uint32 flags;
423 
424 	for (int32 i = 0; i < numFamilies; i++) {
425 		font_family family;
426 		if (get_font_family(i, &family, &flags) == B_OK) {
427 			BFont font;
428 			font_style style;
429 			int32 numStyles = count_font_styles(family);
430 			for (int32 j = 0; j < numStyles; j++) {
431 				if (get_font_style(family, j, &style) == B_OK) {
432 					font.SetFamilyAndStyle(family, style);
433 					if (IsFontUsable(font)) {
434 						BMessage* message = new BMessage(command);
435 						const char* size
436 							= PrefHandler::Default()->getString(PREF_HALF_FONT_SIZE);
437 						message->AddString("font_family", family);
438 						message->AddString("font_style", style);
439 						message->AddString("font_size", size);
440 						char fontMenuLabel[134];
441 						snprintf(fontMenuLabel, sizeof(fontMenuLabel),
442 							"%s - %s", family, style);
443 						BMenu* fontSizeMenu = _MakeFontSizeMenu(fontMenuLabel,
444 							MSG_HALF_FONT_CHANGED, family, style, size);
445 						BMenuItem* item = new BMenuItem(fontSizeMenu, message);
446 						menu->AddItem(item);
447 						if (strcmp(defaultFamily, family) == 0
448 							&& strcmp(defaultStyle, style) == 0)
449 							item->SetMarked(true);
450 					}
451 				}
452 			}
453 		}
454 	}
455 
456 	if (menu->FindMarked() == NULL)
457 		menu->ItemAt(0)->SetMarked(true);
458 
459 	return menu;
460 }
461 
462 
463 /*static*/ BMenu*
464 AppearancePrefView::_MakeFontSizeMenu(const char* label, uint32 command,
465 	const char* family, const char* style, const char* size)
466 {
467 	BMenu* menu = new BMenu(label);
468 	menu->SetRadioMode(true);
469 	menu->SetLabelFromMarked(false);
470 
471 	int32 sizes[] = {
472 		8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, 0
473 	};
474 
475 	bool found = false;
476 
477 	for (uint32 i = 0; sizes[i]; i++) {
478 		BString fontSize;
479 		fontSize << sizes[i];
480 		BMessage* message = new BMessage(command);
481 		message->AddString("font_family", family);
482 		message->AddString("font_style", style);
483 		message->AddString("font_size", fontSize.String());
484 		BMenuItem* item = new BMenuItem(fontSize.String(), message);
485 		menu->AddItem(item);
486 		if (sizes[i] == atoi(size)) {
487 			item->SetMarked(true);
488 			found = true;
489 		}
490 	}
491 
492 	if (!found) {
493 		for (uint32 i = 0; sizes[i]; i++) {
494 			if (sizes[i] > atoi(size)) {
495 				BMessage* message = new BMessage(command);
496 				message->AddString("font_family", family);
497 				message->AddString("font_style", style);
498 				message->AddString("font_size", size);
499 				BMenuItem* item = new BMenuItem(size, message);
500 				item->SetMarked(true);
501 				menu->AddItem(item, i);
502 				break;
503 			}
504 		}
505 	}
506 
507 	return menu;
508 }
509 
510 
511 /*static*/ BPopUpMenu*
512 AppearancePrefView::_MakeMenu(uint32 msg, const char** items,
513 	const char* defaultItemName)
514 {
515 	BPopUpMenu* menu = new BPopUpMenu("");
516 
517 	int32 i = 0;
518 	while (*items) {
519 		if (strcmp((*items), "") == 0)
520 			menu->AddSeparatorItem();
521 		else {
522 			BMessage* message = new BMessage(msg);
523 			menu->AddItem(new BMenuItem((*items), message));
524 		}
525 
526 		items++;
527 		i++;
528 	}
529 
530 	BMenuItem* defaultItem = menu->FindItem(defaultItemName);
531 	if (defaultItem)
532 		defaultItem->SetMarked(true);
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