1 /* vim: set sw=2 ts=2 sts=2 et: */
2 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /*
4 * autoar-gtk-chooser.c
5 * GTK+ widgets to choose archive format and filter
6 *
7 * Copyright (C) 2013, 2014 Ting-Wei Lan
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this program; if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 */
25
26 #include "config.h"
27
28 #include "autoar-gtk-chooser.h"
29
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gtk/gtk.h>
33
34
35 /**
36 * SECTION:autoar-gtk-chooser
37 * @Short_description: GTK+ widgets to choose archive format and filter
38 * @Title: autoar-gtk-chooser
39 * @Include: gnome-autoar/autoar-gtk.h
40 *
41 * autoar-gtk-chooser contains two widgets for users to choose preferred
42 * archive format and filter.
43 **/
44
45 /* autoar_gtk_chooser_simple */
46
47 enum
48 {
49 SIMPLE_COL_FORMAT,
50 SIMPLE_COL_FILTER,
51 SIMPLE_COL_DESCRIPTION,
52 N_SIMPLE_COLS
53 };
54
55 static char*
format_filter_full_description(AutoarFormat format,AutoarFilter filter)56 format_filter_full_description (AutoarFormat format,
57 AutoarFilter filter)
58 {
59 char *description, *extension, *full_description;
60
61 description = autoar_format_filter_get_description (format, filter);
62 extension = autoar_format_filter_get_extension (format, filter);
63 full_description = g_strdup_printf ("%s (*%s)", description, extension);
64
65 g_free (description);
66 g_free (extension);
67
68 return full_description;
69 }
70
71 static gboolean
simple_row_separator_cb(GtkTreeModel * model,GtkTreeIter * iter,void * data)72 simple_row_separator_cb (GtkTreeModel *model,
73 GtkTreeIter *iter,
74 void *data)
75 {
76 char *description, first_char;
77
78 gtk_tree_model_get (model, iter, SIMPLE_COL_DESCRIPTION, &description, -1);
79 first_char = description != NULL ? *description : '\0';
80 g_free (description);
81
82 if (first_char == '\0')
83 return TRUE;
84 else
85 return FALSE;
86 }
87
88 static void
simple_get_variable_row(GtkTreeModel * model,GtkTreeIter * base_iter,GtkTreeIter * dest_iter)89 simple_get_variable_row (GtkTreeModel *model,
90 GtkTreeIter *base_iter,
91 GtkTreeIter *dest_iter)
92 {
93 GtkTreeIter iter;
94
95 iter = *base_iter;
96 if (!gtk_tree_model_iter_previous (model, &iter) ||
97 !gtk_tree_model_iter_previous (model, &iter) ||
98 !simple_row_separator_cb (model, &iter, NULL)) {
99 /* Create two new rows if it does not exist */
100 GtkListStore *store;
101
102 store = GTK_LIST_STORE (model);
103 gtk_list_store_insert_before (store, dest_iter, base_iter);
104 gtk_list_store_insert_before (store, &iter, dest_iter);
105 gtk_list_store_set (store, &iter,
106 SIMPLE_COL_FORMAT, 0,
107 SIMPLE_COL_FILTER, 0,
108 SIMPLE_COL_DESCRIPTION, "", -1);
109 } else {
110 /* Use the existing row */
111
112 iter = *base_iter;
113 gtk_tree_model_iter_previous (model, &iter);
114 *dest_iter = iter;
115 }
116 }
117
118 static void
simple_set_active(GtkComboBox * simple,GtkTreeModel * model,AutoarFormat format,AutoarFilter filter)119 simple_set_active (GtkComboBox *simple,
120 GtkTreeModel *model,
121 AutoarFormat format,
122 AutoarFilter filter)
123 {
124 GtkTreeIter iter, prev;
125 AutoarFormat this_format;
126 AutoarFilter this_filter;
127 int get_format, get_filter;
128 int *previous;
129
130 previous = g_object_get_data ((GObject*)simple, "previous");
131 if (autoar_format_is_valid (format) && autoar_filter_is_valid (filter)) {
132 gtk_tree_model_get_iter_first (model, &iter);
133 do {
134 gtk_tree_model_get (model, &iter,
135 SIMPLE_COL_FORMAT, &this_format,
136 SIMPLE_COL_FILTER, &this_filter, -1);
137 if (this_format == format && this_filter == filter) {
138 gtk_combo_box_set_active_iter (simple, &iter);
139 previous[0] = format;
140 previous[1] = filter;
141 return;
142 }
143 prev = iter;
144 } while (gtk_tree_model_iter_next (model, &iter));
145
146 if (gtk_tree_model_iter_previous (model, &prev)) {
147 GtkTreeIter active;
148 char *description_string;
149
150 simple_get_variable_row (model, &prev, &active);
151 description_string = format_filter_full_description (format, filter);
152 gtk_list_store_set (GTK_LIST_STORE (model), &active,
153 SIMPLE_COL_FORMAT, format,
154 SIMPLE_COL_FILTER, filter,
155 SIMPLE_COL_DESCRIPTION, description_string, -1);
156 g_free (description_string);
157
158 gtk_combo_box_set_active_iter (simple, &active);
159 previous[0] = format;
160 previous[1] = filter;
161 return;
162 }
163 }
164
165 gtk_tree_model_get_iter_first (model, &iter);
166 gtk_combo_box_set_active_iter (simple, &iter);
167 gtk_tree_model_get (model, &iter,
168 SIMPLE_COL_FORMAT, &get_format,
169 SIMPLE_COL_FILTER, &get_filter, -1);
170 previous[0] = format;
171 previous[1] = filter;
172 }
173
174 static void
simple_changed_cb(GtkComboBox * simple,void * data)175 simple_changed_cb (GtkComboBox *simple,
176 void *data) {
177 GtkTreeIter iter;
178 GtkTreeModel* model;
179 int format, filter;
180 int *previous;
181
182 if (!gtk_combo_box_get_active_iter (simple, &iter))
183 return;
184
185 model = gtk_combo_box_get_model (simple);
186 gtk_tree_model_get (model, &iter,
187 SIMPLE_COL_FORMAT, &format,
188 SIMPLE_COL_FILTER, &filter, -1);
189
190 previous = g_object_get_data ((GObject*)simple, "previous");
191
192 if (!format) {
193 GtkWidget *dialog_widget;
194 GtkDialog *dialog;
195 GtkWidget *dialog_content;
196
197 GtkWidget *simple_widget;
198 GtkWidget *advanced_widget;
199 int response;
200
201 simple_widget = GTK_WIDGET (simple);
202 dialog_widget =
203 gtk_dialog_new_with_buttons (
204 _("Choose an archive format"),
205 GTK_WINDOW (gtk_widget_get_ancestor (simple_widget, GTK_TYPE_WINDOW)),
206 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
207 _("Cancel"), GTK_RESPONSE_CANCEL,
208 _("OK"), GTK_RESPONSE_ACCEPT, NULL);
209 dialog = GTK_DIALOG (dialog_widget);
210 gtk_dialog_set_default_response (dialog, GTK_RESPONSE_ACCEPT);
211
212 dialog_content = gtk_dialog_get_content_area (dialog);
213 advanced_widget = autoar_gtk_chooser_advanced_new (previous[0],
214 previous[1]);
215 gtk_container_add (GTK_CONTAINER (dialog_content), advanced_widget);
216 gtk_widget_show_all (dialog_widget);
217
218 response = gtk_dialog_run (dialog);
219 if (response == GTK_RESPONSE_ACCEPT &&
220 gtk_tree_model_iter_previous (model, &iter) &&
221 autoar_gtk_chooser_advanced_get (advanced_widget, &format, &filter))
222 simple_set_active (simple, model, format, filter);
223 else
224 simple_set_active (simple, model, previous[0], previous[1]);
225
226 gtk_widget_destroy (dialog_widget);
227 } else {
228 previous[0] = format;
229 previous[1] = filter;
230 }
231 }
232
233 /**
234 * autoar_gtk_chooser_simple_new:
235 * @default_format: an #AutoarFormat
236 * @default_filter: an #AutoarFilter
237 *
238 * Create a #GtkComboBox with a list of common archive format. There is also
239 * an option called "Other format…", which will use
240 * autoar_gtk_chooser_advanced_new() and
241 * autoar_gtk_chooser_advanced_get() to select less common archive
242 * format. Arguments @default_format and @default_filter are the default archive
243 * format selected on the returned widget. You may want to get the preferred
244 * format of users using autoar_pref_get_default_format() and
245 * autoar_pref_get_default_filter(), or just set them to 1 to select
246 * the default archive format.
247 *
248 * Returns: (transfer full): a new #GtkComboBox widget
249 **/
250 GtkWidget*
autoar_gtk_chooser_simple_new(AutoarFormat default_format,AutoarFilter default_filter)251 autoar_gtk_chooser_simple_new (AutoarFormat default_format,
252 AutoarFilter default_filter)
253 {
254 GtkWidget *simple_widget;
255 GtkComboBox *simple_combo;
256 GtkCellLayout *simple;
257 GtkCellRenderer *cell_renderer;
258
259 GtkTreeModel *model;
260 GtkListStore *store;
261 GtkTreeIter iter;
262 int i;
263
264 int *previous;
265
266 struct format_filter
267 {
268 AutoarFormat format;
269 AutoarFilter filter;
270 };
271
272 struct format_filter defaults [] = {
273 { AUTOAR_FORMAT_ZIP, AUTOAR_FILTER_NONE },
274 { AUTOAR_FORMAT_TAR, AUTOAR_FILTER_NONE },
275 { AUTOAR_FORMAT_TAR, AUTOAR_FILTER_GZIP },
276 { AUTOAR_FORMAT_TAR, AUTOAR_FILTER_BZIP2 },
277 { AUTOAR_FORMAT_TAR, AUTOAR_FILTER_XZ },
278 { AUTOAR_FORMAT_CPIO, AUTOAR_FILTER_NONE },
279 { AUTOAR_FORMAT_7ZIP, AUTOAR_FILTER_NONE },
280 };
281
282 store = gtk_list_store_new (N_SIMPLE_COLS, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
283 model = GTK_TREE_MODEL (store);
284 for (i = 0; i < sizeof (defaults) / sizeof (struct format_filter); i++) {
285 char *description;
286
287 gtk_list_store_append (store, &iter);
288
289 description = format_filter_full_description (defaults[i].format,
290 defaults[i].filter);
291 gtk_list_store_set (store, &iter,
292 SIMPLE_COL_FORMAT, defaults[i].format,
293 SIMPLE_COL_FILTER, defaults[i].filter,
294 SIMPLE_COL_DESCRIPTION, description, -1);
295 g_free (description);
296 }
297
298 gtk_list_store_append (store, &iter);
299 gtk_list_store_set (store, &iter,
300 SIMPLE_COL_FORMAT, 0,
301 SIMPLE_COL_FILTER, 0,
302 SIMPLE_COL_DESCRIPTION, "", -1);
303
304 gtk_list_store_append (store, &iter);
305 gtk_list_store_set (store, &iter,
306 SIMPLE_COL_FORMAT, 0,
307 SIMPLE_COL_FILTER, 0,
308 SIMPLE_COL_DESCRIPTION, _("Other format…"), -1);
309
310 simple_widget = gtk_combo_box_new_with_model (model);
311 simple = GTK_CELL_LAYOUT (simple_widget);
312 simple_combo = GTK_COMBO_BOX (simple_widget);
313 cell_renderer = gtk_cell_renderer_text_new ();
314
315 gtk_cell_layout_pack_start (simple, cell_renderer, FALSE);
316 gtk_cell_layout_add_attribute (simple, cell_renderer, "text", SIMPLE_COL_DESCRIPTION);
317
318 previous = g_new (int, 2);
319 g_object_set_data_full ((GObject*)simple, "previous", previous, g_free);
320 simple_set_active (simple_combo, model, default_format, default_filter);
321
322 gtk_combo_box_set_row_separator_func (simple_combo, simple_row_separator_cb, NULL, NULL);
323 g_signal_connect (simple, "changed", G_CALLBACK (simple_changed_cb), NULL);
324
325 g_object_unref (store);
326
327 return simple_widget;
328 }
329
330 /**
331 * autoar_gtk_chooser_simple_get:
332 * @simple: a #GtkComboBox returned by autoar_gtk_chooser_simple_new()
333 * @format: the place to store the #AutoarFormat selected by the user
334 * @filter: the place to store the #AutoarFilter selected by the user
335 *
336 * Gets the selected archive format of the widget created by
337 * autoar_gtk_chooser_simple_new().
338 *
339 * Returns: %TRUE if @format and @filter are set. %FALSE if there is no
340 * selected item on @simple, so @format and @filter are not modified.
341 **/
342 gboolean
autoar_gtk_chooser_simple_get(GtkWidget * simple,int * format,int * filter)343 autoar_gtk_chooser_simple_get (GtkWidget *simple,
344 int *format,
345 int *filter)
346 {
347 GtkComboBox *combo;
348 GtkTreeModel *model;
349 GtkTreeIter iter;
350
351 if (!gtk_combo_box_get_active_iter (combo = GTK_COMBO_BOX (simple), &iter))
352 return FALSE;
353
354 model = gtk_combo_box_get_model (combo);
355 gtk_tree_model_get (model, &iter,
356 SIMPLE_COL_FORMAT, format,
357 SIMPLE_COL_FILTER, filter, -1);
358 return TRUE;
359 }
360
361
362 /* autoar_gtk_chooser_advanced */
363
364 enum
365 {
366 ADVANCED_FORMAT_COL_FORMAT,
367 ADVANCED_FORMAT_COL_DESCRIPTION,
368 N_ADVANCED_FORMAT_COLS
369 };
370
371 enum
372 {
373 ADVANCED_FILTER_COL_FILTER,
374 ADVANCED_FILTER_COL_DESCRIPTION,
375 N_ADVANCED_FILTER_COLS
376 };
377
378 static void
advanced_update_description_cb(GtkTreeView * unused_variable,GtkWidget * advanced)379 advanced_update_description_cb (GtkTreeView *unused_variable,
380 GtkWidget *advanced)
381 {
382 int format;
383 int filter;
384 GtkLabel *description;
385 char *description_string;
386
387 if (!autoar_gtk_chooser_advanced_get (advanced, &format, &filter))
388 return;
389
390 description = GTK_LABEL (gtk_grid_get_child_at (GTK_GRID (advanced), 0, 1));
391 description_string = format_filter_full_description (format, filter);
392 gtk_label_set_text (description, description_string);
393 g_free (description_string);
394 }
395
396 static GtkTreeModel*
advanced_format_store(void)397 advanced_format_store (void)
398 {
399 GtkListStore *store;
400 int i, last;
401
402 store = gtk_list_store_new (N_ADVANCED_FORMAT_COLS, G_TYPE_INT, G_TYPE_STRING);
403 last = autoar_format_last ();
404 for (i = 1; i < last; i++) {
405 GtkTreeIter iter;
406 const char *description;
407
408 gtk_list_store_append (store, &iter);
409 description = autoar_format_get_description (i);
410 gtk_list_store_set (store, &iter,
411 ADVANCED_FORMAT_COL_FORMAT, i,
412 ADVANCED_FORMAT_COL_DESCRIPTION, description, -1);
413 }
414
415 return GTK_TREE_MODEL (store);
416 }
417
418 static GtkTreeModel*
advanced_filter_store(void)419 advanced_filter_store (void)
420 {
421 GtkListStore *store;
422 int i, last;
423
424 store = gtk_list_store_new (N_ADVANCED_FILTER_COLS, G_TYPE_INT, G_TYPE_STRING);
425 last = autoar_filter_last ();
426 for (i = 1; i < last; i++) {
427 GtkTreeIter iter;
428 const char *description;
429
430 gtk_list_store_append (store, &iter);
431 description = autoar_filter_get_description (i);
432 gtk_list_store_set (store, &iter,
433 ADVANCED_FILTER_COL_FILTER, i,
434 ADVANCED_FILTER_COL_DESCRIPTION, description, -1);
435 }
436
437 return GTK_TREE_MODEL (store);
438 }
439
440 /**
441 * autoar_gtk_chooser_advanced_new:
442 * @default_format: an #AutoarFormat
443 * @default_filter: an #AutoarFilter
444 *
445 * Create a #GtkGrid with two lists. One list shows all available formats,
446 * and the other list shows all available filters.
447 *
448 * Returns: (transfer full): a new #GtkGrid widget
449 **/
450 GtkWidget*
autoar_gtk_chooser_advanced_new(AutoarFormat default_format,AutoarFilter default_filter)451 autoar_gtk_chooser_advanced_new (AutoarFormat default_format,
452 AutoarFilter default_filter)
453 {
454 GtkWidget *advanced_widget;
455 GtkGrid *advanced;
456
457 GtkTreeModel *format_model;
458 GtkWidget *format_widget;
459 GtkTreeView *format;
460 GtkTreeSelection *format_selection;
461 GtkCellRenderer *format_renderer;
462 GtkTreePath *format_path;
463
464 GtkTreeModel *filter_model;
465 GtkWidget *filter_widget;
466 GtkTreeView *filter;
467 GtkTreeSelection *filter_selection;
468 GtkCellRenderer *filter_renderer;
469 GtkTreePath *filter_path;
470
471 GtkWidget *description_widget;
472 GtkLabel *description;
473
474 advanced_widget = gtk_grid_new ();
475 advanced = GTK_GRID (advanced_widget);
476 gtk_grid_set_row_spacing (advanced, 5);
477 gtk_grid_set_column_spacing (advanced, 5);
478 gtk_grid_set_column_homogeneous (advanced, TRUE);
479
480 format_model = advanced_format_store ();
481 format_widget = gtk_tree_view_new_with_model (format_model);
482 format = GTK_TREE_VIEW (format_widget);
483 format_selection = gtk_tree_view_get_selection (format);
484 format_renderer = gtk_cell_renderer_text_new ();
485 gtk_tree_selection_set_mode (format_selection, GTK_SELECTION_SINGLE);
486 gtk_tree_view_insert_column_with_attributes (format, -1, _("Format"),
487 format_renderer, "text",
488 ADVANCED_FORMAT_COL_DESCRIPTION,
489 NULL);
490 if (autoar_format_is_valid (default_format)) {
491 GtkTreeIter iter;
492 gboolean valid;
493 format_path = NULL;
494 for (valid = gtk_tree_model_get_iter_first (format_model, &iter);
495 valid;
496 valid = gtk_tree_model_iter_next (format_model, &iter)) {
497 int get_format;
498 gtk_tree_model_get (format_model, &iter,
499 ADVANCED_FORMAT_COL_FORMAT, &get_format, -1);
500 if (default_format == get_format) {
501 format_path = gtk_tree_model_get_path (format_model, &iter);
502 break;
503 }
504 }
505 if (format_path == NULL)
506 format_path = gtk_tree_path_new_first ();
507 } else {
508 format_path = gtk_tree_path_new_first ();
509 }
510 gtk_tree_view_set_cursor (format, format_path, NULL, FALSE);
511 gtk_tree_path_free (format_path);
512 gtk_grid_attach (advanced, format_widget, 0, 0, 1, 1);
513 g_object_unref (format_model);
514
515 filter_model = advanced_filter_store ();
516 filter_widget = gtk_tree_view_new_with_model (filter_model);
517 filter = GTK_TREE_VIEW (filter_widget);
518 filter_selection = gtk_tree_view_get_selection (filter);
519 filter_renderer = gtk_cell_renderer_text_new ();
520 gtk_tree_selection_set_mode (filter_selection, GTK_SELECTION_SINGLE);
521 gtk_tree_view_insert_column_with_attributes (filter, -1, _("Filter"),
522 filter_renderer, "text",
523 ADVANCED_FILTER_COL_DESCRIPTION,
524 NULL);
525 if (autoar_filter_is_valid (default_filter)) {
526 GtkTreeIter iter;
527 gboolean valid;
528 filter_path = NULL;
529 for (valid = gtk_tree_model_get_iter_first (filter_model, &iter);
530 valid;
531 valid = gtk_tree_model_iter_next (filter_model, &iter)) {
532 int get_filter;
533 gtk_tree_model_get (filter_model, &iter,
534 ADVANCED_FILTER_COL_FILTER, &get_filter, -1);
535 if (default_filter == get_filter) {
536 filter_path = gtk_tree_model_get_path (filter_model, &iter);
537 break;
538 }
539 }
540 if (filter_path == NULL)
541 filter_path = gtk_tree_path_new_first ();
542 } else {
543 filter_path = gtk_tree_path_new_first ();
544 }
545 gtk_tree_view_set_cursor (filter, filter_path, NULL, FALSE);
546 gtk_tree_path_free (filter_path);
547 gtk_grid_attach (advanced, filter_widget, 1, 0, 1, 1);
548 g_object_unref (filter_model);
549
550 description_widget = gtk_label_new (NULL);
551 description = GTK_LABEL (description_widget);
552 gtk_label_set_justify (description, GTK_JUSTIFY_CENTER);
553 gtk_grid_attach (advanced, description_widget, 0, 1, 2, 1);
554
555 g_signal_connect (format_widget, "cursor-changed",
556 G_CALLBACK (advanced_update_description_cb), advanced);
557 g_signal_connect (filter_widget, "cursor-changed",
558 G_CALLBACK (advanced_update_description_cb), advanced);
559
560 /* Run the callback now to set the initial text on the label */
561 advanced_update_description_cb (NULL, advanced_widget);
562
563 return advanced_widget;
564 }
565
566 /**
567 * autoar_gtk_chooser_advanced_get:
568 * @advanced: a #GtkGrid returned by autoar_gtk_chooser_advanced_new()
569 * @format: the place to store the #AutoarFormat selected by the user
570 * @filter: the place to store the #AutoarFilter selected by the user
571 *
572 * Gets the selected archive format of the widget created by
573 * autoar_gtk_chooser_advanced_new().
574 *
575 * Returns: %TRUE if @format and @filter are set. %FALSE if there is no
576 * selected item on @advanced, so @format and @filter are not modified.
577 **/
578 gboolean
autoar_gtk_chooser_advanced_get(GtkWidget * advanced,int * format,int * filter)579 autoar_gtk_chooser_advanced_get (GtkWidget *advanced,
580 int *format,
581 int *filter)
582 {
583 GtkGrid *grid;
584 GtkTreeIter format_iter, filter_iter;
585 GtkTreeView *format_view, *filter_view;
586 GtkTreePath *format_path, *filter_path;
587 GtkTreeModel *format_model, *filter_model;
588
589 grid = GTK_GRID (advanced);
590 format_view = GTK_TREE_VIEW (gtk_grid_get_child_at (grid, 0, 0));
591 filter_view = GTK_TREE_VIEW (gtk_grid_get_child_at (grid, 1, 0));
592
593 gtk_tree_view_get_cursor (format_view, &format_path, NULL);
594 gtk_tree_view_get_cursor (filter_view, &filter_path, NULL);
595 if (format_path == NULL || filter_path == NULL) {
596 gtk_tree_path_free (format_path);
597 gtk_tree_path_free (filter_path);
598 return FALSE;
599 }
600
601 format_model = gtk_tree_view_get_model (format_view);
602 filter_model = gtk_tree_view_get_model (filter_view);
603 if (!gtk_tree_model_get_iter (format_model, &format_iter, format_path) ||
604 !gtk_tree_model_get_iter (filter_model, &filter_iter, filter_path)) {
605 gtk_tree_path_free (format_path);
606 gtk_tree_path_free (filter_path);
607 return FALSE;
608 }
609 gtk_tree_path_free (format_path);
610 gtk_tree_path_free (filter_path);
611
612 gtk_tree_model_get (format_model, &format_iter,
613 ADVANCED_FORMAT_COL_FORMAT, format, -1);
614 gtk_tree_model_get (filter_model, &filter_iter,
615 ADVANCED_FILTER_COL_FILTER, filter, -1);
616
617 return TRUE;
618 }
619