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
36static bool
37IsFontUsable(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
69AppearancePrefView::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
171void
172AppearancePrefView::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
182void
183AppearancePrefView::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
205void
206AppearancePrefView::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
235void
236
237AppearancePrefView::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
362void
363AppearancePrefView::_EnableCustomColors(bool enable)
364{
365	fColorField->SetEnabled(enable);
366	fColorControl->SetEnabled(enable);
367}
368
369
370void
371AppearancePrefView::_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
384void
385AppearancePrefView::_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*
417AppearancePrefView::_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*
464AppearancePrefView::_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*
512AppearancePrefView::_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*
539AppearancePrefView::_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
561void
562AppearancePrefView::_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