⛏️ index : haiku.git

/*
 * Copyright 2002-2025 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		DarkWyrm, darkwyrm@earthlink.net
 *		Rene Gollent, rene@gollent.com
 *		John Scipione, jscipione@gmail.com
 *		Joseph Groover <looncraz@looncraz.net>
 */



#include <stdio.h>

#include <Alert.h>
#include <Catalog.h>
#include <ColorItem.h>
#include <ColorListView.h>
#include <DefaultColors.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <HSL.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <Messenger.h>
#include <Path.h>
#include <SpaceLayoutItem.h>

#include "AppearanceWindow.h"
#include "Colors.h"
#include "ColorsView.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Colors tab"


#define COLOR_DROPPED 'cldp'
#define AUTO_ADJUST_CHANGED 'madj'
#define UPDATE_COLOR 'upcl'
#define ATTRIBUTE_CHOSEN 'atch'

using BPrivate::BColorItem;
using BPrivate::BColorListView;
using BPrivate::BColorPreview;


ColorsView::ColorsView(const char* name)
	:
	BView(name, B_WILL_DRAW)
{
	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

	LoadSettings();

	fAutoSelectCheckBox = new BCheckBox(B_TRANSLATE("Automatically pick secondary colors"),
		new BMessage(AUTO_ADJUST_CHANGED));
	fAutoSelectCheckBox->SetValue(true);

	// Set up list of color attributes
	fAttrList = new BColorListView("AttributeList");

	fScrollView = new BScrollView("ScrollView", fAttrList, 0, false, true);
	fScrollView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

	_CreateItems();

	fColorPreview = new BColorPreview("color preview", "", new BMessage(COLOR_DROPPED));

	fPicker = new BColorControl(B_ORIGIN, B_CELLS_32x8, 8.0,
		"picker", new BMessage(UPDATE_COLOR));

	BLayoutBuilder::Group<>(this, B_VERTICAL)
		.Add(fAutoSelectCheckBox)
		.Add(fScrollView, 10.0)
		.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
			.Add(fColorPreview)
			.AddGlue()
			.Add(fPicker)
			.End()
		.SetInsets(B_USE_WINDOW_SPACING);

	fColorPreview->Parent()->SetExplicitMaxSize(BSize(B_SIZE_UNSET, fPicker->Bounds().Height()));
	fAttrList->SetSelectionMessage(new BMessage(ATTRIBUTE_CHOSEN));
}


ColorsView::~ColorsView()
{
}


void
ColorsView::AttachedToWindow()
{
	fAutoSelectCheckBox->SetTarget(this);
	fPicker->SetTarget(this);
	fAttrList->SetTarget(this);
	fColorPreview->SetTarget(this);

	fAttrList->Select(0);
	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
}


void
ColorsView::MessageReceived(BMessage* message)
{
	if (message->WasDropped()) {
		char* name;
		type_code type;
		rgb_color* color;
		ssize_t size;
		if (message->GetInfo(B_RGB_COLOR_TYPE, 0, &name, &type) == B_OK
			&& message->FindData(name, type, (const void**)&color, &size) == B_OK) {
			BPoint dropLoc = message->DropPoint();
			if (fAttrList->Bounds().Contains(fAttrList->ConvertFromScreen(dropLoc))) {
				// dropped on color list view
				int32 index = fAttrList->IndexOf(fAttrList->ConvertFromScreen(dropLoc));
				bool selected = index == fAttrList->CurrentSelection();
				if (index < 0 || index >= fAttrList->CountItems() || selected)
					_SetCurrentColor(*color);
				else
					_SetColor(index, *color);

				Window()->PostMessage(kMsgUpdate);
			} else if (fColorPreview->Bounds().Contains(fColorPreview->ConvertFromScreen(dropLoc))
				|| fPicker->Bounds().Contains(fPicker->ConvertFromScreen(dropLoc))) {
				// dropped on color preview or color control
				_SetCurrentColor(*color);
				Window()->PostMessage(kMsgUpdate);
			}
		}
	}

	switch (message->what) {
		case UPDATE_COLOR:
		{
			// Received from the color fPicker when its color changes

			rgb_color color = fPicker->ValueAsColor();
			_SetCurrentColor(color);
			Window()->PostMessage(kMsgUpdate);
			break;
		}

		case ATTRIBUTE_CHOSEN:
		{
			// Received when the user chooses a GUI fAttribute from the list

			int32 index = fAttrList->CurrentSelection();
			if (index < 0 || index >= fAttrList->CountItems())
				break;

			BColorItem* item = dynamic_cast<BColorItem*>(fAttrList->ItemAt(index));
			if (item != NULL) {
				fWhich = item->ColorWhich();
				rgb_color color = ui_color(fWhich);
				_SetCurrentColor(color);
			}
			break;
		}

		case AUTO_ADJUST_CHANGED:
		{
			_CreateItems();
			break;
		}

		default:
			BView::MessageReceived(message);
			break;
	}
}


void
ColorsView::LoadSettings()
{
	get_default_colors(&fDefaultColors);
	get_current_colors(&fCurrentColors);
	fPrevColors = fCurrentColors;
}


void
ColorsView::SetDefaults()
{
	_SetUIColors(fDefaultColors);
	_UpdatePreviews(fDefaultColors);

	// Use a default color that stands out to show errors clearly
	rgb_color color = fDefaultColors.GetColor(ui_color_name(fWhich),
		make_color(255, 0, 255));

	fPicker->SetValue(color);
	fColorPreview->SetColor(color);
	fColorPreview->Invalidate();

	Window()->PostMessage(kMsgUpdate);
}


void
ColorsView::Revert()
{
	_SetUIColors(fPrevColors);
	_UpdatePreviews(fPrevColors);

	rgb_color color = fPrevColors.GetColor(ui_color_name(fWhich), make_color(255, 0, 255));
	fPicker->SetValue(color);
	fColorPreview->SetColor(color);
	fColorPreview->Invalidate();

	Window()->PostMessage(kMsgUpdate);
}


bool
ColorsView::IsDefaultable()
{
	return !fDefaultColors.HasSameData(fCurrentColors);
}


bool
ColorsView::IsRevertable()
{
	return !fPrevColors.HasSameData(fCurrentColors);
}


void
ColorsView::_CreateItems()
{
	while (fAttrList->CountItems() > 0)
		delete fAttrList->RemoveItem((int32)0);

	const bool autoSelect = fAutoSelectCheckBox->Value();
	const int32 count = color_description_count();
	for (int32 i = 0; i < count; i++) {
		const ColorDescription& description = *get_color_description(i);
		const color_which which = description.which;
		if (autoSelect) {
			if (which != B_PANEL_BACKGROUND_COLOR
					&& which != B_STATUS_BAR_COLOR
					&& which != B_WINDOW_TAB_COLOR) {
				continue;
			}
		}

		const char* text = B_TRANSLATE_NOCOLLECT(description.text);
		fAttrList->AddItem(new BColorItem(text, which, ui_color(which)));
	}

	if (Window() != NULL)
		fAttrList->Select(0);
}


void
ColorsView::_UpdatePreviews(const BMessage& colors)
{
	rgb_color color;
	for (int32 i = color_description_count() - 1; i >= 0; i--) {
		BColorItem* item = static_cast<BColorItem*>(fAttrList->ItemAt(i));
		if (item == NULL)
			continue;

		color = colors.GetColor(ui_color_name(item->ColorWhich()),
			make_color(255, 0, 255));

		item->SetColor(color);
		fAttrList->InvalidateItem(i);
	}
}


void
ColorsView::_SetUIColors(const BMessage& colors)
{
	set_ui_colors(&colors);
	fCurrentColors = colors;
}


void
ColorsView::_SetCurrentColor(rgb_color color)
{
	_SetColor(fAttrList->CurrentSelection(), color);
	fPicker->SetValue(color);
	fColorPreview->SetColor(color);
}


void
ColorsView::_SetColor(int32 index, rgb_color color)
{
	BColorItem* item = dynamic_cast<BColorItem*>(fAttrList->ItemAt(index));
	if (item != NULL) {
		item->SetColor(color);
		fAttrList->InvalidateItem(index);
		_SetColor(item->ColorWhich(), color);
	}
}


void
ColorsView::_SetColor(color_which which, rgb_color color)
{
	fCurrentColors.SetColor(ui_color_name(which), color);

	if (!fAutoSelectCheckBox->Value()) {
		if (ui_color(which) != color)
			set_ui_color(which, color);
		return;
	}

	// Protect against accidentally overwriting colors.
	if (ui_color(which) == color)
		return;

	if (which == B_PANEL_BACKGROUND_COLOR) {
		const bool isDark = color.IsDark();

		fCurrentColors.SetColor(ui_color_name(B_MENU_BACKGROUND_COLOR), color);

		const rgb_color menuSelectedBackground
			= tint_color(color, isDark ? 0.8 /* lighten "< 1" */ : B_DARKEN_2_TINT);
		fCurrentColors.SetColor(ui_color_name(B_MENU_SELECTED_BACKGROUND_COLOR),
			menuSelectedBackground);

		const rgb_color controlBackground = tint_color(color,
			0.84 /* lighten "< 1" */);
		fCurrentColors.SetColor(ui_color_name(B_CONTROL_BACKGROUND_COLOR), controlBackground);
		fCurrentColors.SetColor(ui_color_name(B_SCROLL_BAR_THUMB_COLOR), controlBackground);

		const rgb_color controlBorder
			= tint_color(color, isDark ? 0.4875 : 1.20 /* lighten/darken "1.5" */);
		fCurrentColors.SetColor(ui_color_name(B_CONTROL_BORDER_COLOR), controlBorder);

		const rgb_color windowBorder = tint_color(color, 0.75);
		fCurrentColors.SetColor(ui_color_name(B_WINDOW_BORDER_COLOR), windowBorder);

		const rgb_color inactiveWindowBorder = tint_color(color, B_LIGHTEN_1_TINT);
		fCurrentColors.SetColor(ui_color_name(B_WINDOW_INACTIVE_TAB_COLOR),
			inactiveWindowBorder);
		fCurrentColors.SetColor(ui_color_name(B_WINDOW_INACTIVE_BORDER_COLOR),
			inactiveWindowBorder);

		const rgb_color listSelectedBackground
			= tint_color(color, isDark ? 0.77 : 1.12 /* lighten/darken "< 1" */ );
		fCurrentColors.SetColor(ui_color_name(B_LIST_SELECTED_BACKGROUND_COLOR),
			listSelectedBackground);

		const color_which fromDefaults[] = {
			B_MENU_ITEM_TEXT_COLOR,
			B_MENU_SELECTED_ITEM_TEXT_COLOR,
			B_MENU_SELECTED_BORDER_COLOR,
			B_PANEL_TEXT_COLOR,
			B_DOCUMENT_BACKGROUND_COLOR,
			B_DOCUMENT_TEXT_COLOR,
			B_CONTROL_TEXT_COLOR,
			B_NAVIGATION_PULSE_COLOR,
			B_WINDOW_INACTIVE_TEXT_COLOR,
			B_LIST_BACKGROUND_COLOR,
			B_LIST_ITEM_TEXT_COLOR,
			B_LIST_SELECTED_ITEM_TEXT_COLOR,

			B_SHINE_COLOR,
			B_SHADOW_COLOR,

			B_LINK_TEXT_COLOR,
			B_LINK_HOVER_COLOR,
			B_LINK_ACTIVE_COLOR,
			B_LINK_VISITED_COLOR,
		};
		for (size_t i = 0; i < B_COUNT_OF(fromDefaults); i++)
			fCurrentColors.SetColor(ui_color_name(fromDefaults[i])
				, BPrivate::GetSystemColor(fromDefaults[i], isDark));
	} else if (which == B_STATUS_BAR_COLOR) {
		const hsl_color statusColorHSL = hsl_color::from_rgb(color);

		hsl_color controlHighlight = statusColorHSL;
		controlHighlight.saturation = max_c(0.2f, controlHighlight.saturation / 2.f);
		fCurrentColors.SetColor(ui_color_name(B_CONTROL_HIGHLIGHT_COLOR)
			, controlHighlight.to_rgb());

		hsl_color controlMark = statusColorHSL;
		controlMark.saturation = max_c(0.2f, controlMark.saturation * 0.67f);
		controlMark.lightness = max_c(0.25f, controlMark.lightness * 0.55f);
		fCurrentColors.SetColor(ui_color_name(B_CONTROL_MARK_COLOR), controlMark.to_rgb());

		rgb_color keyboardNav; {
			hsl_color keyboardNavHSL = statusColorHSL;
			keyboardNavHSL.lightness = max_c(0.2f, keyboardNavHSL.lightness * 0.75f);
			keyboardNav = keyboardNavHSL.to_rgb();

			// Use primary color channel only.
			if (keyboardNav.blue >= max_c(keyboardNav.red, keyboardNav.green))
				keyboardNav.red = keyboardNav.green = 0;
			else if (keyboardNav.red >= max_c(keyboardNav.green, keyboardNav.blue))
				keyboardNav.green = keyboardNav.blue = 0;
			else
				keyboardNav.red = keyboardNav.blue = 0;
		}
		fCurrentColors.SetColor(ui_color_name(B_KEYBOARD_NAVIGATION_COLOR), keyboardNav);
	} else if (which == B_WINDOW_TAB_COLOR) {
		const bool isDark = color.IsDark();
		const hsl_color tabColorHSL = hsl_color::from_rgb(color);
		const float tabColorSaturation
			= tabColorHSL.saturation != 0 ? tabColorHSL.saturation : tabColorHSL.lightness;

		fCurrentColors.SetColor(ui_color_name(B_WINDOW_TEXT_COLOR),
			BPrivate::GetSystemColor(B_WINDOW_TEXT_COLOR, isDark));
		fCurrentColors.SetColor(ui_color_name(B_TOOL_TIP_TEXT_COLOR),
			BPrivate::GetSystemColor(B_TOOL_TIP_TEXT_COLOR, isDark));

		const rgb_color toolTipBackground = tint_color(color, isDark ? 1.7 : 0.15);
		fCurrentColors.SetColor(ui_color_name(B_TOOL_TIP_BACKGROUND_COLOR), toolTipBackground);

		hsl_color success = hsl_color::from_rgb(BPrivate::GetSystemColor(B_SUCCESS_COLOR, isDark));
		success.saturation = max_c(0.25f, tabColorSaturation * 0.68f);
		fCurrentColors.SetColor(ui_color_name(B_SUCCESS_COLOR), success.to_rgb());

		hsl_color failure = hsl_color::from_rgb(BPrivate::GetSystemColor(B_FAILURE_COLOR, isDark));
		failure.saturation = max_c(0.25f, tabColorSaturation);
		fCurrentColors.SetColor(ui_color_name(B_FAILURE_COLOR), failure.to_rgb());
	}
	set_ui_colors(&fCurrentColors);
}