1/*
2 * Copyright 2001-2015, 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 <CharacterSet.h>
17#include <CharacterSetRoster.h>
18#include <CheckBox.h>
19#include <ColorControl.h>
20#include <LayoutBuilder.h>
21#include <Locale.h>
22#include <Menu.h>
23#include <MenuField.h>
24#include <MenuItem.h>
25#include <PopUpMenu.h>
26#include <TextControl.h>
27#include <View.h>
28
29#include "Colors.h"
30#include "Globals.h"
31#include "PrefHandler.h"
32#include "TermConst.h"
33#include "TermWindow.h"
34
35
36#undef B_TRANSLATION_CONTEXT
37#define B_TRANSLATION_CONTEXT "Terminal AppearancePrefView"
38
39
40// #pragma mark -
41
42
43AppearancePrefView::AppearancePrefView(const char* name,
44		const BMessenger& messenger)
45	:
46	BGroupView(name, B_VERTICAL, 5),
47	fTerminalMessenger(messenger)
48{
49	fBlinkCursor = new BCheckBox(
50		B_TRANSLATE("Blinking cursor"),
51			new BMessage(MSG_BLINK_CURSOR_CHANGED));
52
53	fAllowBold = new BCheckBox(
54		B_TRANSLATE("Allow bold text"),
55			new BMessage(MSG_ALLOW_BOLD_CHANGED));
56
57	fUseOptionAsMetaKey = new BCheckBox(
58		B_TRANSLATE("Use left Option as Meta key"),
59			new BMessage(MSG_USE_OPTION_AS_META_CHANGED));
60
61	fWarnOnExit = new BCheckBox(
62		B_TRANSLATE("Confirm exit if active programs exist"),
63			new BMessage(MSG_WARN_ON_EXIT_CHANGED));
64
65	BMenu* fontMenu = _MakeFontMenu(MSG_HALF_FONT_CHANGED,
66		PrefHandler::Default()->getString(PREF_HALF_FONT_FAMILY),
67		PrefHandler::Default()->getString(PREF_HALF_FONT_STYLE));
68	fFontField = new BMenuField(B_TRANSLATE("Font:"), fontMenu);
69
70	BMenu* sizeMenu = new (std::nothrow) BPopUpMenu(
71		B_TRANSLATE_COMMENT("Custom", "Window size"));
72	if (sizeMenu != NULL) {
73		TermWindow::MakeWindowSizeMenu(sizeMenu);
74		sizeMenu->SetLabelFromMarked(true);
75	}
76	fWindowSizeField = new BMenuField(B_TRANSLATE("Window size:"), sizeMenu);
77
78	BMenu* encodingMenu = new (std::nothrow) BPopUpMenu("Text encoding");
79	if (encodingMenu != NULL) {
80		TermWindow::MakeEncodingMenu(encodingMenu);
81		encodingMenu->SetLabelFromMarked(true);
82	}
83	fEncodingField = new BMenuField(B_TRANSLATE("Encoding:"), encodingMenu);
84
85	fTabTitle = new BTextControl("tabTitle", B_TRANSLATE("Tab title:"), "",
86		NULL);
87	fTabTitle->SetModificationMessage(
88		new BMessage(MSG_TAB_TITLE_SETTING_CHANGED));
89	fTabTitle->SetToolTip(BString(B_TRANSLATE(
90		"The pattern specifying the tab titles. The following placeholders\n"
91		"can be used:")) << "\n" << kToolTipSetTabTitlePlaceholders
92		<< "\n" << kToolTipCommonTitlePlaceholders);
93
94	fWindowTitle = new BTextControl("windowTitle", B_TRANSLATE("Window title:"),
95		"", NULL);
96	fWindowTitle->SetModificationMessage(
97		new BMessage(MSG_WINDOW_TITLE_SETTING_CHANGED));
98	fWindowTitle->SetToolTip(BString(B_TRANSLATE(
99		"The pattern specifying the window titles. The following placeholders\n"
100		"can be used:")) << "\n" << kToolTipSetWindowTitlePlaceholders
101		<< "\n" << kToolTipCommonTitlePlaceholders);
102
103	BLayoutBuilder::Group<>(this)
104		.SetInsets(5, 5, 5, 5)
105		.AddGrid(5, 5)
106			.Add(fTabTitle->CreateLabelLayoutItem(), 0, 0)
107			.Add(fTabTitle->CreateTextViewLayoutItem(), 1, 0)
108			.Add(fWindowTitle->CreateLabelLayoutItem(), 0, 1)
109			.Add(fWindowTitle->CreateTextViewLayoutItem(), 1, 1)
110			.Add(fWindowSizeField->CreateLabelLayoutItem(), 0, 2)
111			.Add(fWindowSizeField->CreateMenuBarLayoutItem(), 1, 2)
112			.Add(fFontField->CreateLabelLayoutItem(), 0, 3)
113			.Add(fFontField->CreateMenuBarLayoutItem(), 1, 3)
114			.Add(fEncodingField->CreateLabelLayoutItem(), 0, 4)
115			.Add(fEncodingField->CreateMenuBarLayoutItem(), 1, 4)
116			.End()
117		.AddGlue()
118		.Add(fBlinkCursor)
119		.Add(fAllowBold)
120		.Add(fUseOptionAsMetaKey)
121		.Add(fWarnOnExit);
122
123	fTabTitle->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
124	fWindowTitle->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
125	fFontField->SetAlignment(B_ALIGN_RIGHT);
126	fWindowSizeField->SetAlignment(B_ALIGN_RIGHT);
127	fEncodingField->SetAlignment(B_ALIGN_RIGHT);
128
129	Revert();
130}
131
132
133void
134AppearancePrefView::Revert()
135{
136	PrefHandler* pref = PrefHandler::Default();
137
138	fTabTitle->SetText(pref->getString(PREF_TAB_TITLE));
139	fWindowTitle->SetText(pref->getString(PREF_WINDOW_TITLE));
140
141	fBlinkCursor->SetValue(pref->getBool(PREF_BLINK_CURSOR));
142	fAllowBold->SetValue(pref->getBool(PREF_ALLOW_BOLD));
143	fUseOptionAsMetaKey->SetValue(pref->getBool(PREF_USE_OPTION_AS_META));
144	fWarnOnExit->SetValue(pref->getBool(PREF_WARN_ON_EXIT));
145
146	_SetEncoding(pref->getString(PREF_TEXT_ENCODING));
147	_SetWindowSize(pref->getInt32(PREF_ROWS), pref->getInt32(PREF_COLS));
148
149	const char* family = pref->getString(PREF_HALF_FONT_FAMILY);
150	const char* style = pref->getString(PREF_HALF_FONT_STYLE);
151	const char* size = pref->getString(PREF_HALF_FONT_SIZE);
152
153	_MarkSelectedFont(family, style, size);
154}
155
156
157void
158AppearancePrefView::AttachedToWindow()
159{
160	fTabTitle->SetTarget(this);
161	fWindowTitle->SetTarget(this);
162	fBlinkCursor->SetTarget(this);
163	fAllowBold->SetTarget(this);
164	fUseOptionAsMetaKey->SetTarget(this);
165	fWarnOnExit->SetTarget(this);
166
167	fFontField->Menu()->SetTargetForItems(this);
168	for (int32 i = 0; i < fFontField->Menu()->CountItems(); i++) {
169		BMenu* fontSizeMenu = fFontField->Menu()->SubmenuAt(i);
170		if (fontSizeMenu == NULL)
171			continue;
172
173		fontSizeMenu->SetTargetForItems(this);
174	}
175
176	fWindowSizeField->Menu()->SetTargetForItems(this);
177	fEncodingField->Menu()->SetTargetForItems(this);
178}
179
180
181void
182
183AppearancePrefView::MessageReceived(BMessage* msg)
184{
185	bool modified = false;
186
187	switch (msg->what) {
188		case MSG_HALF_FONT_CHANGED:
189		{
190			const char* family = NULL;
191			const char* style = NULL;
192			const char* size = NULL;
193			if (msg->FindString("font_family", &family) != B_OK
194				|| msg->FindString("font_style", &style) != B_OK
195				|| msg->FindString("font_size", &size) != B_OK) {
196				break;
197			}
198
199			PrefHandler* pref = PrefHandler::Default();
200			const char* currentFamily
201				= pref->getString(PREF_HALF_FONT_FAMILY);
202			const char* currentStyle
203				= pref->getString(PREF_HALF_FONT_STYLE);
204			const char* currentSize
205				= pref->getString(PREF_HALF_FONT_SIZE);
206
207			if (currentFamily == NULL || strcmp(currentFamily, family) != 0
208				|| currentStyle == NULL || strcmp(currentStyle, style) != 0
209				|| currentSize == NULL || strcmp(currentSize, size) != 0) {
210				pref->setString(PREF_HALF_FONT_FAMILY, family);
211				pref->setString(PREF_HALF_FONT_STYLE, style);
212				pref->setString(PREF_HALF_FONT_SIZE, size);
213				_MarkSelectedFont(family, style, size);
214				modified = true;
215			}
216			break;
217		}
218
219		case MSG_COLS_CHANGED:
220		{
221			int rows = msg->FindInt32("rows");
222			int columns = msg->FindInt32("columns");
223			_SetWindowSize(rows, columns);
224			PrefHandler* handler = PrefHandler::Default();
225			if (handler->getInt32(PREF_ROWS) != rows) {
226				PrefHandler::Default()->setInt32(PREF_ROWS, rows);
227				modified = true;
228			}
229			if (handler->getInt32(PREF_COLS) != columns) {
230				PrefHandler::Default()->setInt32(PREF_COLS, columns);
231				modified = true;
232			}
233
234			break;
235		}
236
237		case MSG_BLINK_CURSOR_CHANGED:
238			if (PrefHandler::Default()->getBool(PREF_BLINK_CURSOR)
239				!= fBlinkCursor->Value()) {
240					PrefHandler::Default()->setBool(PREF_BLINK_CURSOR,
241						fBlinkCursor->Value());
242					modified = true;
243			}
244			break;
245
246		case MSG_ALLOW_BOLD_CHANGED:
247			if (PrefHandler::Default()->getBool(PREF_ALLOW_BOLD)
248				!= fAllowBold->Value()) {
249					PrefHandler::Default()->setBool(PREF_ALLOW_BOLD,
250						fAllowBold->Value());
251					modified = true;
252			}
253			break;
254
255		case MSG_USE_OPTION_AS_META_CHANGED:
256			if (PrefHandler::Default()->getBool(PREF_USE_OPTION_AS_META)
257				!= fUseOptionAsMetaKey->Value()) {
258					PrefHandler::Default()->setBool(PREF_USE_OPTION_AS_META,
259						fUseOptionAsMetaKey->Value());
260					modified = true;
261			}
262			break;
263
264		case MSG_WARN_ON_EXIT_CHANGED:
265			if (PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT)
266				!= fWarnOnExit->Value()) {
267					PrefHandler::Default()->setBool(PREF_WARN_ON_EXIT,
268						fWarnOnExit->Value());
269					modified = true;
270			}
271			break;
272
273		case MSG_TAB_TITLE_SETTING_CHANGED:
274		{
275			BString oldValue(PrefHandler::Default()->getString(PREF_TAB_TITLE));
276			if (oldValue != fTabTitle->Text()) {
277				PrefHandler::Default()->setString(PREF_TAB_TITLE,
278					fTabTitle->Text());
279				modified = true;
280			}
281			break;
282		}
283
284		case MSG_WINDOW_TITLE_SETTING_CHANGED:
285		{
286			BString oldValue(PrefHandler::Default()->getString(
287				PREF_WINDOW_TITLE));
288			if (oldValue != fWindowTitle->Text()) {
289				PrefHandler::Default()->setString(PREF_WINDOW_TITLE,
290					fWindowTitle->Text());
291				modified = true;
292			}
293			break;
294		}
295
296		default:
297			BView::MessageReceived(msg);
298			return;
299	}
300
301	if (modified) {
302		fTerminalMessenger.SendMessage(msg);
303
304		BMessenger messenger(this);
305		messenger.SendMessage(MSG_PREF_MODIFIED);
306	}
307}
308
309
310void
311AppearancePrefView::_SetEncoding(const char* name)
312{
313	const BPrivate::BCharacterSet* charset
314		= BPrivate::BCharacterSetRoster::FindCharacterSetByName(name);
315	if (charset == NULL)
316		return;
317	int code = charset->GetConversionID();
318	for (int32 i = 0; i < fEncodingField->Menu()->CountItems(); i++) {
319		BMenuItem* item = fEncodingField->Menu()->ItemAt(i);
320		BMessage* msg = item->Message();
321		if (msg->FindInt32("op") == code) {
322			item->SetMarked(true);
323			break;
324		}
325	}
326}
327
328
329void
330AppearancePrefView::_SetWindowSize(int rows, int cols)
331{
332	for (int32 i = 0; i < fWindowSizeField->Menu()->CountItems(); i++) {
333		BMenuItem* item = fWindowSizeField->Menu()->ItemAt(i);
334		BMessage* msg = item->Message();
335		if (msg->FindInt32("rows") == rows && msg->FindInt32("columns") == cols) {
336			item->SetMarked(true);
337			break;
338		}
339	}
340}
341
342
343/*static*/ BMenu*
344AppearancePrefView::_MakeFontMenu(uint32 command,
345	const char* defaultFamily, const char* defaultStyle)
346{
347	BPopUpMenu* menu = new BPopUpMenu("");
348	int32 numFamilies = count_font_families();
349	for (int32 i = 0; i < numFamilies; i++) {
350		font_family family;
351		uint32 flags;
352		if (get_font_family(i, &family, &flags) == B_OK) {
353			BFont font;
354			font_style style;
355			int32 numStyles = count_font_styles(family);
356			for (int32 j = 0; j < numStyles; j++) {
357				if (get_font_style(family, j, &style) == B_OK) {
358					font.SetFamilyAndStyle(family, style);
359					if (IsFontUsable(font)) {
360						BMessage* message = new BMessage(command);
361						const char* size
362							= PrefHandler::Default()->getString(PREF_HALF_FONT_SIZE);
363						message->AddString("font_family", family);
364						message->AddString("font_style", style);
365						message->AddString("font_size", size);
366						char fontMenuLabel[134];
367						snprintf(fontMenuLabel, sizeof(fontMenuLabel),
368							"%s - %s", family, style);
369						BMenu* fontSizeMenu = _MakeFontSizeMenu(fontMenuLabel,
370							MSG_HALF_FONT_CHANGED, family, style, size);
371						BMenuItem* item = new BMenuItem(fontSizeMenu, message);
372						menu->AddItem(item);
373						if (strcmp(defaultFamily, family) == 0
374							&& strcmp(defaultStyle, style) == 0)
375							item->SetMarked(true);
376					}
377				}
378			}
379		}
380	}
381
382	if (menu->FindMarked() == NULL)
383		menu->ItemAt(0)->SetMarked(true);
384
385	return menu;
386}
387
388
389/*static*/ BMenu*
390AppearancePrefView::_MakeFontSizeMenu(const char* label, uint32 command,
391	const char* family, const char* style, const char* size)
392{
393	BMenu* menu = new BMenu(label);
394	menu->SetRadioMode(true);
395	menu->SetLabelFromMarked(false);
396
397	int32 sizes[] = {
398		8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28, 32, 36, 0
399	};
400
401	bool found = false;
402
403	for (uint32 i = 0; sizes[i]; i++) {
404		BString fontSize;
405		fontSize << sizes[i];
406		BMessage* message = new BMessage(command);
407		message->AddString("font_family", family);
408		message->AddString("font_style", style);
409		message->AddString("font_size", fontSize.String());
410		BMenuItem* item = new BMenuItem(fontSize.String(), message);
411		menu->AddItem(item);
412		if (sizes[i] == atoi(size)) {
413			item->SetMarked(true);
414			found = true;
415		}
416	}
417
418	if (!found) {
419		for (uint32 i = 0; sizes[i]; i++) {
420			if (sizes[i] > atoi(size)) {
421				BMessage* message = new BMessage(command);
422				message->AddString("font_family", family);
423				message->AddString("font_style", style);
424				message->AddString("font_size", size);
425				BMenuItem* item = new BMenuItem(size, message);
426				item->SetMarked(true);
427				menu->AddItem(item, i);
428				break;
429			}
430		}
431	}
432
433	return menu;
434}
435
436
437/*static*/ BPopUpMenu*
438AppearancePrefView::_MakeMenu(uint32 msg, const char** items,
439	const char* defaultItemName)
440{
441	BPopUpMenu* menu = new BPopUpMenu("");
442
443	while (*items) {
444		if (strcmp((*items), "") == 0)
445			menu->AddSeparatorItem();
446		else {
447			BMessage* message = new BMessage(msg);
448			message->AddString("label", *items);
449			BMenuItem* item = new BMenuItem(B_TRANSLATE(*items), message);
450			menu->AddItem(item);
451			if (strcmp(*items, defaultItemName) == 0)
452				item->SetMarked(true);
453		}
454
455		items++;
456	}
457
458	return menu;
459}
460
461
462void
463AppearancePrefView::_MarkSelectedFont(const char* family, const char* style,
464	const char* size)
465{
466	char fontMenuLabel[134];
467	snprintf(fontMenuLabel, sizeof(fontMenuLabel), "%s - %s", family, style);
468
469	// mark the selected font
470	BMenuItem* selectedFont = fFontField->Menu()->FindItem(fontMenuLabel);
471	if (selectedFont != NULL)
472		selectedFont->SetMarked(true);
473
474	// mark the selected font size on all font menus
475	for (int32 i = 0; i < fFontField->Menu()->CountItems(); i++) {
476		BMenu* fontSizeMenu = fFontField->Menu()->SubmenuAt(i);
477		if (fontSizeMenu == NULL)
478			continue;
479
480		BMenuItem* item = fontSizeMenu->FindItem(size);
481		if (item != NULL)
482			item->SetMarked(true);
483	}
484}
485