2 * ========================================================================
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"
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
35 extern HINSTANCE ghInstance
;
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.
55 ASPELLINFO
*aspellinfo
;
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 */
66 emlwrite(_("Speller Cancelled"), NULL
);
70 aspellinfo
= speller_init(lang
);
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! */
77 emlwrite(_("Spelling: initializing Aspell failed"), NULL
);
79 chosen_dict
= -1; /* try a different one next time */
80 speller_close(aspellinfo
);
84 // Back up current mark.
85 w_marko_bak
= curwp
->w_marko
;
86 w_markp_bak
= curwp
->w_markp
;
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.
102 // Clear any set marks.
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
);
119 emlwrite(_("Done checking spelling"), NULL
);
121 speller_close(aspellinfo
);
122 free_word_info_words(&word_info
);
125 curwp
->w_flag
|= WFHARD
|WFMODE
;
132 * handle_cb_editchange() - combobox contents have changed so enable/disable
133 * the Change and Change All buttons accordingly.
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.
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.
193 spell_dlg_next_word(HWND hDlg
, WORD_INFO
*word_info
)
195 if(!get_next_bad_word(word_info
))
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
209 chword_n(int wb_len
, UCS
*cb
)
211 ldelete(wb_len
, NULL
); /* not saved in kill buffer */
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.
224 replace_all(UCS
*oldword
, int oldword_len
, UCS
*newword
)
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))
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
))
254 if(off
== llength(lp
) || !ucs4_isalpha(lgetc(lp
, off
).c
))
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.
276 handle_button_change(HWND hDlg
, WORD_INFO
*word_info
, int do_replace_all
)
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
));
289 // Notify the speller so it'll suggest this word next time around.
290 str_utf8
= lptstr_to_utf8(str_lptstr
);
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
);
301 // Uh oh - massive error.
302 word_info
->utf8_err_message
= _("Spelling: lptstr_to_ucs4() failed");
307 // Move back to start of this word.
308 curwp
->w_doto
= word_info
->word_doto
;
309 curwp
->w_dotp
= word_info
->word_dotp
;
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
;
326 replace_all(word
, word_size
, str_ucs4
);
328 // Skip over the word we just inserted.
329 forwchar(FALSE
, (int)ucs4_strlen(str_ucs4
));
333 // Swap it with the new improved version.
334 chword_n(word_info
->word_size
, str_ucs4
);
336 fs_give((void **)&str_ucs4
);
340 spell_dlg_next_word(hDlg
, word_info
);
344 * center_dialog() - Center a dialog with respect to its parent.
347 center_dialog(HWND hDlg
)
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.
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
);
380 case ID_COMBO_SUGGESTIONS
:
381 if(codeNotify
== CBN_EDITCHANGE
)
382 handle_cb_editchange(hDlg
, hwndCtl
);
385 case ID_BUTTON_CHANGE
:
386 handle_button_change(hDlg
, word_info
, 0);
389 case ID_BUTTON_CHANGEALL
:
390 handle_button_change(hDlg
, word_info
, 1);
393 case ID_BUTTON_IGNOREONCE
:
394 spell_dlg_next_word(hDlg
, word_info
);
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
);
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
);
407 case ID_BUTTON_CANCEL
:
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
;
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
);
436 HANDLE_WM_COMMAND(hDlg
, wParam
, lParam
, spell_dlg_on_command
);
444 * get_next_word() - Get the next word from dot. And skip words in
448 get_next_word(WORD_INFO
*word_info
)
454 while(lgetc(curwp
->w_dotp
, 0).c
== '>')
456 if(curwp
->w_dotp
== curbp
->b_linep
)
459 curwp
->w_dotp
= lforw(curwp
->w_dotp
);
461 curwp
->w_flag
|= WFMOVE
;
465 while(inword() == FALSE
)
467 if(forwchar(FALSE
, 1) == FALSE
)
469 // There is no next word.
474 // If mark is currently set, clear it.
478 // Set mark to beginning of this word.
479 curwp
->w_markp
= curwp
->w_dotp
;
480 curwp
->w_marko
= curwp
->w_doto
;
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
)
495 tmp
= fs_get((word_info
->word_size
+1)*sizeof(UCS
));
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
;
500 word_info
->word_utf8
= ucs4_to_utf8_cpystr_n(tmp
, word_info
->word_size
);
501 fs_give((void **)&tmp
);
507 * get_next_word() - free the word_info members word_utf8 and word_lptstr.
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.
530 get_next_bad_word(WORD_INFO
*word_info
)
532 free_word_info_words(word_info
);
534 while(get_next_word(word_info
))
538 /* pass over words that contain @ */
539 if(strchr(word_info
->word_utf8
, '@'))
542 ret
= speller_check_word(word_info
->aspellinfo
, word_info
->word_utf8
, -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");
557 word_info
->word_lptstr
= utf8_to_lptstr(word_info
->word_utf8
);
561 free_word_info_words(word_info
);