1/*
2 * Copyright 2003-2015, Haiku, Inc. All Rights Reserved.
3 * Copyright (c) 2004 Daniel Furrer <assimil8or@users.sourceforge.net>
4 * Copyright (c) 2003-4 Kian Duffy <myob@users.sourceforge.net>
5 * Copyright (c) 1998,99 Kazuho Okui and Takashi Murai.
6 *
7 * Distributed unter the terms of the MIT License.
8 *
9 * Authors:
10 *		Kian Duffy, myob@users.sourceforge.net
11 *		Daniel Furrer, assimil8or@users.sourceforge.net
12 *		Simon South, simon@simonsouth.net
13 *		Siarzhuk Zharski, zharik@gmx.li
14 */
15
16
17#include "PrefHandler.h"
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
23
24#include <AutoDeleter.h>
25#include <Catalog.h>
26#include <Directory.h>
27#include <Entry.h>
28#include <File.h>
29#include <Font.h>
30#include <GraphicsDefs.h>
31#include <Locale.h>
32#include <Message.h>
33#include <NodeInfo.h>
34#include <Path.h>
35#include <PathFinder.h>
36
37#include "Colors.h"
38#include "Globals.h"
39#include "TermConst.h"
40
41#include <iostream>
42
43/*
44 * Startup preference settings.
45 */
46static const pref_defaults kTermDefaults[] = {
47	{ PREF_COLS,				"80" },
48	{ PREF_ROWS,				"25" },
49
50//	No need for PREF_HALF_FONT_FAMILY/_STYLE/_SIZE defaults here,
51//	these entries will be filled with corresponding params
52//	of the current system fixed font if they are not
53//	available in the settings file
54
55	{ PREF_TEXT_FORE_COLOR,		"  0,   0,   0" },
56	{ PREF_TEXT_BACK_COLOR,		"255, 255, 255" },
57	{ PREF_CURSOR_FORE_COLOR,	"255, 255, 255" },
58	{ PREF_CURSOR_BACK_COLOR,	"  0,   0,   0" },
59	{ PREF_SELECT_FORE_COLOR,	"255, 255, 255" },
60	{ PREF_SELECT_BACK_COLOR,	"  0,   0,   0" },
61
62	{ PREF_IM_FORE_COLOR,		"  0,   0,   0" },
63	{ PREF_IM_BACK_COLOR,		"152, 203, 255" },
64	{ PREF_IM_SELECT_COLOR,		"255, 152, 152" },
65
66	{ PREF_ANSI_BLACK_COLOR,	" 40,  40,  40" },
67	{ PREF_ANSI_RED_COLOR,		"204,   0,   0" },
68	{ PREF_ANSI_GREEN_COLOR,	" 78, 154,   6" },
69	{ PREF_ANSI_YELLOW_COLOR,	"218, 168,   0" },
70	{ PREF_ANSI_BLUE_COLOR,		" 51, 102, 152" },
71	{ PREF_ANSI_MAGENTA_COLOR,	"115,  68, 123" },
72	{ PREF_ANSI_CYAN_COLOR,		"  6, 152, 154" },
73	{ PREF_ANSI_WHITE_COLOR,	"245, 245, 245" },
74
75	{ PREF_ANSI_BLACK_HCOLOR,	"128, 128, 128" },
76	{ PREF_ANSI_RED_HCOLOR,		"255,   0,   0" },
77	{ PREF_ANSI_GREEN_HCOLOR,	"  0, 255,   0" },
78	{ PREF_ANSI_YELLOW_HCOLOR,	"255, 255,   0" },
79	{ PREF_ANSI_BLUE_HCOLOR,	"  0,   0, 255" },
80	{ PREF_ANSI_MAGENTA_HCOLOR,	"255,   0, 255" },
81	{ PREF_ANSI_CYAN_HCOLOR,	"  0, 255, 255" },
82	{ PREF_ANSI_WHITE_HCOLOR,	"255, 255, 255" },
83
84	{ PREF_HISTORY_SIZE,		"10000" },
85
86	{ PREF_TEXT_ENCODING,		"UTF-8" },
87
88	{ PREF_IM_AWARE,			"0"},
89
90	{ PREF_TAB_TITLE,			"%1d: %p%e" },
91	{ PREF_WINDOW_TITLE,		"%T% i: %t" },
92	{ PREF_BLINK_CURSOR,		PREF_TRUE },
93	{ PREF_USE_OPTION_AS_META,	PREF_FALSE },
94	{ PREF_WARN_ON_EXIT,		PREF_TRUE },
95	{ PREF_CURSOR_STYLE,		PREF_BLOCK_CURSOR },
96	{ PREF_EMULATE_BOLD,		PREF_FALSE },
97
98	{ NULL, NULL},
99};
100
101
102PrefHandler *PrefHandler::sPrefHandler = NULL;
103
104
105PrefHandler::PrefHandler(bool loadSettings)
106	:
107	fContainer('Pref')
108{
109	_LoadFromDefault(kTermDefaults);
110
111	if (loadSettings) {
112		BPath path;
113		GetDefaultPath(path);
114		OpenText(path.Path());
115
116		// Add the builtin schemes
117		if (gColorSchemes == NULL) {
118			LoadThemes();
119		}
120	}
121
122	// TODO: If no fixed font is available, be_fixed_font
123	// points to a proportional font.
124	if (IsFontUsable(be_fixed_font))
125		_ConfirmFont(be_fixed_font);
126	else {
127		int32 numFamilies = count_font_families();
128		for (int32 i = 0; i < numFamilies; i++) {
129			font_family family;
130			uint32 flags;
131			if (get_font_family(i, &family, &flags) == B_OK) {
132				font_style style;
133				int32 numStyles = count_font_styles(family);
134				for (int32 j = 0; j < numStyles; j++) {
135					if (get_font_style(family, j, &style) == B_OK) {
136						BFont fallBackFont;
137						fallBackFont.SetFamilyAndStyle(family, style);
138						if (IsFontUsable(fallBackFont)) {
139							_ConfirmFont(&fallBackFont);
140							return;
141						}
142					}
143				}
144			}
145		}
146	}
147}
148
149
150PrefHandler::PrefHandler(const PrefHandler* p)
151{
152	fContainer = p->fContainer;
153}
154
155
156PrefHandler::~PrefHandler()
157{
158}
159
160
161/* static */
162PrefHandler *
163PrefHandler::Default()
164{
165	if (sPrefHandler == NULL)
166		sPrefHandler = new PrefHandler();
167	return sPrefHandler;
168}
169
170
171/* static */
172void
173PrefHandler::DeleteDefault()
174{
175	delete sPrefHandler;
176	sPrefHandler = NULL;
177}
178
179
180/* static */
181void
182PrefHandler::SetDefault(PrefHandler *prefHandler)
183{
184	DeleteDefault();
185	sPrefHandler = prefHandler;
186}
187
188
189/* static */
190status_t
191PrefHandler::GetDefaultPath(BPath& path)
192{
193	status_t status;
194	status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
195	if (status != B_OK)
196		return status;
197
198	status = path.Append("Terminal");
199	if (status != B_OK)
200		return status;
201
202	// Just create the directory. Harmless if already there
203	status = create_directory(path.Path(), 0755);
204	if (status != B_OK)
205		return status;
206
207	return path.Append("Default");
208}
209
210
211status_t
212PrefHandler::OpenText(const char *path)
213{
214	return _LoadFromTextFile(path);
215}
216
217
218void
219PrefHandler::SaveDefaultAsText()
220{
221	BPath path;
222	if (GetDefaultPath(path) == B_OK)
223		SaveAsText(path.Path(), PREFFILE_MIMETYPE);
224}
225
226
227void
228PrefHandler::SaveAsText(const char *path, const char *mimetype,
229	const char *signature)
230{
231	// make sure the target path exists
232#if 0
233	BPath directoryPath(path);
234	if (directoryPath.GetParent(&directoryPath) == B_OK)
235		create_directory(directoryPath.Path(), 0755);
236#endif
237
238	BFile file(path, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
239	char buffer[512];
240	type_code type;
241	const char *key;
242
243	for (int32 i = 0;
244#ifdef B_BEOS_VERSION_DANO
245			fContainer.GetInfo(B_STRING_TYPE, i, &key, &type) == B_OK;
246#else
247			fContainer.GetInfo(B_STRING_TYPE, i, (char**)&key, &type) == B_OK;
248#endif
249			i++) {
250		int len = snprintf(buffer, sizeof(buffer), "\"%s\" , \"%s\"\n",
251				key, getString(key));
252		file.Write(buffer, len);
253	}
254
255	if (mimetype != NULL) {
256		BNodeInfo info(&file);
257		info.SetType(mimetype);
258		info.SetPreferredApp(signature);
259	}
260}
261
262
263static int
264SortByName(const color_scheme *lhs, const color_scheme *rhs)
265{
266	return strcmp(lhs->name, rhs->name);
267}
268
269
270void
271PrefHandler::LoadThemes()
272{
273	gColorSchemes = new BObjectList<const color_scheme>(10, true);
274
275	BStringList paths;
276
277	if (BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY,
278			"Terminal/Themes/", B_FIND_PATH_EXISTING_ONLY, paths) == B_OK)
279		paths.DoForEach(PrefHandler::_LoadThemesFromDirectory);
280
281	if (BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY,
282			"Terminal/Themes/", B_FIND_PATH_EXISTING_ONLY, paths) == B_OK)
283		paths.DoForEach(PrefHandler::_LoadThemesFromDirectory);
284
285	gColorSchemes->SortItems(SortByName);
286}
287
288
289void
290PrefHandler::LoadColorScheme(color_scheme* scheme)
291{
292	scheme->text_fore_color = getRGB(PREF_TEXT_FORE_COLOR);
293	scheme->text_back_color = getRGB(PREF_TEXT_BACK_COLOR);
294	scheme->select_fore_color = getRGB(PREF_SELECT_FORE_COLOR);
295	scheme->select_back_color = getRGB(PREF_SELECT_BACK_COLOR);
296	scheme->cursor_fore_color = getRGB(PREF_CURSOR_FORE_COLOR);
297	scheme->cursor_back_color = getRGB(PREF_CURSOR_BACK_COLOR);
298	scheme->ansi_colors.black = getRGB(PREF_ANSI_BLACK_COLOR);
299	scheme->ansi_colors.red = getRGB(PREF_ANSI_RED_COLOR);
300	scheme->ansi_colors.green = getRGB(PREF_ANSI_GREEN_COLOR);
301	scheme->ansi_colors.yellow = getRGB(PREF_ANSI_YELLOW_COLOR);
302	scheme->ansi_colors.blue = getRGB(PREF_ANSI_BLUE_COLOR);
303	scheme->ansi_colors.magenta = getRGB(PREF_ANSI_MAGENTA_COLOR);
304	scheme->ansi_colors.cyan = getRGB(PREF_ANSI_CYAN_COLOR);
305	scheme->ansi_colors.white = getRGB(PREF_ANSI_WHITE_COLOR);
306	scheme->ansi_colors_h.black = getRGB(PREF_ANSI_BLACK_HCOLOR);
307	scheme->ansi_colors_h.red = getRGB(PREF_ANSI_RED_HCOLOR);
308	scheme->ansi_colors_h.green = getRGB(PREF_ANSI_GREEN_HCOLOR);
309	scheme->ansi_colors_h.yellow = getRGB(PREF_ANSI_YELLOW_HCOLOR);
310	scheme->ansi_colors_h.blue = getRGB(PREF_ANSI_BLUE_HCOLOR);
311	scheme->ansi_colors_h.magenta = getRGB(PREF_ANSI_MAGENTA_HCOLOR);
312	scheme->ansi_colors_h.cyan = getRGB(PREF_ANSI_CYAN_HCOLOR);
313	scheme->ansi_colors_h.white = getRGB(PREF_ANSI_WHITE_HCOLOR);
314}
315
316
317int32
318PrefHandler::getInt32(const char *key)
319{
320	const char *value = fContainer.FindString(key);
321	if (value == NULL)
322		return 0;
323
324	return atoi(value);
325}
326
327
328float
329PrefHandler::getFloat(const char *key)
330{
331	const char *value = fContainer.FindString(key);
332	if (value == NULL)
333		return 0;
334
335	return atof(value);
336}
337
338
339#undef B_TRANSLATION_CONTEXT
340#define B_TRANSLATION_CONTEXT "Terminal getString"
341
342const char*
343PrefHandler::getString(const char *key)
344{
345	const char *buffer;
346	if (fContainer.FindString(key, &buffer) != B_OK)
347		buffer = B_TRANSLATE("Error!");
348
349	//printf("%x GET %s: %s\n", this, key, buf);
350	return buffer;
351}
352
353
354bool
355PrefHandler::getBool(const char *key)
356{
357	const char *value = fContainer.FindString(key);
358	if (value == NULL)
359		return false;
360
361	return strcmp(value, PREF_TRUE) == 0;
362}
363
364
365int
366PrefHandler::getCursor(const char *key)
367{
368	const char *value = fContainer.FindString(key);
369	if (value != NULL && strcmp(value, PREF_BLOCK_CURSOR) != 0) {
370		if (strcmp(value, PREF_UNDERLINE_CURSOR) == 0)
371			return UNDERLINE_CURSOR;
372		if (strcmp(value, PREF_IBEAM_CURSOR) == 0)
373			return IBEAM_CURSOR;
374	}
375	return BLOCK_CURSOR;
376}
377
378
379#undef B_TRANSLATION_CONTEXT
380#define B_TRANSLATION_CONTEXT "Terminal getRGB"
381
382/** Returns RGB data from given key. */
383
384rgb_color
385PrefHandler::getRGB(const char *key)
386{
387	rgb_color col;
388	int r, g, b;
389
390	if (const char *s = fContainer.FindString(key)) {
391		sscanf(s, "%d, %d, %d", &r, &g, &b);
392	} else {
393		fprintf(stderr,
394			"PrefHandler::getRGB(%s) - key not found\n", key);
395		r = g = b = 0;
396	}
397
398	col.red = r;
399	col.green = g;
400	col.blue = b;
401	col.alpha = 255;
402	return col;
403}
404
405
406/** Setting Int32 data with key. */
407
408void
409PrefHandler::setInt32(const char *key, int32 data)
410{
411	char buffer[32];
412	snprintf(buffer, sizeof(buffer), "%d", (int)data);
413	setString(key, buffer);
414}
415
416
417/** Setting Float data with key */
418
419void
420PrefHandler::setFloat(const char *key, float data)
421{
422	char buffer[32];
423	snprintf(buffer, sizeof(buffer), "%g", data);
424	setString(key, buffer);
425}
426
427
428/** Setting Bool data with key */
429
430void
431PrefHandler::setBool(const char *key, bool data)
432{
433	if (data)
434		setString(key, PREF_TRUE);
435	else
436		setString(key, PREF_FALSE);
437}
438
439
440/** Setting CString data with key */
441
442void
443PrefHandler::setString(const char *key, const char *data)
444{
445	//printf("%x SET %s: %s\n", this, key, data);
446	fContainer.RemoveName(key);
447	fContainer.AddString(key, data);
448}
449
450
451/** Setting RGB data with key */
452
453void
454PrefHandler::setRGB(const char *key, const rgb_color data)
455{
456	char buffer[32];
457	snprintf(buffer, sizeof(buffer), "%d, %d, %d", data.red, data.green, data.blue);
458	setString(key, buffer);
459}
460
461
462/** Check any peference stored or not. */
463
464bool
465PrefHandler::IsEmpty() const
466{
467	return fContainer.IsEmpty();
468}
469
470
471void
472PrefHandler::_ConfirmFont(const BFont *fallbackFont)
473{
474	font_family family;
475	font_style style;
476
477	const char *prefFamily = getString(PREF_HALF_FONT_FAMILY);
478	int32 familiesCount = (prefFamily != NULL) ? count_font_families() : 0;
479
480	for (int32 i = 0; i < familiesCount; i++) {
481		if (get_font_family(i, &family) != B_OK
482			|| strcmp(family, prefFamily) != 0)
483			continue;
484
485		const char *prefStyle = getString(PREF_HALF_FONT_STYLE);
486		int32 stylesCount = (prefStyle != NULL) ? count_font_styles(family) : 0;
487
488		for (int32 j = 0; j < stylesCount; j++) {
489			// check style if we can safely use this font
490			if (get_font_style(family, j, &style) == B_OK
491				&& strcmp(style, prefStyle) == 0)
492				return;
493		}
494	}
495
496	// use fall-back font
497	fallbackFont->GetFamilyAndStyle(&family, &style);
498	setString(PREF_HALF_FONT_FAMILY, family);
499	setString(PREF_HALF_FONT_STYLE, style);
500	setInt32(PREF_HALF_FONT_SIZE, fallbackFont->Size());
501}
502
503
504status_t
505PrefHandler::_LoadFromDefault(const pref_defaults* defaults)
506{
507	if (defaults == NULL)
508		return B_ERROR;
509
510	while (defaults->key) {
511		setString(defaults->key, defaults->item);
512		++defaults;
513	}
514
515	return B_OK;
516}
517
518
519/**	Text is "key","Content"
520 *	Comment : Start with '#'
521 */
522
523status_t
524PrefHandler::_LoadFromTextFile(const char * path)
525{
526	char buffer[1024];
527	char key[B_FIELD_NAME_LENGTH], data[512];
528	int n;
529	FILE *file;
530
531	file = fopen(path, "r");
532	if (file == NULL)
533		return B_ENTRY_NOT_FOUND;
534
535	while (fgets(buffer, sizeof(buffer), file) != NULL) {
536		if (*buffer == '#')
537			continue;
538
539		n = sscanf(buffer, "%*[\"]%[^\"]%*[\"]%*[^\"]%*[\"]%[^\"]", key, data);
540		if (n == 2)
541			setString(key, data);
542	}
543
544	fclose(file);
545	return B_OK;
546}
547
548
549bool
550PrefHandler::_LoadThemesFromDirectory(const BString &directory)
551{
552	BDirectory *themes = new BDirectory(directory.String());
553	if (themes == NULL)
554		return false;
555
556	BEntry entry;
557	BPath path;
558	FindColorSchemeByName comparator;
559	while (themes->GetNextEntry(&entry) == B_OK)
560	{
561		if (entry.GetPath(&path) != B_OK)
562			continue;
563
564		PrefHandler *themeHandler = new PrefHandler(false);
565		ObjectDeleter<PrefHandler> themeHandlerDeleter(themeHandler);
566		themeHandler->_LoadFromTextFile(path.Path());
567
568		const char *name = themeHandler->fContainer.GetString(PREF_THEME_NAME, NULL);
569
570		if (name == NULL || strlen(name) == 0)
571			continue;
572
573		comparator.scheme_name = name;
574
575		const color_scheme *scheme = gColorSchemes->FindIf(comparator);
576
577		if (scheme != NULL) {
578			// Scheme with this name exists, replace with this version instead
579			gColorSchemes->RemoveItem(scheme);
580		}
581
582		color_scheme *newScheme = new color_scheme();
583		newScheme->name = strdup(name);
584		themeHandler->LoadColorScheme(newScheme);
585		gColorSchemes->AddItem(newScheme);
586	}
587
588	return false;
589}
590