/* * CDecimalSupport.h * ----------------- * Purpose: Edit field which allows negative and fractional values to be entered * Notes : Alexander Uckun's original code has been modified a bit to suit our purposes. * Authors: OpenMPT Devs * Alexander Uckun * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #pragma once #include "openmpt/all/BuildSettings.hpp" OPENMPT_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////////// /// \class CDecimalSupport /// \brief decimal number support for your control /// \author Alexander Uckun /// \version 1.0 // Copyright (c) 2007 - Alexander Uckun // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. template class CDecimalSupport { protected: /// the locale dependant decimal separator TCHAR m_DecimalSeparator[5]; /// the locale dependant negative sign TCHAR m_NegativeSign[6]; bool m_allowNegative = true, m_allowFractions = true; public: #ifdef BEGIN_MSG_MAP BEGIN_MSG_MAP(CDecimalSupport) ALT_MSG_MAP(8) MESSAGE_HANDLER(WM_CHAR, OnChar) MESSAGE_HANDLER(WM_PASTE, OnPaste) END_MSG_MAP() #endif /// \brief Initialize m_DecimalSeparator and m_NegativeSign /// \remarks calls InitDecimalSeparator and InitNegativeSign CDecimalSupport() { InitDecimalSeparator(); InitNegativeSign(); } /// \brief sets m_DecimalSeparator /// \remarks calls GetLocaleInfo with LOCALE_SDECIMAL to set m_DecimalSeparator /// \param[in] Locale the locale parameter (see GetLocaleInfo) /// \return the number of TCHARs written to the destination buffer int InitDecimalSeparator(LCID Locale = LOCALE_USER_DEFAULT) { return ::GetLocaleInfo(Locale, LOCALE_SDECIMAL, m_DecimalSeparator, sizeof(m_DecimalSeparator) / sizeof(TCHAR)); } /// \brief sets m_NegativeSign /// \remarks calls GetLocaleInfo with LOCALE_SNEGATIVESIGN to set m_NegativeSign /// \param[in] Locale the locale parameter (see GetLocaleInfo) /// \return the number of TCHARs written to the destination buffer int InitNegativeSign(LCID Locale = LOCALE_USER_DEFAULT) { return ::GetLocaleInfo(Locale, LOCALE_SNEGATIVESIGN, m_NegativeSign, sizeof(m_NegativeSign) / sizeof(TCHAR)); } /// callback for the WM_PASTE message /// validates the input /// \param uMsg /// \param wParam /// \param lParam /// \param[out] bHandled true, if the text is a valid number /// \return 0 LRESULT OnPaste(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, bool &bHandled) { bHandled = false; int neg_sign = 0; int dec_point = 0; T* pT = static_cast(this); int nStartChar; int nEndChar; pT->GetSel(nStartChar, nEndChar); TCHAR buffer[limit]; pT->GetWindowText(buffer, limit); // Check if the text already contains a decimal point for (TCHAR* x = buffer; *x; ++x) { if (x - buffer == nStartChar) x += nEndChar - nStartChar; if (*x == m_DecimalSeparator[0] || *x == _T('.')) ++dec_point; if (*x == m_NegativeSign[0] || *x == _T('-')) ++neg_sign; } #ifdef _UNICODE if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return 0; #else if (!IsClipboardFormatAvailable(CF_TEXT)) return 0; #endif if (!OpenClipboard((HWND) *pT)) return 0; #ifdef _UNICODE HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT); #else HGLOBAL hglb = ::GetClipboardData(CF_TEXT); #endif if (hglb != NULL) { TCHAR *lptstr = static_cast(GlobalLock(hglb)); if (lptstr != nullptr) { bHandled = true; for (TCHAR* s = lptstr; *s; ++s) { if ((*s == m_NegativeSign[0] ||*s == _T('-')) && m_allowNegative) { for (TCHAR* t = m_NegativeSign + 1; *t; ++t, ++s) { if (*t != *(s+1)) ++neg_sign; } if (neg_sign || nStartChar > 0) { bHandled = false; break; } ++neg_sign; continue; } if ((*s == m_DecimalSeparator[0] || *s == _T('.')) && m_allowFractions) { for (TCHAR* t = m_DecimalSeparator + 1; *t ; ++t, ++s) { if (*t != *(s+1)) ++dec_point; } if (dec_point) { bHandled = false; break; } ++dec_point; continue; } if (*s == _T('\r')) { // Stop at new line *s = 0; break; } if (*s < _T('0') || *s > _T('9')) { bHandled = false; break; } } if(bHandled) pT->ReplaceSel(lptstr, true); GlobalUnlock(hglb); } } CloseClipboard(); return 0; } /// callback for the WM_CHAR message /// handles the decimal point and the negative sign keys /// \param uMsg /// \param[in] wParam contains the pressed key /// \param lParam /// \param[out] bHandled true, if the key press was handled in this function /// \return 0 LRESULT OnChar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) { bHandled = false; if ((static_cast(wParam) == m_DecimalSeparator[0] || wParam == _T('.')) && m_allowFractions) { T* pT = static_cast(this); int nStartChar; int nEndChar; pT->GetSel(nStartChar, nEndChar); TCHAR buffer[limit]; pT->GetWindowText(buffer, limit); //Verify that the control doesn't already contain a decimal point for (TCHAR* x = buffer; *x; ++x) { if (x - buffer == nStartChar) x += nEndChar - nStartChar; if (*x == m_DecimalSeparator[0]) return 0; } pT->ReplaceSel(m_DecimalSeparator, true); bHandled = true; } if ((static_cast(wParam) == m_NegativeSign[0] || wParam == _T('-')) && m_allowNegative) { T* pT = static_cast(this); int nStartChar; int nEndChar; pT->GetSel(nStartChar, nEndChar); if (nStartChar) return 0; TCHAR buffer[limit]; pT->GetWindowText(buffer, limit); //Verify that the control doesn't already contain a negative sign if (nEndChar == 0 && buffer[0] == m_NegativeSign[0]) return 0; pT->ReplaceSel(m_NegativeSign, true); bHandled = true; } return 0; } /// converts the controls text to double /// \param[out] d the converted value /// \return true on success bool GetDecimalValue(double& d) const { TCHAR szBuff[limit]; static_cast(this)->GetWindowText(szBuff, limit); return TextToDouble(szBuff, d); } /// converts a string to double /// \remarks the decimal separator and the negative sign may change in the string /// \param[in, out] szBuff the string to convert /// \param[out] d the converted value /// \return true on success bool TextToDouble(TCHAR* szBuff, double& d) const { //replace the locale dependant separator with . if (m_DecimalSeparator[0] != _T('.')) { for (TCHAR* x = szBuff; *x; ++x) { if (*x == m_DecimalSeparator[0]) { *x = _T('.'); break; } } } TCHAR* endPtr; //replace the negative sign with - if (szBuff[0] == m_NegativeSign[0]) szBuff[0] = _T('-'); d = _tcstod(szBuff, &endPtr); return *endPtr == _T('\0'); } /// sets a number as the controls text /// \param[in] d the value /// \param[in] count digits after the decimal point void SetFixedValue(double d, int count) { int decimal_pos; int sign; char digits[limit]; _fcvt_s(digits, d, count, &decimal_pos, &sign); return DisplayDecimalValue(digits, decimal_pos, sign); } /// sets a number as the controls text /// \param[in] d the value /// \param[in] count total number of digits void SetDecimalValue(double d, int count) { int decimal_pos; int sign; char digits[limit]; _ecvt_s(digits, d, count, &decimal_pos, &sign); DisplayDecimalValue(digits, decimal_pos, sign); } /// sets a number as the controls text /// \param[in] d the value /// \remarks the total number of digits is calculated using the GetLimitText function void SetDecimalValue(double d) { SetDecimalValue(d, std::min(limit, static_cast(static_cast(this)->GetLimitText())) - 2); } /// sets the controls text /// \param[in] digits array containing the digits /// \param[in] decimal_pos the position of the decimal point /// \param[in] sign 1 if negative void DisplayDecimalValue(const char* digits, int decimal_pos, int sign) { TCHAR szBuff[limit]; DecimalToText(szBuff, limit, digits, decimal_pos, sign); static_cast(this)->SetWindowText(szBuff); } /// convert a digit array to string /// \param[out] szBuff target buffer for output /// \param[in] buflen maximum characters in output buffer /// \param[in] digits array containing the digits /// \param[in] decimal_pos the position of the decimal point /// \param[in] sign 1 if negative void DecimalToText(TCHAR* szBuff, size_t buflen, const char* digits, int decimal_pos, int sign) const { int i = 0; size_t pos = 0; if (sign) { for (const TCHAR *x = m_NegativeSign; *x ; ++x, ++pos) szBuff[pos] = *x; } for (; pos < buflen && digits[i] && i < decimal_pos ; ++i, ++pos) szBuff[pos] = digits[i]; if (decimal_pos < 1) szBuff[pos++] = _T('0'); size_t last_nonzero = pos; for (const TCHAR *x = m_DecimalSeparator; *x ; ++x, ++pos) szBuff[pos] = *x; for (; pos < buflen && decimal_pos < 0; ++decimal_pos, ++pos) szBuff[pos] = _T('0'); for (; pos < buflen && digits[i]; ++i, ++pos) { szBuff[pos] = digits[i]; if (digits[i] != '0') last_nonzero = pos+1; } szBuff[std::min(buflen - 1, last_nonzero)] = _T('\0'); } void AllowNegative(bool allow) { m_allowNegative = allow; } void AllowFractions(bool allow) { m_allowFractions = allow; } }; class CNumberEdit : public CEdit, public CDecimalSupport { public: void SetTempoValue(const TEMPO &t); TEMPO GetTempoValue(); protected: afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg LPARAM OnPaste(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() }; OPENMPT_NAMESPACE_END