select first inserted item
[omnplay] / src / omnplay.cpp
1 /*
2 * omnplay.c -- GTK+ 2 omnplay
3 * Copyright (C) 2011 Maksym Veremeyenko <verem@m1stereo.tv>
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 Foundation,
17 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifndef _GNU_SOURCE
21 #define _GNU_SOURCE
22 #endif
23
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <pthread.h>
34
35 #include "omnplay.h"
36 #include "ui.h"
37 #include "opts.h"
38 #include "timecode.h"
39
40 #include "omplrclnt.h"
41
42 int omnplay_get_content(omnplay_instance_t* app, playlist_item_t *items, int limit,
43 omnplay_get_content_cb_proc proc, void* data)
44 {
45 int r, c = 0;
46 OmPlrClipInfo clip_info;
47 char clip_name[omPlrMaxClipDirLen];
48
49 pthread_mutex_lock(&app->players.lock);
50
51 r = OmPlrClipGetFirst((OmPlrHandle)app->players.item[0].handle, clip_name, sizeof(clip_name));
52 for(; c < limit && !r;)
53 {
54 /* get clip info */
55 clip_info.maxMsTracks = 0;
56 clip_info.size = sizeof(clip_info);
57
58 r = OmPlrClipGetInfo((OmPlrHandle)app->players.item[0].handle, clip_name, &clip_info);
59
60 if(!r)
61 {
62 /* copy item props */
63 strncpy(items[c].id, clip_name, PATH_MAX);
64 items[c].in = clip_info.firstFrame;
65 items[c].dur = clip_info.lastFrame - clip_info.firstFrame;
66
67 /* callback */
68 pthread_mutex_unlock(&app->players.lock);
69 if(proc)
70 proc(app, &items[c], data);
71 pthread_mutex_lock(&app->players.lock);
72
73 c++;
74 };
75
76 r = OmPlrClipGetNext((OmPlrHandle)app->players.item[0].handle, clip_name, sizeof(clip_name));
77 };
78
79 pthread_mutex_unlock(&app->players.lock);
80
81 return c;
82 };
83
84
85 static gboolean on_main_window_delete_event( GtkWidget *widget, GdkEvent *event, gpointer user_data )
86 {
87 g_print ("delete event occurred [start]\n");
88 gdk_threads_leave();
89 omnplay_release((omnplay_instance_t*)user_data);
90 gdk_threads_enter();
91 g_print ("delete event occurred [finish]\n");
92
93 return FALSE;
94 }
95
96 static void on_main_window_destroy( GtkWidget *widget, gpointer user_data )
97 {
98 g_print ("destroy occurred\n");
99 gtk_main_quit();
100 }
101
102 omnplay_instance_t* omnplay_create(int argc, char** argv)
103 {
104 int i, c;
105 omnplay_instance_t* app;
106
107 /* prepare application instance */
108 app = (omnplay_instance_t*)malloc(sizeof(omnplay_instance_t));
109 memset(app, 0, sizeof(omnplay_instance_t));
110
111 /* load parameters from command line */
112 if(!omnplay_opt(argc, argv, app) && app->players.count)
113 app->window = ui_omnplay(app);
114 else
115 omnplay_usage();
116
117 return app;
118 };
119
120 void omnplay_destroy(omnplay_instance_t* app)
121 {
122 free(app);
123 };
124
125 static int find_index_of_playlist_item(omnplay_instance_t* app, int start, int idx)
126 {
127 if(start < 0 || start >= app->playlist.count)
128 return -1;
129
130 while(1)
131 {
132 if(app->playlist.item[start].omn_idx == idx)
133 return start;
134
135 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_END)
136 break;
137
138 start++;
139 };
140
141 return -1;
142 };
143
144 static void omnplay_update_status(omnplay_player_t* player, OmPlrStatus *prev , OmPlrStatus *curr)
145 {
146 int idx;
147 char tc_cur[32], tc_rem[32], state[32], status[32];
148 const char *clip;
149
150 if(curr)
151 {
152 frames2tc(curr->pos - curr->minPos, 25.0, tc_cur);
153 frames2tc(curr->maxPos - curr->pos, 25.0, tc_rem);
154 strcpy(status, "ONLINE");
155 clip = curr->currClipName;
156
157 switch(curr->state)
158 {
159 case omPlrStateStopped: strcpy(state, "STOPPED"); break;
160 case omPlrStateCuePlay: strcpy(state, "CUE_PLAY"); break;
161 case omPlrStatePlay: strcpy(state, "PLAY"); break;
162 case omPlrStateCueRecord: strcpy(state, "CUE_RECORD"); break;
163 case omPlrStateRecord: strcpy(state, "RECORD"); break;
164 };
165 }
166 else
167 {
168 tc_cur[0] = 0;
169 tc_rem[0] = 0;
170 clip = "";
171 state[0] = 0;
172 strcpy(status, "OFFLINE");
173 };
174
175 /* update status in status page */
176 gdk_threads_enter();
177 gtk_label_set_text(GTK_LABEL (player->label_tc_cur), tc_cur);
178 gtk_label_set_text(GTK_LABEL (player->label_tc_rem), tc_rem);
179 gtk_label_set_text(GTK_LABEL (player->label_state), state);
180 gtk_label_set_text(GTK_LABEL (player->label_status), status);
181 gtk_label_set_text(GTK_LABEL (player->label_clip), clip);
182 gdk_flush();
183 gdk_threads_leave();
184
185 /* update remaining time */
186 gdk_threads_enter();
187 pthread_mutex_lock(&player->app->playlist.lock);
188 pthread_mutex_lock(&player->app->players.lock);
189 if(curr->state == omPlrStatePlay || curr->state == omPlrStateCuePlay)
190 {
191 idx = find_index_of_playlist_item(player->app, player->playlist_start, curr->currClipNum);
192 if(idx >= 0)
193 {
194 frames2tc(curr->currClipStartPos + curr->currClipLen - curr->pos, 25.0, tc_rem);
195 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
196 }
197 if(curr->currClipNum != prev->currClipNum && 1 != prev->numClips)
198 {
199 tc_rem[0] = 0;
200 idx = find_index_of_playlist_item(player->app, player->playlist_start, prev->currClipNum);
201 if(idx >= 0)
202 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
203 };
204 }
205 else
206 {
207 tc_rem[0] = 0;
208 idx = find_index_of_playlist_item(player->app, player->playlist_start, curr->currClipNum);
209 if(idx >= 0)
210 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
211 idx = find_index_of_playlist_item(player->app, player->playlist_start, prev->currClipNum);
212 if(idx >= 0)
213 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
214 };
215 pthread_mutex_unlock(&player->app->players.lock);
216 pthread_mutex_unlock(&player->app->playlist.lock);
217 gdk_flush();
218 gdk_threads_leave();
219
220
221 memcpy(prev, curr, sizeof(OmPlrStatus));
222 };
223
224 static void* omnplay_thread_proc(void* data)
225 {
226 int r;
227 OmPlrStatus st_curr, st_prev;
228 omnplay_player_t* player = (omnplay_player_t*)data;
229
230 g_warning("omnplay_thread_proc\n");
231
232 memset(&st_curr, 0, sizeof(OmPlrStatus));
233 memset(&st_prev, 0, sizeof(OmPlrStatus));
234
235 /* connect */
236 pthread_mutex_lock(&player->app->players.lock);
237 r = OmPlrOpen(player->host, player->name, (OmPlrHandle*)&player->handle);
238 pthread_mutex_unlock(&player->app->players.lock);
239 if(r)
240 {
241 g_warning("ERROR: OmPlrOpen(%s, %s) failed with 0x%.8X\n",
242 player->host, player->name, r);
243
244 return (void*)r;
245 };
246
247 /* setup to do not reconnect */
248 pthread_mutex_lock(&player->app->players.lock);
249 OmPlrSetRetryOpen((OmPlrHandle)player->handle, 0);
250 pthread_mutex_unlock(&player->app->players.lock);
251
252 /* setup directory */
253 if(player->app->players.path[0])
254 {
255 pthread_mutex_lock(&player->app->players.lock);
256 r = OmPlrClipSetDirectory((OmPlrHandle)player->handle, player->app->players.path);
257 pthread_mutex_unlock(&player->app->players.lock);
258
259 if(r)
260 {
261 g_warning("ERROR: OmPlrClipSetDirectory(%s) failed with 0x%.8X\n",
262 player->app->players.path, r);
263
264 pthread_mutex_lock(&player->app->players.lock);
265 OmPlrClose((OmPlrHandle)player->handle);
266 pthread_mutex_unlock(&player->app->players.lock);
267
268 return (void*)r;
269 };
270 };
271
272 /* endless loop */
273 for(r = 0 ; !player->app->f_exit && !r;)
274 {
275 /* sleep */
276 #ifdef _WIN32
277 Sleep(100);
278 #else
279 usleep(100000);
280 #endif
281
282 /* get status */
283 pthread_mutex_lock(&player->app->players.lock);
284 st_curr.size = sizeof(OmPlrStatus);
285 r = OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &st_curr);
286 pthread_mutex_unlock(&player->app->players.lock);
287
288 if(r)
289 g_warning("ERROR: OmPlrGetPlayerStatus failed with 0x%.8X\n", r);
290 else
291 if(memcmp(&st_curr, &st_prev, sizeof(OmPlrStatus)))
292 omnplay_update_status(player, &st_prev , &st_curr);
293 };
294
295 pthread_mutex_lock(&player->app->players.lock);
296 OmPlrClose((OmPlrHandle)player->handle);
297 pthread_mutex_unlock(&player->app->players.lock);
298
299 return NULL;
300 };
301
302 void get_selected_items_playlist_proc(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
303 {
304 int idx, *list = (int*)data;
305 gtk_tree_model_get(model, iter, 7, &idx, -1);
306 list[list[0] + 1] = idx;
307 list[0] = list[0] + 1;
308 };
309
310 int* omnplay_selected_idxs_playlist(omnplay_instance_t* app)
311 {
312 int* list = NULL;
313 GtkTreeSelection *selection;
314
315 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid));
316 if(selection)
317 {
318 list = (int*)malloc(sizeof(int) * (MAX_PLAYLIST_ITEMS + 1));
319 memset(list, 0, sizeof(int) * (MAX_PLAYLIST_ITEMS + 1));
320
321 gtk_tree_selection_selected_foreach(
322 selection,
323 get_selected_items_playlist_proc,
324 list);
325
326 if(!list[0])
327 {
328 free(list);
329 list = NULL;
330 };
331 };
332
333 return list;
334 };
335
336 static int idx_in_players_range(omnplay_instance_t* app, int idx)
337 {
338 int i, r = 0;
339
340 for(i = 0; i < app->players.count && !r; i++)
341 {
342 int a, b;
343
344 a = app->players.item[i].playlist_start;
345 b = app->players.item[i].playlist_length;
346
347 if(b <= 0)
348 continue;
349
350 b = a + b - 1;
351
352 if(idx >= a && idx <= b) r = 1;
353 };
354
355 return r;
356 };
357
358 static int idxs_in_players_range(omnplay_instance_t* app, int start, int stop)
359 {
360 int i, r = 0;
361
362 for(i = 0; i < app->players.count && !r; i++)
363 {
364 int a, b;
365
366 a = app->players.item[i].playlist_start;
367 b = app->players.item[i].playlist_length;
368
369 if(b <= 0)
370 continue;
371
372 b = a + b - 1;
373
374 #define IN_RANGE(A,B,C) (A <= C && C <= B)
375 if( IN_RANGE(a,b,start) ||
376 IN_RANGE(a,b,stop) ||
377 IN_RANGE(start,stop,a) ||
378 IN_RANGE(start,stop,b))
379 r = 1;
380 };
381
382 return r;
383 };
384
385 static void omnplay_playlist_block(omnplay_instance_t* app, control_buttons_t button)
386 {
387 int start, stop, r, i;
388 int* list = omnplay_selected_idxs_playlist(app);
389
390 if(!list)
391 return;
392
393 pthread_mutex_lock(&app->playlist.lock);
394 pthread_mutex_lock(&app->players.lock);
395
396 start = list[1];
397 stop = list[list[0]];
398
399 if(!idxs_in_players_range(app, start, stop))
400 {
401 int loop = (button == BUTTON_PLAYLIST_BLOCK_LOOP)?OMNPLAY_PLAYLIST_BLOCK_LOOP:0;
402
403 /* update selected item */
404 for(i = start; i <= stop; i++)
405 {
406 int t = OMNPLAY_PLAYLIST_BLOCK_BODY | loop;
407
408 if(i == start) t |= OMNPLAY_PLAYLIST_BLOCK_BEGIN;
409 if(i == stop) t |= OMNPLAY_PLAYLIST_BLOCK_END;
410
411 app->playlist.item[i].type = (playlist_item_type_t)t;
412
413 omnplay_playlist_draw_item(app, i);
414 };
415
416 /* update border items */
417 if(start && !(app->playlist.item[start - 1].type & OMNPLAY_PLAYLIST_BLOCK_END))
418 {
419 app->playlist.item[start - 1].type = (playlist_item_type_t)(OMNPLAY_PLAYLIST_BLOCK_END
420 | app->playlist.item[start - 1].type);
421 omnplay_playlist_draw_item(app, start - 1);
422 };
423 if((stop + 1) < app->playlist.count && !(app->playlist.item[stop + 1].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN))
424 {
425 app->playlist.item[stop + 1].type = (playlist_item_type_t)(OMNPLAY_PLAYLIST_BLOCK_BEGIN
426 | app->playlist.item[stop + 1].type);
427 omnplay_playlist_draw_item(app, stop + 1);
428 };
429 }
430 else
431 g_warning("omnplay_playlist_block: range [%d %d] do OVERLAP player\n",
432 start, stop);
433
434 pthread_mutex_unlock(&app->players.lock);
435 pthread_mutex_unlock(&app->playlist.lock);
436
437 free(list);
438 };
439
440 static int get_first_selected_item_playlist(omnplay_instance_t* app)
441 {
442 int idx;
443 int* list = omnplay_selected_idxs_playlist(app);
444 if(!list) return -1;
445 idx = list[1];
446 free(list);
447 return idx;
448 };
449
450 static int get_playlist_block(omnplay_instance_t* app, int idx, int* start_ptr, int* stop_ptr)
451 {
452 int start, stop;
453
454 for(start = idx; start >= 0; start--)
455 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN)
456 break;
457
458 for(stop = idx; stop < app->playlist.count; stop++)
459 if(app->playlist.item[stop].type & OMNPLAY_PLAYLIST_BLOCK_END)
460 break;
461
462 g_warning("get_playlist_block: range %d -> %d\n", start, stop);
463
464 /* check block range */
465 if(start >= 0 && stop < app->playlist.count)
466 {
467 *start_ptr = start;
468 *stop_ptr = stop;
469 return (stop - start + 1);
470 };
471
472 return -1;
473 };
474
475 static omnplay_player_t *get_player_at_pos(omnplay_instance_t* app, int pos)
476 {
477 /* check player range */
478 if(app->playlist.item[pos].player > -1 && app->playlist.item[pos].player < app->players.count)
479 return &app->players.item[app->playlist.item[pos].player];
480
481 return NULL;
482 };
483
484 static void omnplay_playlist_delete_items(omnplay_instance_t* app, int* idxs, int count, int sel)
485 {
486 int i, j, idx;
487
488 pthread_mutex_lock(&app->playlist.lock);
489 pthread_mutex_lock(&app->players.lock);
490
491 for(j = 0; j < count; j++)
492 {
493 idx = idxs[j] - j;
494
495 /* fix block types */
496 if( app->playlist.item[idx].type != OMNPLAY_PLAYLIST_ITEM_BLOCK_BODY &&
497 app->playlist.item[idx].type != OMNPLAY_PLAYLIST_ITEM_LOOP_BODY)
498 {
499 if(idx)
500 app->playlist.item[idx - 1].type = (playlist_item_type_t)(app->playlist.item[idx - 1].type |
501 OMNPLAY_PLAYLIST_BLOCK_END);
502 if(idx + 1 < app->playlist.count)
503 app->playlist.item[idx + 1].type = (playlist_item_type_t)(app->playlist.item[idx + 1].type |
504 OMNPLAY_PLAYLIST_BLOCK_BEGIN);
505 };
506
507 /* shift playlist items */
508 memmove
509 (
510 &app->playlist.item[idx],
511 &app->playlist.item[idx + 1],
512 (app->playlist.count - idx - 1) * sizeof(playlist_item_t)
513 );
514
515 /* decrement items count */
516 app->playlist.count--;
517
518 /* increment servers indexes */
519 for(i = 0; i < app->players.count; i++)
520 if(app->players.item[i].playlist_start >= idx)
521 app->players.item[i].playlist_start--;
522
523
524 };
525
526 /* redraw playlist */
527 omnplay_playlist_draw(app);
528
529 /* select */
530 if(sel)
531 {
532 GtkTreePath* path;
533 path = gtk_tree_path_new_from_indices(idxs[0], -1);
534 gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid)), path);
535 gtk_tree_view_set_cursor(GTK_TREE_VIEW(app->playlist_grid), path, NULL, FALSE);
536 gtk_tree_path_free(path);
537 };
538
539 pthread_mutex_unlock(&app->players.lock);
540 pthread_mutex_unlock(&app->playlist.lock);
541 };
542
543 static void omnplay_playlist_item_del(omnplay_instance_t* app)
544 {
545 int i, idx, c;
546 int *list, *list2;
547
548 list = omnplay_selected_idxs_playlist(app);
549 if(!list) return;
550
551 list2 = (int*)malloc(sizeof(int) * list[0]);
552
553 for(i = 0, c = 0; i < list[0]; i++)
554 {
555 /* check for playing block */
556 if(idx_in_players_range(app, list[i + 1]))
557 continue;
558
559 /* save index */
560 list2[c++] = list[i + 1];
561 };
562
563 if(c)
564 omnplay_playlist_delete_items(app, list2, c, 1);
565
566 free(list2);
567 free(list);
568 };
569
570 static int omnplay_playlist_insert_check(omnplay_instance_t* app, int idx, playlist_item_type_t* t)
571 {
572 *t = OMNPLAY_PLAYLIST_ITEM_BLOCK_SINGLE;
573
574 /* before or after playlist */
575 if(!idx || idx == app->playlist.count)
576 return 1;
577
578 /* check for block borders */
579 if( app->playlist.item[idx - 1].type & OMNPLAY_PLAYLIST_BLOCK_END &&
580 app->playlist.item[idx + 0].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN)
581 return 1;
582
583 /* check for playing block */
584 if(idx_in_players_range(app, idx))
585 return 0;
586
587 if(app->playlist.item[idx].type & OMNPLAY_PLAYLIST_BLOCK_LOOP)
588 *t = OMNPLAY_PLAYLIST_ITEM_LOOP_BODY;
589 else
590 *t = OMNPLAY_PLAYLIST_ITEM_BLOCK_BODY;
591
592 return 1;
593 };
594
595 static void omnplay_playlist_insert_items(omnplay_instance_t* app, int idx,
596 playlist_item_t* items, int count)
597 {
598 int i;
599 GtkTreePath* path;
600
601 pthread_mutex_lock(&app->playlist.lock);
602 pthread_mutex_lock(&app->players.lock);
603
604 /* shift playlist items */
605 memmove
606 (
607 &app->playlist.item[idx + count],
608 &app->playlist.item[idx],
609 (app->playlist.count - idx) * sizeof(playlist_item_t)
610 );
611
612 /* copy new items */
613 memcpy
614 (
615 &app->playlist.item[idx],
616 items,
617 count * sizeof(playlist_item_t)
618 );
619
620 /* increment servers indexes */
621 for(i = 0; i < app->players.count; i++)
622 if(app->players.item[i].playlist_start >= idx)
623 app->players.item[i].playlist_start += idx;
624
625 /* increment items count */
626 app->playlist.count += count;
627
628 /* redraw playlist */
629 omnplay_playlist_draw(app);
630
631 /* select */
632 path = gtk_tree_path_new_from_indices(idx, -1);
633 gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid)), path);
634 gtk_tree_view_set_cursor(GTK_TREE_VIEW(app->playlist_grid), path, NULL, FALSE);
635 gtk_tree_path_free(path);
636
637 pthread_mutex_unlock(&app->players.lock);
638 pthread_mutex_unlock(&app->playlist.lock);
639 };
640
641 static void omnplay_playlist_item_add(omnplay_instance_t* app, int after)
642 {
643 int idx;
644 playlist_item_t item;
645 playlist_item_type_t t;
646
647 /* find insert position */
648 idx = get_first_selected_item_playlist(app);
649 if(idx < 0)
650 idx = 0;
651 else
652 idx += (after)?1:0;
653
654 if(!omnplay_playlist_insert_check(app, idx, &t))
655 return;
656
657 g_warning("allowed insert into idx=%d\n", idx);
658
659 /* clear item */
660 memset(&item, 0, sizeof(playlist_item_t));
661 if(ui_playlist_item_dialog(app, &item))
662 {
663 omnplay_library_normalize_item(app, &item);
664 item.type = t;
665 omnplay_playlist_insert_items(app, idx, &item, 1);
666 };
667 };
668
669 static void omnplay_playlist_item_edit(omnplay_instance_t* app)
670 {
671 int idx;
672 playlist_item_t item;
673
674 /* find insert position */
675 idx = get_first_selected_item_playlist(app);
676
677 if(idx < 0)
678 return;
679
680 /* check for playing block */
681 if(idx_in_players_range(app, idx))
682 return;
683
684 item = app->playlist.item[idx];
685
686 if(ui_playlist_item_dialog(app, &item))
687 {
688 omnplay_library_normalize_item(app, &item);
689 app->playlist.item[idx] = item;
690 omnplay_playlist_draw_item(app, idx);
691 };
692 };
693
694 static void omnplay_ctl(omnplay_instance_t* app, control_buttons_t button)
695 {
696 int i, r;
697 int idx, start, stop;
698 omnplay_player_t *player;
699
700 pthread_mutex_lock(&app->playlist.lock);
701
702 idx = get_first_selected_item_playlist(app);
703
704 if(idx < 0)
705 {
706 pthread_mutex_unlock(&app->playlist.lock);
707 return;
708 };
709
710 g_warning("cue: selected item is %d\n", idx);
711
712 if(get_playlist_block(app, idx, &start, &stop) < 0)
713 {
714 pthread_mutex_unlock(&app->playlist.lock);
715 return;
716 };
717
718 g_warning("cue: range %d -> %d\n", start, stop);
719
720 player = get_player_at_pos(app, start);
721
722 if(!player)
723 {
724 pthread_mutex_unlock(&app->playlist.lock);
725 return;
726 };
727
728 pthread_mutex_lock(&app->players.lock);
729
730 if(BUTTON_PLAYER_STOP == button || BUTTON_PLAYER_CUE == button)
731 {
732 /* stop */
733 OmPlrStop((OmPlrHandle)player->handle);
734
735 /* detach previous clips */
736 player->playlist_length = -1;
737 OmPlrDetachAllClips((OmPlrHandle)player->handle);
738 };
739
740 if(BUTTON_PLAYER_CUE == button)
741 {
742 int o, c, p = 0;
743
744 /* Attach clips to timeline */
745 for(i = start, c = 0, o = 0; i <= stop; i++)
746 {
747 OmPlrClipInfo clip;
748
749 /* get clip info */
750 clip.maxMsTracks = 0;
751 clip.size = sizeof(clip);
752 r = OmPlrClipGetInfo((OmPlrHandle)player->handle, app->playlist.item[i].id, &clip);
753
754 if(!r)
755 {
756 unsigned int l;
757
758 g_warning("OmPlrClipGetInfo(%s): firstFrame=%d, lastFrame=%d\n",
759 app->playlist.item[i].id, clip.firstFrame, clip.lastFrame);
760
761 /* should we fix playlist clip timings */
762 if(!(
763 app->playlist.item[i].in >= clip.firstFrame &&
764 app->playlist.item[i].in + app->playlist.item[i].dur <= clip.lastFrame) ||
765 !app->playlist.item[i].dur)
766 {
767 g_warning("cue: item [%s] will be updated [%d;%d]->[%d;%d]\n",
768 app->playlist.item[i].id,
769 app->playlist.item[i].in, app->playlist.item[i].dur,
770 clip.firstFrame, clip.lastFrame - clip.firstFrame);
771
772 app->playlist.item[i].in = clip.firstFrame;
773 app->playlist.item[i].dur = clip.lastFrame - clip.firstFrame;
774 omnplay_playlist_draw_item(app, i);
775 };
776
777 r = OmPlrAttach((OmPlrHandle)player->handle,
778 app->playlist.item[i].id,
779 app->playlist.item[i].in,
780 app->playlist.item[i].in + app->playlist.item[i].dur,
781 0, omPlrShiftModeAfter, &l);
782 };
783
784 if(r)
785 {
786 g_warning("cue: failed with %d, %s\n", r, OmPlrGetErrorString((OmPlrError)r));
787 app->playlist.item[i].omn_idx = -1;
788 app->playlist.item[i].omn_offset = -1;
789 app->playlist.item[i].error |= PLAYLIST_ITEM_ERROR_CUE;
790 }
791 else
792 {
793 app->playlist.item[i].omn_idx = c;
794 app->playlist.item[i].omn_offset = o;
795 app->playlist.item[i].error &= 0xF ^ PLAYLIST_ITEM_ERROR_CUE;
796
797 /* save selected item offset */
798 if(i == idx) p = o;
799
800 c++;
801 o += app->playlist.item[i].dur;
802 };
803 };
804
805 if(c)
806 {
807 OmPlrStatus hs;
808
809 /* Set timeline min/max */
810 OmPlrSetMinPosMin((OmPlrHandle)player->handle);
811 OmPlrSetMaxPosMax((OmPlrHandle)player->handle);
812
813 /* Set timeline position */
814 hs.minPos = 0;
815 hs.size = sizeof(OmPlrStatus);
816 OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &hs);
817 OmPlrSetPos((OmPlrHandle)player->handle, hs.minPos + p);
818
819 /* setup loop */
820 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_LOOP)
821 OmPlrLoop((OmPlrHandle)player->handle, hs.minPos, hs.maxPos);
822 else
823 OmPlrLoop((OmPlrHandle)player->handle, hs.minPos, hs.minPos);
824
825 player->playlist_start = start;
826 player->playlist_length = stop - start + 1;
827
828 /* Cue */
829 OmPlrCuePlay((OmPlrHandle)player->handle, 0.0);
830 };
831 };
832
833 if(BUTTON_PLAYER_PLAY == button)
834 {
835 /* play */
836 OmPlrPlay((OmPlrHandle)player->handle, 1.0);
837 };
838
839 if(BUTTON_PLAYER_PAUSE == button)
840 /* pause */
841 OmPlrPlay((OmPlrHandle)player->handle, 0.0);
842
843 pthread_mutex_unlock(&app->players.lock);
844
845 pthread_mutex_unlock(&app->playlist.lock);
846 };
847
848 static void omnplay_playlist_item_swap(omnplay_instance_t* app, int dir)
849 {
850 int sel, a, b, e = 1;
851 GtkTreePath* path;
852 playlist_item_t item;
853
854 /* find insert position */
855 sel = get_first_selected_item_playlist(app);
856 if(sel < 0)
857 return;
858
859 if(dir < 0)
860 {
861 a = sel - 1;
862 b = sel;
863 sel = a;
864 }
865 else
866 {
867 a = sel;
868 b = sel + 1;
869 sel = b;
870 };
871
872 /* check for playing block */
873 if(idx_in_players_range(app, a) || idx_in_players_range(app, b))
874 return;
875
876 pthread_mutex_lock(&app->playlist.lock);
877 pthread_mutex_lock(&app->players.lock);
878
879 /* swap */
880 item = app->playlist.item[a];
881 app->playlist.item[a] = app->playlist.item[b];
882 app->playlist.item[b] = item;
883
884 /* rewite type */
885 if(app->playlist.item[a].type != app->playlist.item[b].type)
886 {
887 e = 0;
888 app->playlist.item[a].type = OMNPLAY_PLAYLIST_ITEM_BLOCK_SINGLE;
889 app->playlist.item[b].type = OMNPLAY_PLAYLIST_ITEM_BLOCK_SINGLE;
890 };
891
892 /* redraw main items */
893 omnplay_playlist_draw_item(app, a);
894 omnplay_playlist_draw_item(app, b);
895
896 /* fix block types */
897 if(a && !e)
898 {
899 app->playlist.item[a - 1].type = (playlist_item_type_t)(app->playlist.item[a - 1].type |
900 OMNPLAY_PLAYLIST_BLOCK_END);
901 omnplay_playlist_draw_item(app, a - 1);
902 };
903 if(b + 1 < app->playlist.count && !e)
904 {
905 app->playlist.item[b + 1].type = (playlist_item_type_t)(app->playlist.item[b + 1].type |
906 OMNPLAY_PLAYLIST_BLOCK_BEGIN);
907 omnplay_playlist_draw_item(app, b + 1);
908 };
909
910 /* select */
911 path = gtk_tree_path_new_from_indices(sel, -1);
912 gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid)), path);
913 gtk_tree_view_set_cursor(GTK_TREE_VIEW(app->playlist_grid), path, NULL, FALSE);
914 gtk_tree_path_free(path);
915
916 pthread_mutex_unlock(&app->players.lock);
917 pthread_mutex_unlock(&app->playlist.lock);
918 };
919
920 static void omnplay_library_add(omnplay_instance_t* app, int after)
921 {
922 int idx, c, i;
923 playlist_item_t* items;
924 playlist_item_type_t t;
925
926 /* find insert position */
927 idx = get_first_selected_item_playlist(app);
928 if(idx < 0)
929 idx = 0;
930 else
931 idx += (after)?1:0;
932
933 if(!omnplay_playlist_insert_check(app, idx, &t))
934 return;
935
936 items = omnplay_library_get_selected(app, &c);
937
938 /* clear item */
939 if(items)
940 {
941 for(i = 0; i < c; i++)
942 {
943 items[i].type = t;
944 items[i].error = 0;
945 };
946 omnplay_playlist_insert_items(app, idx, items, c);
947 };
948 };
949
950
951 static gboolean omnplay_button_click(omnplay_instance_t* app, control_buttons_t button)
952 {
953 switch(button)
954 {
955 case BUTTON_PLAYLIST_ITEM_ADD:
956 omnplay_playlist_item_add(app, 0);
957 break;
958 case BUTTON_PLAYLIST_ITEM_DEL:
959 omnplay_playlist_item_del(app);
960 break;
961 case BUTTON_PLAYLIST_ITEM_EDIT:
962 omnplay_playlist_item_edit(app);
963 break;
964 case BUTTON_PLAYLIST_LOAD:
965 omnplay_playlist_load(app);
966 break;
967 case BUTTON_PLAYLIST_SAVE:
968 omnplay_playlist_save(app);
969 break;
970 case BUTTON_PLAYLIST_BLOCK_SINGLE:
971 case BUTTON_PLAYLIST_BLOCK_LOOP:
972 omnplay_playlist_block(app, button);
973 break;
974 case BUTTON_PLAYLIST_ITEM_UP:
975 omnplay_playlist_item_swap(app, -1);
976 break;
977 case BUTTON_PLAYLIST_ITEM_DOWN:
978 omnplay_playlist_item_swap(app, +1);
979 break;
980 case BUTTON_PLAYER_CUE:
981 case BUTTON_PLAYER_PLAY:
982 case BUTTON_PLAYER_PAUSE:
983 case BUTTON_PLAYER_STOP:
984 omnplay_ctl(app, button);
985 break;
986 case BUTTON_LIBRARY_ADD:
987 omnplay_library_add(app, 0);
988 break;
989 case BUTTON_LIBRARY_REFRESH:
990 omnplay_library_refresh(app);
991 break;
992 case BUTTON_LIBRARY_FIND:
993 omnplay_library_search(app, 0);
994 break;
995 case BUTTON_LIBRARY_FIND_NEXT:
996 omnplay_library_search(app, 1);
997 break;
998 };
999
1000 return TRUE;
1001 };
1002
1003 static gboolean on_button_click(GtkWidget *button, gpointer user_data)
1004 {
1005 int i;
1006 omnplay_instance_t* app = (omnplay_instance_t*)user_data;
1007
1008 for(i = 1; i < BUTTON_LAST; i++)
1009 if(app->buttons[i] == button)
1010 return omnplay_button_click(app, (control_buttons_t)i);
1011
1012 return FALSE;
1013 };
1014
1015 static void omnplay_playlist_item_copy(omnplay_instance_t* app)
1016 {
1017 int *list, i;
1018
1019 list = omnplay_selected_idxs_playlist(app);
1020 if(!list) return;
1021
1022 for(i = 0; i < list[0]; i++)
1023 app->clipboard.item[i] = app->playlist.item[list[i + 1]];
1024 app->clipboard.count = list[0];
1025
1026 free(list);
1027 };
1028
1029 static void omnplay_playlist_item_paste(omnplay_instance_t* app, int after)
1030 {
1031 int idx, i;
1032 playlist_item_t* items;
1033 playlist_item_type_t t;
1034
1035 /* find insert position */
1036 idx = get_first_selected_item_playlist(app);
1037 if(idx < 0)
1038 idx = 0;
1039 else
1040 idx += (after)?1:0;
1041
1042 if(!omnplay_playlist_insert_check(app, idx, &t))
1043 return;
1044
1045 /* clear item */
1046 if(app->clipboard.count)
1047 {
1048 for(i = 0; i < app->clipboard.count; i++)
1049 {
1050 app->clipboard.item[i].type = t;
1051 app->clipboard.item[i].error = 0;
1052 };
1053 omnplay_playlist_insert_items(app, idx, app->clipboard.item, app->clipboard.count);
1054 };
1055 };
1056
1057 static gboolean on_playlist_grid_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
1058 {
1059 omnplay_instance_t* app = (omnplay_instance_t*)data;
1060
1061 switch(event->keyval)
1062 {
1063 case GDK_C:
1064 case GDK_c:
1065 if(event->state & GDK_CONTROL_MASK)
1066 {
1067 omnplay_playlist_item_copy(app);
1068 return TRUE;
1069 };
1070 break;
1071 case GDK_V:
1072 case GDK_v:
1073 if(event->state & GDK_CONTROL_MASK)
1074 {
1075 omnplay_playlist_item_paste(app, 0);
1076 return TRUE;
1077 };
1078 break;
1079 case GDK_X:
1080 case GDK_x:
1081 if(event->state & GDK_CONTROL_MASK)
1082 {
1083 omnplay_playlist_item_copy(app);
1084 omnplay_playlist_item_del(app);
1085 return TRUE;
1086 };
1087 break;
1088 case GDK_S:
1089 case GDK_s:
1090 if(event->state & GDK_CONTROL_MASK)
1091 {
1092 omnplay_playlist_save(app);
1093 return TRUE;
1094 };
1095 break;
1096 case GDK_O:
1097 case GDK_o:
1098 if(event->state & GDK_CONTROL_MASK)
1099 {
1100 omnplay_playlist_load(app);
1101 return TRUE;
1102 };
1103 break;
1104 case GDK_KEY_uparrow:
1105 if(event->state & GDK_CONTROL_MASK)
1106 {
1107 omnplay_playlist_item_swap(app, -1);
1108 return TRUE;
1109 };
1110 break;
1111 case GDK_KEY_downarrow:
1112 if(event->state & GDK_CONTROL_MASK)
1113 {
1114 omnplay_playlist_item_swap(app, -1);
1115 return TRUE;
1116 };
1117 break;
1118 case GDK_KEY_space:
1119 omnplay_ctl(app, BUTTON_PLAYER_PLAY);
1120 return TRUE;
1121 case GDK_KEY_Return:
1122 omnplay_ctl(app, BUTTON_PLAYER_CUE);
1123 return TRUE;
1124 case GDK_KEY_Insert:
1125 omnplay_playlist_item_add(app, 0);
1126 return TRUE;
1127 case GDK_KEY_Delete:
1128 omnplay_playlist_item_del(app);
1129 return TRUE;
1130 case GDK_E:
1131 case GDK_e:
1132 omnplay_playlist_item_edit(app);
1133 return TRUE;
1134 };
1135
1136 return FALSE;
1137 };
1138
1139 static gboolean on_library_grid_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
1140 {
1141 omnplay_instance_t* app = (omnplay_instance_t*)data;
1142
1143 switch(event->keyval)
1144 {
1145 case GDK_C:
1146 case GDK_c:
1147 if(event->state & GDK_CONTROL_MASK)
1148 {
1149 int count;
1150 playlist_item_t* items;
1151
1152 items = omnplay_library_get_selected(app, &count);
1153
1154 if(items)
1155 {
1156 int i;
1157
1158 for(i = 0; i < count; i++)
1159 app->clipboard.item[i] = items[i];
1160
1161 app->clipboard.count = count;
1162 };
1163
1164 return TRUE;
1165 };
1166 break;
1167 case GDK_V:
1168 case GDK_v:
1169 if(event->state & GDK_CONTROL_MASK)
1170 {
1171 g_warning("CTRL+v\n");
1172 return TRUE;
1173 };
1174 break;
1175 case GDK_X:
1176 case GDK_x:
1177 if(event->state & GDK_CONTROL_MASK)
1178 {
1179 g_warning("CTRL+x\n");
1180 return TRUE;
1181 };
1182 break;
1183 case GDK_KEY_BackSpace:
1184 omnplay_library_add(app, 0);
1185 return TRUE;
1186 case GDK_KEY_F5:
1187 omnplay_library_refresh(app);
1188 return TRUE;
1189 };
1190
1191 return FALSE;
1192 };
1193
1194 static gboolean on_library_grid_button(GtkWidget *widget, GdkEventButton *event, gpointer data)
1195 {
1196 // g_warning("on_library_grid_button: event->button=%d, event->type=%d", event->button, event->type);
1197
1198 if(event->button==1 && event->type==GDK_2BUTTON_PRESS)
1199 {
1200 omnplay_library_add((omnplay_instance_t* )data, 0);
1201 return TRUE;
1202 };
1203
1204 return FALSE;
1205 };
1206
1207 static gboolean on_playlist_grid_button(GtkWidget *widget, GdkEventButton *event, gpointer data)
1208 {
1209 // g_warning("on_playlist_grid_button");
1210
1211 if(event->button==1 && event->type==GDK_2BUTTON_PRESS)
1212 {
1213 omnplay_ctl((omnplay_instance_t* )data, BUTTON_PLAYER_CUE);
1214 return TRUE;
1215 };
1216
1217 return FALSE;
1218 };
1219
1220
1221
1222 static void library_grid_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context,
1223 GtkSelectionData *selection_data, guint info, guint time, gpointer userdata)
1224 {
1225 int c;
1226 playlist_item_t* items;
1227 omnplay_instance_t* app = (omnplay_instance_t*)userdata;
1228
1229 g_warning("library_grid_drag_data_get_cb");
1230
1231 items = omnplay_library_get_selected(app, &c);
1232
1233 /* clear item */
1234 if(items)
1235 {
1236 gtk_selection_data_set(selection_data, selection_data->target, 8,
1237 (const guchar *)items, sizeof(playlist_item_t) * c);
1238 free(items);
1239 };
1240 };
1241
1242 static void playlist_grid_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context,
1243 GtkSelectionData *selection_data, guint info, guint time, gpointer userdata)
1244 {
1245 int *list, i;
1246 playlist_item_t* items;
1247 omnplay_instance_t* app = (omnplay_instance_t*)userdata;
1248
1249 list = omnplay_selected_idxs_playlist(app);
1250 if(!list) return;
1251
1252 /* clear delete flag */
1253 for(i = 0; i < app->playlist.count; i++)
1254 app->playlist.item[i].del = 0;
1255
1256 items = (playlist_item_t*)malloc(sizeof(playlist_item_t) * list[0]);
1257 for(i = 0; i < list[0]; i++)
1258 {
1259 items[i] = app->playlist.item[list[i + 1]];
1260 if(context->action == GDK_ACTION_MOVE)
1261 app->playlist.item[list[i + 1]].del = 1;
1262 }
1263 gtk_selection_data_set(selection_data, selection_data->target, 8,
1264 (const guchar *)items, sizeof(playlist_item_t) * list[0]);
1265
1266 free(items);
1267 free(list);
1268 };
1269
1270 static void library_grid_drag_begin_cb(GtkWidget *widget, GdkDragContext *context, gpointer userdata)
1271 {
1272 g_warning("library_grid_drag_begin_cb");
1273 gtk_drag_source_set_icon_stock(widget, GTK_STOCK_DND);
1274 };
1275
1276 static void playlist_grid_drag_begin_cb(GtkWidget *widget, GdkDragContext *context, gpointer userdata)
1277 {
1278 g_warning("playlist_grid_drag_begin_cb");
1279 gtk_drag_source_set_icon_stock(widget, GTK_STOCK_DND);
1280 };
1281
1282 static void playlist_grid_drag_data_received(GtkWidget *widget, GdkDragContext *context,
1283 gint x, gint y, GtkSelectionData *selection_data, guint info, guint time, gpointer userdata)
1284 {
1285 int c, i, idx;
1286 playlist_item_type_t t;
1287 playlist_item_t* items;
1288 GtkTreePath *path = NULL;
1289 omnplay_instance_t* app = (omnplay_instance_t*)userdata;
1290
1291 g_warning("playlist_grid_drag_data_received: context->action=%d", context->action);
1292
1293 items = (playlist_item_t*)gtk_selection_data_get_data(selection_data);
1294 c = gtk_selection_data_get_length(selection_data);
1295
1296 if(c % sizeof(playlist_item_t))
1297 {
1298 g_warning("playlist_grid_drag_data_received: ODD ITEMS");
1299 }
1300 else
1301 {
1302 c /= sizeof(playlist_item_t);
1303
1304 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL))
1305 {
1306 idx = gtk_tree_path_get_indices(path)[0];
1307 gtk_tree_path_free(path);
1308
1309 g_warning("playlist_grid_drag_data_received: gtk_tree_path_get_indice[0]=%d", idx);
1310
1311 /* normalize, FIX ME */
1312 idx--; if(idx < 0) idx = 0;
1313 }
1314 else
1315 idx = app->playlist.count;
1316
1317 g_warning("playlist_grid_drag_data_received: idx=%d", idx);
1318
1319 if(omnplay_playlist_insert_check(app, idx, &t))
1320 {
1321 for(i = 0; i < c; i++)
1322 {
1323 items[i].type = t;
1324 items[i].error = 0;
1325 };
1326 omnplay_playlist_insert_items(app, idx, items, c);
1327 };
1328 };
1329
1330 /* Finish the drag */
1331 gtk_drag_finish(context, TRUE, FALSE, time);
1332 };
1333
1334 static void playlist_grid_drag_data_delete(GtkWidget *widget, GdkDragContext *context, gpointer userdata)
1335 {
1336 int c, i, *list;
1337 omnplay_instance_t* app = (omnplay_instance_t*)userdata;
1338
1339 g_warning("playlist_grid_drag_data_delete");
1340
1341 list = (int*)malloc(sizeof(int) * MAX_PLAYLIST_ITEMS);
1342
1343 for(i = 0, c = 0; i < app->playlist.count; i++)
1344 if(app->playlist.item[i].del)
1345 if(!idx_in_players_range(app, i))
1346 {
1347 /* save index */
1348 list[c++] = i;
1349 g_warning("playlist_grid_drag_data_delete: i=%d, c=%d", i, c);
1350 };
1351
1352 if(c)
1353 omnplay_playlist_delete_items(app, list, c, 0);
1354
1355 free(list);
1356 };
1357
1358 void omnplay_init(omnplay_instance_t* app)
1359 {
1360 int i;
1361 pthread_mutexattr_t attr;
1362
1363 pthread_mutexattr_init(&attr);
1364 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
1365
1366 gtk_signal_connect( GTK_OBJECT( app->window ), "delete-event",
1367 GTK_SIGNAL_FUNC(on_main_window_delete_event), app);
1368
1369 gtk_signal_connect( GTK_OBJECT( app->window ), "destroy",
1370 GTK_SIGNAL_FUNC(on_main_window_destroy), app);
1371
1372 gtk_widget_add_events(app->playlist_grid, GDK_BUTTON_PRESS_MASK);
1373 gtk_widget_add_events(app->playlist_grid, GDK_KEY_PRESS_MASK);
1374 gtk_signal_connect(GTK_OBJECT(app->playlist_grid), "key-press-event",
1375 GTK_SIGNAL_FUNC(on_playlist_grid_key), app);
1376
1377 gtk_widget_add_events(app->library_grid, GDK_BUTTON_PRESS_MASK);
1378 gtk_widget_add_events(app->library_grid, GDK_KEY_PRESS_MASK);
1379 gtk_signal_connect(GTK_OBJECT(app->library_grid), "key-press-event",
1380 GTK_SIGNAL_FUNC(on_library_grid_key), app);
1381
1382 gtk_signal_connect(GTK_OBJECT(app->playlist_grid), "button-press-event",
1383 GTK_SIGNAL_FUNC(on_playlist_grid_button), app);
1384
1385 gtk_signal_connect(GTK_OBJECT(app->library_grid), "button-press-event",
1386 GTK_SIGNAL_FUNC(on_library_grid_button), app);
1387
1388 /* create lock */
1389 pthread_mutex_init(&app->players.lock, &attr);
1390 pthread_mutex_init(&app->playlist.lock, &attr);
1391 pthread_mutex_init(&app->library.lock, &attr);
1392
1393 /* create a omneon status thread */
1394 for(i = 0; i < app->players.count; i++)
1395 app->players.item[i].thread = g_thread_create(
1396 omnplay_thread_proc, &app->players.item[i], TRUE, NULL);
1397
1398 /* attach buttons click */
1399 for(i = 1; i < BUTTON_LAST; i++)
1400 gtk_signal_connect(GTK_OBJECT(app->buttons[i]), "clicked",
1401 GTK_SIGNAL_FUNC( on_button_click), app );
1402
1403 /* load library */
1404 omnplay_library_load(app);
1405
1406 pthread_mutexattr_destroy(&attr);
1407
1408 /* setup drag n drop source/target */
1409 static GtkTargetEntry drag_targets[] = { { (char*) "application/playlist_item_t", 0, 0 } };
1410
1411 gtk_drag_source_set(app->library_grid, GDK_BUTTON1_MASK,
1412 drag_targets, 1, (GdkDragAction)(GDK_ACTION_COPY));
1413
1414 gtk_drag_source_set(app->playlist_grid, GDK_BUTTON1_MASK,
1415 drag_targets, 1, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_MOVE));
1416
1417 gtk_drag_dest_set(app->playlist_grid, GTK_DEST_DEFAULT_ALL,
1418 drag_targets, 1, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_MOVE));
1419
1420 g_signal_connect (app->library_grid, "drag_data_get", G_CALLBACK(library_grid_drag_data_get_cb), app);
1421 g_signal_connect (app->playlist_grid, "drag_data_get", G_CALLBACK(playlist_grid_drag_data_get_cb), app);
1422 g_signal_connect (app->library_grid, "drag_begin", G_CALLBACK(library_grid_drag_begin_cb), app);
1423 g_signal_connect (app->playlist_grid, "drag_begin", G_CALLBACK(playlist_grid_drag_begin_cb), app);
1424 g_signal_connect (app->playlist_grid, "drag_data_received", G_CALLBACK (playlist_grid_drag_data_received), app);
1425 g_signal_connect (app->playlist_grid, "drag_data_delete", G_CALLBACK (playlist_grid_drag_data_delete), app);
1426 };
1427
1428 void omnplay_release(omnplay_instance_t* app)
1429 {
1430 int i;
1431
1432 app->f_exit = 1;
1433
1434 for(i = 0; i < app->players.count; i++)
1435 /* create a omneon status thread */
1436 g_thread_join(app->players.item[i].thread);
1437
1438 /* destroy lock */
1439 pthread_mutex_destroy(&app->players.lock);
1440
1441 /* destroy lock */
1442 pthread_mutex_destroy(&app->playlist.lock);
1443
1444 /* load library */
1445 omnplay_library_save(app);
1446
1447 /* destroy library lock */
1448 pthread_mutex_destroy(&app->library.lock);
1449 };
1450
1451 void omnplay_playlist_normalize(omnplay_instance_t* app)
1452 {
1453 int i;
1454
1455 /* normalize playlist */
1456 for(i = 0; i < app->playlist.count; i++)
1457 if(omnplay_library_normalize_item(app, &app->playlist.item[i]))
1458 omnplay_playlist_draw_item(app, i);
1459 };
1460
1461 void omnplay_set_status(omnplay_instance_t* app, char* str)
1462 {
1463 gdk_threads_enter();
1464 gtk_label_set_text(GTK_LABEL(app->status_label), str);
1465 gdk_flush();
1466 gdk_threads_leave();
1467 };
1468