Statistics
| Branch: | Revision:

chunker-player / chunker_player / player_gui.c @ 2dfe9736

History | View | Annotate | Download (28.4 KB)

1
/*
2
 *  Copyright (c) 2009-2011 Carmelo Daniele, Dario Marchese, Diego Reforgiato, Giuseppe Tropea
3
 *  developed for the Napa-Wine EU project. See www.napa-wine.eu
4
 *
5
 *  This is free software; see lgpl-2.1.txt
6
 */
7

    
8
#include "player_gui.h"
9
// #include "player_commons.h"
10

    
11
#define SCREEN_BOTTOM_PADDING (BUTTONS_LAYER_OFFSET + BUTTONS_CONTAINER_HEIGHT + STATS_BOX_HEIGHT)
12

    
13
SDL_Cursor *InitSystemCursor(const char *image[]);
14
void AspectRatioResize(float aspect_ratio, int width, int height, int* out_width, int* out_height);
15
void UpdateOverlaySize(float aspect_ratio, int width, int height);
16
void RedrawButtons();
17
void RedrawChannelName();
18
void RedrawStats();
19
void SetupGUI();
20
void ToggleAudio();
21
void PSNRLedCallback();
22

    
23
static char AudioStatsText[255];
24
static char VideoStatsText[255];
25

    
26
static Uint32 last_mousemotion;
27
#define MOUSE_HIDE_DELAY 2000
28

    
29
static float ratio;
30
static float ratios[] = {DEFAULT_RATIO, 4.0/3.0, 16.0/9.0};
31
static int ratios_index;
32

    
33
SDL_Surface *ChannelTitleSurface = NULL;
34
//SDL_Surface *AudioStatisticsSurface = NULL, *VideoStatisticsSurface = NULL;
35
SDL_Rect ChannelTitleRect, AudioStatisticsRect, VideoStatisticsRect, tmpRect;
36
SDL_Color ChannelTitleColor = { 255, 0, 0 }, StatisticsColor = { 255, 255, 255 };
37
SDL_Color ChannelTitleBgColor = { 0, 0, 0 }, StatisticsBgColor = { 0, 0, 0 };
38
TTF_Font *MainFont = NULL;
39
TTF_Font *StatisticsFont = NULL;
40

    
41
/* XPM */
42
static const char *handXPM[] = {
43
/* columns rows colors chars-per-pixel */
44
"32 32 3 1",
45
"  c black",
46
". c gray100",
47
"X c None",
48
/* pixels */
49
"XXXXX  XXXXXXXXXXXXXXXXXXXXXXXXX",
50
"XXXX .. XXXXXXXXXXXXXXXXXXXXXXXX",
51
"XXXX .. XXXXXXXXXXXXXXXXXXXXXXXX",
52
"XXXX .. XXXXXXXXXXXXXXXXXXXXXXXX",
53
"XXXX .. XXXXXXXXXXXXXXXXXXXXXXXX",
54
"XXXX ..   XXXXXXXXXXXXXXXXXXXXXX",
55
"XXXX .. ..   XXXXXXXXXXXXXXXXXXX",
56
"XXXX .. .. ..  XXXXXXXXXXXXXXXXX",
57
"XXXX .. .. .. . XXXXXXXXXXXXXXXX",
58
"   X .. .. .. .. XXXXXXXXXXXXXXX",
59
" ..  ........ .. XXXXXXXXXXXXXXX",
60
" ... ........... XXXXXXXXXXXXXXX",
61
"X .. ........... XXXXXXXXXXXXXXX",
62
"XX . ........... XXXXXXXXXXXXXXX",
63
"XX ............. XXXXXXXXXXXXXXX",
64
"XXX ............ XXXXXXXXXXXXXXX",
65
"XXX ........... XXXXXXXXXXXXXXXX",
66
"XXXX .......... XXXXXXXXXXXXXXXX",
67
"XXXX .......... XXXXXXXXXXXXXXXX",
68
"XXXXX ........ XXXXXXXXXXXXXXXXX",
69
"XXXXX ........ XXXXXXXXXXXXXXXXX",
70
"XXXXX          XXXXXXXXXXXXXXXXX",
71
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
72
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
73
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
74
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
75
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
76
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
77
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
78
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
79
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
80
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
81
"0,0"
82
};
83

    
84
int ChunkerPlayerGUI_Init()
85
{
86
        // some initializations
87
        ratio = DEFAULT_RATIO;
88
        FullscreenWidth = 0;
89
        FullscreenHeight = 0;
90
        Audio_ON = 1;
91
        
92
        UpdateOverlaySize(ratio, DEFAULT_WIDTH, DEFAULT_HEIGHT);
93
        
94
        if(!SilentMode)
95
                SetupGUI();
96
        
97
        return 0;
98
}
99

    
100
int SetVideoMode(int width, int height, int fullscreen)
101
{
102
        if(SilentMode)
103
                return 1;
104
                
105
        // printf("SetVideoMode(%d, %d, %d)\n", width, height, fullscreen);
106
        SDL_LockMutex(OverlayMutex);
107

    
108
        if(fullscreen)
109
        {
110
#ifndef __DARWIN__
111
                MainScreen = SDL_SetVideoMode(width, height, 0, SDL_SWSURFACE | SDL_NOFRAME | SDL_FULLSCREEN);
112
#else
113
                MainScreen = SDL_SetVideoMode(width, height, 24, SDL_SWSURFACE | SDL_NOFRAME | SDL_FULLSCREEN);
114
#endif
115
        }
116
        else
117
        {
118
#ifndef __DARWIN__
119
                MainScreen = SDL_SetVideoMode(width, height, 0, SDL_SWSURFACE | SDL_RESIZABLE);
120
#else
121
                MainScreen = SDL_SetVideoMode(width, height, 24, SDL_SWSURFACE | SDL_RESIZABLE);
122
#endif
123
        }
124

    
125
        if(!MainScreen) {
126
                fprintf(stderr, "ERROR: could not change video mode\n");
127
                return 1;
128
        }
129
        SDL_UnlockMutex(OverlayMutex);
130
        
131
        return 0;
132
}
133

    
134
void ChunkerPlayerGUI_HandleResize(int resize_w, int resize_h)
135
{
136
        if(SilentMode)
137
                return;
138
                
139
        SDL_LockMutex(OverlayMutex);
140
        
141
        int res = SetVideoMode(resize_w, resize_h, FullscreenMode?1:0);
142

    
143
        if(res && FullscreenMode)
144
        {
145
                // an error has occurred while switching to fullscreen mode
146
                
147
                // trying resize without fullscreen mode
148
                FullscreenMode = 0;
149
                res = SetVideoMode(resize_w, resize_h, 0);
150
        }
151
        if(res)
152
        {
153
                // nothing to do
154
                fprintf(stderr, "CRITICAL ERROR: could not change video mode\n");
155
                exit(1);
156
        }
157
        
158
        window_width = resize_w;
159
        window_height = resize_h;
160
        
161
        // update the overlay surface size, mantaining the aspect ratio
162
        UpdateOverlaySize(ratio, resize_w, resize_h);
163
        
164
        // update each button coordinates
165
        int i;
166
        for(i=0; i<NBUTTONS; i++)
167
        {
168
                if(Buttons[i].XOffset > 0)
169
                        Buttons[i].ButtonIconBox.x = Buttons[i].XOffset;
170
                else
171
                        Buttons[i].ButtonIconBox.x = (resize_w + Buttons[i].XOffset);
172
                        
173
                Buttons[i].ButtonIconBox.y = resize_h - Buttons[i].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
174
        }
175
        
176
        RedrawButtons();
177
        RedrawChannelName();
178
        RedrawStats();
179
        
180
        SDL_UnlockMutex(OverlayMutex);
181
}
182

    
183
void ChunkerPlayerGUI_HandleGetFocus()
184
{
185
        if(SilentMode)
186
                return;
187

    
188
        RedrawButtons();
189
        RedrawChannelName();
190
        RedrawStats();
191
}
192

    
193
Uint32 DisableCursor(Uint32 interval, void *param)
194
{
195
        Uint32 time = SDL_GetTicks();
196
        if (time >= last_mousemotion + MOUSE_HIDE_DELAY) {
197
                SDL_ShowCursor(SDL_DISABLE);
198
        }
199

    
200
        return interval;
201
}
202

    
203
void ChunkerPlayerGUI_HandleMouseMotion(int x, int y)
204
{
205
        static SDL_TimerID cursor_cb;
206

    
207
        if(SilentMode)
208
                return;
209
                
210
        int i;
211

    
212
#ifdef __linux__
213
        if (!cursor_cb) {
214
                cursor_cb = SDL_AddTimer(MOUSE_HIDE_DELAY/2, DisableCursor, NULL);
215
        }
216
        if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) SDL_ShowCursor(SDL_ENABLE);
217
        last_mousemotion = SDL_GetTicks();
218
#endif
219

    
220
        for(i=0; i<NBUTTONS; i++)
221
        {
222
                //If the mouse is over the button
223
                if(
224
                        ( x > Buttons[i].ButtonIconBox.x ) && ( x < Buttons[i].ButtonIconBox.x + Buttons[i].ButtonIcon->w )
225
                        && ( y > Buttons[i].ButtonIconBox.y ) && ( y < Buttons[i].ButtonIconBox.y + Buttons[i].ButtonIcon->h )
226
                )
227
                {
228
                    // LED Button 
229
                    if(i>=PSNR_LED_RED_BUTTON_INDEX && i<=PSNR_LED_GREEN_BUTTON_INDEX)
230
                    {
231
                        SDL_SetCursor(defaultCursor);
232
                        break;
233
                    }
234
                        
235
                        Buttons[i].Hover = 1;
236
                        SDL_SetCursor(handCursor);
237
                        break;
238
                }
239
                
240
                else
241
                {
242
                        Buttons[i].Hover = 0;
243
                        SDL_SetCursor(defaultCursor);
244
                }
245
        }
246
}
247

    
248
void ChunkerPlayerGUI_HandleLButton(int x, int y)
249
{
250
        if(SilentMode)
251
                return;
252
                
253
        int i;
254
        for(i=0; i<NBUTTONS; i++)
255
        {
256
                //If the mouse is over the button
257
                if(
258
                        ( x > Buttons[i].ButtonIconBox.x ) && ( x < Buttons[i].ButtonIconBox.x + Buttons[i].ButtonIcon->w )
259
                        && ( y > Buttons[i].ButtonIconBox.y ) && ( y < Buttons[i].ButtonIconBox.y + Buttons[i].ButtonIcon->h )
260
                )
261
                {
262
                        Buttons[i].LButtonUpCallback();
263
                        break;
264
                }
265
        }
266
}
267

    
268
void ChunkerPlayerGUI_HandleKey()
269
{
270
        /*static Uint32 LastTime=0;
271
        static int LastKey=-1;
272

273
        Uint32 Now=SDL_GetTicks();
274
        Uint8* keystate=SDL_GetKeyState(NULL);
275
        if(keystate[SDLK_ESCAPE] &&
276
          (LastKey!=SDLK_ESCAPE || (LastKey==SDLK_ESCAPE && (Now-LastTime>1000))))
277
        {
278
                LastKey=SDLK_ESCAPE;
279
                LastTime=Now;
280
                quit=1;
281
        }*/
282
}
283

    
284
void ChunkerPlayerGUI_Close()
285
{
286
        if(ChannelTitleSurface != NULL)
287
                SDL_FreeSurface( ChannelTitleSurface );
288
        
289
        TTF_CloseFont( MainFont );
290
        TTF_CloseFont( StatisticsFont );
291
        TTF_Quit();
292
        IMG_Quit();
293
}
294

    
295
void RedrawButtons()
296
{
297
        if(SilentMode)
298
                return;
299
                
300
        int i;
301
        for(i=0; i<NBUTTONS; i++)
302
        {
303
                if(!Buttons[i].Visible)
304
                {
305
                        SDL_LockMutex(OverlayMutex);
306
                        SDL_FillRect( MainScreen, &Buttons[i].ButtonIconBox, SDL_MapRGB(MainScreen->format, 0, 0, 0) );
307
                        SDL_UpdateRects(MainScreen, 1, &Buttons[i].ButtonIconBox);
308
                        SDL_UnlockMutex(OverlayMutex);
309
                }
310
        }
311
        for(i=0; i<NBUTTONS; i++)
312
        {
313
                if(Buttons[i].Visible)
314
                {
315
                        if(!Buttons[i].Hover)
316
                        {
317
                                SDL_LockMutex(OverlayMutex);
318
                                SDL_BlitSurface(Buttons[i].ButtonIcon, NULL, MainScreen, &Buttons[i].ButtonIconBox);
319
                                SDL_UpdateRects(MainScreen, 1, &Buttons[i].ButtonIconBox);
320
                                SDL_UnlockMutex(OverlayMutex);
321
                        }
322
                        else
323
                        {
324
                                SDL_LockMutex(OverlayMutex);
325
                                SDL_BlitSurface(Buttons[i].ButtonHoverIcon, NULL, MainScreen, &(Buttons[i].ButtonIconBox));
326
                                SDL_UpdateRects(MainScreen, 1, &(Buttons[i].ButtonIconBox));
327
                                SDL_UnlockMutex(OverlayMutex);
328
                        }
329
                }
330
        }
331
}
332

    
333
void RedrawChannelName()
334
{
335
        if(SilentMode)
336
                return;
337
                
338
        if(ChannelTitleSurface != NULL)
339
        {
340
                SDL_LockMutex(OverlayMutex);
341
                SDL_FillRect( MainScreen, &ChannelTitleRect, SDL_MapRGB(MainScreen->format, 0, 0, 0) );
342
                SDL_UpdateRect(MainScreen, ChannelTitleRect.x, ChannelTitleRect.y, ChannelTitleRect.w, ChannelTitleRect.h);
343
                
344
                ChannelTitleRect.w = ChannelTitleSurface->w;
345
                ChannelTitleRect.h = ChannelTitleSurface->h;
346
                ChannelTitleRect.x = ((FullscreenMode?FullscreenWidth:window_width) - ChannelTitleRect.w)/2;
347
                ChannelTitleRect.y = Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIconBox.y+5;
348
                SDL_BlitSurface(ChannelTitleSurface, NULL, MainScreen, &ChannelTitleRect);
349
                SDL_UpdateRects(MainScreen, 1, &ChannelTitleRect);
350
                SDL_UnlockMutex(OverlayMutex);
351
        }
352
}
353

    
354
void ChunkerPlayerGUI_ToggleFullscreen()
355
{
356
        if(SilentMode)
357
                return;
358

    
359
        SDL_LockMutex(OverlayMutex);
360
                
361
        int i, done = 0;
362
        //If the screen is windowed
363
        if( !FullscreenMode )
364
        {
365
                int res = SetVideoMode(FullscreenWidth, FullscreenHeight, 1);
366
                //Set the window state flag
367
                FullscreenMode = 1;
368

    
369
                if(res)
370
                {
371
                        fprintf(stderr, "ERROR: an error has occurred while switching to fullscreen mode\n");
372
                }
373
                else
374
                {
375
                        // update the overlay surface size, mantaining the aspect ratio
376
                        UpdateOverlaySize(ratio, FullscreenWidth, FullscreenHeight);
377
                        
378
                        // update each button coordinates
379
                        for(i=0; i<NBUTTONS; i++)
380
                        {
381
                                if(Buttons[i].XOffset > 0)
382
                                        Buttons[i].ButtonIconBox.x = Buttons[i].XOffset;
383
                                else
384
                                        Buttons[i].ButtonIconBox.x = (FullscreenWidth + Buttons[i].XOffset);
385
                                        
386
                                Buttons[i].ButtonIconBox.y = FullscreenHeight - Buttons[i].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
387
                        }
388
                        
389
                        Buttons[FULLSCREEN_BUTTON_INDEX].Visible = 0;
390
                        Buttons[NO_FULLSCREEN_BUTTON_INDEX].Visible = 1;
391
                        
392
                        done = 1;
393
                }
394
        }
395
        
396
        //If the screen is fullscreen
397
        if(FullscreenMode && !done)
398
        {
399
                int res = SetVideoMode(window_width, window_height, 0);
400
                if(res)
401
                {
402
                        // nothing to do
403
                        fprintf(stderr, "CRITICAL ERROR: could not change video mode\n");
404
                        exit(1);
405
                }
406
                
407
                // update the overlay surface size, mantaining the aspect ratio
408
                UpdateOverlaySize(ratio, window_width, window_height);
409
                
410
                // update each button coordinates
411
                for(i=0; i<NBUTTONS; i++)
412
                {
413
                        if(Buttons[i].XOffset > 0)
414
                                Buttons[i].ButtonIconBox.x = Buttons[i].XOffset;
415
                        else
416
                                Buttons[i].ButtonIconBox.x = (window_width + Buttons[i].XOffset);
417
                                
418
                        Buttons[i].ButtonIconBox.y = window_height - Buttons[i].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
419
                }
420
                
421
                //Set the window state flag
422
                FullscreenMode = 0;
423
                
424
                Buttons[FULLSCREEN_BUTTON_INDEX].Visible = 1;
425
                Buttons[NO_FULLSCREEN_BUTTON_INDEX].Visible = 0;
426
        }
427

    
428
        RedrawButtons();
429
        RedrawChannelName();
430
        RedrawStats();
431
        
432
        SDL_UnlockMutex(OverlayMutex);
433
}
434

    
435
void ChunkerPlayerGUI_AspectRatioResize(float aspect_ratio, int width, int height, int* out_width, int* out_height)
436
{
437
        AspectRatioResize(aspect_ratio, width, height, out_width, out_height);
438
}
439

    
440
void AspectRatioResize(float aspect_ratio, int width, int height, int* out_width, int* out_height)
441
{
442
        int h,w;
443
        h = (int)((float)width/aspect_ratio);
444
        if(h<=height)
445
        {
446
                w = width;
447
        }
448
        else
449
        {
450
                w = (int)((float)height*aspect_ratio);
451
                h = height;
452
        }
453
        *out_width = w;
454
        *out_height = h;
455
}
456

    
457
/**
458
 * Updates the overlay surface size, mantaining the aspect ratio
459
 */
460
void UpdateOverlaySize(float aspect_ratio, int width, int height)
461
{
462
        // height -= (BUTTONS_LAYER_OFFSET + BUTTONS_CONTAINER_HEIGHT);
463
        height -= SCREEN_BOTTOM_PADDING;
464
        int h = 0, w = 0, x, y;
465
        AspectRatioResize(aspect_ratio, width, height, &w, &h);
466
        x = (width - w) / 2;
467
        y = (height - h) / 2;
468
        SDL_LockMutex(OverlayMutex);
469
        OverlayRect.x = x;
470
        OverlayRect.y = y;
471
        OverlayRect.w = w;
472
        OverlayRect.h = h;
473
        // SDL_FillRect( SDL_GetVideoSurface(), NULL, SDL_MapRGB(SDL_GetVideoSurface()->format, 0,0,0) );
474
        SDL_UpdateRect(MainScreen, 0, 0, 0, 0);
475
        SDL_UnlockMutex(OverlayMutex);
476
}
477

    
478
void GetScreenSizeFromOverlay(int overlayWidth, int overlayHeight, int* screenWidth, int* screenHeight)
479
{
480
        *screenHeight = overlayHeight + SCREEN_BOTTOM_PADDING;
481
        *screenWidth = overlayWidth;
482
}
483

    
484
/* From the SDL documentation. */
485
SDL_Cursor *InitSystemCursor(const char *image[])
486
{
487
        int i, row, col;
488
        Uint8 data[4*32];
489
        Uint8 mask[4*32];
490
        int hot_x, hot_y;
491

    
492
        i = -1;
493
        for ( row=0; row<32; ++row ) {
494
                for ( col=0; col<32; ++col ) {
495
                        if ( col % 8 ) {
496
                                data[i] <<= 1;
497
                                mask[i] <<= 1;
498
                        } else {
499
                                ++i;
500
                                data[i] = mask[i] = 0;
501
                        }
502
                        
503
                        switch (image[4+row][col]) {
504
                                case ' ':
505
                                        data[i] |= 0x01;
506
                                        mask[i] |= 0x01;
507
                                        break;
508
                                case '.':
509
                                        mask[i] |= 0x01;
510
                                        break;
511
                                case 'X':
512
                                        break;
513
                        }
514
                }
515
        }
516
        
517
        sscanf(image[4+row], "%d,%d", &hot_x, &hot_y);
518
        return SDL_CreateCursor(data, mask, 32, 32, hot_x, hot_y);
519
}
520

    
521
void SetupGUI()
522
{
523
        //Initialize SDL_ttf 
524
        if( TTF_Init() == -1 )
525
        {
526
                printf("TTF_Init: Failed to init SDL_ttf library!\n");
527
                printf("TTF_Init: %s\n", TTF_GetError());
528
                exit(1);
529
        }
530
        
531
        //Open the font
532
        MainFont = TTF_OpenFont(MAIN_FONT_FILE , MAIN_FONT_SIZE );
533
        StatisticsFont = TTF_OpenFont(STATS_FONT_FILE, STATS_FONT_SIZE );
534
        
535
        //If there was an error in loading the font
536
        if( MainFont == NULL)
537
        {
538
                printf("Cannot initialize GUI, %s file not found\n", MAIN_FONT_FILE);
539
                exit(1);
540
        }
541
        if( StatisticsFont == NULL )
542
        {
543
                printf("Cannot initialize GUI, %s file not found\n", STATS_FONT_FILE);
544
                exit(1);
545
        }
546
        
547
        // init SDL_image
548
        int flags=IMG_INIT_JPG|IMG_INIT_PNG;
549
        int initted=IMG_Init(flags);
550
        if(initted&flags != flags)
551
        {
552
                printf("IMG_Init: Failed to init required jpg and png support!\n");
553
                printf("IMG_Init: %s\n", IMG_GetError());
554
                exit(1);
555
        }
556
        
557
        SDL_VideoInfo* InitialVideoInfo = SDL_GetVideoInfo();
558
        FullscreenWidth = InitialVideoInfo->current_w;
559
        FullscreenHeight = InitialVideoInfo->current_h;
560

    
561
        SDL_Surface *temp;
562
        int screen_w = 0, screen_h = 0;
563

    
564
        if(OverlayRect.w > BUTTONS_CONTAINER_WIDTH)
565
                screen_w = OverlayRect.w;
566
        else
567
                screen_w = BUTTONS_CONTAINER_WIDTH;
568
        screen_h = OverlayRect.h + SCREEN_BOTTOM_PADDING;
569

    
570
        SDL_WM_SetCaption("Filling buffer...", NULL);
571
        
572
        // Make a screen to put our video
573
        SetVideoMode(screen_w, screen_h, 0);
574
        
575
        window_width = screen_w;
576
        window_height = screen_h;
577
        
578
        /** Setting up cursors */
579
        defaultCursor = SDL_GetCursor();
580
        handCursor = InitSystemCursor(handXPM);
581
        
582
        /** Init Buttons */
583
        int i;
584
        for(i=0; i<NBUTTONS; i++)
585
        {
586
                SButton* tmp = &(Buttons[i]);
587
                tmp->Hover = 0;
588
                tmp->ToggledButton = NULL;
589
                tmp->Visible = 1;
590
                tmp->HoverCallback = NULL;
591
                tmp->LButtonUpCallback = NULL;
592
        }
593
        
594
        /** Loading icons */
595
        
596
        // fullscreen
597
        temp = IMG_Load(FULLSCREEN_ICON_FILE);
598
        if (temp == NULL) {
599
                fprintf(stderr, "Error loading %s: %s\n", FULLSCREEN_ICON_FILE, SDL_GetError());
600
                exit(1);
601
        }
602
        Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
603
        if(Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIcon == NULL)
604
        {
605
                printf("ERROR in SDL_DisplayFormatAlpha, cannot load fullscreen button, error message: '%s'\n", SDL_GetError());
606
                exit(1);
607
        }
608
        SDL_FreeSurface(temp);
609
        
610
        // fullscreen hover
611
        temp = IMG_Load(FULLSCREEN_HOVER_ICON_FILE);
612
        if (temp == NULL) {
613
                fprintf(stderr, "Error loading %s: %s\n", FULLSCREEN_HOVER_ICON_FILE, SDL_GetError());
614
                exit(1);
615
        }
616
        Buttons[FULLSCREEN_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
617
        SDL_FreeSurface(temp);
618

    
619
        // no fullscreen
620
        temp = IMG_Load(NOFULLSCREEN_ICON_FILE);
621
        if (temp == NULL) {
622
                fprintf(stderr, "Error loading %s: %s\n", NOFULLSCREEN_ICON_FILE, SDL_GetError());
623
                exit(1);
624
        }
625
        Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
626
        SDL_FreeSurface(temp);
627

    
628
        // no fullscreen hover
629
        temp = IMG_Load(NOFULLSCREEN_HOVER_ICON_FILE);
630
        if (temp == NULL) {
631
                fprintf(stderr, "Error loading %s: %s\n", NOFULLSCREEN_HOVER_ICON_FILE, SDL_GetError());
632
                exit(1);
633
        }
634
        Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
635
        SDL_FreeSurface(temp);
636
        
637
        // channel up
638
        temp = IMG_Load(CHANNEL_UP_ICON_FILE);
639
        if (temp == NULL) {
640
                fprintf(stderr, "Error loading %s: %s\n", CHANNEL_UP_ICON_FILE, SDL_GetError());
641
                exit(1);
642
        }
643
        Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
644
        Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
645
        SDL_FreeSurface(temp);
646
        
647
        // channel down
648
        temp = IMG_Load(CHANNEL_DOWN_ICON_FILE);
649
        if (temp == NULL) {
650
                fprintf(stderr, "Error loading %s: %s\n", CHANNEL_DOWN_ICON_FILE, SDL_GetError());
651
                exit(1);
652
        }
653
        Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
654
        Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
655
        SDL_FreeSurface(temp);
656
        
657
        // audio OFF
658
        temp = IMG_Load(AUDIO_OFF_ICON_FILE);
659
        if (temp == NULL) {
660
                fprintf(stderr, "Error loading %s: %s\n", AUDIO_OFF_ICON_FILE, SDL_GetError());
661
                exit(1);
662
        }
663
        Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
664
        Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
665
        SDL_FreeSurface(temp);
666
        
667
        // audio ON
668
        temp = IMG_Load(AUDIO_ON_ICON_FILE);
669
        if (temp == NULL) {
670
                fprintf(stderr, "Error loading %s: %s\n", AUDIO_ON_ICON_FILE, SDL_GetError());
671
                exit(1);
672
        }
673
        Buttons[AUDIO_ON_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
674
        Buttons[AUDIO_ON_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
675
        SDL_FreeSurface(temp);
676
        
677
        // PSNR RED LED
678
        temp = IMG_Load(PSNR_LED_RED_ICON_FILE);
679
        if (temp == NULL) {
680
                fprintf(stderr, "Error loading %s: %s\n", PSNR_LED_RED_ICON_FILE, SDL_GetError());
681
                exit(1);
682
        }
683
        Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
684
        Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
685
        SDL_FreeSurface(temp);
686
        
687
        // PSNR YELLOW LED
688
        temp = IMG_Load(PSNR_LED_YELLOW_ICON_FILE);
689
        if (temp == NULL) {
690
                fprintf(stderr, "Error loading %s: %s\n", PSNR_LED_YELLOW_ICON_FILE, SDL_GetError());
691
                exit(1);
692
        }
693
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
694
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
695
        SDL_FreeSurface(temp);
696
        
697
        // PSNR GREEN LED
698
        temp = IMG_Load(PSNR_LED_GREEN_ICON_FILE);
699
        if (temp == NULL) {
700
                fprintf(stderr, "Error loading %s: %s\n", PSNR_LED_GREEN_ICON_FILE, SDL_GetError());
701
                exit(1);
702
        }
703
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonIcon = SDL_DisplayFormatAlpha(temp);
704
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonHoverIcon = SDL_DisplayFormatAlpha(temp);
705
        SDL_FreeSurface(temp);
706

    
707
        /** Setting up icon boxes */
708
        Buttons[FULLSCREEN_BUTTON_INDEX].XOffset = Buttons[NO_FULLSCREEN_BUTTON_INDEX].XOffset = 20;
709
        Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIconBox.x = 20;
710
        Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIconBox.w = Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIcon->w;
711
        Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIconBox.h = Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIcon->h;
712
        Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
713
        
714
        Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonIconBox.x = 20;
715
        Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonIconBox.w = Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonIcon->w;
716
        Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonIconBox.h = Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonIcon->h;
717
        Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[NO_FULLSCREEN_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
718
        
719
        Buttons[CHANNEL_UP_BUTTON_INDEX].XOffset = -61;
720
        Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonIconBox.w = Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonIcon->w;
721
        Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonIconBox.h = Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonIcon->h;
722
        Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonIconBox.x = (screen_w + Buttons[CHANNEL_UP_BUTTON_INDEX].XOffset);
723
        Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[CHANNEL_UP_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
724
        
725
        Buttons[CHANNEL_DOWN_BUTTON_INDEX].XOffset = -36;
726
        Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonIconBox.w = Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonIcon->w;
727
        Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonIconBox.h = Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonIcon->h;
728
        Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonIconBox.x = (screen_w + Buttons[CHANNEL_DOWN_BUTTON_INDEX].XOffset);
729
        Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[CHANNEL_DOWN_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
730
        
731
        Buttons[AUDIO_OFF_BUTTON_INDEX].XOffset = Buttons[AUDIO_ON_BUTTON_INDEX].XOffset = 70;
732
        Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonIconBox.x = 20;
733
        Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonIconBox.w = Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonIcon->w;
734
        Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonIconBox.h = Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonIcon->h;
735
        Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[AUDIO_OFF_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
736
        Buttons[AUDIO_OFF_BUTTON_INDEX].Visible = 1;
737

    
738
        Buttons[AUDIO_ON_BUTTON_INDEX].ButtonIconBox.x = 20;
739
        Buttons[AUDIO_ON_BUTTON_INDEX].ButtonIconBox.w = Buttons[AUDIO_ON_BUTTON_INDEX].ButtonIcon->w;
740
        Buttons[AUDIO_ON_BUTTON_INDEX].ButtonIconBox.h = Buttons[AUDIO_ON_BUTTON_INDEX].ButtonIcon->h;
741
        Buttons[AUDIO_ON_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[AUDIO_ON_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
742
        Buttons[AUDIO_ON_BUTTON_INDEX].Visible = 0;
743
        
744
        Buttons[PSNR_LED_RED_BUTTON_INDEX].XOffset = -106;
745
        Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonIconBox.w = Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonIcon->w;
746
        Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonIconBox.h = Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonIcon->h;
747
        Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonIconBox.x = (screen_w + Buttons[PSNR_LED_RED_BUTTON_INDEX].XOffset);
748
        Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[PSNR_LED_RED_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
749
        Buttons[PSNR_LED_RED_BUTTON_INDEX].Visible = 0;
750
        
751
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].XOffset = -106;
752
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonIconBox.w = Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonIcon->w;
753
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonIconBox.h = Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonIcon->h;
754
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonIconBox.x = (screen_w + Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].XOffset);
755
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
756
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].Visible = 0;
757
        
758
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].XOffset = -106;
759
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonIconBox.w = Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonIcon->w;
760
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonIconBox.h = Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonIcon->h;
761
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonIconBox.x = (screen_w + Buttons[PSNR_LED_GREEN_BUTTON_INDEX].XOffset);
762
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonIconBox.y = screen_h - Buttons[PSNR_LED_GREEN_BUTTON_INDEX].ButtonIconBox.h - (SCREEN_BOTTOM_PADDING/2);
763
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].Visible = 1;
764
        
765
        // Setting up buttons events
766
        Buttons[FULLSCREEN_BUTTON_INDEX].ToggledButton = &(Buttons[NO_FULLSCREEN_BUTTON_INDEX]);
767
        Buttons[FULLSCREEN_BUTTON_INDEX].LButtonUpCallback = &ChunkerPlayerGUI_ToggleFullscreen;
768
        Buttons[NO_FULLSCREEN_BUTTON_INDEX].LButtonUpCallback = &ChunkerPlayerGUI_ToggleFullscreen;
769
        Buttons[CHANNEL_UP_BUTTON_INDEX].LButtonUpCallback = &ZapUp;
770
        Buttons[CHANNEL_DOWN_BUTTON_INDEX].LButtonUpCallback = &ZapDown;
771
        Buttons[AUDIO_OFF_BUTTON_INDEX].LButtonUpCallback = &ToggleAudio;
772
        Buttons[AUDIO_ON_BUTTON_INDEX].LButtonUpCallback = &ToggleAudio;
773
        Buttons[PSNR_LED_RED_BUTTON_INDEX].LButtonUpCallback = &PSNRLedCallback;
774
        Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].LButtonUpCallback = &PSNRLedCallback;
775
        Buttons[PSNR_LED_GREEN_BUTTON_INDEX].LButtonUpCallback = &PSNRLedCallback;
776
}
777

    
778
void ChunkerPlayerGUI_SetChannelTitle(char* title)
779
{
780
        if(SilentMode)
781
                return;
782
                
783
        SDL_LockMutex(OverlayMutex);
784
        
785
        SDL_FreeSurface( ChannelTitleSurface );
786
        // ChannelTitleSurface = TTF_RenderText_Solid( MainFont, channel->Title, ChannelTitleColor );
787
        ChannelTitleSurface = TTF_RenderText_Shaded( MainFont, title, ChannelTitleColor, ChannelTitleBgColor );
788
        if(ChannelTitleSurface == NULL)
789
        {
790
                printf("WARNING: CANNOT RENDER CHANNEL TITLE\n");
791
        }
792
        
793
        SDL_UnlockMutex(OverlayMutex);
794
        
795
        RedrawChannelName();
796
}
797

    
798
void ChunkerPlayerGUI_SetStatsText(char* audio_text, char* video_text, int ledstatus)
799
{
800
        if(SilentMode)
801
                return;
802
                
803
        if(audio_text == NULL)
804
                audio_text = AudioStatsText;
805
        
806
        if(video_text == NULL)
807
                video_text = VideoStatsText;
808

    
809
        if((strlen(audio_text) > 255) || (strlen(video_text) > 255))
810
        {
811
                printf("WARNING IN player_gui.c: stats text too long, could not refresh text\n");
812
                return;
813
        }
814
        
815
        strcpy(AudioStatsText, audio_text);
816
        strcpy(VideoStatsText, video_text);
817
        
818
        switch(ledstatus)
819
    {
820
    case LED_RED:
821
        // PSNR LED RED
822
        Buttons[PSNR_LED_RED_BUTTON_INDEX].Visible = 1;
823
                Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].Visible = 0;
824
                Buttons[PSNR_LED_GREEN_BUTTON_INDEX].Visible = 0;
825
            break;
826
        case LED_YELLOW:
827
            // PSNR LED YELLOW
828
            Buttons[PSNR_LED_RED_BUTTON_INDEX].Visible = 0;
829
                Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].Visible = 1;
830
                Buttons[PSNR_LED_GREEN_BUTTON_INDEX].Visible = 0;
831
            break;
832
        case LED_GREEN:
833
            // PSNR LED GREEN
834
            Buttons[PSNR_LED_RED_BUTTON_INDEX].Visible = 0;
835
                Buttons[PSNR_LED_YELLOW_BUTTON_INDEX].Visible = 0;
836
                Buttons[PSNR_LED_GREEN_BUTTON_INDEX].Visible = 1;
837
            break;
838
        }
839
        
840
        RedrawStats();
841
        RedrawButtons();
842
}
843

    
844
void RedrawStats()
845
{
846
        if(SilentMode)
847
                return;
848
                
849
        SDL_Surface *text_surface;
850

    
851
        SDL_LockMutex(OverlayMutex);
852
        
853
        // clear stats text
854
        SDL_FillRect( MainScreen, &VideoStatisticsRect, SDL_MapRGB(MainScreen->format, 0, 0, 0) );
855
        SDL_FillRect( MainScreen, &AudioStatisticsRect, SDL_MapRGB(MainScreen->format, 0, 0, 0) );
856
        SDL_UpdateRect(MainScreen, VideoStatisticsRect.x, VideoStatisticsRect.y, VideoStatisticsRect.w, VideoStatisticsRect.h);
857
        SDL_UpdateRect(MainScreen, AudioStatisticsRect.x, AudioStatisticsRect.y, AudioStatisticsRect.w, AudioStatisticsRect.h);
858
        
859
        
860
        text_surface = TTF_RenderText_Shaded( StatisticsFont, AudioStatsText, StatisticsColor, StatisticsBgColor );
861
    if (text_surface != NULL)
862
    {
863
                AudioStatisticsRect.w = text_surface->w;
864
                AudioStatisticsRect.h = text_surface->h;
865
                AudioStatisticsRect.x = ((FullscreenMode?FullscreenWidth:window_width) - AudioStatisticsRect.w)>>1;
866
                AudioStatisticsRect.y = Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIconBox.y+(STATS_FONT_SIZE<<1)+STATS_BOX_HEIGHT;
867

    
868
        SDL_BlitSurface(text_surface, NULL, MainScreen, &AudioStatisticsRect);
869
        SDL_FreeSurface(text_surface);
870
    }
871
    else
872
    {
873
        // report error
874
    }
875
    
876
        text_surface = TTF_RenderText_Shaded( StatisticsFont, VideoStatsText, StatisticsColor, StatisticsBgColor );
877
    if (text_surface != NULL)
878
    {
879
                VideoStatisticsRect.w = text_surface->w;
880
                VideoStatisticsRect.h = text_surface->h;
881
                VideoStatisticsRect.x = ((FullscreenMode?FullscreenWidth:window_width) - VideoStatisticsRect.w)>>1;
882
                VideoStatisticsRect.y = Buttons[FULLSCREEN_BUTTON_INDEX].ButtonIconBox.y+(STATS_FONT_SIZE)+STATS_BOX_HEIGHT;
883

    
884
        SDL_BlitSurface(text_surface, NULL, MainScreen, &VideoStatisticsRect);
885
        SDL_FreeSurface(text_surface);
886
    }
887
    else
888
    {
889
        // report error
890
    }
891
    
892
    SDL_UpdateRect(MainScreen, VideoStatisticsRect.x, VideoStatisticsRect.y, VideoStatisticsRect.w, VideoStatisticsRect.h);
893
        SDL_UpdateRect(MainScreen, AudioStatisticsRect.x, AudioStatisticsRect.y, AudioStatisticsRect.w, AudioStatisticsRect.h);
894
        
895
        SDL_UnlockMutex(OverlayMutex);
896
}
897

    
898
void ChunkerPlayerGUI_SetupOverlayRect(SChannel* channel)
899
{
900
        ratio = channel->Ratio;
901
        int w, h;
902
        GetScreenSizeFromOverlay(channel->Width, channel->Height, &w, &h);
903
        UpdateOverlaySize(ratio, w, h);
904
        // UpdateOverlaySize(ratio, channel->Width, channel->Height);
905
}
906

    
907
void ChunkerPlayerGUI_ForceResize(int width, int height)
908
{
909
        FullscreenMode = 0;
910
        int w, h;
911
        GetScreenSizeFromOverlay(width, height, &w, &h);
912
        ChunkerPlayerGUI_HandleResize(w, h);
913
}
914

    
915
void ToggleAudio()
916
{
917
        if(Audio_ON)
918
        {
919
                SDL_PauseAudio(1);
920
                Buttons[AUDIO_OFF_BUTTON_INDEX].Visible = 0;
921
                Buttons[AUDIO_ON_BUTTON_INDEX].Visible = 1;
922
                Audio_ON = 0;
923
        }
924
        else
925
        {
926
                SDL_PauseAudio(0);
927
                Buttons[AUDIO_OFF_BUTTON_INDEX].Visible = 1;
928
                Buttons[AUDIO_ON_BUTTON_INDEX].Visible = 0;
929
                Audio_ON = 1;
930
        }
931
        
932
        RedrawButtons();
933
}
934

    
935
void PSNRLedCallback()
936
{
937
}
938

    
939
void ChunkerPlayerGUI_ChannelSwitched()
940
{
941
        if(!Audio_ON)
942
        {
943
                SDL_PauseAudio(1);
944
                Buttons[AUDIO_OFF_BUTTON_INDEX].Visible = 0;
945
                Buttons[AUDIO_ON_BUTTON_INDEX].Visible = 1;
946
        }
947
        
948
        RedrawButtons();
949
}
950

    
951
void ChunkerPlayerGUI_SetChannelRatio(float r)
952
{
953
  ratios[0] = r;
954
  ratios_index = 0;
955
  ratio = ratios[ratios_index];
956
}
957

    
958
void ChunkerPlayerGUI_ChangeRatio()
959
{
960
        ratios_index = (ratios_index + 1) % (sizeof(ratios) / sizeof(ratios[0]));
961
        ratio = ratios[ratios_index];
962

    
963
fprintf(stderr, "resizing to %f\n", ratio);
964
        if (!FullscreenMode) {
965
                UpdateOverlaySize(ratio, window_width, window_height);
966
        } else {
967
                UpdateOverlaySize(ratio, FullscreenWidth, FullscreenHeight);
968
        }
969
}