|
Lines 1-5
Link Here
|
| 1 |
/* gtkspell - a spell-checking addon for GTK's TextView widget |
1 |
/* gtkspell |
| 2 |
* Copyright (c) 2002 Evan Martin. |
2 |
* Copyright (c) 2002 Evan Martin. |
|
|
3 |
* Copyright (c) 2004 Nathan Fredrickson. |
| 4 |
* |
| 5 |
* This library is free software; you can redistribute it and/or |
| 6 |
* modify it under the terms of the GNU Lesser General Public |
| 7 |
* License as published by the Free Software Foundation; either |
| 8 |
* version 2.1 of the License, or (at your option) any later version. |
| 9 |
* |
| 10 |
* This library is distributed in the hope that it will be useful, |
| 11 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 |
* Lesser General Public License for more details. |
| 14 |
* |
| 15 |
* You should have received a copy of the GNU Lesser General Public |
| 16 |
* License along with this library; if not, write to the |
| 17 |
* Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| 18 |
* Boston, MA 02111-1307, USA. |
| 19 |
* |
| 3 |
*/ |
20 |
*/ |
| 4 |
|
21 |
|
| 5 |
/* vim: set ts=4 sw=4 wm=5 : */ |
22 |
/* vim: set ts=4 sw=4 wm=5 : */ |
|
Lines 7-48
Link Here
|
| 7 |
#include <gtk/gtk.h> |
24 |
#include <gtk/gtk.h> |
| 8 |
#include <libintl.h> |
25 |
#include <libintl.h> |
| 9 |
#include <locale.h> |
26 |
#include <locale.h> |
|
|
27 |
#include <enchant.h> |
| 10 |
#include "../config.h" |
28 |
#include "../config.h" |
| 11 |
#include "gtkspell.h" |
29 |
#include "gtkspell.h" |
| 12 |
|
30 |
|
| 13 |
#define _(String) dgettext (PACKAGE, String) |
31 |
#define _(String) dgettext(PACKAGE, String) |
| 14 |
|
32 |
#define N_(String) (String) |
| 15 |
#ifdef HAVE_ASPELL_H |
|
|
| 16 |
#define USING_ASPELL |
| 17 |
#include <aspell.h> |
| 18 |
#elif defined HAVE_PSPELL_H |
| 19 |
#define USING_PSPELL |
| 20 |
#include <pspell/pspell.h> |
| 21 |
#define AspellSpeller PspellManager |
| 22 |
#define speller manager |
| 23 |
#define aspell_speller_check pspell_manager_check |
| 24 |
#define aspell_speller_add_to_session pspell_manager_add_to_session |
| 25 |
#define aspell_speller_add_to_personal pspell_manager_add_to_personal |
| 26 |
#define aspell_speller_save_all_word_lists pspell_manager_save_all_word_lists |
| 27 |
#define aspell_speller_store_replacement pspell_manager_store_replacement |
| 28 |
#define AspellWordList PspellWordList |
| 29 |
#define AspellStringEnumeration PspellStringEmulation |
| 30 |
#define aspell_speller_suggest pspell_manager_suggest |
| 31 |
#define aspell_word_list_elements pspell_word_list_elements |
| 32 |
#define aspell_string_enumeration_next pspell_string_emulation_next |
| 33 |
#define delete_aspell_string_enumeration delete_pspell_string_emulation |
| 34 |
#define AspellConfig PspellConfig |
| 35 |
#define AspellCanHaveError PspellCanHaveError |
| 36 |
#define new_aspell_config new_pspell_config |
| 37 |
#define aspell_config_replace pspell_config_replace |
| 38 |
#define new_aspell_speller new_pspell_manager |
| 39 |
#define delete_aspell_config delete_pspell_config |
| 40 |
#define aspell_error_message pspell_error_message |
| 41 |
#define delete_aspell_speller delete_pspell_manager |
| 42 |
#define to_aspell_speller to_pspell_manager |
| 43 |
#define aspell_error_number pspell_error_number |
| 44 |
#define aspell pspell |
| 45 |
#endif |
| 46 |
|
33 |
|
| 47 |
const int debug = 0; |
34 |
const int debug = 0; |
| 48 |
const int quiet = 0; |
35 |
const int quiet = 0; |
|
Lines 53-60
struct _GtkSpell {
Link Here
|
| 53 |
GtkTextMark *mark_insert_start; |
40 |
GtkTextMark *mark_insert_start; |
| 54 |
GtkTextMark *mark_insert_end; |
41 |
GtkTextMark *mark_insert_end; |
| 55 |
gboolean deferred_check; |
42 |
gboolean deferred_check; |
| 56 |
AspellSpeller *speller; |
43 |
EnchantBroker *broker; |
|
|
44 |
EnchantDict *speller; |
| 57 |
GtkTextMark *mark_click; |
45 |
GtkTextMark *mark_click; |
|
|
46 |
gchar *lang; |
| 58 |
}; |
47 |
}; |
| 59 |
|
48 |
|
| 60 |
static void gtkspell_free(GtkSpell *spell); |
49 |
static void gtkspell_free(GtkSpell *spell); |
|
Lines 122-131
static void
Link Here
|
| 122 |
check_word(GtkSpell *spell, GtkTextBuffer *buffer, |
111 |
check_word(GtkSpell *spell, GtkTextBuffer *buffer, |
| 123 |
GtkTextIter *start, GtkTextIter *end) { |
112 |
GtkTextIter *start, GtkTextIter *end) { |
| 124 |
char *text; |
113 |
char *text; |
|
|
114 |
|
| 115 |
if (!spell->speller) |
| 116 |
return; |
| 117 |
|
| 125 |
text = gtk_text_buffer_get_text(buffer, start, end, FALSE); |
118 |
text = gtk_text_buffer_get_text(buffer, start, end, FALSE); |
| 126 |
if (debug) g_print("checking: %s\n", text); |
119 |
if (debug) g_print("checking: %s\n", text); |
| 127 |
if (g_unichar_isdigit(*text) == FALSE) /* don't check numbers */ |
120 |
if (g_unichar_isdigit(*text) == FALSE) /* don't check numbers */ |
| 128 |
if (aspell_speller_check(spell->speller, text, -1) == FALSE) |
121 |
if (enchant_dict_check(spell->speller, text, strlen(text)) != 0) |
| 129 |
gtk_text_buffer_apply_tag(buffer, spell->tag_highlight, start, end); |
122 |
gtk_text_buffer_apply_tag(buffer, spell->tag_highlight, start, end); |
| 130 |
g_free(text); |
123 |
g_free(text); |
| 131 |
} |
124 |
} |
|
Lines 295-307
add_to_dictionary(GtkWidget *menuitem, G
Link Here
|
| 295 |
GtkTextIter start, end; |
288 |
GtkTextIter start, end; |
| 296 |
GtkTextBuffer *buffer; |
289 |
GtkTextBuffer *buffer; |
| 297 |
|
290 |
|
|
|
291 |
if (!spell->speller) |
| 292 |
return; |
| 293 |
|
| 298 |
buffer = gtk_text_view_get_buffer(spell->view); |
294 |
buffer = gtk_text_view_get_buffer(spell->view); |
| 299 |
|
295 |
|
| 300 |
get_word_extents_from_mark(buffer, &start, &end, spell->mark_click); |
296 |
get_word_extents_from_mark(buffer, &start, &end, spell->mark_click); |
| 301 |
word = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); |
297 |
word = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); |
| 302 |
|
298 |
|
| 303 |
aspell_speller_add_to_personal(spell->speller, word, strlen(word)); |
299 |
enchant_dict_add_to_personal(spell->speller, word, strlen(word)); |
| 304 |
aspell_speller_save_all_word_lists(spell->speller); |
|
|
| 305 |
|
300 |
|
| 306 |
gtkspell_recheck_all(spell); |
301 |
gtkspell_recheck_all(spell); |
| 307 |
|
302 |
|
|
Lines 319-325
ignore_all(GtkWidget *menuitem, GtkSpell
Link Here
|
| 319 |
get_word_extents_from_mark(buffer, &start, &end, spell->mark_click); |
314 |
get_word_extents_from_mark(buffer, &start, &end, spell->mark_click); |
| 320 |
word = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); |
315 |
word = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); |
| 321 |
|
316 |
|
| 322 |
aspell_speller_add_to_session(spell->speller, word, strlen(word)); |
317 |
enchant_dict_add_to_session(spell->speller, word, strlen(word)); |
| 323 |
|
318 |
|
| 324 |
gtkspell_recheck_all(spell); |
319 |
gtkspell_recheck_all(spell); |
| 325 |
|
320 |
|
|
Lines 332-337
replace_word(GtkWidget *menuitem, GtkSpe
Link Here
|
| 332 |
const char *newword; |
327 |
const char *newword; |
| 333 |
GtkTextIter start, end; |
328 |
GtkTextIter start, end; |
| 334 |
GtkTextBuffer *buffer; |
329 |
GtkTextBuffer *buffer; |
|
|
330 |
|
| 331 |
if (!spell->speller) |
| 332 |
return; |
| 335 |
|
333 |
|
| 336 |
buffer = gtk_text_view_get_buffer(spell->view); |
334 |
buffer = gtk_text_view_get_buffer(spell->view); |
| 337 |
|
335 |
|
|
Lines 348-380
replace_word(GtkWidget *menuitem, GtkSpe
Link Here
|
| 348 |
gtk_text_buffer_delete(buffer, &start, &end); |
346 |
gtk_text_buffer_delete(buffer, &start, &end); |
| 349 |
gtk_text_buffer_insert(buffer, &start, newword, -1); |
347 |
gtk_text_buffer_insert(buffer, &start, newword, -1); |
| 350 |
|
348 |
|
| 351 |
aspell_speller_store_replacement(spell->speller, |
349 |
enchant_dict_store_replacement(spell->speller, |
| 352 |
oldword, strlen(oldword), |
350 |
oldword, strlen(oldword), |
| 353 |
newword, strlen(newword)); |
351 |
newword, strlen(newword)); |
| 354 |
|
352 |
|
| 355 |
g_free(oldword); |
353 |
g_free(oldword); |
| 356 |
} |
354 |
} |
| 357 |
|
355 |
|
| 358 |
GtkWidget* |
356 |
#define KNOWN_LANGUAGES 39 |
|
|
357 |
static gchar *known_languages [KNOWN_LANGUAGES * 2 + 1] = { |
| 358 |
"br", N_("Breton"), |
| 359 |
"ca", N_("Catalan"), |
| 360 |
"cs", N_("Czech"), |
| 361 |
"da", N_("Danish"), |
| 362 |
"de-DE", N_("German (Germany)"), |
| 363 |
"de-AT", N_("German (Austria)"), |
| 364 |
"de-CH", N_("German (Swiss)"), |
| 365 |
"el", N_("Greek"), |
| 366 |
"en", N_("English"), |
| 367 |
"en-US", N_("English (American)"), |
| 368 |
"en-GB", N_("English (British)"), |
| 369 |
"en-CA", N_("English (Canadian)"), |
| 370 |
"eo", N_("Esperanto"), |
| 371 |
"es", N_("Spanish"), |
| 372 |
"fi", N_("Finnish"), |
| 373 |
"fo", N_("Faroese"), |
| 374 |
"fr-FR", N_("French (France)"), |
| 375 |
"fr-CH", N_("French (Swiss)"), |
| 376 |
"hu", N_("Hungarian"), |
| 377 |
"ga", N_("Irish"), |
| 378 |
"gl", N_("Galician"), |
| 379 |
"he", N_("Hebrew"), |
| 380 |
"it", N_("Italian"), |
| 381 |
"la", N_("Latin"), |
| 382 |
"lt", N_("Lithuanian"), |
| 383 |
"nb-NO", N_("Norwegian (Bokmal)"), |
| 384 |
"nl", N_("Dutch"), |
| 385 |
"nn-NO", N_("Norwegian (Nyorsk)"), |
| 386 |
"no", N_("Norwegian"), |
| 387 |
"pl", N_("Polish"), |
| 388 |
"pt-PT", N_("Portuguese (Portugal)"), |
| 389 |
"pt-BR", N_("Portuguese (Brazilian)"), |
| 390 |
"ru", N_("Russian"), |
| 391 |
"sc", N_("Sardinian"), |
| 392 |
"sk", N_("Slovak"), |
| 393 |
"sl", N_("Slovenian"), |
| 394 |
"sv", N_("Swedish"), |
| 395 |
"uk", N_("Ukrainian"), |
| 396 |
"yi", N_("Yiddish"), |
| 397 |
NULL |
| 398 |
}; |
| 399 |
|
| 400 |
static void |
| 401 |
dict_describe (const char * const lang_tag, |
| 402 |
const char * const provider_name, |
| 403 |
const char * const provider_desc, |
| 404 |
const char * const provider_file, |
| 405 |
void * user_data) { |
| 406 |
printf("%s | %s | %s | %s\n", lang_tag, provider_name, provider_desc, provider_file); |
| 407 |
} |
| 408 |
|
| 409 |
/* replace any 1 hyphen with an underscore. converts en-US to en_US */ |
| 410 |
static gchar * |
| 411 |
normalize_language (const gchar *language) { |
| 412 |
gchar *dup, *hyphen; |
| 413 |
|
| 414 |
dup = g_strdup(language); |
| 415 |
hyphen = strchr(dup, '-'); |
| 416 |
if (hyphen) |
| 417 |
*hyphen = '_'; |
| 418 |
return dup; |
| 419 |
} |
| 420 |
|
| 421 |
static GSList * |
| 422 |
get_languages (GtkSpell *spell) { |
| 423 |
EnchantBroker *broker = spell->broker; |
| 424 |
gchar * lang; |
| 425 |
|
| 426 |
GSList *langs; |
| 427 |
gint i; |
| 428 |
|
| 429 |
langs = NULL; |
| 430 |
|
| 431 |
for (i=0; known_languages[i]; i++) { |
| 432 |
lang = normalize_language(known_languages [i]); |
| 433 |
i++; |
| 434 |
|
| 435 |
if (enchant_broker_dict_exists(spell->broker, lang)) { |
| 436 |
/* |
| 437 |
enchant_dict_describe(spell->speller, dict_describe, NULL); |
| 438 |
printf("Language: %s (%s)\n", known_languages[i], lang); |
| 439 |
*/ |
| 440 |
langs = g_slist_append(langs, GINT_TO_POINTER(i - 1)); |
| 441 |
} |
| 442 |
|
| 443 |
g_free(lang); |
| 444 |
} |
| 445 |
|
| 446 |
return langs; |
| 447 |
} |
| 448 |
|
| 449 |
static GtkWidget* |
| 359 |
build_suggestion_menu(GtkSpell *spell, GtkTextBuffer *buffer, |
450 |
build_suggestion_menu(GtkSpell *spell, GtkTextBuffer *buffer, |
| 360 |
const char *word) { |
451 |
const char *word) { |
| 361 |
const char *suggestion; |
452 |
const char *suggestion; |
| 362 |
GtkWidget *topmenu, *menu; |
453 |
GtkWidget *topmenu, *menu; |
| 363 |
GtkWidget *mi; |
454 |
GtkWidget *mi; |
| 364 |
GtkWidget *hbox; |
455 |
GtkWidget *hbox; |
| 365 |
int count = 0; |
|
|
| 366 |
void *spelldata; |
456 |
void *spelldata; |
| 367 |
const AspellWordList *suggestions; |
457 |
char ** suggestions; |
| 368 |
AspellStringEnumeration *elements; |
458 |
size_t n_suggs, i; |
| 369 |
char *label; |
459 |
char *label; |
| 370 |
|
460 |
|
| 371 |
topmenu = menu = gtk_menu_new(); |
461 |
topmenu = menu = gtk_menu_new(); |
| 372 |
|
462 |
|
| 373 |
suggestions = aspell_speller_suggest(spell->speller, word, -1); |
463 |
if (!spell->speller) |
| 374 |
elements = aspell_word_list_elements(suggestions); |
464 |
return topmenu; |
| 375 |
|
465 |
|
| 376 |
suggestion = aspell_string_enumeration_next(elements); |
466 |
suggestions = enchant_dict_suggest(spell->speller, word, strlen(word), &n_suggs); |
| 377 |
if (suggestion == NULL) { |
467 |
|
|
|
468 |
if (suggestions == NULL || !n_suggs) { |
| 378 |
/* no suggestions. put something in the menu anyway... */ |
469 |
/* no suggestions. put something in the menu anyway... */ |
| 379 |
GtkWidget *label; |
470 |
GtkWidget *label; |
| 380 |
label = gtk_label_new(""); |
471 |
label = gtk_label_new(""); |
|
Lines 386-393
build_suggestion_menu(GtkSpell *spell, G
Link Here
|
| 386 |
gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi); |
477 |
gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi); |
| 387 |
} else { |
478 |
} else { |
| 388 |
/* build a set of menus with suggestions. */ |
479 |
/* build a set of menus with suggestions. */ |
| 389 |
while (suggestion != NULL) { |
480 |
for (i = 0; i < n_suggs; i++) { |
| 390 |
if (count == 10) { |
481 |
if (i != 0 && i % 10 == 0) { |
| 391 |
mi = gtk_menu_item_new(); |
482 |
mi = gtk_menu_item_new(); |
| 392 |
gtk_widget_show(mi); |
483 |
gtk_widget_show(mi); |
| 393 |
gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); |
484 |
gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); |
|
Lines 398-417
build_suggestion_menu(GtkSpell *spell, G
Link Here
|
| 398 |
|
489 |
|
| 399 |
menu = gtk_menu_new(); |
490 |
menu = gtk_menu_new(); |
| 400 |
gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); |
491 |
gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); |
| 401 |
count = 0; |
|
|
| 402 |
} |
492 |
} |
| 403 |
mi = gtk_menu_item_new_with_label(suggestion); |
493 |
|
|
|
494 |
mi = gtk_menu_item_new_with_label(suggestions[i]); |
| 404 |
g_signal_connect(G_OBJECT(mi), "activate", |
495 |
g_signal_connect(G_OBJECT(mi), "activate", |
| 405 |
G_CALLBACK(replace_word), spell); |
496 |
G_CALLBACK(replace_word), spell); |
| 406 |
gtk_widget_show(mi); |
497 |
gtk_widget_show(mi); |
| 407 |
gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); |
498 |
gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); |
| 408 |
count++; |
|
|
| 409 |
suggestion = aspell_string_enumeration_next(elements); |
| 410 |
} |
499 |
} |
| 411 |
} |
500 |
} |
| 412 |
|
501 |
|
| 413 |
delete_aspell_string_enumeration(elements); |
|
|
| 414 |
|
| 415 |
/* Separator */ |
502 |
/* Separator */ |
| 416 |
mi = gtk_menu_item_new(); |
503 |
mi = gtk_menu_item_new(); |
| 417 |
gtk_widget_show(mi); |
504 |
gtk_widget_show(mi); |
|
Lines 437-442
build_suggestion_menu(GtkSpell *spell, G
Link Here
|
| 437 |
gtk_widget_show_all(mi); |
524 |
gtk_widget_show_all(mi); |
| 438 |
gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); |
525 |
gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); |
| 439 |
|
526 |
|
|
|
527 |
enchant_dict_free_suggestions(spell->speller, suggestions); |
| 528 |
|
| 440 |
return topmenu; |
529 |
return topmenu; |
| 441 |
} |
530 |
} |
| 442 |
|
531 |
|
|
Lines 515-523
popup_menu_event(GtkTextView *view, GtkS
Link Here
|
| 515 |
|
604 |
|
| 516 |
static gboolean |
605 |
static gboolean |
| 517 |
gtkspell_set_language_internal(GtkSpell *spell, const gchar *lang, GError **error) { |
606 |
gtkspell_set_language_internal(GtkSpell *spell, const gchar *lang, GError **error) { |
| 518 |
AspellConfig *config; |
|
|
| 519 |
AspellCanHaveError *err; |
| 520 |
AspellSpeller *speller; |
| 521 |
|
607 |
|
| 522 |
if (lang == NULL) { |
608 |
if (lang == NULL) { |
| 523 |
lang = g_getenv("LANG"); |
609 |
lang = g_getenv("LANG"); |
|
Lines 529-554
gtkspell_set_language_internal(GtkSpell
Link Here
|
| 529 |
} |
615 |
} |
| 530 |
} |
616 |
} |
| 531 |
|
617 |
|
| 532 |
config = new_aspell_config(); |
618 |
if (!spell->broker) |
| 533 |
if (lang) |
619 |
spell->broker = enchant_broker_init(); |
| 534 |
aspell_config_replace(config, "language-tag", lang); |
|
|
| 535 |
aspell_config_replace(config, "encoding", "utf-8"); |
| 536 |
err = new_aspell_speller(config); |
| 537 |
delete_aspell_config(config); |
| 538 |
|
620 |
|
| 539 |
if (aspell_error_number(err) != 0) { |
621 |
if (spell->speller) { |
| 540 |
#ifdef USING_ASPELL |
622 |
enchant_broker_free_dict(spell->broker, spell->speller); |
| 541 |
g_set_error(error, GTKSPELL_ERROR, GTKSPELL_ERROR_BACKEND, |
623 |
spell->speller = NULL; |
| 542 |
"aspell: %s", aspell_error_message(err)); |
624 |
} |
| 543 |
#elif defined USING_PSPELL |
625 |
|
|
|
626 |
if (!lang) |
| 627 |
lang = "en"; |
| 628 |
|
| 629 |
spell->speller = enchant_broker_request_dict (spell->broker, lang); |
| 630 |
if (!spell->speller) { |
| 544 |
g_set_error(error, GTKSPELL_ERROR, GTKSPELL_ERROR_BACKEND, |
631 |
g_set_error(error, GTKSPELL_ERROR, GTKSPELL_ERROR_BACKEND, |
| 545 |
"pspell: %s", aspell_error_message(err)); |
632 |
_("enchant error for language: %s"), lang); |
| 546 |
#endif |
|
|
| 547 |
return FALSE; |
633 |
return FALSE; |
| 548 |
} |
634 |
} |
| 549 |
if (spell->speller) |
635 |
|
| 550 |
delete_aspell_speller(spell->speller); |
636 |
if (spell->lang) |
| 551 |
spell->speller = to_aspell_speller(err); |
637 |
g_free(spell->lang); |
|
|
638 |
|
| 639 |
spell->lang = g_strdup(lang); |
| 552 |
|
640 |
|
| 553 |
return TRUE; |
641 |
return TRUE; |
| 554 |
} |
642 |
} |
|
Lines 705-712
gtkspell_free(GtkSpell *spell) {
Link Here
|
| 705 |
gtk_text_buffer_delete_mark(buffer, spell->mark_insert_end); |
793 |
gtk_text_buffer_delete_mark(buffer, spell->mark_insert_end); |
| 706 |
gtk_text_buffer_delete_mark(buffer, spell->mark_click); |
794 |
gtk_text_buffer_delete_mark(buffer, spell->mark_click); |
| 707 |
|
795 |
|
| 708 |
delete_aspell_speller(spell->speller); |
796 |
if (spell->broker) { |
|
|
797 |
if (spell->speller) |
| 798 |
enchant_broker_free_dict (spell->broker, spell->speller); |
| 799 |
|
| 800 |
enchant_broker_free (spell->broker); |
| 801 |
} |
| 709 |
|
802 |
|
|
|
803 |
if (spell->lang) { |
| 804 |
g_free(spell->lang); |
| 805 |
} |
| 806 |
|
| 710 |
g_signal_handlers_disconnect_matched(spell->view, |
807 |
g_signal_handlers_disconnect_matched(spell->view, |
| 711 |
G_SIGNAL_MATCH_DATA, |
808 |
G_SIGNAL_MATCH_DATA, |
| 712 |
0, 0, NULL, NULL, |
809 |
0, 0, NULL, NULL, |