fixes for custom headers and displaying of headers
[claws.git] / src / prefs_headers.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program 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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33
34 #include "intl.h"
35 #include "main.h"
36 #include "prefs.h"
37 #include "prefs_headers.h"
38 #include "prefs_common.h"
39 #include "prefs_account.h"
40 #include "mainwindow.h"
41 #include "foldersel.h"
42 #include "manage_window.h"
43 #include "customheader.h"
44 #include "utils.h"
45 #include "gtkutils.h"
46 #include "alertpanel.h"
47 #include "folder.h"
48
49 static struct Headers {
50         GtkWidget *window;
51
52         GtkWidget *close_btn;
53
54         GtkWidget *hdr_combo;
55         GtkWidget *hdr_entry;
56         GtkWidget *key_entry;
57         GtkWidget *headers_clist;
58 } headers;
59
60 /*
61    parameter name, default value, pointer to the prefs variable, data type,
62    pointer to the widget pointer,
63    pointer to the function for data setting,
64    pointer to the function for widget setting
65  */
66
67 #define VSPACING                12
68 #define VSPACING_NARROW         4
69 #define DEFAULT_ENTRY_WIDTH     80
70 #define PREFSBUFSIZE            1024
71
72 /* widget creating functions */
73 static void prefs_headers_create                (void);
74
75 static void prefs_headers_set_dialog    (PrefsAccount * ac);
76 static void prefs_headers_set_list      (PrefsAccount * ac);
77 static gint prefs_headers_clist_set_row (PrefsAccount * ac,
78                                          gint    row);
79
80 /* callback functions */
81 static void prefs_headers_select_dest_cb        (void);
82 static void prefs_headers_register_cb   (void);
83 static void prefs_headers_substitute_cb (void);
84 static void prefs_headers_delete_cb     (void);
85 static void prefs_headers_up            (void);
86 static void prefs_headers_down          (void);
87 static void prefs_headers_select                (GtkCList       *clist,
88                                          gint            row,
89                                          gint            column,
90                                          GdkEvent       *event);
91
92 static void prefs_headers_dest_radio_button_toggled     (void);
93 static void prefs_headers_notrecv_radio_button_toggled  (void);
94
95 static void prefs_headers_key_pressed   (GtkWidget      *widget,
96                                          GdkEventKey    *event,
97                                          gpointer        data);
98 static void prefs_headers_close         (GtkButton      *button);
99
100 static PrefsAccount * cur_ac = NULL;
101
102 void prefs_headers_open(PrefsAccount * ac)
103 {
104         if (!headers.window) {
105                 prefs_headers_create();
106         }
107
108         manage_window_set_transient(GTK_WINDOW(headers.window));
109         gtk_widget_grab_focus(headers.close_btn);
110
111         prefs_headers_set_dialog(ac);
112
113         cur_ac = ac;
114
115         gtk_widget_show(headers.window);
116 }
117
118 static void prefs_headers_create(void)
119 {
120         GtkWidget *window;
121         GtkWidget *vbox;
122         GtkWidget *close_btn;
123         GtkWidget *confirm_area;
124
125         GtkWidget *vbox1;
126
127         GtkWidget *table1;
128         GtkWidget *hdr_label;
129         GtkWidget *hdr_combo;
130         GtkWidget *key_label;
131         GtkWidget *key_entry;
132
133         GtkWidget *reg_hbox;
134         GtkWidget *btn_hbox;
135         GtkWidget *arrow;
136         GtkWidget *reg_btn;
137         GtkWidget *subst_btn;
138         GtkWidget *del_btn;
139
140         GtkWidget *ch_hbox;
141         GtkWidget *ch_scrolledwin;
142         GtkWidget *headers_clist;
143
144         GtkWidget *btn_vbox;
145         GtkWidget *up_btn;
146         GtkWidget *down_btn;
147
148         gchar *title[] = {_("Custom headers")};
149
150         debug_print(_("Creating headers setting window...\n"));
151
152         window = gtk_window_new (GTK_WINDOW_DIALOG);
153         gtk_container_set_border_width (GTK_CONTAINER (window), 8);
154         gtk_window_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
155         gtk_window_set_modal (GTK_WINDOW (window), TRUE);
156         gtk_window_set_policy (GTK_WINDOW (window), FALSE, TRUE, FALSE);
157
158         vbox = gtk_vbox_new (FALSE, 6);
159         gtk_widget_show (vbox);
160         gtk_container_add (GTK_CONTAINER (window), vbox);
161
162         gtkut_button_set_create (&confirm_area, &close_btn, _("Close"),
163                                  NULL, NULL, NULL, NULL);
164         gtk_widget_show (confirm_area);
165         gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
166         gtk_widget_grab_default (close_btn);
167
168         gtk_window_set_title (GTK_WINDOW(window),
169                               _("Headers setting"));
170         gtk_signal_connect (GTK_OBJECT(window), "delete_event",
171                             GTK_SIGNAL_FUNC(gtk_widget_hide_on_delete), NULL);
172         gtk_signal_connect (GTK_OBJECT(window), "key_press_event",
173                             GTK_SIGNAL_FUNC(prefs_headers_key_pressed), NULL);
174         gtk_signal_connect (GTK_OBJECT(window), "focus_in_event",
175                             GTK_SIGNAL_FUNC(manage_window_focus_in), NULL);
176         gtk_signal_connect (GTK_OBJECT(window), "focus_out_event",
177                             GTK_SIGNAL_FUNC(manage_window_focus_out), NULL);
178         gtk_signal_connect (GTK_OBJECT(close_btn), "clicked",
179                             GTK_SIGNAL_FUNC(prefs_headers_close), NULL);
180
181         vbox1 = gtk_vbox_new (FALSE, VSPACING);
182         gtk_widget_show (vbox1);
183         gtk_box_pack_start (GTK_BOX (vbox), vbox1, TRUE, TRUE, 0);
184         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2);
185
186         table1 = gtk_table_new (2, 2, FALSE);
187         gtk_widget_show (table1);
188         gtk_box_pack_start (GTK_BOX (vbox1), table1,
189                             FALSE, FALSE, 0);
190         gtk_table_set_row_spacings (GTK_TABLE (table1), 8);
191         gtk_table_set_col_spacings (GTK_TABLE (table1), 8);
192
193         hdr_label = gtk_label_new (_("Header"));
194         gtk_widget_show (hdr_label);
195         gtk_table_attach (GTK_TABLE (table1), hdr_label, 0, 1, 0, 1,
196                           GTK_EXPAND | GTK_SHRINK | GTK_FILL,
197                           0, 0, 0);
198         gtk_misc_set_alignment (GTK_MISC (hdr_label), 0, 0.5);
199         
200         hdr_combo = gtk_combo_new ();
201         gtk_widget_show (hdr_combo);
202         gtk_table_attach (GTK_TABLE (table1), hdr_combo, 0, 1, 1, 2,
203                           GTK_EXPAND | GTK_SHRINK | GTK_FILL,
204                           0, 0, 0);
205         gtk_widget_set_usize (hdr_combo, 150 /* 96 */, -1);
206         gtkut_combo_set_items (GTK_COMBO (hdr_combo),
207                                "User-Agent", "X-Operating-System", NULL);
208
209         key_label = gtk_label_new (_("Value"));
210         gtk_widget_show (key_label);
211         gtk_table_attach (GTK_TABLE (table1), key_label, 1, 2, 0, 1,
212                           GTK_EXPAND | GTK_SHRINK | GTK_FILL,
213                           0, 0, 0);
214         gtk_misc_set_alignment (GTK_MISC (key_label), 0, 0.5);
215         
216         key_entry = gtk_entry_new ();
217         gtk_widget_show (key_entry);
218         gtk_table_attach (GTK_TABLE (table1), key_entry, 1, 2, 1, 2,
219                           GTK_EXPAND | GTK_SHRINK | GTK_FILL,
220                           0, 0, 0);
221
222         /* register / substitute / delete */
223
224         reg_hbox = gtk_hbox_new (FALSE, 4);
225         gtk_widget_show (reg_hbox);
226         gtk_box_pack_start (GTK_BOX (vbox1), reg_hbox,
227                             FALSE, FALSE, 0);
228
229         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
230         gtk_widget_show (arrow);
231         gtk_box_pack_start (GTK_BOX (reg_hbox), arrow, FALSE, FALSE, 0);
232         gtk_widget_set_usize (arrow, -1, 16);
233
234         btn_hbox = gtk_hbox_new (TRUE, 4);
235         gtk_widget_show (btn_hbox);
236         gtk_box_pack_start (GTK_BOX (reg_hbox), btn_hbox, FALSE, FALSE, 0);
237
238         reg_btn = gtk_button_new_with_label (_("Add"));
239         gtk_widget_show (reg_btn);
240         gtk_box_pack_start (GTK_BOX (btn_hbox), reg_btn, FALSE, TRUE, 0);
241         gtk_signal_connect (GTK_OBJECT (reg_btn), "clicked",
242                             GTK_SIGNAL_FUNC (prefs_headers_register_cb), NULL);
243
244         subst_btn = gtk_button_new_with_label (_(" Substitute "));
245         gtk_widget_show (subst_btn);
246         gtk_box_pack_start (GTK_BOX (btn_hbox), subst_btn, FALSE, TRUE, 0);
247         gtk_signal_connect (GTK_OBJECT (subst_btn), "clicked",
248                             GTK_SIGNAL_FUNC (prefs_headers_substitute_cb),
249                             NULL);
250
251         del_btn = gtk_button_new_with_label (_("Delete"));
252         gtk_widget_show (del_btn);
253         gtk_box_pack_start (GTK_BOX (btn_hbox), del_btn, FALSE, TRUE, 0);
254         gtk_signal_connect (GTK_OBJECT (del_btn), "clicked",
255                             GTK_SIGNAL_FUNC (prefs_headers_delete_cb), NULL);
256
257
258         ch_hbox = gtk_hbox_new (FALSE, 8);
259         gtk_widget_show (ch_hbox);
260         gtk_box_pack_start (GTK_BOX (vbox1), ch_hbox,
261                             TRUE, TRUE, 0);
262
263         ch_scrolledwin = gtk_scrolled_window_new (NULL, NULL);
264         gtk_widget_set_usize (ch_scrolledwin, -1, 100);
265         gtk_widget_show (ch_scrolledwin);
266         gtk_box_pack_start (GTK_BOX (ch_hbox), ch_scrolledwin,
267                             TRUE, TRUE, 0);
268         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ch_scrolledwin),
269                                         GTK_POLICY_AUTOMATIC,
270                                         GTK_POLICY_AUTOMATIC);
271
272         headers_clist = gtk_clist_new_with_titles(1, title);
273         gtk_widget_show (headers_clist);
274         gtk_container_add (GTK_CONTAINER (ch_scrolledwin), headers_clist);
275         gtk_clist_set_column_width (GTK_CLIST (headers_clist), 0, 80);
276         gtk_clist_set_selection_mode (GTK_CLIST (headers_clist),
277                                       GTK_SELECTION_BROWSE);
278         GTK_WIDGET_UNSET_FLAGS (GTK_CLIST (headers_clist)->column[0].button,
279                                 GTK_CAN_FOCUS);
280         gtk_signal_connect (GTK_OBJECT (headers_clist), "select_row",
281                             GTK_SIGNAL_FUNC (prefs_headers_select),
282                             NULL);
283
284
285         btn_vbox = gtk_vbox_new (FALSE, 8);
286         gtk_widget_show (btn_vbox);
287         gtk_box_pack_start (GTK_BOX (ch_hbox), btn_vbox, FALSE, FALSE, 0);
288
289         up_btn = gtk_button_new_with_label (_("Up"));
290         gtk_widget_show (up_btn);
291         gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0);
292         gtk_signal_connect (GTK_OBJECT (up_btn), "clicked",
293                             GTK_SIGNAL_FUNC (prefs_headers_up), NULL);
294
295         down_btn = gtk_button_new_with_label (_("Down"));
296         gtk_widget_show (down_btn);
297         gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0);
298         gtk_signal_connect (GTK_OBJECT (down_btn), "clicked",
299                             GTK_SIGNAL_FUNC (prefs_headers_down), NULL);
300
301
302         gtk_widget_show_all(window);
303
304         headers.window    = window;
305         headers.close_btn = close_btn;
306
307         headers.hdr_combo  = hdr_combo;
308         headers.hdr_entry  = GTK_COMBO (hdr_combo)->entry;
309         headers.key_entry  = key_entry;
310         headers.headers_clist   = headers_clist;
311 }
312
313 void prefs_headers_read_config(PrefsAccount * ac)
314 {
315         gchar *rcpath;
316         FILE *fp;
317         gchar buf[PREFSBUFSIZE];
318         CustomHeader *ch;
319
320         debug_print(_("Reading headers configuration...\n"));
321
322         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, HEADERS_RC, NULL);
323         if ((fp = fopen(rcpath, "r")) == NULL) {
324                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
325                 g_free(rcpath);
326                 ac->customhdr_list = NULL;
327                 return;
328         }
329         g_free(rcpath);
330
331         /* remove all previous headers list */
332         while (ac->customhdr_list != NULL) {
333                 ch = (CustomHeader *)ac->customhdr_list->data;
334                 custom_header_free(ch);
335                 ac->customhdr_list = g_slist_remove(ac->customhdr_list, ch);
336         }
337  
338         while (fgets(buf, sizeof(buf), fp) != NULL) {
339                 g_strchomp(buf);
340                 ch = custom_header_read_str(buf);
341                 if (ch) {
342                         if (ch->account_id == ac->account_id)
343                                 ac->customhdr_list =
344                                         g_slist_append(ac->customhdr_list, ch);
345                         else
346                                 custom_header_free(ch);
347                 }
348         }
349  
350         fclose(fp);
351 }
352
353 void prefs_headers_write_config(PrefsAccount * ac)
354 {
355         gchar *rcpath;
356         PrefFile *pfile;
357         GSList *cur;
358         gchar buf[PREFSBUFSIZE];
359         FILE * fp;
360         CustomHeader *ch;
361
362         GSList *all_hdrs = NULL;
363
364         debug_print(_("Writing headers configuration...\n"));
365
366         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, HEADERS_RC, NULL);
367
368         if ((fp = fopen(rcpath, "r")) == NULL) {
369                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
370         }
371         else {
372                 all_hdrs = NULL;
373
374                 while (fgets(buf, sizeof(buf), fp) != NULL) {
375                         g_strchomp(buf);
376                         ch = custom_header_read_str(buf);
377                         if (ch) {
378                                 if (ch->account_id != ac->account_id)
379                                         all_hdrs =
380                                                 g_slist_append(all_hdrs, ch);
381                                 else
382                                         custom_header_free(ch);
383                         }
384                 }
385
386                 fclose(fp);
387         }
388
389         if ((pfile = prefs_write_open(rcpath)) == NULL) {
390                 g_warning(_("failed to write configuration to file\n"));
391                 g_free(rcpath);
392                 return;
393         }
394
395         for (cur = all_hdrs; cur != NULL; cur = cur->next) {
396                 CustomHeader *hdr = (CustomHeader *)cur->data;
397                 gchar *chstr;
398
399                 chstr = custom_header_get_str(hdr);
400                 if (fputs(chstr, pfile->fp) == EOF ||
401                     fputc('\n', pfile->fp) == EOF) {
402                         FILE_OP_ERROR(rcpath, "fputs || fputc");
403                         prefs_write_close_revert(pfile);
404                         g_free(rcpath);
405                         g_free(chstr);
406                         return;
407                 }
408                 g_free(chstr);
409         }
410
411         for (cur = ac->customhdr_list; cur != NULL; cur = cur->next) {
412                 CustomHeader *hdr = (CustomHeader *)cur->data;
413                 gchar *chstr;
414
415                 chstr = custom_header_get_str(hdr);
416                 if (fputs(chstr, pfile->fp) == EOF ||
417                     fputc('\n', pfile->fp) == EOF) {
418                         FILE_OP_ERROR(rcpath, "fputs || fputc");
419                         prefs_write_close_revert(pfile);
420                         g_free(rcpath);
421                         g_free(chstr);
422                         return;
423                 }
424                 g_free(chstr);
425         }
426
427         g_free(rcpath);
428
429         while (all_hdrs != NULL) {
430                 ch = (CustomHeader *)all_hdrs->data;
431                 custom_header_free(ch);
432                 all_hdrs = g_slist_remove(all_hdrs, ch);
433         }
434
435         if (prefs_write_close(pfile) < 0) {
436                 g_warning(_("failed to write configuration to file\n"));
437                 return;
438         }
439 }
440
441 static void prefs_headers_set_dialog(PrefsAccount * ac)
442 {
443         GtkCList *clist = GTK_CLIST(headers.headers_clist);
444         GSList *cur;
445         gchar *ch_str[1];
446         gint row;
447
448         gtk_clist_freeze(clist);
449         gtk_clist_clear(clist);
450
451         ch_str[0] = _("(New)");
452         row = gtk_clist_append(clist, ch_str);
453         gtk_clist_set_row_data(clist, row, NULL);
454
455         for (cur = ac->customhdr_list; cur != NULL; cur = cur->next) {
456                 CustomHeader *ch = (CustomHeader *)cur->data;
457
458                 ch_str[0] = g_strdup_printf("%s: %s", ch->name, ch->value);
459                 row = gtk_clist_append(clist, ch_str);
460                 gtk_clist_set_row_data(clist, row, ch);
461
462                 g_free(ch_str[0]);
463         }
464
465         gtk_clist_thaw(clist);
466 }
467
468 static void prefs_headers_set_list(PrefsAccount * ac)
469 {
470         gint row = 1;
471         CustomHeader *ch;
472
473         g_slist_free(ac->customhdr_list);
474         ac->customhdr_list = NULL;
475
476         while ((ch = gtk_clist_get_row_data(GTK_CLIST(headers.headers_clist),
477                 row)) != NULL) {
478                 ac->customhdr_list = g_slist_append(ac->customhdr_list,
479                                                       ch);
480                 row++;
481         }
482 }
483
484 #define GET_ENTRY(entry) \
485         entry_text = gtk_entry_get_text(GTK_ENTRY(entry))
486
487 static gint prefs_headers_clist_set_row(PrefsAccount * ac, gint row)
488 {
489         GtkCList *clist = GTK_CLIST(headers.headers_clist);
490         CustomHeader *ch;
491         gchar *entry_text;
492         gchar *ch_str[1];
493
494         g_return_val_if_fail(row != 0, -1);
495
496         GET_ENTRY(headers.hdr_entry);
497         if (entry_text[0] == '\0') {
498                 alertpanel_error(_("Header name is not set."));
499                 return -1;
500         }
501
502         ch = g_new0(CustomHeader, 1);
503
504         ch->account_id = ac->account_id;
505
506         ch->name = g_strdup(entry_text);
507
508         GET_ENTRY(headers.key_entry);
509         if (entry_text[0] != '\0')
510                 ch->value = g_strdup(entry_text);
511
512         ch_str[0] = g_strdup_printf("%s: %s", ch->name, ch->value);
513
514         if (row < 0)
515                 row = gtk_clist_append(clist, ch_str);
516         else {
517                 CustomHeader *tmpch;
518
519                 gtk_clist_set_text(clist, row, 0, ch_str[0]);
520                 tmpch = gtk_clist_get_row_data(clist, row);
521                 if (tmpch)
522                         custom_header_free(tmpch);
523         }
524
525         gtk_clist_set_row_data(clist, row, ch);
526
527         g_free(ch_str[0]);
528
529         prefs_headers_set_list(cur_ac);
530
531         return row;
532 }
533
534 static void prefs_headers_register_cb(void)
535 {
536         prefs_headers_clist_set_row(cur_ac, -1);
537 }
538
539 static void prefs_headers_substitute_cb(void)
540 {
541         GtkCList *clist = GTK_CLIST(headers.headers_clist);
542         CustomHeader *ch;
543         gint row;
544
545         if (!clist->selection) return;
546
547         row = GPOINTER_TO_INT(clist->selection->data);
548         if (row == 0) return;
549
550         ch = gtk_clist_get_row_data(clist, row);
551         if (!ch) return;
552
553         prefs_headers_clist_set_row(cur_ac, row);
554 }
555
556 static void prefs_headers_delete_cb(void)
557 {
558         GtkCList *clist = GTK_CLIST(headers.headers_clist);
559         CustomHeader *ch;
560         gint row;
561
562         if (!clist->selection) return;
563         row = GPOINTER_TO_INT(clist->selection->data);
564         if (row == 0) return;
565
566         if (alertpanel(_("Delete header"),
567                        _("Do you really want to delete this header?"),
568                        _("Yes"), _("No"), NULL) == G_ALERTALTERNATE)
569                 return;
570
571         ch = gtk_clist_get_row_data(clist, row);
572         custom_header_free(ch);
573         gtk_clist_remove(clist, row);
574         cur_ac->customhdr_list = g_slist_remove(cur_ac->customhdr_list, ch);
575 }
576
577 static void prefs_headers_up(void)
578 {
579         GtkCList *clist = GTK_CLIST(headers.headers_clist);
580         gint row;
581
582         if (!clist->selection) return;
583
584         row = GPOINTER_TO_INT(clist->selection->data);
585         if (row > 1) {
586                 gtk_clist_row_move(clist, row, row - 1);
587                 prefs_headers_set_list(cur_ac);
588         }
589 }
590
591 static void prefs_headers_down(void)
592 {
593         GtkCList *clist = GTK_CLIST(headers.headers_clist);
594         gint row;
595
596         if (!clist->selection) return;
597
598         row = GPOINTER_TO_INT(clist->selection->data);
599         if (row > 0 && row < clist->rows - 1) {
600                 gtk_clist_row_move(clist, row, row + 1);
601                 prefs_headers_set_list(cur_ac);
602         }
603 }
604
605 #define ENTRY_SET_TEXT(entry, str) \
606         gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "")
607
608 static void prefs_headers_select(GtkCList *clist, gint row, gint column,
609                                 GdkEvent *event)
610 {
611         CustomHeader *ch;
612         CustomHeader default_ch = { "", NULL };
613  
614         ch = gtk_clist_get_row_data(clist, row);
615         if (!ch)
616                 ch = &default_ch;
617  
618         ENTRY_SET_TEXT(headers.hdr_entry, ch->name);
619         ENTRY_SET_TEXT(headers.key_entry, ch->value);
620 }
621
622 static void prefs_headers_key_pressed(GtkWidget *widget, GdkEventKey *event,
623                                      gpointer data)
624 {
625         if (event && event->keyval == GDK_Escape)
626                 gtk_widget_hide(headers.window);
627 }
628
629 static void prefs_headers_close(GtkButton *button)
630 {
631         prefs_headers_write_config(cur_ac);
632         gtk_widget_hide(headers.window);
633 }