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