fixed first start for theme video
[livewp] / applet / src / livewp-actor.c
1 /*vim: set sw=4 ts=4 et: */
2 /*
3  * This file is part of Live Wallpaper (livewp)
4  * 
5  * Copyright (C) 2010 Vlad Vasiliev
6  * Copyright (C) 2010 Tanya Makova
7  *       for the code
8  * 
9  * This software is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1 of
12  * the License, or (at your option) any later version.
13  * 
14  * This software is distributed in the hope that it will be useful, but
15  * 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 software; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22  * 02110-1301 USA
23 */
24 /*******************************************************************************/
25 #include "livewp-actor.h"
26
27 Actor* 
28 init_object(AWallpaperPlugin *desktop_plugin, 
29             gchar * name, 
30             gchar * filename, 
31             gint x, 
32             gint y, 
33             gint z, 
34             gint width, 
35             gint height, 
36             gboolean visible, 
37             gboolean load_image,
38             gint scale, 
39             gint opacity, 
40             void (*pfunc_change)(Actor*),
41             void (*pfunc_probability)(Actor*),
42             GPtrArray *child
43            )
44 {
45     Actor *actor = NULL;
46     actor = g_new0(Actor, 1);
47     actor->x = x;
48     actor->y = y;
49     actor->z = z;
50     actor->width = width;
51     actor->height = height;
52     actor->visible = visible;
53     actor->scale = scale;
54     actor->opacity = opacity;
55     actor->filename = g_strdup(filename);
56     actor->name = g_strdup(name);
57     actor->func_change = (gpointer)pfunc_change; 
58     actor->func_probability = (gpointer)pfunc_probability;
59     actor->child = child;
60     if (load_image)
61         create_hildon_actor(actor, desktop_plugin);
62     else 
63          actor->widget = NULL;
64     actor->time_start_animation = 0;
65     actor->duration_animation = 0;
66     return actor;
67 }
68
69 void 
70 destroy_actor(Actor *actor)
71 {
72     if (actor){
73         if (actor->child){
74             g_ptr_array_free(actor->child, TRUE);
75         }
76         if (actor->filename)
77             g_free(actor->filename);
78         if (actor->name)
79             g_free(actor->name);
80         gtk_widget_destroy(actor->widget);
81         //actor->widget = NULL;
82         g_free(actor);
83     }
84 }
85 static gint 
86 path_line(gint x0, gint x1, double t)
87 {
88     // уравниение прямой
89     return ((x1 - x0) * t + x0);
90 }
91 void
92 set_actor_scale(Actor *actor, double scalex, double scaley)
93 {
94     hildon_animation_actor_set_scale(
95             HILDON_ANIMATION_ACTOR(actor->widget), 
96             scalex, 
97             scaley
98     );
99
100 }
101
102 void 
103 set_actor_visible(Actor *actor, gboolean visible)
104 {
105     hildon_animation_actor_set_show(HILDON_ANIMATION_ACTOR(actor->widget), visible);
106 }
107
108 void
109 set_actor_position(Actor *actor, gint x, gint y, gint z, AWallpaperPlugin *desktop_plugin)
110 {
111     hildon_animation_actor_set_position_full(HILDON_ANIMATION_ACTOR (actor->widget), 
112                                              x-desktop_plugin->priv->xapplet, 
113                                              y-desktop_plugin->priv->yapplet, 
114                                              z);
115 }
116
117 int get_notify_count(gchar *notify_type)
118 {
119     sqlite3 *db = NULL;
120     sqlite3_stmt *res = NULL;
121     gint rc = 0, result = 0;
122     gchar sql[1024];
123
124     rc = sqlite3_open("/home/user/.config/hildon-desktop/notifications.db", &db);
125     if (rc){
126         fprintf(stderr, "error open db %d %s\n", rc, sqlite3_errmsg(db));
127     }else {
128         snprintf(sql, sizeof(sql)-1, "select count(id) from notifications where icon_name='%s'", notify_type);
129         rc = sqlite3_prepare(db, sql, sizeof(sql)-1, &res, NULL);
130         if (rc != SQLITE_OK){
131             fprintf(stderr, "error prepare %d %s\n", rc, sql);
132         }
133         if (sqlite3_step(res) != SQLITE_ROW){
134             fprintf(stderr, "not sqlite_row\n");
135         }
136         result = sqlite3_column_int(res, 0);
137         //fprintf(stderr, "count missing calls = %d\n", call_count);
138         sqlite3_finalize(res);
139
140         sqlite3_close(db);
141     }
142     return result;
143 }
144 gchar * read_notification()
145 {
146     gchar *message = "";
147     gint count = 0;
148     
149     fprintf(stderr, "read notification \n");
150     count = get_notify_count("general_missed");
151     if (count > 0){
152         message = g_strdup_printf("%s: %d", _("Missed calls"), count);
153     }
154     count = get_notify_count("general_sms");
155     if (count > 0){
156         if (message){
157             message = g_strdup_printf("%s \n%s: %d", message, _("Missed sms"), count);
158         }else {
159             message = g_strdup_printf("%s: %d", _("Missed sms"), count);
160         }
161     }
162     count = get_notify_count("general_chat");
163     if (count > 0){
164         if (message){
165             message = g_strdup_printf("%s \n%s: %d", message, _("Missed chat"), count);
166         }else {
167             message = g_strdup_printf("%s: %d", _("Missed chat"), count);
168         }
169     }
170     count = get_notify_count("qgn_list_messagin");
171     if (count > 0){
172         if (message){
173             message = g_strdup_printf("%s \n%s: %d", message, _("Missed mail"), count);
174         }else {
175             message = g_strdup_printf("%s: %d", _("Missed mail"), count);
176         }
177     }
178     fprintf(stderr, "notify=%s\n", message);
179     return message;
180 }
181
182 void 
183 change_billboard(Actor * actor, AWallpaperPlugin *desktop_plugin)
184 {
185     gint count = 0;
186     Actor *a = NULL;
187      
188     //fprintf(stderr, "change_billboard\n");   
189     
190     if (desktop_plugin->priv->scene->notification < time(NULL)){
191         count = get_notify_count("general_missed");
192         a = g_ptr_array_index(actor->child, 0);
193         if (count > 0){
194             set_actor_visible(a, TRUE);            
195         }else {
196             set_actor_visible(a, FALSE);
197         }
198         count = get_notify_count("general_sms");
199         a = g_ptr_array_index(actor->child, 3);
200         if (count > 0){
201             set_actor_visible(a, TRUE);            
202         }else {
203             set_actor_visible(a, FALSE);
204         }
205         count = get_notify_count("general_chat");
206         a = g_ptr_array_index(actor->child, 1);
207         if (count > 0){
208             set_actor_visible(a, TRUE);            
209         }else {
210             set_actor_visible(a, FALSE);
211         }
212         count = get_notify_count("qgn_list_messagin");
213         a = g_ptr_array_index(actor->child, 2);
214         if (count > 0){
215             set_actor_visible(a, TRUE);            
216         }else {
217             set_actor_visible(a, FALSE);
218         }
219
220         desktop_plugin->priv->scene->notification = FALSE;
221     }
222     actor->time_start_animation = time(NULL) + 20;    
223 }
224
225 #if 0
226 void 
227 change_billboard1(Actor * actor, AWallpaperPlugin *desktop_plugin)
228 {
229     GtkWidget *label;
230     sqlite3 *db = NULL;
231     sqlite3_stmt *res = NULL;
232     gchar *errMsg = NULL, *message;
233     gchar sql[1024];
234     gint call_count=0, sms_count=0, rc=0;
235     GtkListStore *list = NULL;
236     PangoFontDescription *pfd = NULL;
237     
238     rc = sqlite3_open("/home/user/.rtcom-eventlogger/el.db", &db);
239     if (rc){
240         fprintf(stderr, "error open db %d %s\n", rc, sqlite3_errmsg(db));
241     }else {
242         snprintf(sql, sizeof(sql)-1, "select count(id) from Events where event_type_id=%d", 3);
243
244         rc = sqlite3_prepare(db, sql, sizeof(sql)-1, &res, NULL);
245         if (rc != SQLITE_OK){
246             fprintf(stderr, "error prepare %d %s\n", rc, sql);
247         }
248         if (sqlite3_step(res) != SQLITE_ROW){
249             fprintf(stderr, "not sqlite_row\n");
250         }
251         call_count = sqlite3_column_int(res, 0);
252         //fprintf(stderr, "count missing calls = %d\n", call_count);
253         sqlite3_finalize(res);
254
255         snprintf(sql, sizeof(sql)-1, "select count(id) from Events where event_type_id=%d and is_read=%d", 7, 0);
256         rc = sqlite3_prepare(db, sql, sizeof(sql)-1, &res, NULL);
257         if (rc != SQLITE_OK){
258             fprintf(stderr, "error prepare %d %s\n", rc, sql);
259         }
260         if (sqlite3_step(res) != SQLITE_ROW){
261             fprintf(stderr, "not sqlite_row\n");
262         }
263         sms_count = sqlite3_column_int(res, 0);
264         //fprintf(stderr, "count sms = %d\n", sms_count);
265         sqlite3_finalize(res);
266
267
268         sqlite3_close(db);
269     }
270     label = actor->image;
271     message = g_markup_printf_escaped("<span bgcolor=\"%s\" foreground=\"%s\">Missed calls: %d Unread sms: %d</span>", "#FFFFFF", "#000000", call_count, sms_count);
272     gtk_label_set_markup(GTK_LABEL(label), message);
273     g_free(message);
274     pfd = pango_font_description_from_string("Sans 14");
275     gtk_widget_modify_font(GTK_WIDGET(label), NULL);
276     gtk_widget_modify_font(GTK_WIDGET(label), pfd);
277     pango_font_description_free(pfd);
278     actor->time_start_animation = time(NULL) + 20;    
279 }
280 #endif
281
282 void 
283 change_moon(Actor * actor, AWallpaperPlugin *desktop_plugin)
284 {
285     gint phase;
286     char *newfile;
287     gint x0 = 150,
288          x1 = 650, 
289          x, y;
290     struct timeval tvb;     
291     suseconds_t ms;
292     long sec;
293     double t;
294 #if 0
295     gint y0, y1, x2, y2;
296     double a, b, c;
297     a = (double)(y2 - (double)(x2*(y1-y0) + x1*y0 - x0*y1)/(x1-x0))/(x2*(x2-x0-x1)+x0*x1);
298     b = (double)(y1-y0)/(x1-x0) - (double)a*(x0+x1);
299     c = (double)(x1*y0 - x0*y1)/(x1-x0) + (double)a*x0*x1;
300     fprintf(stderr, "a=%f, b=%f, c=%f\n", a, b, c);
301 #endif
302     gettimeofday(&tvb, NULL);
303     
304     ms = tvb.tv_usec;
305     sec = tvb.tv_sec;
306
307     if (actor){
308         if (desktop_plugin->priv->scene->daytime == TIME_NIGHT){
309             if (!actor->visible){
310                 actor->visible = TRUE;
311                 phase = get_moon_phase();
312                 newfile = g_strdup_printf( "%s%d.png", actor->name, phase);
313                 if (actor->filename)
314                     g_free(actor->filename);
315                 actor->filename = newfile;
316                 actor->time_start_animation = sec - fast_rnd(60 * 60);
317                 actor->duration_animation = 1 * 60 * 60;
318                 create_hildon_actor(actor, desktop_plugin);
319
320             }
321             t = (double)((double)sec+(double)ms/1000000 - actor->time_start_animation) / actor->duration_animation;
322             if (t <= 1)
323                 x = path_line(x0, x1, t);
324             else 
325                 x = path_line(x1, x0, t-1);
326             y = 0.001920*x*x - 1.536*x + 337.2;
327             //y = a*x*x + b*x + c;
328
329             set_actor_position(actor, x, y, actor->z, desktop_plugin);
330
331             if (t>=2){
332                 actor->time_start_animation = sec;
333             }
334
335          }else if (actor->visible){
336             actor->visible = FALSE;
337             fprintf(stderr, "destroy moon \n");
338             destroy_hildon_actor(actor);
339             actor->time_start_animation = 0;
340         } 
341     }
342     
343 }
344
345 void 
346 change_sun(Actor * actor, AWallpaperPlugin *desktop_plugin)
347 {
348     double alt, azm;
349     gint x, y;
350
351     //fprintf(stderr, "change sun\n");
352     if (actor){
353         if (desktop_plugin->priv->scene->daytime != TIME_NIGHT){
354             if (!actor->visible){
355                 actor->visible = TRUE;
356                 create_hildon_actor(actor, desktop_plugin);
357             }
358             get_sun_pos(&alt, &azm);
359             get_sun_screen_pos(alt, azm, &x, &y);
360             actor->x = x;
361             actor->y = y;
362             set_actor_position(actor, x, y, actor->z, desktop_plugin);
363             actor->time_start_animation = time(NULL) + 60;
364          }else if (actor->visible){
365             actor->visible = FALSE;
366             destroy_hildon_actor(actor);
367             actor->time_start_animation = 0;
368         } 
369     }
370     
371 }
372
373 void 
374 change_tram(Actor * actor, AWallpaperPlugin *desktop_plugin)
375 {
376     gint x0 = -300, y0 = 225, scale0 = 100,
377          x1 = 800, y1 = 162, scale1 = 130, 
378          x, y, scale;
379     struct timeval tvb;     
380     suseconds_t ms;
381     long sec;
382     double t;
383
384     //fprintf(stderr, "change tram\n");
385     gettimeofday(&tvb, NULL);
386     
387     ms = tvb.tv_usec;
388     sec = tvb.tv_sec;
389     
390     if (!actor->visible){
391         actor->visible = TRUE;
392         if (desktop_plugin->priv->scene->daytime == TIME_NIGHT){
393             if (actor->filename)
394                 g_free(actor->filename);
395             actor->filename = g_strdup("tram_dark.png");
396         } else{
397             if (actor->filename)
398                 g_free(actor->filename);
399             actor->filename = g_strdup("tram.png");
400         }
401         create_hildon_actor(actor, desktop_plugin);
402     }
403     t = (double)((double)sec+(double)ms/1000000 - actor->time_start_animation) / actor->duration_animation;
404     x = path_line(x0, x1, t);
405     y = path_line(y0, y1, t);
406     scale = path_line(scale0, scale1, t);
407     set_actor_position(actor, x, y, actor->z, desktop_plugin);
408     set_actor_scale(actor, (double)scale/100, (double)scale/100);
409     if (t >= 1){
410         /* stop animation */
411         actor->visible = FALSE;
412         destroy_hildon_actor(actor);
413         actor->time_start_animation = sec + fast_rnd(60);
414     }
415 }
416
417 void
418 change_plane1(Actor *actor, AWallpaperPlugin *desktop_plugin)
419 {
420     gint x0 = 620, y0 = 233,
421          x1 = 79, y1 = -146, 
422          x, y;
423     struct timeval tvb;     
424     suseconds_t ms;
425     long sec;
426     double t;
427
428     gettimeofday(&tvb, NULL);
429     
430     ms = tvb.tv_usec;
431     sec = tvb.tv_sec;
432 //    fprintf(stderr, "1 %f - %d\n", sec+(double)ms/100000, now);
433    
434     if (desktop_plugin->priv->scene->daytime != TIME_NIGHT){
435         if (actor->time_start_animation == 0){
436             actor->time_start_animation = sec + fast_rnd(180);
437             return;
438         }
439     }
440     if (!actor->visible){
441         actor->visible = TRUE;
442         create_hildon_actor(actor, desktop_plugin);
443     }
444     t = (double)((double)sec+(double)ms/1000000 - actor->time_start_animation) / actor->duration_animation;
445     x = path_line(x0, x1, t);
446     y = path_line(y0, y1, t);
447     //scale = path_line(scale0, scale1, t);
448     set_actor_position(actor, x, y, actor->z, desktop_plugin);
449     if (t >= 1){
450         /* stop animation */
451         actor->visible = FALSE;
452         destroy_hildon_actor(actor);
453         if (desktop_plugin->priv->scene->daytime == TIME_NIGHT) 
454             actor->time_start_animation = 0;
455         else 
456             actor->time_start_animation = sec + fast_rnd(180);
457     }
458
459 }
460
461 void
462 change_plane2(Actor *actor, AWallpaperPlugin *desktop_plugin)
463 {
464     gint x0 = -actor->width, y0 = 45,
465          x1 = 800, y1 = 20, 
466          x, y;
467     struct timeval tvb;     
468     suseconds_t ms;
469     long sec;
470     double t;
471
472     gettimeofday(&tvb, NULL);
473     
474     ms = tvb.tv_usec;
475     sec = tvb.tv_sec;
476 //    fprintf(stderr, "1 %f - %d\n", sec+(double)ms/100000, now);
477     if (desktop_plugin->priv->scene->daytime != TIME_NIGHT){
478         if (actor->time_start_animation == 0){
479             actor->time_start_animation = sec + fast_rnd(180);
480             return;
481         }
482     }
483     if (!actor->visible){
484         actor->visible = TRUE;
485         create_hildon_actor(actor, desktop_plugin);
486     }
487
488     t = (double)((double)sec+(double)ms/1000000 - actor->time_start_animation) / actor->duration_animation;
489     x = path_line(x0, x1, t);
490     y = path_line(y0, y1, t);
491     //scale = path_line(scale0, scale1, t);
492     set_actor_position(actor, x, y, actor->z, desktop_plugin);
493     if (t >= 1){
494         /* stop animation */
495         actor->visible = FALSE;
496         destroy_hildon_actor(actor);
497         if (desktop_plugin->priv->scene->daytime == TIME_NIGHT) 
498             actor->time_start_animation = 0;
499         else 
500             actor->time_start_animation = sec + fast_rnd(180);
501     }
502
503 }
504
505 void
506 change_cloud(Actor *actor, AWallpaperPlugin *desktop_plugin)
507 {
508     gint x0, y0 = 300, scale0 = 100,
509          x1, y1 = -actor->height, scale1 = 150, 
510          x, y, scale;
511     struct timeval tvb;     
512     suseconds_t ms;
513     long sec;
514     double t;
515     gchar *newfile;
516
517     //fprintf(stderr, "change cloud\n");
518     gettimeofday(&tvb, NULL);
519     
520     ms = tvb.tv_usec;
521     sec = tvb.tv_sec;
522    
523     if (!actor->visible){
524         actor->visible = TRUE;
525         if (desktop_plugin->priv->scene->daytime == TIME_NIGHT){
526             newfile = g_strdup_printf("%s_dark.png", actor->name);
527         }else{
528             newfile = g_strdup_printf("%s.png", actor->name);
529         } 
530         if (actor->filename)
531             g_free(actor->filename);
532         actor->filename = newfile;
533          
534         create_hildon_actor(actor, desktop_plugin);
535     }
536     t = (double)((double)sec+(double)ms/1000000 - actor->time_start_animation) / actor->duration_animation;
537     
538     if (desktop_plugin->priv->scene->wind_orientation == 1){
539         x0 = -actor->width;
540         x1 = 800;
541     }
542     else {
543         x0 = 800;
544         x1 = -actor->width;
545     }
546
547     x = path_line(x0, x1, t);    
548     y = -desktop_plugin->priv->scene->wind_angle * (x - x0) + actor->y;
549     scale = path_line(scale0, scale1, (double)(y - y0)/(y1 - y0));
550
551     set_actor_position(actor, x, y, actor->z, desktop_plugin);
552     set_actor_scale(actor, (double)scale/100, (double)scale/100);
553     if ((y < y1 || y > y0) || t >= 1){
554         /* stop animation */
555         actor->visible = FALSE;
556         destroy_hildon_actor(actor);
557         actor->time_start_animation = sec + fast_rnd(300);
558         actor->y = fast_rnd(300);
559     }
560
561 }
562
563 void
564 change_wind(Actor *actor, AWallpaperPlugin *desktop_plugin)
565 {
566     desktop_plugin->priv->scene->wind_orientation = fast_rnd(2);
567     if (desktop_plugin->priv->scene->wind_orientation == 0) desktop_plugin->priv->scene->wind_orientation = -1;
568     desktop_plugin->priv->scene->wind_angle = (double)(fast_rnd(200) - 100) / 100;
569     actor->time_start_animation = time(NULL) + (fast_rnd(10) + 10) * 60;
570     //fprintf(stderr, "change wind orient = %d angle = %f after = %d\n", scene.wind_orientation, scene.wind_angle, actor->time_start_animation-time(NULL));
571 }
572
573 void 
574 change_window1(Actor * actor, AWallpaperPlugin *desktop_plugin)
575 {
576     gint now = time(NULL);
577     if (desktop_plugin->priv->scene->daytime == TIME_DAY){
578         if (actor->widget){
579             actor->visible = FALSE;
580             destroy_hildon_actor(actor);
581         }
582         actor->time_start_animation = 0;
583         return;
584     }else {
585         if (!actor->widget)
586             create_hildon_actor(actor, desktop_plugin);
587         if (actor->time_start_animation == 0){
588             actor->time_start_animation = now + fast_rnd(30);
589             return;
590         }
591     }
592
593     if (!actor->visible)
594         actor->visible = TRUE;
595     else 
596         actor->visible = FALSE;
597     set_actor_visible(actor, actor->visible);
598     actor->time_start_animation = now + fast_rnd(60) + 10;
599
600 }
601
602 void 
603 change_signal(Actor * actor, AWallpaperPlugin *desktop_plugin)
604 {
605     gint now = time(NULL);
606     Actor *a;
607     a = g_ptr_array_index(actor->child, 0);
608     if (a->visible)
609         a->visible = FALSE;
610     else 
611         a->visible = TRUE;
612     set_actor_visible(a, a->visible);
613     
614     a = g_ptr_array_index(actor->child, 1);
615     if (a->visible)
616         a->visible = FALSE;
617     else 
618         a->visible = TRUE;
619     set_actor_visible(a, a->visible);
620
621     actor->time_start_animation = now + fast_rnd(30) + 10;
622 }
623
624 void
625 change_layer(Actor * actor, AWallpaperPlugin *desktop_plugin)
626 {
627     gint y, speed1 = 8, speed2 = 16;
628     Actor *a;
629
630     if (!desktop_plugin->priv->rich_animation) return;
631
632     a = g_ptr_array_index(actor->child, 0);
633     y = a->y + speed1;
634     if (y > 480) y = -480;
635     set_actor_position(a, a->x, y, a->z, desktop_plugin);
636     a->y = y;
637     
638     a = g_ptr_array_index(actor->child, 1);
639     y = a->y + speed1;
640     if (y > 480) y = -480;
641     set_actor_position(a, a->x, y, a->z, desktop_plugin);
642     a->y = y;
643
644     a = g_ptr_array_index(actor->child, 2);
645     y = a->y + speed2;
646     if (y > 480) y = -480;
647     set_actor_position(a, a->x, y, a->z, desktop_plugin);
648     a->y = y;
649
650     a = g_ptr_array_index(actor->child, 3);
651     y = a->y + speed2;
652     if (y > 480) y = -480;
653     set_actor_position(a, a->x, y, a->z, desktop_plugin);
654     a->y = y;
655 }
656
657 void 
658 change_static_actor(Actor * actor, AWallpaperPlugin *desktop_plugin)
659 {
660     gchar *newfile;
661     newfile = g_strdup_printf("%s%d.png", actor->name, desktop_plugin->priv->scene->daytime); 
662     if (actor->filename)
663             g_free(actor->filename);
664     actor->filename = newfile;
665     change_hildon_actor(actor, desktop_plugin);
666 }
667
668 void 
669 change_static_actor_with_corner(Actor * actor, AWallpaperPlugin *desktop_plugin)
670 {
671     gchar buffer[2048];
672
673     if (desktop_plugin->priv->right_corner)
674         gtk_widget_destroy(desktop_plugin->priv->right_corner);
675     snprintf(buffer, sizeof(buffer) - 1, "%s/%s/town%i_right_corner.png", \
676                                   THEME_PATH, desktop_plugin->priv->theme, desktop_plugin->priv->scene->daytime);
677     desktop_plugin->priv->right_corner = gtk_image_new_from_file (buffer);
678     if (desktop_plugin->priv->right_corner){
679         gtk_fixed_put(GTK_FIXED(desktop_plugin->priv->main_widget), desktop_plugin->priv->right_corner, 0, 0);
680         gtk_widget_show (desktop_plugin->priv->right_corner);
681     }
682     change_static_actor(actor, desktop_plugin);
683
684 }