Add support for tab-completion when selecting by rule
[alpine.git] / pico / osdep / mswin_spell.c
blobf0392cf4fcea4f08f408aae5b5604fbe5b681fd2
1 /*
2 * ========================================================================
3 * MSWIN_SPELL.C
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
14 #include "../headers.h"
16 #include "mswin_aspell.h"
17 #include "mswin_spell.h"
19 #include <windowsx.h>
21 typedef struct WORD_INFO
23 ASPELLINFO *aspellinfo; // Aspell information
25 const char *utf8_err_message; // Error message, if any
27 char *word_utf8; // utf-8
28 LPTSTR word_lptstr;
30 int word_doto; // UCS
31 LINE *word_dotp; //
32 int word_size; //
33 } WORD_INFO;
35 extern HINSTANCE ghInstance;
36 extern HWND ghTTYWnd;
39 * Function prototypes
41 static int get_next_bad_word(WORD_INFO *word_info);
42 static void free_word_info_words(WORD_INFO *word_info);
43 static INT_PTR CALLBACK spell_dlg_proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
46 * spell() - check for potentially missspelled words and offer them for
47 * correction. Microsoft Windows specific version.
49 int
50 spell(int f, int n)
52 int w_marko_bak;
53 LINE *w_markp_bak;
54 WORD_INFO word_info;
55 ASPELLINFO *aspellinfo;
56 char *lang = NULL;
58 emlwrite(_("Checking spelling..."), NULL); /* greetings! */
60 memset(&word_info, 0, sizeof(WORD_INFO));
62 lang = speller_choice(dictionary, &chosen_dict);
64 if (chosen_dict == -2){ /* user cancelled */
65 chosen_dict = -1;
66 emlwrite(_("Speller Cancelled"), NULL);
67 return 1;
70 aspellinfo = speller_init(lang);
71 if(!aspellinfo ||
72 (word_info.utf8_err_message = speller_get_error_message(aspellinfo)))
74 if(word_info.utf8_err_message)
75 emlwrite((char *)word_info.utf8_err_message, NULL); /* sad greetings! */
76 else
77 emlwrite(_("Spelling: initializing Aspell failed"), NULL);
79 chosen_dict = -1; /* try a different one next time */
80 speller_close(aspellinfo);
81 return -1;
84 // Back up current mark.
85 w_marko_bak = curwp->w_marko;
86 w_markp_bak = curwp->w_markp;
88 setimark(0, 1);
89 gotobob(0, 1);
91 word_info.aspellinfo = aspellinfo;
93 if(get_next_bad_word(&word_info))
95 DialogBoxParam(ghInstance, MAKEINTRESOURCE(DLG_SPELL), ghTTYWnd,
96 spell_dlg_proc, (LPARAM)&word_info);
99 // Restore original mark if possible.
100 if(curwp->w_markp)
102 // Clear any set marks.
103 setmark(0, 1);
105 if(w_markp_bak && llength(w_markp_bak))
107 // Make sure we don't set mark off the line.
108 if(w_marko_bak >= llength(w_markp_bak))
109 w_marko_bak = llength(w_markp_bak) - 1;
111 curwp->w_marko = w_marko_bak;
112 curwp->w_markp = w_markp_bak;
116 if(word_info.utf8_err_message)
117 emlwrite((char *)word_info.utf8_err_message, NULL);
118 else
119 emlwrite(_("Done checking spelling"), NULL);
121 speller_close(aspellinfo);
122 free_word_info_words(&word_info);
124 swapimark(0, 1);
125 curwp->w_flag |= WFHARD|WFMODE;
127 update();
128 return 1;
132 * handle_cb_editchange() - combobox contents have changed so enable/disable
133 * the Change and Change All buttons accordingly.
135 static void
136 handle_cb_editchange(HWND hDlg, HWND hwnd_combo)
138 int text_len = ComboBox_GetTextLength(hwnd_combo);
140 Button_Enable(GetDlgItem(hDlg, ID_BUTTON_CHANGE), !!text_len);
141 Button_Enable(GetDlgItem(hDlg, ID_BUTTON_CHANGEALL), !!text_len);
145 * spell_dlg_proc_set_word() - Set up the dialog to display spelling
146 * information and suggestions for the current word.
148 static void
149 spell_dlg_proc_set_word(HWND hDlg, WORD_INFO *word_info)
151 TCHAR dlg_title[256];
152 HWND hwnd_combo = GetDlgItem(hDlg, ID_COMBO_SUGGESTIONS);
154 // Clear the combobox.
155 ComboBox_ResetContent(hwnd_combo);
157 // Set the dialog box title
158 _sntprintf(dlg_title, ARRAYSIZE(dlg_title), TEXT("Not in Dictionary: %s"),
159 word_info->word_lptstr);
160 dlg_title[ARRAYSIZE(dlg_title) - 1] = 0;
161 SetWindowText(hDlg, dlg_title);
163 // Populate the combobox with suggestions
164 if(speller_suggestion_init(word_info->aspellinfo, word_info->word_utf8, -1))
166 const char *suggestion_utf8;
168 while(suggestion_utf8 = speller_suggestion_getnext(word_info->aspellinfo))
170 LPTSTR suggestion_lptstr = utf8_to_lptstr((LPSTR)suggestion_utf8);
172 if(suggestion_lptstr)
174 ComboBox_AddString(hwnd_combo, suggestion_lptstr);
175 fs_give((void **)&suggestion_lptstr);
179 // Select the first one.
180 ComboBox_SetCurSel(hwnd_combo, 0);
182 speller_suggestion_close(word_info->aspellinfo);
185 handle_cb_editchange(hDlg, hwnd_combo);
189 * spell_dlg_next_word() - Move to the next misspelled word and display
190 * the information for it. If no more bad words, kill the dialog.
192 static void
193 spell_dlg_next_word(HWND hDlg, WORD_INFO *word_info)
195 if(!get_next_bad_word(word_info))
197 EndDialog(hDlg, 0);
198 return;
201 spell_dlg_proc_set_word(hDlg, word_info);
205 * chword_n() - change the given word_len pointed to by the curwp->w_dot
206 * pointers to the word in cb
208 static void
209 chword_n(int wb_len, UCS *cb)
211 ldelete(wb_len, NULL); /* not saved in kill buffer */
213 while(*cb != '\0')
214 linsert(1, *cb++);
216 curwp->w_flag |= WFEDIT;
220 * replace_all() - replace all instances of oldword with newword from
221 * the current '.' on. Mark is where curwp will get restored to.
223 static void
224 replace_all(UCS *oldword, int oldword_len, UCS *newword)
226 int wrap;
228 // Mark should be at "." right now - ie the start of the word we're
229 // checking. We're going to use mark below to restore curwp since
230 // chword_n can potentially realloc LINES and it knows about markp.
231 assert(curwp->w_markp);
232 assert(curwp->w_markp == curwp->w_dotp);
233 assert(curwp->w_marko == curwp->w_doto);
235 curwp->w_bufp->b_mode |= MDEXACT; /* case sensitive */
236 while(forscan(&wrap, oldword, 0, NULL, 0, 1))
238 if(wrap)
239 break; /* wrap NOT allowed! */
242 * We want to minimize the number of substrings that we report
243 * as matching a misspelled word...
245 if(lgetc(curwp->w_dotp, 0).c != '>')
247 LINE *lp = curwp->w_dotp; /* for convenience */
248 int off = curwp->w_doto;
250 if(off == 0 || !ucs4_isalpha(lgetc(lp, off - 1).c))
252 off += oldword_len;
254 if(off == llength(lp) || !ucs4_isalpha(lgetc(lp, off).c))
256 // Replace this word
257 chword_n(oldword_len, newword);
262 forwchar(0, 1); /* move on... */
265 curwp->w_bufp->b_mode ^= MDEXACT; /* case insensitive */
267 curwp->w_dotp = curwp->w_markp;
268 curwp->w_doto = curwp->w_marko;
272 * handle_button_change() - Someone pressed the Change or Change All button.
273 * So deal with it.
275 static void
276 handle_button_change(HWND hDlg, WORD_INFO *word_info, int do_replace_all)
278 int str_lptstr_len;
279 TCHAR str_lptstr[128];
281 // Get the length of what they want to change to.
282 str_lptstr_len = ComboBox_GetText(GetDlgItem(hDlg, ID_COMBO_SUGGESTIONS),
283 str_lptstr, ARRAYSIZE(str_lptstr));
284 if(str_lptstr_len)
286 UCS *str_ucs4;
287 char *str_utf8;
289 // Notify the speller so it'll suggest this word next time around.
290 str_utf8 = lptstr_to_utf8(str_lptstr);
291 if(str_utf8)
293 speller_replace_word(word_info->aspellinfo,
294 word_info->word_utf8, -1, str_utf8, -1);
295 fs_give((void **)&str_utf8);
298 str_ucs4 = lptstr_to_ucs4(str_lptstr);
299 if(!str_ucs4)
301 // Uh oh - massive error.
302 word_info->utf8_err_message = _("Spelling: lptstr_to_ucs4() failed");
303 EndDialog(hDlg, -1);
304 return;
307 // Move back to start of this word.
308 curwp->w_doto = word_info->word_doto;
309 curwp->w_dotp = word_info->word_dotp;
311 if(do_replace_all)
313 int i;
314 int word_size;
315 UCS word[128];
317 word_size = word_info->word_size;
318 if(word_size >= ARRAYSIZE(word) - 1)
319 word_size = ARRAYSIZE(word) - 1;
321 for(i = 0; i < word_size; i++)
322 word[i] = lgetc(curwp->w_dotp, curwp->w_doto + i).c;
324 word[i] = 0;
326 replace_all(word, word_size, str_ucs4);
328 // Skip over the word we just inserted.
329 forwchar(FALSE, (int)ucs4_strlen(str_ucs4));
331 else
333 // Swap it with the new improved version.
334 chword_n(word_info->word_size, str_ucs4);
336 fs_give((void **)&str_ucs4);
339 update();
340 spell_dlg_next_word(hDlg, word_info);
344 * center_dialog() - Center a dialog with respect to its parent.
346 static void
347 center_dialog(HWND hDlg)
349 int dx;
350 int dy;
351 RECT rcDialog;
352 RECT rcParent;
354 GetWindowRect(GetParent(hDlg), &rcParent);
355 GetWindowRect(hDlg, &rcDialog);
357 dx = ((rcParent.right - rcParent.left) -
358 (rcDialog.right - rcDialog.left)) / 2 + rcParent.left - rcDialog.left;
360 dy = ((rcParent.bottom - rcParent.top) -
361 (rcDialog.bottom - rcDialog.top)) / 2 + rcParent.top - rcDialog.top;
363 OffsetRect(&rcDialog, dx, dy);
365 SetWindowPos(hDlg, NULL, rcDialog.left, rcDialog.top, 0, 0,
366 SWP_NOSIZE | SWP_NOZORDER);
370 * spell_dlg_on_command() - Handle the WM_COMMAND stuff for the spell dialog.
372 static void
373 spell_dlg_on_command(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
375 WORD_INFO *word_info =
376 (WORD_INFO *)(LONG_PTR)GetWindowLongPtr(hDlg, GWLP_USERDATA);
378 switch(id)
380 case ID_COMBO_SUGGESTIONS:
381 if(codeNotify == CBN_EDITCHANGE)
382 handle_cb_editchange(hDlg, hwndCtl);
383 break;
385 case ID_BUTTON_CHANGE:
386 handle_button_change(hDlg, word_info, 0);
387 break;
389 case ID_BUTTON_CHANGEALL:
390 handle_button_change(hDlg, word_info, 1);
391 break;
393 case ID_BUTTON_IGNOREONCE:
394 spell_dlg_next_word(hDlg, word_info);
395 break;
397 case ID_BUTTON_IGNOREALL:
398 speller_ignore_all(word_info->aspellinfo, word_info->word_utf8, -1);
399 spell_dlg_next_word(hDlg, word_info);
400 break;
402 case ID_BUTTON_ADD_TO_DICT:
403 speller_add_to_dictionary(word_info->aspellinfo, word_info->word_utf8, -1);
404 spell_dlg_next_word(hDlg, word_info);
405 break;
407 case ID_BUTTON_CANCEL:
408 EndDialog(hDlg, 0);
409 break;
414 * spell_dlg_proc() - Main spell dialog proc.
416 static INT_PTR CALLBACK
417 spell_dlg_proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
419 WORD_INFO *word_info;
421 switch(message)
423 case WM_INITDIALOG:
424 center_dialog(hDlg);
426 // Limit how much junk they can type in.
427 ComboBox_LimitText(GetDlgItem(hDlg, ID_COMBO_SUGGESTIONS), 128);
429 word_info = (WORD_INFO *)lParam;
430 SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)word_info);
432 spell_dlg_proc_set_word(hDlg, word_info);
433 return TRUE;
435 case WM_COMMAND:
436 HANDLE_WM_COMMAND(hDlg, wParam, lParam, spell_dlg_on_command);
437 return TRUE;
440 return FALSE;
444 * get_next_word() - Get the next word from dot. And skip words in
445 * quoted lines.
447 static int
448 get_next_word(WORD_INFO *word_info)
450 UCS *tmp = NULL;
451 int i;
453 // Skip quoted lines
454 while(lgetc(curwp->w_dotp, 0).c == '>')
456 if(curwp->w_dotp == curbp->b_linep)
457 return 0;
459 curwp->w_dotp = lforw(curwp->w_dotp);
460 curwp->w_doto = 0;
461 curwp->w_flag |= WFMOVE;
464 // Move to a word.
465 while(inword() == FALSE)
467 if(forwchar(FALSE, 1) == FALSE)
469 // There is no next word.
470 return 0;
474 // If mark is currently set, clear it.
475 if(curwp->w_markp)
476 setmark(0, 1);
478 // Set mark to beginning of this word.
479 curwp->w_markp = curwp->w_dotp;
480 curwp->w_marko = curwp->w_doto;
482 // Get word length
483 word_info->word_doto = curwp->w_doto;
484 word_info->word_dotp = curwp->w_dotp;
485 word_info->word_size = 0;
487 while((inword() != FALSE) && (word_info->word_dotp == curwp->w_dotp))
489 word_info->word_size++;
491 if(forwchar(FALSE, 1) == FALSE)
492 break;
495 tmp = fs_get((word_info->word_size+1)*sizeof(UCS));
496 if(tmp != NULL){
497 for(i = 0; i < word_info->word_size; i++)
498 tmp[i] = word_info->word_dotp->l_text[word_info->word_doto+i].c;
499 tmp[i] = 0;
500 word_info->word_utf8 = ucs4_to_utf8_cpystr_n(tmp, word_info->word_size);
501 fs_give((void **)&tmp);
503 return 1;
507 * get_next_word() - free the word_info members word_utf8 and word_lptstr.
509 static void
510 free_word_info_words(WORD_INFO *word_info)
512 if(word_info->word_utf8)
514 fs_give((void **)&word_info->word_utf8);
515 word_info->word_utf8 = NULL;
518 if(word_info->word_lptstr)
520 fs_give((void **)&word_info->word_lptstr);
521 word_info->word_lptstr = NULL;
526 * get_next_bad_word() - Search from '.' for the next word we think is
527 * rotten. Mark it and highlight it.
529 static int
530 get_next_bad_word(WORD_INFO *word_info)
532 free_word_info_words(word_info);
534 while(get_next_word(word_info))
536 int ret;
538 /* pass over words that contain @ */
539 if(strchr(word_info->word_utf8, '@'))
540 ret = 1;
541 else
542 ret = speller_check_word(word_info->aspellinfo, word_info->word_utf8, -1);
544 if(ret == -1)
546 word_info->utf8_err_message =
547 speller_get_error_message(word_info->aspellinfo);
548 if(!word_info->utf8_err_message)
549 word_info->utf8_err_message = _("Spelling: speller_check_word() failed");
550 return 0;
552 else if(ret == 0)
554 // Highlight word.
555 update();
557 word_info->word_lptstr = utf8_to_lptstr(word_info->word_utf8);
558 return 1;
561 free_word_info_words(word_info);
564 return 0;