Browse Source

Merge pull request #839 from dcog989/improve-color-display-logic

Improve color print logic
sabrogden 8 months ago
parent
commit
d77df1611c
1 changed files with 318 additions and 153 deletions
  1. 318 153
      src/QListCtrl.cpp

+ 318 - 153
src/QListCtrl.cpp

@@ -12,6 +12,10 @@
 #include "DrawHTML.h"
 #include "..\Shared\TextConvert.h"
 #include <cmath>
+#include <vector>
+#include <string>
+#include <cwchar>   // For swscanf
+#include <algorithm> // For std::round
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
@@ -494,215 +498,376 @@ void CQListCtrl::OnCustomdrawList(NMHDR* pNMHDR, LRESULT* pResult)
 	}
 }
 
+
+// Helper function implementation to check for valid hex characters
+bool CQListCtrl::IsHexString(const CString& str)
+{
+	if (str.IsEmpty()) {
+		return false;
+	}
+	for (int i = 0; i < str.GetLength(); ++i) {
+		if (!iswxdigit(str[i])) {
+			return false;
+		}
+	}
+	return true;
+}
+
+// Helper function implementation for HSL to RGB conversion
+COLORREF CQListCtrl::HslToRgb(double h, double s, double l)
+{
+	// Input h(0-360), s(0-1), l(0-1)
+	// Output COLORREF (RGB)
+
+	double r, g, b;
+
+	if (s == 0)
+	{
+		r = g = b = l; // Achromatic (gray)
+	}
+	else
+	{
+		auto hue2rgb = [&](double p, double q, double t)
+			{
+				if (t < 0) t += 1;
+				if (t > 1) t -= 1;
+				if (t < 1.0 / 6.0) return p + (q - p) * 6 * t;
+				if (t < 1.0 / 2.0) return q;
+				if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6;
+				return p;
+			};
+
+		// Normalize h to 0-1 range
+		double h_norm = h / 360.0;
+
+		double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+		double p = 2 * l - q;
+		r = hue2rgb(p, q, h_norm + 1.0 / 3.0);
+		g = hue2rgb(p, q, h_norm);
+		b = hue2rgb(p, q, h_norm - 1.0 / 3.0);
+	}
+
+	// Scale RGB values to 0-255 and round
+	int R = static_cast<int>(std::round(r * 255));
+	int G = static_cast<int>(std::round(g * 255));
+	int B = static_cast<int>(std::round(b * 255));
+
+	// Clamp values to 0-255 just in case of floating point inaccuracies
+	R = max(0, min(255, R));
+	G = max(0, min(255, G));
+	B = max(0, min(255, B));
+
+	return RGB(R, G, B);
+}
+
+
 void CQListCtrl::DrawCopiedColorCode(CString& csText, CRect& rcText, CDC* pDC)
 {
-	if (CGetSetOptions::m_bDrawCopiedColorCode == FALSE)
+	if (CGetSetOptions::m_bDrawCopiedColorCode == FALSE || csText.IsEmpty())
 		return;
 
-	CString trimmedText = CString(csText).Trim(_T("»")).Trim().Trim(_T("#"));
-	trimmedText = trimmedText.Trim(';');
-	trimmedText = trimmedText.MakeLower();
+	// 1. Initial Cleaning (Remove specific surrounding chars and leading/trailing whitespace)
+	CString cleanedText = csText;
+	cleanedText.Trim(_T("»")); // Remove potential ditto mark first
+	cleanedText.Trim();        // Trim leading/trailing whitespace
+	cleanedText.TrimRight(_T(";")); // Remove trailing semicolon
+	cleanedText.Trim();        // Trim again in case semicolon had spaces after it
 
-	// Helper function to draw the color box
+	// Store original case potentially for display, but use lower for parsing
+	CString originalCleanedText = cleanedText;
+	CString parseText = cleanedText;
+	parseText.MakeLower(); // Use lower case for keyword matching ('rgb', 'hsl', '0x')
+
+	// 2. Helper function to draw the color box
 	auto DrawColorBox = [&](COLORREF color)
 		{
 			CRect pastedRect(rcText);
-			pastedRect.right = pastedRect.left + m_windowDpi->Scale(rcText.Height());
+			int boxSize = m_windowDpi->Scale(rcText.Height());
+			// Prevent overly large boxes if text rect somehow becomes huge
+			boxSize = min(boxSize, m_windowDpi->Scale(20)); 
+			pastedRect.right = pastedRect.left + boxSize;
+			pastedRect.bottom = pastedRect.top + boxSize; // Make it square
+
 			pDC->FillSolidRect(pastedRect, color);
-			rcText.left += m_windowDpi->Scale(rcText.Height());
+
+			// Adjust text rect to be after the color box + padding
+			rcText.left += boxSize;
 			rcText.left += m_windowDpi->Scale(ROW_LEFT_BORDER);
+			// Restore original (potentially mixed-case) text for drawing
+			csText = originalCleanedText;
 		};
 
-	// --- Hex Color Parsing ---
-	if (trimmedText.GetLength() >= 3 && trimmedText.GetLength() <= 10 && // Account for 0x prefix
-		(trimmedText.GetLength() == 3 || trimmedText.GetLength() == 4 || trimmedText.GetLength() == 6 || trimmedText.GetLength() == 8 || trimmedText.GetLength() == 10))
-	{
-		int r, g, b, a = 255; // Default alpha to fully opaque
-		int scanRet = 0;
+	// 3. --- Hex Color Parsing ---
+	CString hexString;
+	bool isHex = false;
+	int hexLength = 0;
 
-		// Handle 0xAARRGGBB format
-		if (trimmedText.GetLength() == 10 && trimmedText.Find(_T("0x")) == 0)
+	if (parseText.GetLength() >= 2 && parseText.Left(2) == _T("0x"))
+	{
+		// Format: 0xRRGGBB (8 chars total) or 0xAARRGGBB (10 chars total)
+		hexString = parseText.Mid(2); // Get part after 0x
+		hexLength = hexString.GetLength();
+		if ((hexLength == 6 || hexLength == 8) && IsHexString(hexString))
 		{
-			unsigned int colorValue;
-			scanRet = swscanf(trimmedText, _T("0x%x"), &colorValue);
-			if (scanRet == 1)
-			{
-				a = (colorValue >> 24) & 0xFF;
-				r = (colorValue >> 16) & 0xFF;
-				g = (colorValue >> 8) & 0xFF;
-				b = colorValue & 0xFF;
-
-				DrawColorBox(RGB(r, g, b));
-				return; //exit early
-			}
+			isHex = true;
+			// Keep hexString as is (6 or 8 hex digits)
 		}
-		else
+	}
+	else if (parseText.GetLength() >= 1 && parseText.Left(1) == _T("#"))
+	{
+		// Format: #RGB (4), #RGBA (5), #RRGGBB (7), #RRGGBBAA (9)
+		hexString = parseText.Mid(1); // Get part after #
+		hexLength = hexString.GetLength();
+		if ((hexLength == 3 || hexLength == 4 || hexLength == 6 || hexLength == 8) && IsHexString(hexString))
 		{
-			//expand 3 and 4 value hex to 6 and 8
-			if (trimmedText.GetLength() == 3 || trimmedText.GetLength() == 4)
+			isHex = true;
+			// Expand #RGB to #RRGGBB and #RGBA to #RRGGBBAA if needed
+			if (hexLength == 3) // RGB -> RRGGBB
 			{
-				CString expandedText;
-				for (int i = 0; i < trimmedText.GetLength(); i++)
-				{
-					expandedText += trimmedText[i];
-					expandedText += trimmedText[i];
-				}
-				trimmedText = expandedText;
+				hexString.Format(_T("%c%c%c%c%c%c"), hexString[0], hexString[0], hexString[1], hexString[1], hexString[2], hexString[2]);
+			}
+			else if (hexLength == 4) // RGBA -> RRGGBBAA
+			{
+				hexString.Format(_T("%c%c%c%c%c%c%c%c"), hexString[0], hexString[0], hexString[1], hexString[1], hexString[2], hexString[2], hexString[3], hexString[3]);
 			}
+			// hexString is now 6 or 8 hex digits
+		}
+	}
 
+	if (isHex)
+	{
+		int r = 0, g = 0, b = 0, a = 255; // Default alpha to fully opaque
+		int scanRet = 0;
+		unsigned int hexValue = 0;
 
-			if (trimmedText.GetLength() == 6)
+		// Use swscanf to parse the final 6 or 8 digit hex string
+		scanRet = swscanf(hexString, _T("%x"), &hexValue);
+
+		if (scanRet == 1)
+		{
+			if (hexString.GetLength() == 8) // RRGGBBAA or AARRGGBB (handle both standards, CSS uses RGBA, some contexts use ARGB)
 			{
-				scanRet = swscanf(trimmedText, _T("%02x%02x%02x"), &r, &g, &b);
+				// Assuming RRGGBBAA based on CSS standard (# format expansion)
+				// If parsing 0x format, the original code assumed ARGB. Let's stick to CSS standard RGBA for consistency here.
+				// If you need strict 0xAARRGGBB, add specific check for "0x" prefix here.
+				r = (hexValue >> 24) & 0xFF;
+				g = (hexValue >> 16) & 0xFF;
+				b = (hexValue >> 8) & 0xFF;
+				a = hexValue & 0xFF;
+				// If input was 0x format AND you require 0xAARRGGBB specifically:
+				// if (parseText.Left(2) == _T("0x")) {
+				//     a = (hexValue >> 24) & 0xFF;
+				//     r = (hexValue >> 16) & 0xFF;
+				//     g = (hexValue >> 8) & 0xFF;
+				//     b = hexValue & 0xFF;
+				// }
 			}
-			else if (trimmedText.GetLength() == 8)
+			else // 6 digits (RRGGBB)
 			{
-				scanRet = swscanf(trimmedText, _T("%02x%02x%02x%02x"), &r, &g, &b, &a);
+				r = (hexValue >> 16) & 0xFF;
+				g = (hexValue >> 8) & 0xFF;
+				b = hexValue & 0xFF;
+				a = 255; // No alpha info
 			}
 
-			if (scanRet >= 3)
-			{
-				DrawColorBox(RGB(r, g, b));
-				return; //exit early
+			// Basic validation on parsed values (should be redundant if IsHexString worked)
+			if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 255) {
+				DrawColorBox(RGB(r, g, b)); // Draw using RGB, ignore alpha for the solid box
+				return; // Valid hex format found and drawn
 			}
 		}
 	}
 
-	// --- RGB Color Parsing ---
-	if (trimmedText.Find(_T("rgb")) == 0 || trimmedText.Find(',') != -1)
+
+	// 4. --- RGB Color Parsing ---
+	// Formats: rgb(R, G, B), rgba(R, G, B, A), rgb(R G B), rgb(R G B / A)
+	// R, G, B are 0-255. A is 0.0-1.0
+	// Percentage formats like rgb(100%, 0%, 0%) are NOT handled by this swscanf logic.
+	if (parseText.StartsWith(_T("rgb")) && parseText.EndsWith(_T(")")))
 	{
-		int r, g, b, a = 255; // Default alpha
-		double alpha = 1.0;
-		CString noRGB = trimmedText.Trim(_T("rgb(")).Trim(_T("rgba(")).Trim(')');
+		CString content;
+		bool isRgba = parseText.StartsWith(_T("rgba"));
+		int prefixLen = isRgba ? 5 : 4; // Length of "rgba(" or "rgb("
+		content = parseText.Mid(prefixLen, parseText.GetLength() - prefixLen - 1); // Extract content within ()
+		content.Trim(); // Trim spaces within parentheses
 
-		int scanRet = swscanf(noRGB, _T("%d,%d,%d,%lf"), &r, &g, &b, &alpha);
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noRGB, _T("%d,%d,%d"), &r, &g, &b);
+		int r = -1, g = -1, b = -1;
+		double a_double = 1.0; // Default alpha
+		int scanRet = 0;
+		bool parsed = false;
+
+		// Try parsing comma-separated format first (most common)
+		// Check if it contains commas before trying swscanf
+		if (content.Find(',') != -1) {
+			scanRet = swscanf(content, _T("%d ,%d ,%d , %lf"), &r, &g, &b, &a_double); // rgba(R, G, B, A)
+			if (scanRet >= 3) { // Need at least R, G, B
+				parsed = true;
+				if (isRgba && scanRet != 4) parsed = false; // rgba() must have 4 values
+				if (!isRgba && scanRet == 4) parsed = false; // rgb() must not have 4 values
+			}
 		}
 
-		//space separated values, with optional alpha
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noRGB, _T("%d %d %d / %lf"), &r, &g, &b, &alpha);
+		// Try parsing space-separated format if comma parse failed or wasn't attempted
+		// Check for slash for alpha separation
+		if (!parsed && content.Find('/') != -1) {
+			scanRet = swscanf(content, _T("%d %d %d / %lf"), &r, &g, &b, &a_double); // rgb(R G B / A)
+			if (scanRet == 4) { // Must have exactly R G B / A
+			    // This format is valid for both rgb() and rgba() in modern CSS, check values
+				parsed = true;
+			}
 		}
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noRGB, _T("%d %d %d"), &r, &g, &b);
+		else if (!parsed) { // Try space separated without alpha
+			scanRet = swscanf(content, _T("%d %d %d"), &r, &g, &b); // rgb(R G B)
+			if (scanRet == 3) { // Must have exactly R G B
+				// Check for extraneous characters after the numbers
+				CString check_str;
+				check_str.Format(_T("%d %d %d"), r, g, b);
+				if (content == check_str) // Ensure the whole string was consumed
+				{
+					parsed = true;
+					a_double = 1.0; // Set default alpha
+				}
+			}
 		}
 
-		if (scanRet >= 3)
+		if (parsed)
 		{
-			if (scanRet == 4) // If alpha was parsed (either comma or slash format)
+			// Validate ranges: R, G, B (0-255), A (0.0-1.0)
+			if (r >= 0 && r <= 255 &&
+				g >= 0 && g <= 255 &&
+				b >= 0 && b <= 255 &&
+				a_double >= 0.0 && a_double <= 1.0)
 			{
-				a = static_cast<int>(alpha * 255);
+				DrawColorBox(RGB(r, g, b)); // Draw using RGB part
+				return; // Valid RGB/RGBA format found and drawn
 			}
-			DrawColorBox(RGB(r, g, b));
-			return;
 		}
 	}
 
-	// --- HSL Color Parsing ---
-	if (trimmedText.Find(_T("hsl")) == 0)
-	{
-		int h;
-		double s, l, alpha = 1.0; //default to 1.0
-		int scanRet = 0;
-
-		CString noHSL = trimmedText.Trim(_T("hsl(")).Trim(_T("hsla(")).Trim(')');
 
-		//parse with comma separation
-		scanRet = swscanf(noHSL, _T("%d,%lf%%,%lf%%,%lf"), &h, &s, &l, &alpha);
+	// 5. --- HSL Color Parsing ---
+	// Formats: hsl(H, S%, L%), hsla(H, S%, L%, A), hsl(H S% L%), hsl(H S% L% / A)
+	// Also supports 'deg' suffix for Hue.
+	// H is 0-360, S/L are 0-100%, A is 0.0-1.0
+	if (parseText.StartsWith(_T("hsl")) && parseText.EndsWith(_T(")")))
+	{
+		CString content;
+		bool isHsla = parseText.StartsWith(_T("hsla"));
+		int prefixLen = isHsla ? 5 : 4; // Length of "hsla(" or "hsl("
+		content = parseText.Mid(prefixLen, parseText.GetLength() - prefixLen - 1); // Extract content within ()
+		content.Trim(); // Trim spaces within parentheses
 
-		//parse with comma and without percent signs
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noHSL, _T("%d,%lf,%lf,%lf"), &h, &s, &l, &alpha);
-		}
+		int h = -1;
+		double s = -1.0, l = -1.0, a_double = 1.0; // Default alpha
+		int scanRet = 0;
+		bool parsed = false;
+		wchar_t degSuffix[5] = { 0 }; // To capture 'deg' if present
+		wchar_t sPercent = 0, lPercent = 0; // To capture '%' signs
+
+		// --- Try parsing comma-separated formats ---
+		if (!parsed && content.Find(',') != -1) {
+			// hsla(Hdeg, S%, L%, A)
+			scanRet = swscanf(content, _T("%ddeg , %lf %% , %lf %% , %lf"), &h, &s, &l, &a_double);
+			if (scanRet >= 3) { // H, S, L required
+				parsed = true;
+				if (isHsla && scanRet != 4) parsed = false; // hsla requires 4
+				if (!isHsla && scanRet == 4) parsed = false; // hsl requires 3
+			}
 
-		//parse with deg, percent and optional alpha
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noHSL, _T("%ddeg %lf%% %lf%% / %lf"), &h, &s, &l, &alpha);
-		}
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noHSL, _T("%ddeg, %lf%%, %lf%%, %lf"), &h, &s, &l, &alpha);
-		}
-		if (scanRet < 3) //without alpha
-		{
-			scanRet = swscanf(noHSL, _T("%ddeg %lf%% %lf%%"), &h, &s, &l);
-		}
-		//parse with deg and without percent signs and optional alpha
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noHSL, _T("%ddeg %lf %lf / %lf"), &h, &s, &l, &alpha);
-		}
-		if (scanRet < 3) //without alpha
-		{
-			scanRet = swscanf(noHSL, _T("%ddeg %lf %lf"), &h, &s, &l);
-		}
-		//parse space separated values, optional alpha
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noHSL, _T("%d %lf %lf / %lf"), &h, &s, &l, &alpha);
-		}
-		if (scanRet < 3)
-		{
-			scanRet = swscanf(noHSL, _T("%d %lf %lf"), &h, &s, &l);
+			// hsla(H, S%, L%, A) - No 'deg'
+			if (!parsed) {
+				scanRet = swscanf(content, _T("%d , %lf %% , %lf %% , %lf"), &h, &s, &l, &a_double);
+				if (scanRet >= 3) {
+					parsed = true;
+					if (isHsla && scanRet != 4) parsed = false;
+					if (!isHsla && scanRet == 4) parsed = false;
+				}
+			}
 		}
 
-		if (scanRet >= 3)
-		{
-			// Normalize HSL values
-			h = h % 360; // Hue: 0-360
-			if (h < 0)
-			{
-				h += 360;
+		// --- Try parsing space-separated formats ---
+		if (!parsed && content.Find(',') == -1) { // Only if no commas found
+			// hsl(Hdeg S% L% / A)
+			scanRet = swscanf(content, _T("%ddeg %lf %% %lf %% / %lf"), &h, &s, &l, &a_double);
+			if (scanRet == 4) { // Must match exactly 4 components
+				parsed = true;
 			}
-			s = s / 100.0; // Saturation: 0.0 - 1.0   (convert percentage)
-			if (s > 1.0)
-			{
-				s = 1.0;
-			}
-			if (s < 0.0)
-			{
-				s = 0.0;
+
+			// hsl(H S% L% / A) - No 'deg'
+			if (!parsed) {
+				scanRet = swscanf(content, _T("%d %lf %% %lf %% / %lf"), &h, &s, &l, &a_double);
+				if (scanRet == 4) {
+					parsed = true;
+				}
 			}
 
-			l = l / 100.0; // Lightness: 0.0 - 1.0  (convert percentage)
-			if (l > 1.0) l = 1.0;
-			if (l < 0.0) l = 0.0;
+			// hsl(Hdeg S% L%) - No alpha
+			if (!parsed) {
+				// Need to ensure the *entire* string is consumed to avoid matching prefix of alpha version
+				// We store the scanned values and reconstruct to compare
+				scanRet = swscanf(content, _T("%ddeg %lf %% %lf %%"), &h, &s, &l);
+				if (scanRet == 3) {
+					CString check_str;
+					// Format considering potential floating point variations slightly
+					// However, simple comparison might be sufficient if input is clean
+					check_str.Format(_T("%ddeg %.0f%% %.0f%%"), h, s, l);
+					// Crude check - might fail if input has different spacing or precision
+					// A better check involves checking remaining chars after scan, but swscanf makes this hard.
+					// Let's try a simpler length/content check for now.
+					CString temp_content = content;
+					temp_content.Remove(' '); // Remove spaces for comparison
+					CString temp_check = check_str;
+					temp_check.Remove(' ');
+					if (temp_content == temp_check) { // Basic check if format matches structure
+						parsed = true;
+						a_double = 1.0; // Set default alpha
+					}
+				}
+			}
 
-			// HSL to RGB conversion
-			double r, g, b;
-			if (s == 0)
-			{
-				r = g = b = l; // Achromatic (gray)
+			// hsl(H S% L%) - No 'deg', No alpha
+			if (!parsed) {
+				scanRet = swscanf(content, _T("%d %lf %% %lf %%"), &h, &s, &l);
+				if (scanRet == 3) {
+					CString check_str;
+					check_str.Format(_T("%d %.0f%% %.0f%%"), h, s, l);
+					CString temp_content = content;
+					temp_content.Remove(' ');
+					CString temp_check = check_str;
+					temp_check.Remove(' ');
+					if (temp_content == temp_check) {
+						parsed = true;
+						a_double = 1.0;
+					}
+				}
 			}
-			else
+		}
+
+		if (parsed)
+		{
+			// Validate ranges: H (any number, will be normalized), S/L (0-100), A (0.0-1.0)
+			// Note: Hue can be negative or > 360, it will be normalized later.
+			if (s >= 0.0 && s <= 100.0 &&
+				l >= 0.0 && l <= 100.0 &&
+				a_double >= 0.0 && a_double <= 1.0)
 			{
-				auto hue2rgb = [&](double p, double q, double t)
-					{
-						if (t < 0) t += 1;
-						if (t > 1) t -= 1;
-						if (t < 1.0 / 6.0) return p + (q - p) * 6 * t;
-						if (t < 1.0 / 2.0) return q;
-						if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6;
-						return p;
-					};
-
-				double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
-				double p = 2 * l - q;
-				r = hue2rgb(p, q, h / 360.0 + 1.0 / 3.0);
-				g = hue2rgb(p, q, h / 360.0);
-				b = hue2rgb(p, q, h / 360.0 - 1.0 / 3.0);
-			}
+				// Normalize HSL values for conversion
+				h = h % 360; // Hue: Normalize to 0-359
+				if (h < 0) {
+					h += 360;
+				}
+				double s_norm = s / 100.0; // Saturation: 0.0 - 1.0
+				double l_norm = l / 100.0; // Lightness: 0.0 - 1.0
 
-			DrawColorBox(RGB((int)std::round(r * 255), (int)std::round(g * 255), (int)std::round(b * 255)));
-			return;
+				COLORREF rgbColor = HslToRgb(static_cast<double>(h), s_norm, l_norm);
+				DrawColorBox(rgbColor);
+				return; // Valid HSL/HSLA format found and drawn
+			}
 		}
 	}
+
+	// If we reach here, no valid format was detected or parsed correctly.
+	// The original csText remains unchanged, and no color box is drawn.
 }
 
 BOOL CQListCtrl::DrawRtfText(int nItem, CRect& crRect, CDC* pDC)