Make WIN_WindowFromPoint take a POINT instead of a POINT16.
[wine] / dlls / msvcrt / locale.c
1 /*
2  * msvcrt.dll locale functions
3  *
4  * Copyright 2000 Jon Griffiths
5  */
6 #include "winnt.h"
7 #include "msvcrt.h"
8
9 DEFAULT_DEBUG_CHANNEL(msvcrt);
10
11 /* FIXME: Need to hold locale for each LC_* type and aggregate
12  * string to produce lc_all.
13  */
14 #define MAX_ELEM_LEN 64 /* Max length of country/language/CP string */
15 #define MAX_LOCALE_LENGTH 256
16 char MSVCRT_current_lc_all[MAX_LOCALE_LENGTH];
17 LCID MSVCRT_current_lc_all_lcid;
18 int MSVCRT_current_lc_all_cp;
19
20 /* MT */
21 extern CRITICAL_SECTION MSVCRT_locale_cs;
22 #define LOCK_LOCALE    EnterCriticalSection(&MSVCRT_locale_cs)
23 #define UNLOCK_LOCALE  LeaveCriticalSection(&MSVCRT_locale_cs)
24
25 /* ctype data modified when the locale changes */
26 extern WORD MSVCRT__ctype [257];
27 extern WORD MSVCRT_current_ctype[257];
28 extern WORD* MSVCRT__pctype;
29
30 /* mbctype data modified when the locale changes */
31 extern int MSVCRT___mb_cur_max;
32 extern unsigned char MSVCRT_mbctype[257];
33
34 #define MSVCRT_LEADBYTE  0x8000
35
36 /* Locales */
37 #define MSVCRT_LC_ALL           0
38 #define MSVCRT_LC_COLLATE       1
39 #define MSVCRT_LC_CTYPE         2
40 #define MSVCRT_LC_MONETARY      3
41 #define MSVCRT_LC_NUMERIC       4
42 #define MSVCRT_LC_TIME          5
43 #define MSVCRT_LC_MIN           MSVCRT_LC_ALL
44 #define MSVCRT_LC_MAX           MSVCRT_LC_TIME
45
46 /* Friendly country strings & iso codes for synonym support.
47  * Based on MS documentation for setlocale().
48  */
49 static const char* _country_synonyms[] =
50 {
51   "Hong Kong","HK",
52   "Hong-Kong","HK",
53   "New Zealand","NZ",
54   "New-Zealand","NZ",
55   "PR China","CN",
56   "PR-China","CN",
57   "United Kingdom","GB",
58   "United-Kingdom","GB",
59   "Britain","GB",
60   "England","GB",
61   "Great Britain","GB",
62   "United States","US",
63   "United-States","US",
64   "America","US"
65 };
66
67 /* INTERNAL: Map a synonym to an ISO code */
68 static void remap_synonym(char *name)
69 {
70   size_t i;
71   for (i = 0; i < sizeof(_country_synonyms)/sizeof(char*); i += 2 )
72   {
73     if (!strcasecmp(_country_synonyms[i],name))
74     {
75       TRACE(":Mapping synonym %s to %s\n",name,_country_synonyms[i+1]);
76       name[0] = _country_synonyms[i+1][0];
77       name[1] = _country_synonyms[i+1][1];
78       name[2] = '\0';
79       return;
80     }
81   }
82 }
83
84 /* Note: Flags are weighted in order of matching importance */
85 #define FOUND_LANGUAGE         0x4
86 #define FOUND_COUNTRY          0x2
87 #define FOUND_CODEPAGE         0x1
88
89 typedef struct {
90   char search_language[MAX_ELEM_LEN];
91   char search_country[MAX_ELEM_LEN];
92   char search_codepage[MAX_ELEM_LEN];
93   char found_language[MAX_ELEM_LEN];
94   char found_country[MAX_ELEM_LEN];
95   char found_codepage[MAX_ELEM_LEN];
96   unsigned int match_flags;
97   LANGID found_lang_id;
98 } locale_search_t;
99
100 #define CONTINUE_LOOKING TRUE
101 #define STOP_LOOKING     FALSE
102
103 /* INTERNAL: Get and compare locale info with a given string */
104 static int compare_info(LCID lcid, DWORD flags, char* buff, const char* cmp)
105 {
106   buff[0] = 0;
107   GetLocaleInfoA(lcid, flags|LOCALE_NOUSEROVERRIDE,buff, MAX_ELEM_LEN);
108   if (!buff[0] || !cmp[0])
109     return 0;
110   /* Partial matches are allowed, e.g. "Germ" matches "Germany" */
111   return !strncasecmp(cmp, buff, strlen(cmp));
112 }
113
114 static BOOL CALLBACK
115 find_best_locale_proc(HMODULE hModule WINE_UNUSED, LPCSTR type WINE_UNUSED,
116                       LPCSTR name WINE_UNUSED, WORD LangID, LONG lParam)
117 {
118   locale_search_t *res = (locale_search_t *)lParam;
119   const LCID lcid = MAKELCID(LangID, SORT_DEFAULT);
120   char buff[MAX_ELEM_LEN];
121   unsigned int flags = 0;
122
123   if(PRIMARYLANGID(LangID) == LANG_NEUTRAL)
124     return CONTINUE_LOOKING;
125
126   /* Check Language */
127   if (compare_info(lcid,LOCALE_SISO639LANGNAME,buff,res->search_language) ||
128       compare_info(lcid,LOCALE_SABBREVLANGNAME,buff,res->search_language) ||
129       compare_info(lcid,LOCALE_SENGLANGUAGE,buff,res->search_language))
130   {
131     TRACE(":Found language: %s->%s\n", res->search_language, buff);
132     flags |= FOUND_LANGUAGE;
133     memcpy(res->found_language,res->search_language,MAX_ELEM_LEN);
134   }
135   else if (res->match_flags & FOUND_LANGUAGE)
136   {
137     return CONTINUE_LOOKING;
138   }
139
140   /* Check Country */
141   if (compare_info(lcid,LOCALE_SISO3166CTRYNAME,buff,res->search_country) ||
142       compare_info(lcid,LOCALE_SABBREVCTRYNAME,buff,res->search_country) ||
143       compare_info(lcid,LOCALE_SENGCOUNTRY,buff,res->search_country))
144   {
145     TRACE("Found country:%s->%s\n", res->search_country, buff);
146     flags |= FOUND_COUNTRY;
147     memcpy(res->found_country,res->search_country,MAX_ELEM_LEN);
148   }
149   else if (res->match_flags & FOUND_COUNTRY)
150   {
151     return CONTINUE_LOOKING;
152   }
153
154   /* Check codepage */
155   if (compare_info(lcid,LOCALE_IDEFAULTCODEPAGE,buff,res->search_codepage) ||
156       (compare_info(lcid,LOCALE_IDEFAULTANSICODEPAGE,buff,res->search_codepage)))
157   {
158     TRACE("Found codepage:%s->%s\n", res->search_codepage, buff);
159     flags |= FOUND_CODEPAGE;
160     memcpy(res->found_codepage,res->search_codepage,MAX_ELEM_LEN);
161   }
162   else if (res->match_flags & FOUND_CODEPAGE)
163   {
164     return CONTINUE_LOOKING;
165   }
166
167   if (flags > res->match_flags)
168   {
169     /* Found a better match than previously */
170     res->match_flags = flags;
171     res->found_lang_id = LangID;
172   }
173   if (flags & (FOUND_LANGUAGE & FOUND_COUNTRY & FOUND_CODEPAGE))
174   {
175     TRACE(":found exact locale match\n");
176     return STOP_LOOKING;
177   }
178   return CONTINUE_LOOKING;
179 }
180
181 extern int atoi(const char *);
182
183 /* Internal: Find the LCID for a locale specification */
184 static LCID MSVCRT_locale_to_LCID(locale_search_t* locale)
185 {
186   LCID lcid;
187   EnumResourceLanguagesA(GetModuleHandleA("KERNEL32"), RT_STRINGA,
188                          (LPCSTR)LOCALE_ILANGUAGE,find_best_locale_proc,
189                          (LONG)locale);
190
191   if (!locale->match_flags)
192     return 0;
193
194   /* If we were given something that didn't match, fail */
195   if (locale->search_country[0] && !(locale->match_flags & FOUND_COUNTRY))
196     return 0;
197
198   lcid =  MAKELCID(locale->found_lang_id, SORT_DEFAULT);
199
200   /* Populate partial locale, translating LCID to locale string elements */
201   if (!locale->found_codepage[0])
202   {
203     /* Even if a codepage is not enumerated for a locale
204      * it can be set if valid */
205     if (locale->search_codepage[0])
206     {
207       if (IsValidCodePage(atoi(locale->search_codepage)))
208         memcpy(locale->found_codepage,locale->search_codepage,MAX_ELEM_LEN);
209       else
210       {
211         /* Special codepage values: OEM & ANSI */
212         if (strcasecmp(locale->search_codepage,"OCP"))
213         {
214           GetLocaleInfoA(lcid, LOCALE_IDEFAULTCODEPAGE,
215                          locale->found_codepage, MAX_ELEM_LEN);
216         }
217         if (strcasecmp(locale->search_codepage,"ACP"))
218         {
219           GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE,
220                          locale->found_codepage, MAX_ELEM_LEN);
221         }
222         else
223           return 0;
224
225         if (!atoi(locale->found_codepage))
226            return 0;
227       }
228     }
229     else
230     {
231       /* Prefer ANSI codepages if present */
232       GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE,
233                      locale->found_codepage, MAX_ELEM_LEN);
234       if (!locale->found_codepage[0] || !atoi(locale->found_codepage))
235           GetLocaleInfoA(lcid, LOCALE_IDEFAULTCODEPAGE,
236                          locale->found_codepage, MAX_ELEM_LEN);
237     }
238   }
239   GetLocaleInfoA(lcid, LOCALE_SENGLANGUAGE|LOCALE_NOUSEROVERRIDE,
240                  locale->found_language, MAX_ELEM_LEN);
241   GetLocaleInfoA(lcid, LOCALE_SENGCOUNTRY|LOCALE_NOUSEROVERRIDE,
242                  locale->found_country, MAX_ELEM_LEN);
243   return lcid;
244 }
245
246 extern int snprintf(char *, int, const char *, ...);
247
248 /* INTERNAL: Set ctype behaviour for a codepage */
249 static void msvcrt_set_ctype(unsigned int codepage, LCID lcid)
250 {
251   CPINFO cp;
252
253   memset(&cp, 0, sizeof(CPINFO));
254
255   if (GetCPInfo(codepage, &cp))
256   {
257     int i;
258     char str[3];
259     unsigned char *traverse = (unsigned char *)cp.LeadByte;
260
261     memset(MSVCRT_current_ctype, 0, sizeof(MSVCRT__ctype));
262     MSVCRT_current_lc_all_cp = codepage;
263
264     /* Switch ctype macros to MBCS if needed */
265     MSVCRT___mb_cur_max = cp.MaxCharSize;
266
267     /* Set remaining ctype flags: FIXME: faster way to do this? */
268     str[1] = str[2] = 0;
269     for (i = 0; i < 256; i++)
270     {
271       if (!(MSVCRT__pctype[i] & MSVCRT_LEADBYTE))
272       {
273         str[0] = i;
274         GetStringTypeA(lcid, CT_CTYPE1, str, 1, MSVCRT__pctype + i);
275       }
276     }
277
278     /* Set leadbyte flags */
279     while (traverse[0] || traverse[1])
280     {
281       for( i = traverse[0]; i <= traverse[1]; i++ )
282         MSVCRT_current_ctype[i+1] |= MSVCRT_LEADBYTE;
283       traverse += 2;
284     };
285   }
286 }
287
288
289 /*********************************************************************
290  *              setlocale (MSVCRT.@)
291  */
292 char* MSVCRT_setlocale(int category, const char* locale)
293 {
294   LCID lcid = 0;
295   locale_search_t lc;
296   int haveLang, haveCountry, haveCP;
297   char* next;
298   int lc_all = 0;
299
300   TRACE("(%d %s)\n",category,locale);
301
302   if (category < MSVCRT_LC_MIN || category > MSVCRT_LC_MAX)
303     return NULL;
304
305   if (locale == NULL)
306   {
307     /* Report the current Locale */
308     return MSVCRT_current_lc_all;
309   }
310
311   LOCK_LOCALE;
312
313   if (locale[0] == 'L' && locale[1] == 'C' && locale[2] == '_')
314   {
315     FIXME(":restore previous locale not implemented!\n");
316     /* FIXME: Easiest way to do this is parse the string and
317      * call this function recursively with its elements,
318      * Where they differ for each lc_ type.
319      */
320     UNLOCK_LOCALE;
321     return MSVCRT_current_lc_all;
322   }
323
324   /* Default Locale: Special case handling */
325   if (!strlen(locale) || ((toupper(locale[0]) == 'C') && !locale[1]))
326   {
327     MSVCRT_current_lc_all[0] = 'C';
328     MSVCRT_current_lc_all[1] = '\0';
329     MSVCRT_current_lc_all_cp = GetACP();
330
331     switch (category) {
332     case MSVCRT_LC_ALL:
333       lc_all = 1; /* Fall through all cases ... */
334     case MSVCRT_LC_COLLATE:
335       if (!lc_all) break;
336     case MSVCRT_LC_CTYPE:
337       /* Restore C locale ctype info */
338       MSVCRT___mb_cur_max = 1;
339       memcpy(MSVCRT_current_ctype, MSVCRT__ctype, sizeof(MSVCRT__ctype));
340       memset(MSVCRT_mbctype, 0, sizeof(MSVCRT_mbctype));
341       if (!lc_all) break;
342     case MSVCRT_LC_MONETARY:
343       if (!lc_all) break;
344     case MSVCRT_LC_NUMERIC:
345       if (!lc_all) break;
346     case MSVCRT_LC_TIME:
347     }
348     UNLOCK_LOCALE;
349     return MSVCRT_current_lc_all;
350   }
351
352   /* Get locale elements */
353   haveLang = haveCountry = haveCP = 0;
354   memset(&lc,0,sizeof(lc));
355
356   next = strchr(locale,'_');
357   if (next && next != locale)
358   {
359     haveLang = 1;
360     strncpy(lc.search_language,locale,next-locale);
361     locale += next-locale+1;
362   }
363
364   next = strchr(locale,'.');
365   if (next)
366   {
367     haveCP = 1;
368     if (next == locale)
369     {
370       locale++;
371       strncpy(lc.search_codepage, locale, MAX_ELEM_LEN);
372     }
373     else
374     {
375       if (haveLang)
376       {
377         haveCountry = 1;
378         strncpy(lc.search_country,locale,next-locale);
379         locale += next-locale+1;
380       }
381       else
382       {
383         haveLang = 1;
384         strncpy(lc.search_language,locale,next-locale);
385         locale += next-locale+1;
386       }
387       strncpy(lc.search_codepage, locale, MAX_ELEM_LEN);
388     }
389   }
390   else
391   {
392     if (haveLang)
393     {
394       haveCountry = 1;
395       strncpy(lc.search_country, locale, MAX_ELEM_LEN);
396     }
397     else
398     {
399       haveLang = 1;
400       strncpy(lc.search_language, locale, MAX_ELEM_LEN);
401     }
402   }
403
404   if (haveCountry)
405     remap_synonym(lc.search_country);
406
407   if (haveCP && !haveCountry && !haveLang)
408   {
409     FIXME(":Codepage only locale not implemented\n");
410     /* FIXME: Use default lang/country and skip locale_to_LCID()
411      * call below...
412      */
413     UNLOCK_LOCALE;
414     return NULL;
415   }
416
417   lcid = MSVCRT_locale_to_LCID(&lc);
418
419   TRACE(":found LCID %ld\n",lcid);
420
421   if (lcid == 0)
422   {
423     UNLOCK_LOCALE;
424     return NULL;
425   }
426
427   MSVCRT_current_lc_all_lcid = lcid;
428
429   snprintf(MSVCRT_current_lc_all,MAX_LOCALE_LENGTH,"%s_%s.%s",
430            lc.found_language,lc.found_country,lc.found_codepage);
431
432   switch (category) {
433   case MSVCRT_LC_ALL:
434     lc_all = 1; /* Fall through all cases ... */
435   case MSVCRT_LC_COLLATE:
436     if (!lc_all) break;
437   case MSVCRT_LC_CTYPE:
438     msvcrt_set_ctype(atoi(lc.found_codepage),lcid);
439     if (!lc_all) break;
440   case MSVCRT_LC_MONETARY:
441     if (!lc_all) break;
442   case MSVCRT_LC_NUMERIC:
443     if (!lc_all) break;
444   case MSVCRT_LC_TIME:
445   }
446   UNLOCK_LOCALE;
447   return MSVCRT_current_lc_all;
448 }
449
450
451 /*********************************************************************
452  *              _Getdays (MSVCRT.@)
453  */
454 const char* _Getdays(void)
455 {
456   static const char *MSVCRT_days = ":Sun:Sunday:Mon:Monday:Tue:Tuesday:Wed:"
457                             "Wednesday:Thu:Thursday:Fri:Friday:Sat:Saturday";
458   /* FIXME: Use locale */
459   TRACE("(void) semi-stub\n");
460   return MSVCRT_days;
461 }
462
463 /*********************************************************************
464  *              _Getmonths (MSVCRT.@)
465  */
466 const char* _Getmonths(void)
467 {
468   static const char *MSVCRT_months = ":Jan:January:Feb:February:Mar:March:Apr:"
469                 "April:May:May:Jun:June:Jul:July:Aug:August:Sep:September:Oct:"
470                 "October:Nov:November:Dec:December";
471   /* FIXME: Use locale */
472   TRACE("(void) semi-stub\n");
473   return MSVCRT_months;
474 }
475
476 /*********************************************************************
477  *              _Getnames (MSVCRT.@)
478  */
479 const char* _Getnames(void)
480 {
481   /* FIXME: */
482   TRACE("(void) stub\n");
483   return "";
484 }
485
486 /*********************************************************************
487  *              _Strftime (MSVCRT.@)
488  */
489 const char* _Strftime(char *out, unsigned int len, const char *fmt,
490                                      const void *tm, void *foo)
491 {
492   /* FIXME: */
493   TRACE("(%p %d %s %p %p) stub\n", out, len, fmt, tm, foo);
494   return "";
495 }
496
497 /* FIXME: MBCP probably belongs in mbcs.c */
498
499 /*********************************************************************
500  *              _setmbcp (MSVCRT.@)
501  */
502 void _setmbcp(int cp)
503 {
504   LOCK_LOCALE;
505   if (MSVCRT_current_lc_all_cp != cp)
506   {
507     /* FIXME: set ctype behaviour for this cp */
508     MSVCRT_current_lc_all_cp = cp;
509   }
510   UNLOCK_LOCALE;
511 }
512
513 /*********************************************************************
514  *              _getmbcp (MSVCRT.@)
515  */
516 int _getmbcp(void)
517 {
518   return MSVCRT_current_lc_all_cp;
519 }
520