initial commit
[goodguy/history.git] / cinelerra-5.0 / cinelerra / dbwindow.C
1
2
3 #include "bcmenuitem.h"
4 #include "bctimer.h"
5 #include "commercials.h"
6 #include "dbwindow.h"
7 #include "keys.h"
8 #include "mediadb.h"
9 #include "mwindow.h"
10 #include "mwindowgui.h"
11 #include "mutex.h"
12 #include "theme.h"
13
14 #include <string.h>
15 #include <strings.h>
16
17 static inline int min(int a,int b) { return a<b ? a : b; }
18 static inline int max(int a,int b) { return a>b ? a : b; }
19
20 #define MAX_SEARCH 100
21
22 DbWindow::
23 DbWindow(MWindow *mwindow)
24  : Thread(1, 0, 0)
25 {
26         this->mwindow = mwindow;
27         window_lock = new Mutex("DbWindow::window_lock");
28         db_lock = new Mutex("DbWindow::db_lock");
29         mdb = new MDb(this);
30         gui = 0;
31 }
32
33 DbWindow::MDb::MDb(DbWindow *d)
34  : Garbage("DbWindow::MDb"), dwin(d)
35 {
36         if( !d->mwindow->has_commercials() ) return;
37          openDb();  detachDb();
38 }
39
40 DbWindow::MDb::~MDb()
41 {
42         closeDb();
43 }
44
45 DbWindow::~DbWindow()
46 {
47         stop();
48         delete gui;
49         mdb->remove_user();
50         delete window_lock;
51         delete db_lock;
52 }
53
54 void DbWindow::
55 start()
56 {
57         window_lock->lock("DbWindow::start1");
58         if( Thread::running() ) {
59                 gui->lock_window("DbWindow::start2");
60                 gui->raise_window();
61                 gui->unlock_window();
62         }
63         else {
64                 delete gui;
65                 gui = new DbWindowGUI(this);
66                 Thread::start();
67         }
68         window_lock->unlock();
69 }
70
71
72 void DbWindow::
73 stop()
74 {
75         if( Thread::running() ) {
76                 window_lock->lock("DbWindow::stop");
77                 if( gui ) gui->set_done(1);
78                 window_lock->unlock();
79                 Thread::cancel();
80                 Thread::join();
81         }
82 }
83
84 void DbWindow::
85 run()
86 {
87         gui->lock_window("DbWindow::run");
88         gui->create_objects();
89         gui->search(MAX_SEARCH,"");
90         gui->start_drawing();
91         gui->unlock_window();
92         gui->run_window();
93         window_lock->lock("DbWindow::stop");
94         delete gui;  gui = 0;
95         window_lock->unlock();
96 }
97
98
99 DbWindowTitleText::
100 DbWindowTitleText(DbWindowGUI *gui, int x, int y)
101  : BC_CheckBox(x, y, &gui->title_text_enable, "titles")
102 {
103         this->gui = gui;
104 }
105
106 DbWindowTitleText::~DbWindowTitleText()
107 {
108 }
109
110 int DbWindowTitleText::
111 handle_event()
112 {
113         gui->title_text_enable = get_value();
114         if( !gui->title_text_enable ) gui->info_text->update(1);
115         return 1;
116 }
117
118
119 DbWindowInfoText::
120 DbWindowInfoText(DbWindowGUI *gui, int x, int y)
121  : BC_CheckBox(x, y, &gui->info_text_enable, "info")
122 {
123         this->gui = gui;
124 }
125
126 DbWindowInfoText::~DbWindowInfoText()
127 {
128 }
129
130 int DbWindowInfoText::
131 handle_event()
132 {
133         gui->info_text_enable = get_value();
134         if( !gui->info_text_enable ) gui->title_text->update(1);
135         return 1;
136 }
137
138
139 DbWindowMatchCase::
140 DbWindowMatchCase(DbWindowGUI *gui, int x, int y)
141  : BC_CheckBox(x, y, &gui->match_case_enable, "match case")
142 {
143         this->gui = gui;
144 }
145
146 DbWindowMatchCase::~DbWindowMatchCase()
147 {
148 }
149
150 int DbWindowMatchCase::
151 handle_event()
152 {
153         gui->match_case_enable = get_value();
154         return 1;
155 }
156
157
158 DbWindowText::
159 DbWindowText(DbWindowGUI *gui, int x, int y, int w)
160  : BC_TextBox(x, y, w, 1, "") 
161 {
162         this->gui = gui;
163 }
164
165 DbWindowText::~DbWindowText()
166 {
167 }
168
169
170 int DbWindowText::
171 handle_event()
172 {
173         return 1;
174 }
175
176 int DbWindowText::
177 keypress_event()
178 {
179         switch(get_keypress())
180         {
181         case RETURN:
182                 gui->search(MAX_SEARCH, get_text());
183                 return 1;
184         }
185
186         return BC_TextBox::keypress_event();
187 }
188
189
190 DbWindowScan::
191 DbWindowScan(MWindow *mwindow)
192  : BC_MenuItem("Media DB", "Shift-M", 'M')
193 {
194         set_shift();
195         this->mwindow = mwindow;
196 }
197
198 DbWindowScan::~DbWindowScan()
199 {
200 }
201
202 int DbWindowScan::
203 handle_event()
204 {
205         mwindow->commit_commercial();
206         mwindow->gui->db_window->start();
207         return 1;
208 }
209
210 DbWindowStart::
211 DbWindowStart(DbWindowGUI *gui, int x, int y)
212  : BC_GenericButton(x, y, "Search")
213 {
214         this->gui = gui;
215 }
216
217 DbWindowStart::~DbWindowStart()
218 {
219 }
220
221 int DbWindowStart::
222 handle_event()
223 {
224         gui->search(MAX_SEARCH,"");
225         return 1;
226 }
227
228 DbWindowDeleteItems::
229 DbWindowDeleteItems(DbWindowGUI *gui, int x, int y)
230  : BC_GenericButton(x, y, "Delete")
231 {
232         this->gui = gui;
233 }
234
235 DbWindowDeleteItems::~DbWindowDeleteItems()
236 {
237 }
238
239 int DbWindowDeleteItems::
240 handle_event()
241 {
242         gui->search_list->stop_view_popup();
243         gui->delete_items();
244         return 1;
245 }
246
247
248 DbWindowCancel::
249 DbWindowCancel(DbWindowGUI *gui, int x, int y)
250  : BC_CancelButton(x, y)
251 {
252         this->gui = gui;
253 }
254
255 DbWindowCancel::~DbWindowCancel()
256 {
257 }
258
259 int DbWindowCancel::
260 handle_event()
261 {
262         gui->set_done(1);
263         return 1;
264 }
265
266
267 DbWindowList::
268 DbWindowList(DbWindowGUI *gui, int x, int y, int w, int h)
269  : BC_ListBox(x, y, w, h, LISTBOX_TEXT, &gui->search_items[0],
270         &gui->search_column_titles[0], &gui->search_column_widths[0],
271         sizeof_col, 0, 0, LISTBOX_MULTIPLE)
272 {
273         this->gui = gui;
274         this->thread = gui->ticon_thread;
275         this->view_popup = 0;
276         this->view_idx = -1;
277         view_ticon = new DbWindowTIcon(gui, &DbWindowTIcon::draw_popup);
278         set_sort_column(gui->sort_column);
279         set_sort_order(gui->sort_order);
280         set_allow_drag_column(1);
281 }
282
283 DbWindowList::~DbWindowList()
284 {
285         delete view_ticon;
286         delete view_popup;
287 }
288
289 int DbWindowList::
290 handle_event()
291 {
292         return 1;
293 }
294
295 int DbWindowList::
296 stop_view_popup()
297 {
298         if( !view_popup ) return 0;
299         BC_Popup *vp = view_popup;
300         view_popup = 0;
301         delete vp;
302         view_idx = -1;
303         return 1;
304 }
305
306 int DbWindowList::
307 keypress_event()
308 {
309         switch(get_keypress()) {
310         case ESC:
311                 if( stop_view_popup() ) return 1;
312                 break;
313         case DELETE:
314                 gui->del_items->handle_event();
315                 break;
316         }
317         return 0;
318 }
319
320 int DbWindowList::
321 sort_order_event()
322 {
323         gui->stop_drawing(1);
324         gui->sort_events(get_sort_column(), get_sort_order());
325         gui->start_drawing(1);
326         return 1;
327 }
328
329 int DbWindowList::
330 move_column_event()
331 {
332         gui->stop_drawing(1);
333         gui->move_column(get_from_column(), get_to_column());
334         gui->start_drawing(1);
335         return 1;
336 }
337
338 int DbWindowList::update_images()
339 {
340         thread->t_heap.remove_all();
341         if( view_popup ) {
342                 view_ticon->age = 0;
343                 thread->t_heap.append(view_ticon);
344         }
345
346         int i = get_first_visible();
347         int k = sizeof_col;
348         while( --k >= 0 && gui->search_columns[k] != col_ticon );
349         if( k >= 0 && i >= 0 ) {
350                 int sz = gui->search_results.size();
351                 int last = get_last_visible();
352                 if( last < sz ) sz = last+1;
353                 for( int n=0; i<sz; ++i, ++n ) {
354                         BC_ListBoxItem *item = gui->search_items[k][i];
355                         int x = item->get_text_x() - get_xposition() + 7;
356                         int y = item->get_text_y() + get_title_h() - get_yposition() + 5;
357                         DbWindowTIcon *ticon = thread->get_ticon(n);
358                         ticon->update_image(gui->search_results[i]->id, x, y);
359                 }
360         }
361         return 0;
362 }
363
364 int DbWindowList::draw_images()
365 {
366         DbWindowTIconThread *thread = gui->ticon_thread;
367         thread->image_update = 1;
368         thread->start_drawing();
369         return 0;
370 }
371
372 int DbWindowList::
373 update()
374 {
375         BC_ListBox::update(gui->search_items, &gui->search_column_titles[0],
376                         &gui->search_column_widths[0], sizeof_col);
377         return 0;
378 }
379
380
381
382 void DbWindowGUI::
383 create_objects()
384 {
385         int pady = BC_TextBox::calculate_h(this, MEDIUMFONT, 0, 1) + 5;
386         int padx = BC_Title::calculate_w(this, (char*)"X", MEDIUMFONT);
387         int x = padx/2, y = pady/4;
388         text_x = x;  text_y = y;
389         BC_Title *title = new BC_Title(text_x, text_y, "Text:", MEDIUMFONT, YELLOW);
390         add_subwindow(title);  x += title->get_w();
391         search_text = new DbWindowText(this, x, y, get_w()-x-10);
392         add_subwindow(search_text);
393         x = padx;  y += pady + 5;
394         title_text = new DbWindowTitleText(this, x, y);
395         add_subwindow(title_text);  x += title_text->get_w() + padx;
396         info_text = new DbWindowInfoText(this, x, y);
397         add_subwindow(info_text);  x += title_text->get_w() + padx;
398         match_case = new DbWindowMatchCase(this, x, y);
399         add_subwindow(match_case); x += match_case->get_w() + padx;
400         x = padx;  y += pady + 5;
401         search_x = 10;
402         search_y = get_h() - DbWindowStart::calculate_h() - 10;
403         search_start = new DbWindowStart(this, search_x, search_y);
404         add_subwindow(search_start);
405         del_items_x = search_x + DbWindowStart::calculate_w(this, search_start->get_text()) + 15;
406         del_items_y = search_y;
407         del_items = new DbWindowDeleteItems(this, del_items_x, del_items_y);
408         add_subwindow(del_items);
409         cancel_w = DbWindowCancel::calculate_w();
410         cancel_h = DbWindowCancel::calculate_h();
411         cancel_x = get_w() - cancel_w - 10;
412         cancel_y = get_h() - cancel_h - 10;
413         cancel = new DbWindowCancel(this, cancel_x, cancel_y);
414         add_subwindow(cancel);
415         ticon_thread = new DbWindowTIconThread(this);
416         list_x = x;  list_y = y;
417         int list_w = get_w()-10 - list_x;
418         int list_h = min(search_y, cancel_y)-10 - list_y;
419         search_list = new DbWindowList(this, list_x, list_y, list_w, list_h);
420         add_subwindow(search_list);
421         search_list->show_window();
422         canvas_x = 0;
423         canvas_y = search_list->get_title_h();
424         canvas_w = search_list->get_w();
425         canvas_h = search_list->get_h() - canvas_y;
426         canvas = new DbWindowCanvas(this, canvas_x, canvas_y, canvas_w, canvas_h);
427         canvas->use_auxwindow(search_list);
428         canvas->create_objects(0);
429         set_icon(dwindow->mwindow->theme->get_image("record_icon"));
430 }
431
432
433 DbWindowGUI::
434 DbWindowGUI(DbWindow *dwindow)
435  : BC_Window(PROGRAM_NAME ": DbWindow",
436         dwindow->mwindow->gui->get_abs_cursor_x(1) - 900 / 2,
437         dwindow->mwindow->gui->get_abs_cursor_y(1) - 600 / 2,
438         900, 600, 400, 400)
439 {
440         this->dwindow = dwindow;
441
442         search_start = 0;
443         search_list = 0;
444         cancel = 0;
445
446         search_x = search_y = 0;
447         cancel_x = cancel_y = cancel_w = cancel_h = 0;
448         del_items_x = del_items_y = 0;
449         list_x = list_y = list_w = list_h = 0;
450         sort_column = 1;  sort_order = 0;
451
452         title_text_enable = 1;
453         info_text_enable = 1;
454         match_case_enable = 1;
455
456         search_columns[col_ticon] = col_ticon;
457         search_columns[col_id] = col_id;
458         search_columns[col_length] = col_length;
459         search_columns[col_source] = col_source;
460         search_columns[col_title] = col_title;
461         search_columns[col_start_time] = col_start_time;
462         search_columns[col_access_time] = col_access_time;
463         search_columns[col_access_count] = col_access_count;
464         search_column_titles[col_ticon] = "ticon";
465         search_column_titles[col_id] = "Id";
466         search_column_titles[col_length] = "length";
467         search_column_titles[col_source] = "Source";
468         search_column_titles[col_title] = "Title";
469         search_column_titles[col_start_time] = "Start time";
470         search_column_titles[col_access_time] = "Access time";
471         search_column_titles[col_access_count] = "count";
472         search_column_widths[col_ticon] = 90;
473         search_column_widths[col_id] = 60;
474         search_column_widths[col_length] = 80;
475         search_column_widths[col_source] = 50;
476         search_column_widths[col_title] = 160;
477         search_column_widths[col_start_time] = 140;
478         search_column_widths[col_access_time] = 140;
479         search_column_widths[col_access_count] = 60;
480         int wd = 0;
481         for( int i=0; i<sizeof_col; ++i )
482                 if( i != col_title ) wd += search_column_widths[i];
483         search_column_widths[col_title] = get_w() - wd - 40;
484 }
485
486 DbWindowGUI::~DbWindowGUI()
487 {
488         for( int i=0; i<sizeof_col; ++i )
489                 search_items[i].remove_all_objects();
490         delete ticon_thread;
491         delete canvas;
492 }
493
494 int DbWindowGUI::
495 resize_event(int w, int h)
496 {
497         stop_drawing(1);
498         int cancel_x = w - BC_CancelButton::calculate_w() - 10;
499         int cancel_y = h - BC_CancelButton::calculate_h() - 10;
500         cancel->reposition_window(cancel_x, cancel_y);
501         search_x = 10;
502         search_y = h - BC_GenericButton::calculate_h() - 10;
503         search_start->reposition_window(search_x, search_y);
504         del_items_x = search_x + DbWindowStart::calculate_w(this, search_start->get_text()) + 15;
505         del_items_y = search_y;
506         del_items->reposition_window(del_items_x,del_items_y);
507         int list_w = w-10 - list_x;
508         int list_h = min(search_y, cancel_y)-10 - list_y;
509         canvas_w = SWIDTH;  canvas_h = SHEIGHT;
510         canvas_x = cancel_x - canvas_w - 15;
511         canvas_y = get_h() - canvas_h - 10;
512         canvas->reposition_window(0, canvas_x, canvas_y, canvas_w, canvas_h);
513 //      int wd = 0;
514 //      for( int i=0; i<sizeof_col; ++i )
515 //              if( i != col_title ) wd += search_column_widths[i];
516 //      search_column_widths[col_title] = w - wd - 40;
517         search_list->reposition_window(list_x, list_y, list_w, list_h);
518         start_drawing(1);
519         return 1;
520 }
521
522 int DbWindowGUI::
523 close_event()
524 {
525         set_done(1);
526         return 1;
527 }
528
529 int DbWindowGUI::
530 stop_drawing(int locked)
531 {
532         if( locked ) unlock_window();
533         ticon_thread->stop_drawing();
534         if( locked ) lock_window("DbWindowGUI::");
535         return 0;
536 }
537
538 int DbWindowGUI::
539 start_drawing(int update)
540 {
541         if( update ) ticon_thread->list_update = 1;
542         ticon_thread->start_drawing();
543         return 0;
544 }
545
546 int DbWindowGUI::search_string(const char *text, const char *sp)
547 {
548         if( sp ) {
549                 int n = strlen(text);
550                 for( const char *cp=sp; *cp!=0; ++cp ) {
551                         if( match_case_enable ? !strncmp(text, cp, n) :
552                                 !strncasecmp(text, cp, n) ) return 1;
553                 }
554         }
555         return 0;
556 }
557
558 void DbWindowGUI::search_clips(MediaDb *mdb, int n, const char *text)
559 {
560         //Db::write_blocked by(mdb->db.Clip_set);
561         if( !mdb->clips_first_id() ) do {
562                 if( (title_text_enable && search_string(text, mdb->clip_title())) ||
563                     (info_text_enable  && search_string(text, mdb->clip_path())) ) {
564                         int id = mdb->clip_id();
565                         double system_time = mdb->clip_system_time();
566                         double start_time = 0;
567                         if( system_time > 0 )
568                                 start_time = system_time + mdb->clip_position();
569                         double access_time = 0;  int access_count = 0;
570                         if( !mdb->views_clip_id(id) ) {
571                                  access_time = mdb->views_access_time();
572                                  access_count =  mdb->views_access_count();
573                         }
574                         double length = mdb->clip_frames() / mdb->clip_framerate();
575                         search_results.append(new DbWindowItem(id, mdb->clip_title(),
576                                 mdb->clip_path(), length, start_time, access_time, access_count));
577                 }
578         } while( --n >= 0 && !mdb->clips_next_id() );
579 }
580
581 void DbWindowGUI::search(int n, const char *text)
582 {
583         stop_drawing(1);
584         search_results.remove_all();
585         DbWindow::MDb *mdb = dwindow->mdb;
586         if( !mdb->attach_rd() ) {
587                 search_clips(mdb, n, text);
588                 mdb->detach();
589         }
590         start_drawing(1);
591 }
592
593 int DbWindowGUI::delete_selection(MediaDb *mdb)
594 {
595         int n = 0;
596         for( int k=0; k<search_results.size(); ++k ) {
597                 if( !search_items[0][k]->get_selected() ) continue;
598                 int id = search_results[k]->id;
599                 if( mdb->del_clip_set(id) ) {
600                         printf("failed delete clip id %d\n",id);
601                         continue;
602                 }
603                 search_results.remove_object_number(k);
604                 for( int i=0; i<sizeof_col; ++i )
605                         search_items[i].remove_object_number(k);
606                 ++n;  --k;
607         }
608         if( n > 0 ) mdb->commitDb();
609         return 0;
610 }
611
612 void DbWindowGUI::delete_items()
613 {
614         stop_drawing();
615         DbWindow::MDb *mdb = dwindow->mdb;
616         if( !mdb->attach_wr() ) {
617                 delete_selection(mdb);
618                 mdb->detach();
619         }
620         start_drawing(1);
621 }
622
623 void DbWindowGUI::
624 update()
625 {
626
627         for( int i=0; i<sizeof_col; ++i )
628                 search_items[i].remove_all_objects();
629
630         char text[BCTEXTLEN];
631         int n = search_results.size();
632         for( int i=0; i<n; ++i ) {
633                 DbWindowItem *item = search_results[i];
634                 for( int k=0; k<sizeof_col; ++k ) {
635                         const char *cp = 0;  text[0] = 0;
636                         switch( search_columns[k] ) {
637                         case col_ticon:
638                                 cp = "";
639                                 break;
640                         case col_id: {
641                                 sprintf(text,"%4d", item->id);
642                                 cp = text;
643                                 break; }
644                         case col_length: {
645                                 sprintf(text, "%8.3f", item->length);
646                                 cp = text;
647                                 break; }
648                         case col_source:
649                                 cp = item->source;
650                                 break;
651                         case col_title:
652                                 cp = item->title;
653                                 break;
654                         case col_start_time: {
655                                 time_t st = item->start_time;
656                                 if( st > 0 ) {
657                                         struct tm stm;  localtime_r(&st,&stm);
658                                         sprintf(text,"%02d:%02d:%02d %02d/%02d/%02d",
659                                                 stm.tm_hour, stm.tm_min, stm.tm_sec,
660                                                 stm.tm_year%100, stm.tm_mon, stm.tm_mday);
661                                 }
662                                 cp = text;
663                                 break; }
664                         case col_access_time: {
665                                 time_t at = item->access_time;
666                                 if( at > 0 ) {
667                                         struct tm atm;  localtime_r(&at,&atm);
668                                         sprintf(text,"%02d/%02d/%02d %02d:%02d:%02d",
669                                                 atm.tm_year%100, atm.tm_mon, atm.tm_mday,
670                                                 atm.tm_hour, atm.tm_min, atm.tm_sec);
671                                 }
672                                 cp = text;
673                                 break; }
674                         case col_access_count: {
675                                 sprintf(text,"%4d", item->access_count);
676                                 cp = text;
677                                 break; }
678                         }
679                         if( !cp ) continue;
680                         BC_ListBoxItem *item = new BC_ListBoxItem(cp, LTYELLOW);
681                         if( search_columns[k] == col_ticon ) {
682                                 item->set_text_w(SWIDTH+10);
683                                 item->set_text_h(SHEIGHT+5);
684                                 item->set_searchable(0);
685                         }
686                         search_items[k].append(item);
687                 }
688         }
689
690         search_list->update();
691 }
692
693
694 DbWindowItem::
695 DbWindowItem(int id, const char *source, const char *title,
696         double length, double start_time,
697         double access_time, int access_count)
698 {
699         this->id = id;
700         if( !source ) source = "";
701         this->source = new char[strlen(source)+1];
702         strcpy(this->source,source);
703         if( !title ) title = "";
704         this->title = new char[strlen(title)+1];
705         strcpy(this->title,title);
706         this->length = length;
707         this->start_time = start_time;
708         this->access_time = access_time;
709         this->access_count = access_count;
710 }
711
712 DbWindowItem::
713 ~DbWindowItem()
714 {
715         delete source;
716         delete title;
717 }
718
719 #define CmprFn(nm,key) int DbWindowGUI:: \
720 cmpr_##nm(const void *a, const void *b) { \
721   DbWindowItem *&ap = *(DbWindowItem **)a; \
722   DbWindowItem *&bp = *(DbWindowItem **)b; \
723   int n = key; if( !n ) n = ap->no-bp->no; \
724   return n; \
725 }
726
727 extern long timezone;
728
729 CmprFn(id_dn, ap->id - bp->id)
730 CmprFn(id_up, bp->id - ap->id)
731 CmprFn(length_dn, ap->length - bp->length)
732 CmprFn(length_up, bp->length - ap->length)
733 CmprFn(Source_dn, strcasecmp(ap->source, bp->source))
734 CmprFn(source_dn, strcmp(ap->source, bp->source))
735 CmprFn(Source_up, strcasecmp(bp->source, ap->source))
736 CmprFn(source_up, strcmp(bp->source, ap->source))
737 CmprFn(Title_dn, strcasecmp(ap->title, bp->title))
738 CmprFn(title_dn, strcmp(ap->title, bp->title))
739 CmprFn(Title_up, strcasecmp(bp->title, ap->title))
740 CmprFn(title_up, strcmp(bp->title, ap->title))
741 CmprFn(start_time_dn, fmod(ap->start_time-timezone,24*3600) - fmod(bp->start_time-timezone,24*3600))
742 CmprFn(start_time_up, fmod(bp->start_time-timezone,24*3600) - fmod(ap->start_time-timezone,24*3600))
743 CmprFn(access_time_dn, ap->access_time - bp->access_time)
744 CmprFn(access_time_up, bp->access_time - ap->access_time)
745 CmprFn(access_count_dn, ap->access_count - bp->access_count)
746 CmprFn(access_count_up, bp->access_count - ap->access_count)
747
748
749 void DbWindowGUI::
750 sort_events(int column, int order)
751 {
752         sort_column = column;  sort_order = order;
753         if( search_columns[sort_column] == col_ticon ) return;
754         int n = search_results.size();
755         if( !n ) return;
756         DbWindowItem **items = &search_results[0];
757         for( int i=0; i<n; ++i ) items[i]->no = i;
758
759         int(*cmpr)(const void *, const void *) = 0;
760         switch( search_columns[sort_column] ) {
761         case col_id:
762                 cmpr = sort_order ? cmpr_id_up : cmpr_id_dn;
763                 break;
764         case col_length:
765                 cmpr = sort_order ? cmpr_length_up : cmpr_length_dn;
766                 break;
767         case col_source:
768                 cmpr = match_case_enable ?
769                         (sort_order ? cmpr_Source_up : cmpr_Source_dn) :
770                         (sort_order ? cmpr_source_up : cmpr_source_dn) ;
771                 break;
772         case col_title:
773                 cmpr = match_case_enable ?
774                         (sort_order ? cmpr_Title_up : cmpr_Title_dn) :
775                         (sort_order ? cmpr_title_up : cmpr_title_dn) ;
776                 break;
777         case col_start_time:
778                 cmpr = sort_order ? cmpr_start_time_up : cmpr_start_time_dn;
779                 break;
780         case col_access_time:
781                 cmpr = sort_order ? cmpr_access_time_up : cmpr_access_time_dn;
782                 break;
783         case col_access_count:
784                 cmpr = sort_order ? cmpr_access_count_up : cmpr_access_count_dn;
785                 break;
786         }
787
788         if( cmpr ) qsort(items, n, sizeof(*items), cmpr);
789 }
790
791 void DbWindowGUI::
792 move_column(int src, int dst)
793 {
794         if( src == dst ) return;
795         int src_column = search_columns[src];
796         const char *src_column_title = search_column_titles[src];
797         int src_column_width = search_column_widths[src];
798         if( src < dst ) {
799                 for( int i=src; i<dst; ++i ) {
800                         search_columns[i] = search_columns[i+1];
801                         search_column_titles[i] = search_column_titles[i+1];
802                         search_column_widths[i] = search_column_widths[i+1];
803                 }
804         }
805         else {
806                 for( int i=src; i>dst; --i ) {
807                         search_columns[i] = search_columns[i-1];
808                         search_column_titles[i] = search_column_titles[i-1];
809                         search_column_widths[i] = search_column_widths[i-1];
810                 }
811         }
812         search_columns[dst] = src_column;
813         search_column_titles[dst] = src_column_title;
814         search_column_widths[dst] = src_column_width;
815         int k = sizeof_col;
816         while( --k >= 0 && search_columns[k] != col_ticon );
817         if( k >= 0 ) search_list->set_master_column(k,0);
818 }
819
820
821 DbWindowCanvas::
822 DbWindowCanvas(DbWindowGUI *gui, int x, int y, int w, int h)
823  : Canvas(gui->dwindow->mwindow, gui, x, y, w, h, w, h, 0)
824 {
825         this->gui = gui;
826         this->is_fullscreen = 0;
827 }
828
829 DbWindowCanvas::
830 ~DbWindowCanvas()
831 {
832 }
833
834 void DbWindowCanvas::
835 flash_canvas()
836 {
837         BC_WindowBase* wdw = get_canvas();
838         wdw->lock_window("DbWindowCanvas::flash_canvas");
839         wdw->flash();
840         wdw->unlock_window();
841 }
842
843 void DbWindowCanvas::
844 draw_frame(VFrame *frame, int x, int y, int w, int h)
845 {
846         BC_WindowBase* wdw = get_canvas();
847         int min_y = gui->search_list->get_title_h();
848         int max_y = gui->search_list->get_view_h()+min_y;
849         min_y += 2;  max_y -= 2;
850         int sx = 0, sy = 0, sw = frame->get_w(), sh = frame->get_h();
851         int dy = min_y - y;
852         if( dy > 0 ) { sy += dy;  y = min_y;   sh -= dy;  h -= dy; }
853         dy = y+h - max_y;
854         if( dy > 0 ) { sh -= dy;  h -= dy; }
855         sh &= ~1;
856         if( sh > 0 ) {
857                 //wdw->lock_window("DbWindowCanvas::draw_frame");
858                 wdw->draw_vframe(frame, x,y,w,(h&~1), sx,sy,sw,sh, 0);
859                 //wdw->unlock_window();
860         }
861 }
862
863
864 DbWindowTIconThread::
865 DbWindowTIconThread(DbWindowGUI *gui)
866  : Thread(1, 0, 0)
867 {
868         this->gui = gui;
869         mdb = gui->dwindow->mdb;
870         mdb->add_user();
871         ticon_lock = new Mutex("DbWindowTIconThread::ticon_lock");
872         draw_lock = new Condition(0, "DbWindowTIconThread::draw_lock", 0);
873         stop_lock = new Condition(0, "DbWindowTIconThread::stop_lock", 0);
874         timer = new Timer();
875         done = 0;  interrupted = 1;
876         list_update = image_update = 0;
877         Thread::start();
878 }
879
880 DbWindowTIconThread::
881 ~DbWindowTIconThread()
882 {
883         stop_drawing();
884         done = 1;
885         draw_lock->unlock();
886         if( Thread::running() ) {
887                 Thread::cancel();
888                 Thread::join();
889         }
890         mdb->remove_user();
891         t_heap.remove_all();
892         ticons.remove_all_objects();
893         delete draw_lock;
894         delete stop_lock;
895 }
896
897 void DbWindowTIconThread::start_drawing()
898 {
899         if( interrupted ) {
900                 interrupted = 0;
901                 draw_lock->unlock();
902         }
903 }
904
905 void DbWindowTIconThread::stop_drawing()
906 {
907         if( !interrupted ) {
908                 interrupted = 1;
909                 stop_lock->lock("DbWindowTIconThread::stop_draw");
910         }
911 }
912
913
914 DbWindowTIcon::
915 DbWindowTIcon(DbWindowGUI *gui, int (DbWindowTIcon::*draw)() )
916 {
917         this->gui = gui;
918         this->draw = draw;
919         age = 0;  x = y = 0;
920         swidth = (SWIDTH+1) & ~1;
921         sheight = (SHEIGHT+1) & ~1;
922         frame = new VFrame(swidth, sheight, BC_YUV420P);
923         memset(frame->get_y(),0,swidth * sheight);
924         memset(frame->get_u(),0x80,swidth/2 * sheight/2);
925         memset(frame->get_v(),0x80,swidth/2 * sheight/2);
926         clip_id = -1;  clip_size = 0;
927         frame_id = frames = -1;
928         seq_no = 0;
929         prefix_size = suffix_offset = -1;
930         framerate = frame_period = 0;
931 }
932
933 DbWindowTIcon::
934 ~DbWindowTIcon()
935 {
936         delete frame;
937 }
938
939
940 void DbWindowTIcon::
941 update_image(int clip_id, int x, int y)
942 {
943         DbWindowTIconThread *thread = gui->ticon_thread;
944         DbWindow::MDb *mdb = gui->dwindow->mdb;
945         if( !mdb->attach_rd() ) {
946                 if( !mdb->clip_id(clip_id) ) {
947                         this->clip_id = clip_id;
948                         this->x = x;  this->y = y;
949                         this->clip_size = mdb->clip_size();;
950                         this->prefix_size = mdb->clip_prefix_size();
951                         this->suffix_offset = mdb->clip_frames() - clip_size;
952                         this->framerate = mdb->clip_framerate();;
953                         this->frame_period = 1000. / framerate;
954                         thread->add_ticon(this, 0);
955                 }
956                 mdb->detach();
957         }
958 }
959
960 int DbWindowTIcon::
961 get_seq_frame()
962 {
963         int ret, no = seq_no++;
964         if( seq_no >= clip_size ) seq_no = 0;
965         DbWindow::MDb *mdb = gui->dwindow->mdb;
966         if( !mdb->attach_rd() ) {
967                 if( !no ) frame_id = -1;
968                 else if( no >= prefix_size ) no += suffix_offset;
969                 if( !(ret=mdb->get_sequences(clip_id, no)) ) {
970                         if( mdb->timeline_sequence_no() == no )
971                                 frame_id = mdb->timeline_frame_id();
972                         uint8_t *yp = frame->get_y();
973                         if( frame_id >= 0 ) {
974                                 int sw, sh;
975                                 ret = mdb->get_image(frame_id, yp, sw, sh);
976                         }
977                         else
978                                 memset(yp,0,swidth*sheight);
979                 }
980                 else if( no > 0 ) ret = 0;
981                 mdb->detach();
982         }
983         return ret;
984 }
985
986 int DbWindowTIcon::
987 draw_frame()
988 {
989         gui->canvas->draw_frame(frame, x, y, swidth, sheight);
990         return 0;
991 }
992
993 int DbWindowTIcon::
994 draw_popup()
995 {
996         BC_Popup *vpopup = gui->search_list->view_popup;
997         if( !vpopup ) return 1;
998         vpopup->lock_window("DbWindowTIcon::draw_popup");
999         vpopup->draw_vframe(frame, 0,0,vpopup->get_w(),vpopup->get_h(), 0,0,swidth,sheight, 0);
1000         vpopup->flash();
1001         vpopup->unlock_window();
1002         return 0;
1003 }
1004
1005 DbWindowTIcon *DbWindowTIconThread::get_ticon(int i)
1006 {
1007         while( i >= ticons.size() ) {
1008                 DbWindowTIcon *ticon = new DbWindowTIcon(gui, &DbWindowTIcon::draw_frame);
1009                 ticons.append(ticon);
1010         }
1011         return ticons[i];
1012 }
1013
1014 DbWindowTIcon *DbWindowTIconThread::low_ticon()
1015 {
1016         mLock by(ticon_lock);
1017         int sz = t_heap.size();
1018         if( !sz ) return 0;
1019         DbWindowTIcon *ticon = t_heap[0];
1020         int i = 0;
1021         for( int k; (k=2*(i+1)) < sz; i=k ) {
1022                 if( t_heap[k]->age > t_heap[k-1]->age ) --k;
1023                 t_heap[i] = t_heap[k];
1024         }
1025         DbWindowTIcon *last = t_heap[--sz];
1026         t_heap.remove_number(sz);
1027         double age = last->age;
1028         for( int k; i>0 && age<t_heap[k=(i-1)/2]->age; i=k )
1029                 t_heap[i] = t_heap[k];
1030         t_heap[i] = last;
1031         return ticon;
1032 }
1033
1034 void DbWindowTIconThread::add_ticon(DbWindowTIcon *ticon, double age)
1035 {
1036         mLock by(ticon_lock);
1037         ticon->age = age;
1038         int i = t_heap.size();  t_heap.append(ticon);
1039         for( int k; i>0 && age<t_heap[(k=(i-1)/2)]->age; i=k )
1040                 t_heap[i] = t_heap[k];
1041         t_heap[i] = ticon;
1042 }
1043
1044 int DbWindowList::
1045 selection_changed()
1046 {
1047         gui->stop_drawing(1);
1048         delete view_popup;
1049         view_popup = 0;
1050         int idx = get_selection_number(0, 0);
1051         if( idx >= 0 && get_selection_number(0, 1) < 0 ) {
1052                 if( view_idx != idx ) {
1053                         view_idx = idx;
1054                         BC_ListBoxItem *item = gui->search_items[0][idx];
1055                         int x = item->get_text_x() - get_xposition();
1056                         int y = item->get_text_y() + get_title_h() - get_yposition();
1057                         int w = 4*SWIDTH, h = 4*SHEIGHT;
1058                         x += gui->get_x() - 2*SWIDTH;
1059                         y += gui->get_y() - 4*SHEIGHT;
1060                         view_popup = new BC_Popup(this, x, y, w, h, BLACK);
1061                         view_ticon->update_image(gui->search_results[idx]->id, 0, 0);
1062                 }
1063         }
1064         else
1065                 view_idx = -1;
1066         gui->start_drawing(0);
1067         return 0;
1068 }
1069
1070 void DbWindowTIconThread::
1071 run()
1072 {
1073
1074         while(!done) {
1075                 draw_lock->lock("DbWindowTIcon::run");
1076                 if( done ) break;
1077                 int do_flash = 0;
1078                 int64_t last_flash = 0;
1079                 while( !interrupted && !done ) {
1080                         double period = 0;
1081                         gui->lock_window("DbWindowTIconThread::run");
1082                         if( list_update ) {
1083                                 list_update = 0;
1084                                 gui->update();
1085                         }
1086                         if( image_update ) {
1087                                 image_update = 0;
1088                                 gui->search_list->update_images();
1089                                 timer->update();
1090                                 do_flash = 0;  last_flash = 0;
1091
1092                         }
1093                         gui->unlock_window();
1094                         DbWindowTIcon *ticon = low_ticon();
1095                         if( !ticon ) interrupted = 1;
1096                         if( interrupted ) break;
1097                         int64_t past = timer->get_difference();
1098                         int64_t delay = ticon->age - past;
1099 //printf("delay %6ld  clip %3d  seq %d\n",delay,ticon->clip_id, ticon->seq_no);
1100                         if( delay < 10 ) {
1101                                 if( !ticon->get_seq_frame() ) ticon->draw_image();
1102                                 period = !ticon->seq_no ? 2000 : ticon->frame_period;
1103                                 if( past > last_flash + 100 ) do_flash = 1;
1104                         }
1105                         else
1106                                 do_flash = 1;
1107                         if( do_flash ) {
1108                                 do_flash = 0;
1109                                 last_flash = past;
1110                                 gui->canvas->flash_canvas();
1111                         }
1112                         add_ticon(ticon, ticon->age + period);
1113                         if( delay > 0 )
1114                                 Timer::delay(delay > 333 ? 333 : delay);
1115                 }
1116                 stop_lock->unlock();
1117         }
1118
1119         t_heap.remove_all();
1120         ticons.remove_all_objects();
1121 }
1122
1123
1124