- 滑块最小值时不再有超出圆钮的填充残留,三处滑块统一为共享实现 - 透明度/RGB/色号支持点击或双击直接输入,整段高亮、首次键入替换、回车提交、Ctrl+C 复制 - 双击信号行或分组名进入重命名编辑;分组竖线改为绘制而非字符 - 拖拽信号时曲线虚影跟随鼠标纵向位移 - 数据层新增展示名覆盖,面板改为持有可写数据集合
415 lines
8.0 KiB
C++
415 lines
8.0 KiB
C++
#include "Utf8Util.hpp"
|
|
|
|
#include <windows.h>
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
namespace jdi
|
|
{
|
|
|
|
namespace
|
|
{
|
|
/** @brief 判断字符是否为需要裁掉的空白字符。 */
|
|
bool IsSpaceChar(wchar_t c)
|
|
{
|
|
return c == L' ' || c == L'\t' || c == L'\r' || c == L'\n' || c == L'\v' || c == L'\f' ||
|
|
c == static_cast<wchar_t>(0x00A0);
|
|
}
|
|
|
|
/** @brief 判断字符是否为 ASCII 大写字母。 */
|
|
bool IsUpperAscii(wchar_t c)
|
|
{
|
|
return c >= L'A' && c <= L'Z';
|
|
}
|
|
}
|
|
|
|
std::wstring Utf8ToWide(const char* text, int length)
|
|
{
|
|
if (text == nullptr)
|
|
{
|
|
return std::wstring();
|
|
}
|
|
|
|
int byteCount = (length < 0) ? static_cast<int>(std::strlen(text)) : length;
|
|
if (byteCount <= 0)
|
|
{
|
|
return std::wstring();
|
|
}
|
|
|
|
int wideCount = MultiByteToWideChar(CP_UTF8, 0, text, byteCount, nullptr, 0);
|
|
if (wideCount <= 0)
|
|
{
|
|
return std::wstring();
|
|
}
|
|
|
|
std::wstring result(static_cast<size_t>(wideCount), L'\0');
|
|
MultiByteToWideChar(CP_UTF8, 0, text, byteCount, &result[0], wideCount);
|
|
return result;
|
|
}
|
|
|
|
std::string WideToUtf8(const wchar_t* text)
|
|
{
|
|
if (text == nullptr)
|
|
{
|
|
return std::string();
|
|
}
|
|
|
|
int wideCount = static_cast<int>(std::wcslen(text));
|
|
if (wideCount <= 0)
|
|
{
|
|
return std::string();
|
|
}
|
|
|
|
int byteCount = WideCharToMultiByte(CP_UTF8, 0, text, wideCount, nullptr, 0, nullptr, nullptr);
|
|
if (byteCount <= 0)
|
|
{
|
|
return std::string();
|
|
}
|
|
|
|
std::string result(static_cast<size_t>(byteCount), '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, text, wideCount, &result[0], byteCount, nullptr, nullptr);
|
|
return result;
|
|
}
|
|
|
|
std::wstring Trim(const std::wstring& text)
|
|
{
|
|
size_t begin = 0;
|
|
size_t end = text.size();
|
|
while (begin < end && IsSpaceChar(text[begin]))
|
|
{
|
|
++begin;
|
|
}
|
|
while (end > begin && IsSpaceChar(text[end - 1]))
|
|
{
|
|
--end;
|
|
}
|
|
return text.substr(begin, end - begin);
|
|
}
|
|
|
|
std::wstring StripQuotes(const std::wstring& text)
|
|
{
|
|
std::wstring trimmed = Trim(text);
|
|
if (trimmed.size() >= 2)
|
|
{
|
|
const wchar_t first = trimmed.front();
|
|
const wchar_t last = trimmed.back();
|
|
if ((first == L'"' && last == L'"') || (first == L'\'' && last == L'\''))
|
|
{
|
|
trimmed = trimmed.substr(1, trimmed.size() - 2);
|
|
}
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
std::wstring ToLowerAscii(const std::wstring& text)
|
|
{
|
|
std::wstring result = text;
|
|
for (wchar_t& c : result)
|
|
{
|
|
if (IsUpperAscii(c))
|
|
{
|
|
c = static_cast<wchar_t>(c - L'A' + L'a');
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool StartsWith(const std::wstring& text, const std::wstring& prefix)
|
|
{
|
|
if (prefix.size() > text.size())
|
|
{
|
|
return false;
|
|
}
|
|
return text.compare(0, prefix.size(), prefix) == 0;
|
|
}
|
|
|
|
bool ContainsInsensitive(const std::wstring& text, const std::wstring& needle)
|
|
{
|
|
if (needle.empty())
|
|
{
|
|
return true;
|
|
}
|
|
if (needle.size() > text.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const size_t last = text.size() - needle.size();
|
|
for (size_t i = 0; i <= last; ++i)
|
|
{
|
|
size_t j = 0;
|
|
while (j < needle.size())
|
|
{
|
|
wchar_t a = text[i + j];
|
|
wchar_t b = needle[j];
|
|
if (IsUpperAscii(a))
|
|
{
|
|
a = static_cast<wchar_t>(a - L'A' + L'a');
|
|
}
|
|
if (IsUpperAscii(b))
|
|
{
|
|
b = static_cast<wchar_t>(b - L'A' + L'a');
|
|
}
|
|
if (a != b)
|
|
{
|
|
break;
|
|
}
|
|
++j;
|
|
}
|
|
if (j == needle.size())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TryParseDouble(const char* begin, const char* end, double& value)
|
|
{
|
|
if (begin == nullptr || end == nullptr || begin >= end)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const char* p = begin;
|
|
while (p < end && (*p == ' ' || *p == '\t'))
|
|
{
|
|
++p;
|
|
}
|
|
const char* q = end;
|
|
while (q > p && (q[-1] == ' ' || q[-1] == '\t' || q[-1] == '\r' || q[-1] == '\n'))
|
|
{
|
|
--q;
|
|
}
|
|
if (p >= q)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
char buffer[96];
|
|
const size_t length = static_cast<size_t>(q - p);
|
|
const char* text = nullptr;
|
|
std::string dynamic;
|
|
if (length < sizeof(buffer))
|
|
{
|
|
std::memcpy(buffer, p, length);
|
|
buffer[length] = '\0';
|
|
text = buffer;
|
|
}
|
|
else
|
|
{
|
|
dynamic.assign(p, q);
|
|
text = dynamic.c_str();
|
|
}
|
|
|
|
char* stop = nullptr;
|
|
const double parsed = std::strtod(text, &stop);
|
|
if (stop == text)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
value = parsed;
|
|
return true;
|
|
}
|
|
|
|
std::wstring FormatNumber(double value, int decimals)
|
|
{
|
|
if (decimals < 0)
|
|
{
|
|
decimals = 0;
|
|
}
|
|
if (decimals > 12)
|
|
{
|
|
decimals = 12;
|
|
}
|
|
|
|
wchar_t buffer[96];
|
|
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.*f", decimals, value);
|
|
return std::wstring(buffer);
|
|
}
|
|
|
|
std::wstring FormatValue(double value)
|
|
{
|
|
if (std::isnan(value))
|
|
{
|
|
return L"NaN";
|
|
}
|
|
if (std::isinf(value))
|
|
{
|
|
return (value > 0.0) ? L"+Inf" : L"-Inf";
|
|
}
|
|
|
|
wchar_t buffer[96];
|
|
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.6g", value);
|
|
return std::wstring(buffer);
|
|
}
|
|
|
|
std::wstring FormatTick(double value, double step)
|
|
{
|
|
if (value == 0.0)
|
|
{
|
|
return L"0";
|
|
}
|
|
if (!std::isfinite(value))
|
|
{
|
|
return FormatValue(value);
|
|
}
|
|
|
|
int decimals = 0;
|
|
if (step > 0.0 && std::isfinite(step))
|
|
{
|
|
const double magnitude = std::ceil(-std::log10(step) - 1.0e-9);
|
|
if (magnitude > 0.0)
|
|
{
|
|
decimals = static_cast<int>(magnitude);
|
|
}
|
|
}
|
|
if (decimals > 9)
|
|
{
|
|
decimals = 9;
|
|
}
|
|
|
|
wchar_t buffer[96];
|
|
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.*f", decimals, value);
|
|
std::wstring text(buffer);
|
|
if (text.size() > 15)
|
|
{
|
|
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.4e", value);
|
|
text.assign(buffer);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
std::wstring FormatCount(unsigned long long value)
|
|
{
|
|
wchar_t buffer[32];
|
|
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%llu", value);
|
|
|
|
std::wstring digits(buffer);
|
|
std::wstring result;
|
|
result.reserve(digits.size() + digits.size() / 3);
|
|
|
|
const size_t length = digits.size();
|
|
for (size_t i = 0; i < length; ++i)
|
|
{
|
|
if (i > 0 && ((length - i) % 3u) == 0u)
|
|
{
|
|
result.push_back(L',');
|
|
}
|
|
result.push_back(digits[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double NiceStep(double range, int targetTicks)
|
|
{
|
|
if (!(range > 0.0) || !std::isfinite(range) || targetTicks <= 0)
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
const double raw = range / static_cast<double>(targetTicks);
|
|
const double magnitude = std::pow(10.0, std::floor(std::log10(raw)));
|
|
const double normalized = raw / magnitude;
|
|
|
|
double nice = 10.0;
|
|
if (normalized <= 1.0)
|
|
{
|
|
nice = 1.0;
|
|
}
|
|
else if (normalized <= 2.0)
|
|
{
|
|
nice = 2.0;
|
|
}
|
|
else if (normalized <= 5.0)
|
|
{
|
|
nice = 5.0;
|
|
}
|
|
|
|
return nice * magnitude;
|
|
}
|
|
|
|
void BuildTicks(double minValue, double maxValue, double step, std::vector<double>& ticks)
|
|
{
|
|
ticks.clear();
|
|
if (!std::isfinite(minValue) || !std::isfinite(maxValue) || !(step > 0.0) || !std::isfinite(step))
|
|
{
|
|
return;
|
|
}
|
|
if (maxValue < minValue)
|
|
{
|
|
const double temp = minValue;
|
|
minValue = maxValue;
|
|
maxValue = temp;
|
|
}
|
|
|
|
const long long first = static_cast<long long>(std::ceil(minValue / step - 1.0e-9));
|
|
const long long last = static_cast<long long>(std::floor(maxValue / step + 1.0e-9));
|
|
if (last < first)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const long long count = last - first + 1;
|
|
if (count > 512)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ticks.reserve(static_cast<size_t>(count));
|
|
for (long long index = first; index <= last; ++index)
|
|
{
|
|
ticks.push_back(static_cast<double>(index) * step);
|
|
}
|
|
}
|
|
|
|
bool CopyTextToClipboard(HWND owner, const std::wstring& text)
|
|
{
|
|
if (text.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (OpenClipboard(owner) == FALSE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool success = false;
|
|
if (EmptyClipboard() != FALSE)
|
|
{
|
|
const SIZE_T bytes = (text.size() + 1) * sizeof(wchar_t);
|
|
HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, bytes);
|
|
if (handle != nullptr)
|
|
{
|
|
void* target = GlobalLock(handle);
|
|
if (target != nullptr)
|
|
{
|
|
std::memcpy(target, text.c_str(), bytes);
|
|
GlobalUnlock(handle);
|
|
if (SetClipboardData(CF_UNICODETEXT, handle) != nullptr)
|
|
{
|
|
success = true;
|
|
}
|
|
else
|
|
{
|
|
GlobalFree(handle);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GlobalFree(handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
CloseClipboard();
|
|
return success;
|
|
}
|
|
|
|
} // namespace jdi
|