⛏️ index : haiku.git

/*
 * Copyright 2003-2015, Haiku, Inc. All Rights Reserved.
 * Copyright (c) 2004 Daniel Furrer <assimil8or@users.sourceforge.net>
 * Copyright (c) 2003-4 Kian Duffy <myob@users.sourceforge.net>
 * Copyright (c) 1998,99 Kazuho Okui and Takashi Murai.
 *
 * Distributed unter the terms of the MIT License.
 *
 * Authors:
 *		Kian Duffy, myob@users.sourceforge.net
 *		Daniel Furrer, assimil8or@users.sourceforge.net
 *		Simon South, simon@simonsouth.net
 *		Siarzhuk Zharski, zharik@gmx.li
 */


#include "PrefHandler.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <AutoDeleter.h>
#include <Catalog.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <Font.h>
#include <GraphicsDefs.h>
#include <Locale.h>
#include <Message.h>
#include <NodeInfo.h>
#include <Path.h>
#include <PathFinder.h>

#include "Colors.h"
#include "Globals.h"
#include "TermConst.h"

#include <iostream>

/*
 * Startup preference settings.
 */
static const pref_defaults kTermDefaults[] = {
	{ PREF_COLS,				"80" },
	{ PREF_ROWS,				"25" },

//	No need for PREF_HALF_FONT_FAMILY/_STYLE/_SIZE defaults here,
//	these entries will be filled with corresponding params
//	of the current system fixed font if they are not
//	available in the settings file

	{ PREF_TEXT_FORE_COLOR,		"  0,   0,   0" },
	{ PREF_TEXT_BACK_COLOR,		"255, 255, 255" },
	{ PREF_CURSOR_FORE_COLOR,	"255, 255, 255" },
	{ PREF_CURSOR_BACK_COLOR,	"  0,   0,   0" },
	{ PREF_SELECT_FORE_COLOR,	"255, 255, 255" },
	{ PREF_SELECT_BACK_COLOR,	"  0,   0,   0" },

	{ PREF_IM_FORE_COLOR,		"  0,   0,   0" },
	{ PREF_IM_BACK_COLOR,		"152, 203, 255" },
	{ PREF_IM_SELECT_COLOR,		"255, 152, 152" },

	{ PREF_ANSI_BLACK_COLOR,	" 40,  40,  40" },
	{ PREF_ANSI_RED_COLOR,		"204,   0,   0" },
	{ PREF_ANSI_GREEN_COLOR,	" 78, 154,   6" },
	{ PREF_ANSI_YELLOW_COLOR,	"218, 168,   0" },
	{ PREF_ANSI_BLUE_COLOR,		" 51, 102, 152" },
	{ PREF_ANSI_MAGENTA_COLOR,	"115,  68, 123" },
	{ PREF_ANSI_CYAN_COLOR,		"  6, 152, 154" },
	{ PREF_ANSI_WHITE_COLOR,	"245, 245, 245" },

	{ PREF_ANSI_BLACK_HCOLOR,	"128, 128, 128" },
	{ PREF_ANSI_RED_HCOLOR,		"255,   0,   0" },
	{ PREF_ANSI_GREEN_HCOLOR,	"  0, 255,   0" },
	{ PREF_ANSI_YELLOW_HCOLOR,	"255, 255,   0" },
	{ PREF_ANSI_BLUE_HCOLOR,	"  0,   0, 255" },
	{ PREF_ANSI_MAGENTA_HCOLOR,	"255,   0, 255" },
	{ PREF_ANSI_CYAN_HCOLOR,	"  0, 255, 255" },
	{ PREF_ANSI_WHITE_HCOLOR,	"255, 255, 255" },

	{ PREF_HISTORY_SIZE,		"10000" },

	{ PREF_TEXT_ENCODING,		"UTF-8" },

	{ PREF_IM_AWARE,			"0"},

	{ PREF_TAB_TITLE,			"%1d: %p%e" },
	{ PREF_WINDOW_TITLE,		"%T% i: %t" },
	{ PREF_BLINK_CURSOR,		PREF_TRUE },
	{ PREF_USE_OPTION_AS_META,	PREF_FALSE },
	{ PREF_WARN_ON_EXIT,		PREF_TRUE },
	{ PREF_CURSOR_STYLE,		PREF_BLOCK_CURSOR },
	{ PREF_EMULATE_BOLD,		PREF_FALSE },

	{ NULL, NULL},
};


PrefHandler *PrefHandler::sPrefHandler = NULL;


PrefHandler::PrefHandler(bool loadSettings)
	:
	fContainer('Pref')
{
	_LoadFromDefault(kTermDefaults);

	if (loadSettings) {
		BPath path;
		GetDefaultPath(path);
		OpenText(path.Path());

		// Add the builtin schemes
		if (gColorSchemes == NULL) {
			LoadThemes();
		}
	}

	// TODO: If no fixed font is available, be_fixed_font
	// points to a proportional font.
	if (IsFontUsable(be_fixed_font))
		_ConfirmFont(be_fixed_font);
	else {
		int32 numFamilies = count_font_families();
		for (int32 i = 0; i < numFamilies; i++) {
			font_family family;
			uint32 flags;
			if (get_font_family(i, &family, &flags) == B_OK) {
				font_style style;
				int32 numStyles = count_font_styles(family);
				for (int32 j = 0; j < numStyles; j++) {
					if (get_font_style(family, j, &style) == B_OK) {
						BFont fallBackFont;
						fallBackFont.SetFamilyAndStyle(family, style);
						if (IsFontUsable(fallBackFont)) {
							_ConfirmFont(&fallBackFont);
							return;
						}
					}
				}
			}
		}
	}
}


PrefHandler::PrefHandler(const PrefHandler* p)
{
	fContainer = p->fContainer;
}


PrefHandler::~PrefHandler()
{
}


/* static */
PrefHandler *
PrefHandler::Default()
{
	if (sPrefHandler == NULL)
		sPrefHandler = new PrefHandler();
	return sPrefHandler;
}


/* static */
void
PrefHandler::DeleteDefault()
{
	delete sPrefHandler;
	sPrefHandler = NULL;
}


/* static */
void
PrefHandler::SetDefault(PrefHandler *prefHandler)
{
	DeleteDefault();
	sPrefHandler = prefHandler;
}


/* static */
status_t
PrefHandler::GetDefaultPath(BPath& path)
{
	status_t status;
	status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
	if (status != B_OK)
		return status;

	status = path.Append("Terminal");
	if (status != B_OK)
		return status;

	// Just create the directory. Harmless if already there
	status = create_directory(path.Path(), 0755);
	if (status != B_OK)
		return status;

	return path.Append("Default");
}


status_t
PrefHandler::OpenText(const char *path)
{
	return _LoadFromTextFile(path);
}


void
PrefHandler::SaveDefaultAsText()
{
	BPath path;
	if (GetDefaultPath(path) == B_OK)
		SaveAsText(path.Path(), PREFFILE_MIMETYPE);
}


void
PrefHandler::SaveAsText(const char *path, const char *mimetype,
	const char *signature)
{
	// make sure the target path exists
#if 0
	BPath directoryPath(path);
	if (directoryPath.GetParent(&directoryPath) == B_OK)
		create_directory(directoryPath.Path(), 0755);
#endif

	BFile file(path, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
	char buffer[512];
	type_code type;
	const char *key;

	for (int32 i = 0;
#ifdef B_BEOS_VERSION_DANO
			fContainer.GetInfo(B_STRING_TYPE, i, &key, &type) == B_OK;
#else
			fContainer.GetInfo(B_STRING_TYPE, i, (char**)&key, &type) == B_OK;
#endif
			i++) {
		int len = snprintf(buffer, sizeof(buffer), "\"%s\" , \"%s\"\n",
				key, getString(key));
		file.Write(buffer, len);
	}

	if (mimetype != NULL) {
		BNodeInfo info(&file);
		info.SetType(mimetype);
		info.SetPreferredApp(signature);
	}
}


static int
SortByName(const color_scheme *lhs, const color_scheme *rhs)
{
	return strcmp(lhs->name, rhs->name);
}


void
PrefHandler::LoadThemes()
{
	gColorSchemes = new BObjectList<const color_scheme, true>(10);

	BStringList paths;

	if (BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY,
			"Terminal/Themes/", B_FIND_PATH_EXISTING_ONLY, paths) == B_OK)
		paths.DoForEach(PrefHandler::_LoadThemesFromDirectory);

	if (BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY,
			"Terminal/Themes/", B_FIND_PATH_EXISTING_ONLY, paths) == B_OK)
		paths.DoForEach(PrefHandler::_LoadThemesFromDirectory);

	gColorSchemes->SortItems(SortByName);
}


void
PrefHandler::LoadColorScheme(color_scheme* scheme)
{
	scheme->text_fore_color = getRGB(PREF_TEXT_FORE_COLOR);
	scheme->text_back_color = getRGB(PREF_TEXT_BACK_COLOR);
	scheme->select_fore_color = getRGB(PREF_SELECT_FORE_COLOR);
	scheme->select_back_color = getRGB(PREF_SELECT_BACK_COLOR);
	scheme->cursor_fore_color = getRGB(PREF_CURSOR_FORE_COLOR);
	scheme->cursor_back_color = getRGB(PREF_CURSOR_BACK_COLOR);
	scheme->ansi_colors.black = getRGB(PREF_ANSI_BLACK_COLOR);
	scheme->ansi_colors.red = getRGB(PREF_ANSI_RED_COLOR);
	scheme->ansi_colors.green = getRGB(PREF_ANSI_GREEN_COLOR);
	scheme->ansi_colors.yellow = getRGB(PREF_ANSI_YELLOW_COLOR);
	scheme->ansi_colors.blue = getRGB(PREF_ANSI_BLUE_COLOR);
	scheme->ansi_colors.magenta = getRGB(PREF_ANSI_MAGENTA_COLOR);
	scheme->ansi_colors.cyan = getRGB(PREF_ANSI_CYAN_COLOR);
	scheme->ansi_colors.white = getRGB(PREF_ANSI_WHITE_COLOR);
	scheme->ansi_colors_h.black = getRGB(PREF_ANSI_BLACK_HCOLOR);
	scheme->ansi_colors_h.red = getRGB(PREF_ANSI_RED_HCOLOR);
	scheme->ansi_colors_h.green = getRGB(PREF_ANSI_GREEN_HCOLOR);
	scheme->ansi_colors_h.yellow = getRGB(PREF_ANSI_YELLOW_HCOLOR);
	scheme->ansi_colors_h.blue = getRGB(PREF_ANSI_BLUE_HCOLOR);
	scheme->ansi_colors_h.magenta = getRGB(PREF_ANSI_MAGENTA_HCOLOR);
	scheme->ansi_colors_h.cyan = getRGB(PREF_ANSI_CYAN_HCOLOR);
	scheme->ansi_colors_h.white = getRGB(PREF_ANSI_WHITE_HCOLOR);
}


int32
PrefHandler::getInt32(const char *key)
{
	const char *value = fContainer.FindString(key);
	if (value == NULL)
		return 0;

	return atoi(value);
}


float
PrefHandler::getFloat(const char *key)
{
	const char *value = fContainer.FindString(key);
	if (value == NULL)
		return 0;

	return atof(value);
}


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Terminal getString"

const char*
PrefHandler::getString(const char *key)
{
	const char *buffer;
	if (fContainer.FindString(key, &buffer) != B_OK)
		buffer = B_TRANSLATE("Error!");

	//printf("%x GET %s: %s\n", this, key, buf);
	return buffer;
}


bool
PrefHandler::getBool(const char *key)
{
	const char *value = fContainer.FindString(key);
	if (value == NULL)
		return false;

	return strcmp(value, PREF_TRUE) == 0;
}


int
PrefHandler::getCursor(const char *key)
{
	const char *value = fContainer.FindString(key);
	if (value != NULL && strcmp(value, PREF_BLOCK_CURSOR) != 0) {
		if (strcmp(value, PREF_UNDERLINE_CURSOR) == 0)
			return UNDERLINE_CURSOR;
		if (strcmp(value, PREF_IBEAM_CURSOR) == 0)
			return IBEAM_CURSOR;
	}
	return BLOCK_CURSOR;
}


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Terminal getRGB"

/** Returns RGB data from given key. */

rgb_color
PrefHandler::getRGB(const char *key)
{
	rgb_color col;
	int r, g, b;

	if (const char *s = fContainer.FindString(key)) {
		sscanf(s, "%d, %d, %d", &r, &g, &b);
	} else {
		fprintf(stderr,
			"PrefHandler::getRGB(%s) - key not found\n", key);
		r = g = b = 0;
	}

	col.red = r;
	col.green = g;
	col.blue = b;
	col.alpha = 255;
	return col;
}


/** Setting Int32 data with key. */

void
PrefHandler::setInt32(const char *key, int32 data)
{
	char buffer[32];
	snprintf(buffer, sizeof(buffer), "%d", (int)data);
	setString(key, buffer);
}


/** Setting Float data with key */

void
PrefHandler::setFloat(const char *key, float data)
{
	char buffer[32];
	snprintf(buffer, sizeof(buffer), "%g", data);
	setString(key, buffer);
}


/** Setting Bool data with key */

void
PrefHandler::setBool(const char *key, bool data)
{
	if (data)
		setString(key, PREF_TRUE);
	else
		setString(key, PREF_FALSE);
}


/** Setting CString data with key */

void
PrefHandler::setString(const char *key, const char *data)
{
	//printf("%x SET %s: %s\n", this, key, data);
	fContainer.RemoveName(key);
	fContainer.AddString(key, data);
}


/** Setting RGB data with key */

void
PrefHandler::setRGB(const char *key, const rgb_color data)
{
	char buffer[32];
	snprintf(buffer, sizeof(buffer), "%d, %d, %d", data.red, data.green, data.blue);
	setString(key, buffer);
}


/** Check any peference stored or not. */

bool
PrefHandler::IsEmpty() const
{
	return fContainer.IsEmpty();
}


void
PrefHandler::_ConfirmFont(const BFont *fallbackFont)
{
	font_family family;
	font_style style;

	const char *prefFamily = getString(PREF_HALF_FONT_FAMILY);
	int32 familiesCount = (prefFamily != NULL) ? count_font_families() : 0;

	for (int32 i = 0; i < familiesCount; i++) {
		if (get_font_family(i, &family) != B_OK
			|| strcmp(family, prefFamily) != 0)
			continue;

		const char *prefStyle = getString(PREF_HALF_FONT_STYLE);
		int32 stylesCount = (prefStyle != NULL) ? count_font_styles(family) : 0;

		for (int32 j = 0; j < stylesCount; j++) {
			// check style if we can safely use this font
			if (get_font_style(family, j, &style) == B_OK
				&& strcmp(style, prefStyle) == 0)
				return;
		}
	}

	// use fall-back font
	fallbackFont->GetFamilyAndStyle(&family, &style);
	setString(PREF_HALF_FONT_FAMILY, family);
	setString(PREF_HALF_FONT_STYLE, style);
	setInt32(PREF_HALF_FONT_SIZE, fallbackFont->Size());
}


status_t
PrefHandler::_LoadFromDefault(const pref_defaults* defaults)
{
	if (defaults == NULL)
		return B_ERROR;

	while (defaults->key) {
		setString(defaults->key, defaults->item);
		++defaults;
	}

	return B_OK;
}


/**	Text is "key","Content"
 *	Comment : Start with '#'
 */

status_t
PrefHandler::_LoadFromTextFile(const char * path)
{
	char buffer[1024];
	char key[B_FIELD_NAME_LENGTH], data[512];
	int n;
	FILE *file;

	file = fopen(path, "r");
	if (file == NULL)
		return B_ENTRY_NOT_FOUND;

	while (fgets(buffer, sizeof(buffer), file) != NULL) {
		if (*buffer == '#')
			continue;

		n = sscanf(buffer, "%*[\"]%[^\"]%*[\"]%*[^\"]%*[\"]%[^\"]", key, data);
		if (n == 2)
			setString(key, data);
	}

	fclose(file);
	return B_OK;
}


bool
PrefHandler::_LoadThemesFromDirectory(const BString &directory)
{
	BDirectory *themes = new BDirectory(directory.String());
	if (themes == NULL)
		return false;

	BEntry entry;
	BPath path;
	FindColorSchemeByName comparator;
	while (themes->GetNextEntry(&entry) == B_OK)
	{
		if (entry.GetPath(&path) != B_OK)
			continue;

		PrefHandler *themeHandler = new PrefHandler(false);
		ObjectDeleter<PrefHandler> themeHandlerDeleter(themeHandler);
		themeHandler->_LoadFromTextFile(path.Path());

		const char *name = themeHandler->fContainer.GetString(PREF_THEME_NAME, NULL);

		if (name == NULL || strlen(name) == 0)
			continue;

		comparator.scheme_name = name;

		const color_scheme *scheme = gColorSchemes->FindIf(comparator);

		if (scheme != NULL) {
			// Scheme with this name exists, replace with this version instead
			gColorSchemes->RemoveItem(scheme);
		}

		color_scheme *newScheme = new color_scheme();
		newScheme->name = strdup(name);
		themeHandler->LoadColorScheme(newScheme);
		gColorSchemes->AddItem(newScheme);
	}

	return false;
}