1 /*
2  * GNU Typist  - interactive typing tutor program for UNIX systems
3  *
4  * Copyright (C) 2011  GNU Typist Development Team <bug-gtypist@gnu.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 #include "utf8.h"
22 
23 #ifdef HAVE_PDCURSES
24 #include <curses.h>
25 #else
26 #include <ncurses.h>
27 #endif
28 
29 #include <stdlib.h>
30 #include <iconv.h>
31 #include <errno.h>
32 #include <ctype.h>
33 #include <wctype.h>
34 #ifdef MINGW
35 #include <Windows.h>
36 #endif
37 #include "gettext.h"
38 #define _(String) gettext (String)
39 
40 extern char* locale_encoding;
41 extern int isUTF8Locale;
42 
widen(const char * text)43 wchar_t* widen(const char* text)
44 {
45 #ifdef MINGW
46   /* MultiByteToWideChar() (as opposed to mbstowcs) will convert to UTF-16,
47      (2-byte wchar_t) which is all that minGW+PDCurses support */
48   int numChars = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, NULL);
49   wchar_t* wideText = malloc((numChars+1) * sizeof(wchar_t));
50   int convresult = MultiByteToWideChar(CP_UTF8, 0, text,
51 				       -1, wideText,
52 				       numChars);
53 #else
54   int numChars = utf8len(text);
55   wchar_t* wideText = malloc((numChars+1) * sizeof(wchar_t));
56   int convresult = mbstowcs(wideText, text, numChars+1);
57 #endif
58 
59   if (convresult != numChars)
60   {
61       fatal_error(_("couldn't convert UTF-8 to wide characters"), "?");
62   }
63 
64   return wideText;
65 }
66 
convertUTF8ToCurrentEncoding(const char * UTF8Input)67 char* convertUTF8ToCurrentEncoding(const char* UTF8Input)
68 {
69     iconv_t cd = iconv_open(locale_encoding, "UTF-8");
70     if (cd == (iconv_t) -1)
71     {
72         endwin();
73         printf("Error in iconv_open()\n");
74     }
75     size_t inleft = strlen(UTF8Input);
76     char* inptr = (char*)UTF8Input;
77     size_t outleft = inleft;
78     char* outptr = (char*)malloc(outleft + 1);
79     char* outptr_orig = outptr;
80     size_t nconv = iconv(cd, &inptr, &inleft, &outptr, &outleft);
81 
82     /*
83     endwin();
84     printf("inleft=%d, outleft=%d, nconv=%d\n", inleft, outleft, nconv);
85     */
86     /* TODO: catch EILSEQ?? => no, all errors should lead to termination of gtypist! */
87 
88     if (nconv == (size_t) -1)
89     {
90         int err = errno;
91         char buffer[2048];
92         sprintf(buffer, "iconv() failed on '%s': %s\n"
93                 "You should probably use a UTF-8 locale for the selected lesson!\n",
94                 UTF8Input,
95                 strerror(err));
96         fatal_error(_(buffer), "?");
97     }
98 
99     iconv_close(cd);
100     int numberChars = strlen(UTF8Input) - outleft;
101     outptr_orig[numberChars] = '\0';
102     return outptr_orig;
103 }
104 
convertFromUTF8(const char * UTF8Text)105 wchar_t* convertFromUTF8(const char* UTF8Text)
106 {
107     if (isUTF8Locale)
108     {
109         return widen(UTF8Text);
110     }
111     else
112     {
113         char* textWithCurrentEncoding = convertUTF8ToCurrentEncoding(UTF8Text);
114         int numChars = strlen(textWithCurrentEncoding);
115         wchar_t* wrappedAs_wchar_t = (wchar_t*)malloc((numChars+1) * sizeof(wchar_t));
116         int i;
117         for (i = 0; i < numChars; i++)
118         {
119             wrappedAs_wchar_t[i] = (unsigned char)textWithCurrentEncoding[i];
120         }
121         wrappedAs_wchar_t[numChars] = L'\0';
122         free(textWithCurrentEncoding);
123         return wrappedAs_wchar_t;
124     }
125 }
126 
mvwideaddstr(int y,int x,const char * UTF8Text)127 void mvwideaddstr(int y, int x, const char* UTF8Text)
128 {
129     move(y,x);
130     wideaddstr(UTF8Text);
131 }
132 
wideaddstr(const char * UTF8Text)133 void wideaddstr(const char* UTF8Text)
134 {
135     if (isUTF8Locale)
136     {
137         addstr(UTF8Text);
138     }
139     else
140     {
141         char* textWithCurrentEncoding = convertUTF8ToCurrentEncoding(UTF8Text);
142         addstr(textWithCurrentEncoding);
143         free(textWithCurrentEncoding);
144     }
145 }
146 
wideaddstr_rev(const char * UTF8Text)147 void wideaddstr_rev(const char* UTF8Text)
148 {
149     attron(A_REVERSE);
150     wideaddstr(UTF8Text);
151     attroff(A_REVERSE);
152 }
153 
wideaddch(wchar_t c)154 void wideaddch(wchar_t c)
155 {
156   cchar_t c2;
157   wchar_t wc[2];
158   int result;
159 
160   if (!isUTF8Locale)
161   {
162       addch(c);
163       return;
164   }
165 
166   wc[0] = c;
167   wc[1] = L'\0';
168 
169   result = setcchar(&c2, wc, 0, 0, NULL);
170   if (result != OK)
171   {
172       fatal_error(_("error in setcchar()"), "?");
173   }
174   add_wch(&c2);
175 }
176 
wideaddch_rev(wchar_t c)177 void wideaddch_rev(wchar_t c)
178 {
179   attron(A_REVERSE);
180   wideaddch(c);
181   attroff(A_REVERSE);
182 }
183 
utf8len(const char * UTF8Text)184 int utf8len(const char* UTF8Text)
185 {
186     if (isUTF8Locale)
187     {
188 #ifdef MINGW
189         return MultiByteToWideChar(CP_UTF8, 0, UTF8Text, -1, NULL, NULL) - 1;
190 #else
191         return mbstowcs(NULL, UTF8Text, 0);
192 #endif
193     }
194     else
195     {
196         /* the behavior of mbstowcs depends on LC_CTYPE!  That's why
197            we cannot use mbstowcs() for non-utf8 locales */
198         char* textWithCurrentEncoding = convertUTF8ToCurrentEncoding(UTF8Text);
199         int len = strlen(textWithCurrentEncoding);
200         free(textWithCurrentEncoding);
201         return len;
202     }
203 }
204 
iswideupper(wchar_t c)205 int iswideupper(wchar_t c)
206 {
207     if (isUTF8Locale)
208     {
209         return iswupper(c);
210     }
211     else
212     {
213         return isupper(c);
214     }
215 }
216 
towideupper(wchar_t c)217 wchar_t towideupper(wchar_t c)
218 {
219     if (isUTF8Locale)
220     {
221         return towupper(c);
222     }
223     else
224     {
225         return toupper(c);
226     }
227 }
228 
get_widech(int * c)229 int get_widech(int* c)
230 {
231     int ch;
232 
233     if (isUTF8Locale)
234     {
235         int retcode = get_wch( &ch );
236         if( retcode == ERR )
237 	    return ERR;
238         /*
239            ncurses' KEY_BACKSPACE (0x107) collides with polish "c with
240            acute (0x107) => we need to encode KEY_BACKSPACE!
241         */
242         if (retcode == KEY_CODE_YES && ch == KEY_BACKSPACE)
243         {
244             ch = GTYPIST_KEY_BACKSPACE;
245         }
246 
247 
248 #ifdef MINGW
249 	// MinGW defines wint_t as a short int, for compatibility with Windows.
250 	ch = ch & 0x0ffff;
251 #endif
252     }
253     else
254     {
255         ch = getch();
256         if( ch == ERR )
257 	    return ERR;
258     }
259 
260 #ifdef HAVE_PDCURSES
261     // make sure PDCurses returns recognisable newline characters
262     if( ch == 0x0D )
263 	ch = 0x0A;
264 #endif
265 
266 #ifndef HAVE_PDCURSES /* this fix is not necessary for the Windows port */
267     /* Avoid that Alt+<anything> is treated as ESC, which could lead
268        to accidentally aborting a lesson */
269     if (ch == 27) /* ASCII_ESC */
270     {
271         /* undo the halfdelay() from getch_fl() */
272         cbreak();
273         /* switch to non-blocking mode */
274         nodelay(stdscr, TRUE);
275         int ch2;
276 	if (get_wch(&ch2) != -1)
277         {
278             /* this is NOT escape because curses sends another key */
279             ch = ch2;
280         }
281         /* switch to blocking mode */
282         nodelay(stdscr, FALSE);
283     }
284 #endif
285 
286     *c = ch;
287 
288     return OK;
289 }
290 
291 /*
292   Local Variables:
293   tab-width: 8
294   End:
295 */
296 
297