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