1
0

CDecimalSupport.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /*
  2. * CDecimalSupport.h
  3. * -----------------
  4. * Purpose: Edit field which allows negative and fractional values to be entered
  5. * Notes : Alexander Uckun's original code has been modified a bit to suit our purposes.
  6. * Authors: OpenMPT Devs
  7. * Alexander Uckun
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #pragma once
  11. #include "openmpt/all/BuildSettings.hpp"
  12. OPENMPT_NAMESPACE_BEGIN
  13. ///////////////////////////////////////////////////////////////////////////////
  14. /// \class CDecimalSupport
  15. /// \brief decimal number support for your control
  16. /// \author Alexander Uckun
  17. /// \version 1.0
  18. // Copyright (c) 2007 - Alexander Uckun
  19. //
  20. // All rights reserved.
  21. //
  22. // Redistribution and use in source and binary forms, with or without
  23. // modification, are permitted provided that the following conditions are
  24. // met:
  25. //
  26. // 1. Redistributions of source code must retain the above copyright notice,
  27. // this list of conditions and the following disclaimer.
  28. //
  29. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  30. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  31. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  32. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  33. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  34. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  35. // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  36. // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  37. // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  38. // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  39. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  40. template <class T, int limit = _CVTBUFSIZE>
  41. class CDecimalSupport
  42. {
  43. protected:
  44. /// the locale dependant decimal separator
  45. TCHAR m_DecimalSeparator[5];
  46. /// the locale dependant negative sign
  47. TCHAR m_NegativeSign[6];
  48. bool m_allowNegative = true, m_allowFractions = true;
  49. public:
  50. #ifdef BEGIN_MSG_MAP
  51. BEGIN_MSG_MAP(CDecimalSupport)
  52. ALT_MSG_MAP(8)
  53. MESSAGE_HANDLER(WM_CHAR, OnChar)
  54. MESSAGE_HANDLER(WM_PASTE, OnPaste)
  55. END_MSG_MAP()
  56. #endif
  57. /// \brief Initialize m_DecimalSeparator and m_NegativeSign
  58. /// \remarks calls InitDecimalSeparator and InitNegativeSign
  59. CDecimalSupport()
  60. {
  61. InitDecimalSeparator();
  62. InitNegativeSign();
  63. }
  64. /// \brief sets m_DecimalSeparator
  65. /// \remarks calls GetLocaleInfo with LOCALE_SDECIMAL to set m_DecimalSeparator
  66. /// \param[in] Locale the locale parameter (see GetLocaleInfo)
  67. /// \return the number of TCHARs written to the destination buffer
  68. int InitDecimalSeparator(LCID Locale = LOCALE_USER_DEFAULT)
  69. {
  70. return ::GetLocaleInfo(Locale, LOCALE_SDECIMAL, m_DecimalSeparator, sizeof(m_DecimalSeparator) / sizeof(TCHAR));
  71. }
  72. /// \brief sets m_NegativeSign
  73. /// \remarks calls GetLocaleInfo with LOCALE_SNEGATIVESIGN to set m_NegativeSign
  74. /// \param[in] Locale the locale parameter (see GetLocaleInfo)
  75. /// \return the number of TCHARs written to the destination buffer
  76. int InitNegativeSign(LCID Locale = LOCALE_USER_DEFAULT)
  77. {
  78. return ::GetLocaleInfo(Locale, LOCALE_SNEGATIVESIGN, m_NegativeSign, sizeof(m_NegativeSign) / sizeof(TCHAR));
  79. }
  80. /// callback for the WM_PASTE message
  81. /// validates the input
  82. /// \param uMsg
  83. /// \param wParam
  84. /// \param lParam
  85. /// \param[out] bHandled true, if the text is a valid number
  86. /// \return 0
  87. LRESULT OnPaste(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, bool &bHandled)
  88. {
  89. bHandled = false;
  90. int neg_sign = 0;
  91. int dec_point = 0;
  92. T* pT = static_cast<T*>(this);
  93. int nStartChar;
  94. int nEndChar;
  95. pT->GetSel(nStartChar, nEndChar);
  96. TCHAR buffer[limit];
  97. pT->GetWindowText(buffer, limit);
  98. // Check if the text already contains a decimal point
  99. for (TCHAR* x = buffer; *x; ++x)
  100. {
  101. if (x - buffer == nStartChar) x += nEndChar - nStartChar;
  102. if (*x == m_DecimalSeparator[0] || *x == _T('.')) ++dec_point;
  103. if (*x == m_NegativeSign[0] || *x == _T('-')) ++neg_sign;
  104. }
  105. #ifdef _UNICODE
  106. if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return 0;
  107. #else
  108. if (!IsClipboardFormatAvailable(CF_TEXT)) return 0;
  109. #endif
  110. if (!OpenClipboard((HWND) *pT)) return 0;
  111. #ifdef _UNICODE
  112. HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
  113. #else
  114. HGLOBAL hglb = ::GetClipboardData(CF_TEXT);
  115. #endif
  116. if (hglb != NULL)
  117. {
  118. TCHAR *lptstr = static_cast<TCHAR *>(GlobalLock(hglb));
  119. if (lptstr != nullptr)
  120. {
  121. bHandled = true;
  122. for (TCHAR* s = lptstr; *s; ++s)
  123. {
  124. if ((*s == m_NegativeSign[0] ||*s == _T('-')) && m_allowNegative)
  125. {
  126. for (TCHAR* t = m_NegativeSign + 1; *t; ++t, ++s)
  127. {
  128. if (*t != *(s+1)) ++neg_sign;
  129. }
  130. if (neg_sign || nStartChar > 0)
  131. {
  132. bHandled = false;
  133. break;
  134. }
  135. ++neg_sign;
  136. continue;
  137. }
  138. if ((*s == m_DecimalSeparator[0] || *s == _T('.')) && m_allowFractions)
  139. {
  140. for (TCHAR* t = m_DecimalSeparator + 1; *t ; ++t, ++s)
  141. {
  142. if (*t != *(s+1)) ++dec_point;
  143. }
  144. if (dec_point)
  145. {
  146. bHandled = false;
  147. break;
  148. }
  149. ++dec_point;
  150. continue;
  151. }
  152. if (*s == _T('\r'))
  153. {
  154. // Stop at new line
  155. *s = 0;
  156. break;
  157. }
  158. if (*s < _T('0') || *s > _T('9'))
  159. {
  160. bHandled = false;
  161. break;
  162. }
  163. }
  164. if(bHandled) pT->ReplaceSel(lptstr, true);
  165. GlobalUnlock(hglb);
  166. }
  167. }
  168. CloseClipboard();
  169. return 0;
  170. }
  171. /// callback for the WM_CHAR message
  172. /// handles the decimal point and the negative sign keys
  173. /// \param uMsg
  174. /// \param[in] wParam contains the pressed key
  175. /// \param lParam
  176. /// \param[out] bHandled true, if the key press was handled in this function
  177. /// \return 0
  178. LRESULT OnChar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
  179. {
  180. bHandled = false;
  181. if ((static_cast<TCHAR>(wParam) == m_DecimalSeparator[0] || wParam == _T('.')) && m_allowFractions)
  182. {
  183. T* pT = static_cast<T*>(this);
  184. int nStartChar;
  185. int nEndChar;
  186. pT->GetSel(nStartChar, nEndChar);
  187. TCHAR buffer[limit];
  188. pT->GetWindowText(buffer, limit);
  189. //Verify that the control doesn't already contain a decimal point
  190. for (TCHAR* x = buffer; *x; ++x)
  191. {
  192. if (x - buffer == nStartChar) x += nEndChar - nStartChar;
  193. if (*x == m_DecimalSeparator[0]) return 0;
  194. }
  195. pT->ReplaceSel(m_DecimalSeparator, true);
  196. bHandled = true;
  197. }
  198. if ((static_cast<TCHAR>(wParam) == m_NegativeSign[0] || wParam == _T('-')) && m_allowNegative)
  199. {
  200. T* pT = static_cast<T*>(this);
  201. int nStartChar;
  202. int nEndChar;
  203. pT->GetSel(nStartChar, nEndChar);
  204. if (nStartChar) return 0;
  205. TCHAR buffer[limit];
  206. pT->GetWindowText(buffer, limit);
  207. //Verify that the control doesn't already contain a negative sign
  208. if (nEndChar == 0 && buffer[0] == m_NegativeSign[0]) return 0;
  209. pT->ReplaceSel(m_NegativeSign, true);
  210. bHandled = true;
  211. }
  212. return 0;
  213. }
  214. /// converts the controls text to double
  215. /// \param[out] d the converted value
  216. /// \return true on success
  217. bool GetDecimalValue(double& d) const
  218. {
  219. TCHAR szBuff[limit];
  220. static_cast<const T*>(this)->GetWindowText(szBuff, limit);
  221. return TextToDouble(szBuff, d);
  222. }
  223. /// converts a string to double
  224. /// \remarks the decimal separator and the negative sign may change in the string
  225. /// \param[in, out] szBuff the string to convert
  226. /// \param[out] d the converted value
  227. /// \return true on success
  228. bool TextToDouble(TCHAR* szBuff, double& d) const
  229. {
  230. //replace the locale dependant separator with .
  231. if (m_DecimalSeparator[0] != _T('.'))
  232. {
  233. for (TCHAR* x = szBuff; *x; ++x)
  234. {
  235. if (*x == m_DecimalSeparator[0])
  236. {
  237. *x = _T('.');
  238. break;
  239. }
  240. }
  241. }
  242. TCHAR* endPtr;
  243. //replace the negative sign with -
  244. if (szBuff[0] == m_NegativeSign[0]) szBuff[0] = _T('-');
  245. d = _tcstod(szBuff, &endPtr);
  246. return *endPtr == _T('\0');
  247. }
  248. /// sets a number as the controls text
  249. /// \param[in] d the value
  250. /// \param[in] count digits after the decimal point
  251. void SetFixedValue(double d, int count)
  252. {
  253. int decimal_pos;
  254. int sign;
  255. char digits[limit];
  256. _fcvt_s(digits, d, count, &decimal_pos, &sign);
  257. return DisplayDecimalValue(digits, decimal_pos, sign);
  258. }
  259. /// sets a number as the controls text
  260. /// \param[in] d the value
  261. /// \param[in] count total number of digits
  262. void SetDecimalValue(double d, int count)
  263. {
  264. int decimal_pos;
  265. int sign;
  266. char digits[limit];
  267. _ecvt_s(digits, d, count, &decimal_pos, &sign);
  268. DisplayDecimalValue(digits, decimal_pos, sign);
  269. }
  270. /// sets a number as the controls text
  271. /// \param[in] d the value
  272. /// \remarks the total number of digits is calculated using the GetLimitText function
  273. void SetDecimalValue(double d)
  274. {
  275. SetDecimalValue(d, std::min(limit, static_cast<int>(static_cast<const T*>(this)->GetLimitText())) - 2);
  276. }
  277. /// sets the controls text
  278. /// \param[in] digits array containing the digits
  279. /// \param[in] decimal_pos the position of the decimal point
  280. /// \param[in] sign 1 if negative
  281. void DisplayDecimalValue(const char* digits, int decimal_pos, int sign)
  282. {
  283. TCHAR szBuff[limit];
  284. DecimalToText(szBuff, limit, digits, decimal_pos, sign);
  285. static_cast<T*>(this)->SetWindowText(szBuff);
  286. }
  287. /// convert a digit array to string
  288. /// \param[out] szBuff target buffer for output
  289. /// \param[in] buflen maximum characters in output buffer
  290. /// \param[in] digits array containing the digits
  291. /// \param[in] decimal_pos the position of the decimal point
  292. /// \param[in] sign 1 if negative
  293. void DecimalToText(TCHAR* szBuff, size_t buflen, const char* digits, int decimal_pos, int sign) const
  294. {
  295. int i = 0;
  296. size_t pos = 0;
  297. if (sign)
  298. {
  299. for (const TCHAR *x = m_NegativeSign; *x ; ++x, ++pos) szBuff[pos] = *x;
  300. }
  301. for (; pos < buflen && digits[i] && i < decimal_pos ; ++i, ++pos) szBuff[pos] = digits[i];
  302. if (decimal_pos < 1) szBuff[pos++] = _T('0');
  303. size_t last_nonzero = pos;
  304. for (const TCHAR *x = m_DecimalSeparator; *x ; ++x, ++pos) szBuff[pos] = *x;
  305. for (; pos < buflen && decimal_pos < 0; ++decimal_pos, ++pos) szBuff[pos] = _T('0');
  306. for (; pos < buflen && digits[i]; ++i, ++pos)
  307. {
  308. szBuff[pos] = digits[i];
  309. if (digits[i] != '0') last_nonzero = pos+1;
  310. }
  311. szBuff[std::min(buflen - 1, last_nonzero)] = _T('\0');
  312. }
  313. void AllowNegative(bool allow)
  314. {
  315. m_allowNegative = allow;
  316. }
  317. void AllowFractions(bool allow)
  318. {
  319. m_allowFractions = allow;
  320. }
  321. };
  322. class CNumberEdit : public CEdit, public CDecimalSupport<CNumberEdit>
  323. {
  324. public:
  325. void SetTempoValue(const TEMPO &t);
  326. TEMPO GetTempoValue();
  327. protected:
  328. afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
  329. afx_msg LPARAM OnPaste(WPARAM wParam, LPARAM lParam);
  330. DECLARE_MESSAGE_MAP()
  331. };
  332. OPENMPT_NAMESPACE_END