Third set of 50 GPL attribution for CV-Contributors added +
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / swindow.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 "bchash.h"
22 #include "bctimer.h"
23 #include "condition.h"
24 #include "cstrdup.h"
25 #include "edl.h"
26 #include "filesystem.h"
27 #include "language.h"
28 #include "localsession.h"
29 #include "mainerror.h"
30 #include "mainmenu.h"
31 #include "mainsession.h"
32 #include "mainundo.h"
33 #include "mwindow.h"
34 #include "mwindowgui.h"
35 #include "mutex.h"
36 #include "strack.h"
37 #include "swindow.h"
38 #include "theme.h"
39 #include "track.h"
40 #include "tracks.h"
41
42 #include<ctype.h>
43 #include<errno.h>
44 #include<stdint.h>
45 #include<stdlib.h>
46 #include<string.h>
47 #include<unistd.h>
48
49 SWindowOK::SWindowOK(SWindowGUI *gui, int x, int y)
50  : BC_OKButton(x, y)
51 {
52         this->gui = gui;
53 }
54
55 SWindowOK::~SWindowOK()
56 {
57 }
58
59 int SWindowOK::button_press_event()
60 {
61         if(get_buttonpress() == 1 && is_event_win() && cursor_inside()) {
62                 gui->stop(0);
63                 return 1;
64         }
65         return 0;
66 }
67
68 int SWindowOK::keypress_event()
69 {
70         return context_help_check_and_show();
71 }
72
73
74 SWindowCancel::SWindowCancel(SWindowGUI *gui, int x, int y)
75  : BC_CancelButton(x, y)
76 {
77         this->gui = gui;
78 }
79
80 SWindowCancel::~SWindowCancel()
81 {
82 }
83
84 int SWindowCancel::button_press_event()
85 {
86         if(get_buttonpress() == 1 && is_event_win() && cursor_inside()) {
87                 gui->stop(1);
88                 return 1;
89         }
90         return 0;
91 }
92
93
94 SWindowLoadPath::SWindowLoadPath(SWindowGUI *gui, int x, int y, char *path)
95  : BC_TextBox(x, y, xS(200), 1, path)
96 {
97         this->sw_gui = gui;
98 }
99
100 SWindowLoadPath::~SWindowLoadPath()
101 {
102 }
103
104 int SWindowLoadPath::handle_event()
105 {
106         calculate_suggestions();
107         strcpy(sw_gui->script_path, get_text());
108         return 1;
109 }
110
111
112 SWindowLoadFile::SWindowLoadFile(SWindowGUI *gui, int x, int y)
113  : BC_GenericButton(x, y, _("Load"))
114 {
115         this->sw_gui = gui;
116 }
117
118 SWindowLoadFile::~SWindowLoadFile()
119 {
120 }
121
122 int SWindowLoadFile::handle_event()
123 {
124         if( sw_gui->script_path[0] ) {
125                 sw_gui->load_path->set_suggestions(0,0);
126                 sw_gui->load_script();
127                 sw_gui->set_script_pos(0);
128         }
129         else {
130                 eprintf(_("script text file path required"));
131         }
132         return 1;
133 }
134
135 SWindowSaveFile::SWindowSaveFile(SWindowGUI *gui, int x, int y)
136  : BC_GenericButton(x, y, _("Save"))
137 {
138         this->sw_gui = gui;
139 }
140
141 SWindowSaveFile::~SWindowSaveFile()
142 {
143 }
144
145 int SWindowSaveFile::handle_event()
146 {
147         if( sw_gui->script_path[0] ) {
148                 sw_gui->save_spumux_data();
149         }
150         else {
151                 eprintf(_("script file path required"));
152         }
153         return 1;
154 }
155
156
157
158
159 void SWindowGUI::create_objects()
160 {
161         lock_window("SWindowGUI::create_objects");
162         int x = xS(10), y = yS(10);
163         BC_Title *title = new BC_Title(x, y, _("Path:"));
164         add_subwindow(title);
165         int x1 = x + title->get_w() + xpad, y1 = y;
166         load_path = new SWindowLoadPath(this, x1, y1, script_path);
167         add_subwindow(load_path);
168         x1 += load_path->get_w() + 2*xpad;
169         add_subwindow(load_file = new SWindowLoadFile(this, x1, y1));
170         x1 += load_file->get_w() + 2*xpad;
171         add_subwindow(save_file = new SWindowSaveFile(this, x1, y1));
172         x1 += save_file->get_w() + 2*xpad;
173         add_subwindow(save_format = new SWindowSaveFormat(this, x1, y1));
174         save_format->create_objects();
175         y += max(load_path->get_h(), load_file->get_h()) + ypad;
176         x1 = x + ypad, y1 = y;
177         BC_Title *title1, *title2;
178         add_subwindow(title1 = new BC_Title(x1, y1, _("File Size:")));
179         y += title1->get_h() + ypad;
180         int y2 = y;
181         add_subwindow(title2 = new BC_Title(x1, y2, _("Entries:")));
182         int x2 = x1 + max(title1->get_w(), title2->get_w()) + xpad;
183         add_subwindow(script_filesz = new BC_Title(x2, y1, "0", MEDIUMFONT, YELLOW));
184         add_subwindow(script_entries = new BC_Title(x2, y2, "0", MEDIUMFONT, YELLOW));
185         int x3 = x2 + max(script_entries->get_w()*8, script_filesz->get_w()*8) + xpad;
186         add_subwindow(title1 = new BC_Title(x3, y1, _("Lines:")));
187         add_subwindow(title2 = new BC_Title(x3, y2, _("Texts:")));
188         int x4 = x3 + max(title1->get_w(), title2->get_w()) + xpad;
189         add_subwindow(script_lines = new BC_Title(x4, y1, "0", MEDIUMFONT, YELLOW));
190         add_subwindow(script_texts = new BC_Title(x4, y2, "0", MEDIUMFONT, YELLOW));
191         int x5 = x4 + max(script_lines->get_w()*8, script_texts->get_w()*8) + 2*xpad;
192         add_subwindow(prev_script = new ScriptPrev(this, x5, y1));
193         add_subwindow(next_script = new ScriptNext(this, x5, y2));
194         int x6 = x5 + max(prev_script->get_w(), next_script->get_w()) + 2*xpad;
195         add_subwindow(paste_script = new ScriptPaste(this, x6, y1));
196         add_subwindow(clear_script = new ScriptClear(this, x6, y2));
197         y += max(title1->get_h(), title2->get_h()) + 2*ypad;
198
199         script_position = new ScriptPosition(this, x, y, xS(100));
200         script_position->create_objects();
201         x1 = x + script_position->get_w() + xpad;
202         add_subwindow(script_scroll = new ScriptScroll(this, x1, y, get_w()-x1-xpad));
203         y += script_scroll->get_h() + 2*ypad;
204         x1 = x + xpad;
205         blank_line = new char[2];
206         blank_line[0] = ' ';  blank_line[1] = 0;
207         add_subwindow(script_title = new BC_Title(x1, y, _("Script Text:")));
208         y += script_title->get_h() + ypad;
209         int rows = (ok_y - y - BC_Title::calculate_h(this,_("Line Text:")) -
210                 4*ypad) / text_rowsz - 4;
211         int w1 = get_w() - x1 - xpad;
212         script_entry = new ScriptEntry(this, x1, y, w1, rows, blank_line);
213         script_entry->create_objects();
214         y += script_entry->get_h() + ypad;
215         add_subwindow(line_title = new BC_Title(x1, y, _("Line Text:")));
216         y += line_title->get_h() + ypad;
217         line_entry = new ScriptEntry(this, x1, y, w1, 4);
218         line_entry->create_objects();
219         ok = new SWindowOK(this, ok_x, ok_y);
220         add_subwindow(ok);
221         cancel = new SWindowCancel(this, cancel_x, cancel_y);
222         add_subwindow(cancel);
223         unlock_window();
224 }
225
226 void SWindowGUI::load()
227 {
228         const char *script_text =
229         _("Adding Subtitles: quick \"How To\" (= or * indicates comment)\n"
230         "*2345678901234567890123456789012345678901234567890123456789\n"
231         "For regular DVD subtitles, put script in a text file. "
232         "Lines can be any length but they will be broken up "
233         "to fit according to some criteria below.\n"
234         "Running text used as script lines will be broken into multilple lines.\n"
235         "The target line length is 60 characters.\n"
236         "Punctuation may be flagged to create an early line break.\n"
237         "Single carriage return ends an individual script line.\n"
238         "Double carriage return indicates the end of an entry.\n"
239         "Whitespace at beginning or end of line is removed.\n"
240         "You can edit the active line in the Line Text box.\n"
241         "\n"
242         "== A new entry is here for illustration purposes.\n"
243         "*  Entry 2\n"
244         "This is the second entry.\n");
245
246         if( script_path[0] && !access(script_path,R_OK) ) {
247                 load_script();
248         }
249         else {
250                 script_path[0] = 0;
251                 load_path->update(script_path);
252                 FILE *fp = fmemopen((void *)script_text, strlen(script_text), "r");
253                 load_script(fp);
254         }
255         int text_no = script_text_no;
256         script_text_no = -1;
257         load_selection(script_entry_no, text_no);
258 }
259
260 SWindowGUI::SWindowGUI(SWindow *swindow, int x, int y, int w, int h)
261  : BC_Window(_(PROGRAM_NAME ": Subtitle"), x, y, w, h, xS(600), yS(500),
262         1, 0, 0 , -1, swindow->mwindow->get_cwindow_display())
263 {
264         this->swindow = swindow;
265         xpad = xS(8);
266         ypad = yS(8);
267
268         ok = 0;
269         ok_w = BC_OKButton::calculate_w();
270         ok_h = BC_OKButton::calculate_h();
271         ok_x = xS(10);
272         ok_y = h - ok_h - yS(10);
273         cancel = 0;
274         cancel_w = BC_CancelButton::calculate_w();
275         cancel_h = BC_CancelButton::calculate_h();
276         cancel_x = w - cancel_w - xS(10);
277         cancel_y = h - cancel_h - yS(10);
278
279         load_path = 0;
280         load_file = 0;
281         save_file = 0;
282         script_filesz = 0;
283         script_lines = 0;
284         script_entries = 0;
285         script_texts = 0;
286         script_entry_no = 0;
287         script_text_no = 0;
288         script_line_no = 0;
289         script_text_lines = 0;
290         prev_script = 0;
291         next_script = 0;
292         paste_script = 0;
293         clear_script = 0;
294         script_position = 0;
295         script_entry = 0;
296         line_entry = 0;
297         script_scroll = 0;
298         blank_line = 0;
299         text_font = MEDIUMFONT;
300         text_rowsz = get_text_ascent(text_font)+1 + get_text_descent(text_font)+1;
301         sub_format = SUB_FORMAT_SRT;
302 // *** CONTEXT_HELP ***
303         context_help_set_keyword("Subtitles");
304 }
305
306 SWindowGUI::~SWindowGUI()
307 {
308         delete script_entry;
309         delete line_entry;
310         delete script_position;
311         delete [] blank_line;
312 }
313
314 void SWindowGUI::stop(int v)
315 {
316         if( !swindow->gui_done ) {
317                 swindow->gui_done = 1;
318                 set_done(v);
319         }
320 }
321
322 int SWindowGUI::translation_event()
323 {
324         swindow->mwindow->session->swindow_x = get_x();
325         swindow->mwindow->session->swindow_y = get_y();
326         return 0;
327 }
328
329 int SWindowGUI::resize_event(int w, int h)
330 {
331         swindow->mwindow->session->swindow_w = w;
332         swindow->mwindow->session->swindow_h = h;
333
334         ok_x = xS(10);
335         ok_y = h - ok_h - yS(10);
336         ok->reposition_window(ok_x, ok_y);
337         cancel_x = w - cancel_w - xS(10);
338         cancel_y = h - cancel_h - yS(10);
339         cancel->reposition_window(cancel_x, cancel_y);
340
341         int x = script_position->get_x();
342         int y = script_position->get_y();
343         int hh = script_position->get_h();
344         int ww = script_position->get_w();
345         int x1 = x + ww + xpad;
346         int w1 = w - x1 - xpad;
347         script_scroll->reposition_window(x1, y, w1);
348         y += hh + 2*ypad;
349         script_title->reposition_window(x, y);
350         y += script_title->get_h() + ypad;
351         w1 = w - x - xpad;
352         int rows = (ok_y - y - line_title->get_h() - 4*ypad) / text_rowsz - 4;
353         script_entry->reposition_window(x, y, w1, rows);
354         y += script_entry->get_h() + 2*ypad;
355         line_title->reposition_window(x, y);
356         y += line_title->get_h() + ypad;
357         line_entry->reposition_window(x, y, w1, 4);
358         return 0;
359 }
360
361 void SWindowGUI::load_defaults()
362 {
363         BC_Hash *defaults = swindow->mwindow->defaults;
364         defaults->get("SUBTTL_SCRIPT_PATH", script_path);
365         script_entry_no = defaults->get("SUBTTL_SCRIPT_ENTRY_NO", script_entry_no);
366         script_text_no = defaults->get("SUBTTL_SCRIPT_TEXT_NO", script_text_no);
367         sub_format = defaults->get("SUBTTL_SCRIPT_FORMAT", sub_format);
368 }
369
370 void SWindowGUI::save_defaults()
371 {
372         BC_Hash *defaults = swindow->mwindow->defaults;
373         defaults->update("SUBTTL_SCRIPT_PATH", script_path);
374         defaults->update("SUBTTL_SCRIPT_ENTRY_NO", script_entry_no);
375         defaults->update("SUBTTL_SCRIPT_TEXT_NO", script_text_no);
376         defaults->update("SUBTTL_SCRIPT_FORMAT", sub_format);
377 }
378
379 void SWindowGUI::set_script_pos(int64_t entry_no, int text_no)
380 {
381         script_entry_no = entry_no<0 ? 0 :
382                 entry_no>script.size()-1 ? script.size()-1 : entry_no;
383         script_text_no = text_no-1;
384         load_next_selection();
385 }
386
387 int SWindowGUI::load_selection(int pos, int row)
388 {
389         if( pos < 0 || pos >= script.size() ) return 1;
390         ScriptLines *texts = script[pos];
391         char *rp = texts->get_text_row(row);
392         if( !rp || *rp == '=' || *rp == '*' || *rp=='\n' ) return 1;
393         if( pos != script_entry_no || script_text_no < 0 ) {
394                 script_entry_no = pos;
395                 script_scroll->update_value(script_entry_no);
396                 script_position->update(script_entry_no);
397                 script_entry->set_text(texts->text);
398                 script_entry->set_text_row(0);
399         }
400         script_text_no = row;
401         char line[BCTEXTLEN], *bp = line;
402         char *ep = bp+sizeof(line)-1, *cp = rp;
403         while( bp < ep && *cp && *cp!='\n' ) *bp++ = *cp++;
404         *bp = 0;  bp = texts->text;
405         int char1 = rp-bp, char2 = cp-bp;
406         script_entry->set_selection(char1, char2, char2);
407         int rows = script_entry->get_rows();
408         int rows2 = rows / 2;
409         int rows1 = texts->lines+1 - rows;
410         if( (row-=rows2) > rows1 ) row = rows1;
411         if( row < 0 ) row = 0;
412         script_entry->set_text_row(row);
413         line_entry->update(line);
414         line_entry->set_text_row(0);
415         return 0;
416 }
417
418 int SWindowGUI::load_prev_selection()
419 {
420         int pos = script_entry_no, row = script_text_no;
421         if( pos < 0 || pos >= script.size() ) return 1;
422         for(;;) {
423                 if( --row < 0 ) {
424                         if( --pos < 0 ) break;
425                         row = script[pos]->lines;
426                         continue;
427                 }
428                 if( !load_selection(pos, row) )
429                         return 0;
430         }
431         return 1;
432 }
433
434 int SWindowGUI::load_next_selection()
435 {
436         int pos = script_entry_no, row = script_text_no;
437         if( pos < 0 || pos >= script.size() ) return 1;
438         for(;;) {
439                 if( ++row >= script[pos]->lines ) {
440                         if( ++pos >= script.size() ) break;
441                         row = -1;
442                         continue;
443                 }
444                 if( !load_selection(pos, row) )
445                         return 0;
446         }
447         return 1;
448 }
449
450 int SWindowGUI::update_selection()
451 {
452         EDL *edl = swindow->mwindow->edl;
453         LocalSession *lsn = edl->local_session;
454         double position = lsn->get_selectionstart();
455         Edit *edit = 0;
456         Tracks *tracks = edl->tracks;
457         for( Track *track=tracks->first; track && !edit; track=track->next ) {
458                 if( !track->is_armed() ) continue;
459                 if( track->data_type != TRACK_SUBTITLE ) continue;
460                 int64_t pos = track->to_units(position,0);
461                 edit = track->edits->editof(pos, PLAY_FORWARD, 0);
462         }
463         if( !edit ) return 1;
464         SEdit *sedit = (SEdit *)edit;
465         line_entry->update(sedit->get_text());
466         line_entry->set_text_row(0);
467         return 0;
468 }
469
470 int MWindow::paste_subtitle_text(char *text, double start, double end)
471 {
472         gui->lock_window("MWindow::paste_subtitle_text 1");
473         undo->update_undo_before();
474
475         Tracks *tracks = edl->tracks;
476         for( Track *track=tracks->first; track; track=track->next ) {
477                 if( track->data_type != TRACK_SUBTITLE ) continue;
478                 if( !track->is_armed() ) continue;
479                 int64_t start_i = track->to_units(start, 0);
480                 int64_t end_i = track->to_units(end, 1);
481                 track->edits->clear(start_i,end_i);
482                 SEdit *sedit = (SEdit *)track->edits->create_silence(start_i,end_i);
483                 strcpy(sedit->get_text(),text);
484                 track->edits->optimize();
485         }
486
487         save_backup();
488         undo->update_undo_after(_("paste subttl"), LOAD_EDITS | LOAD_PATCHES);
489
490         sync_parameters(CHANGE_EDL);
491         restart_brender();
492         gui->update(0, NORMAL_DRAW, 1, 0, 0, 0, 0);
493         gui->unlock_window();
494
495         return 0;
496 }
497
498 int SWindowGUI::paste_text(const char *text, double start, double end)
499 {
500         char stext[BCTEXTLEN], *cp = stext;
501         if( text ) { // remap charset, reformat if needed
502                 for( const char *bp=text; *bp!=0; ++bp,++cp ) {
503                         switch( *bp ) {
504                         case '\n':  *cp = '|';  break;
505                         default:    *cp = *bp;  break;
506                         }
507                 }
508         }
509         if( cp > stext && *(cp-1) == '|' ) --cp;
510         *cp = 0;
511         return swindow->mwindow->paste_subtitle_text(stext, start, end);
512 }
513
514 int SWindowGUI::paste_selection()
515 {
516         EDL *edl = swindow->mwindow->edl;
517         LocalSession *lsn = edl->local_session;
518         double start = lsn->get_selectionstart();
519         double end = lsn->get_selectionend();
520         if( start >= end ) return 1;
521         if( lsn->inpoint_valid() && lsn->outpoint_valid() ) {
522                 lsn->set_inpoint(lsn->get_outpoint());
523                 lsn->unset_outpoint();
524         }
525         paste_text(line_entry->get_text(), start, end);
526         load_next_selection();
527         return 0;
528 }
529
530 int SWindowGUI::clear_selection()
531 {
532         EDL *edl = swindow->mwindow->edl;
533         double start = edl->local_session->get_selectionstart();
534         double end = edl->local_session->get_selectionend();
535         if( end > start )
536                 paste_text(0, start, end);
537         return 0;
538 }
539
540
541 ScriptPrev::ScriptPrev(SWindowGUI *gui, int x, int y)
542  : BC_GenericButton(x, y, _("Prev"))
543 {
544         sw_gui = gui;
545 }
546
547 ScriptPrev::~ScriptPrev()
548 {
549 }
550
551 int ScriptPrev::handle_event()
552 {
553         sw_gui->load_prev_selection();
554         return 1;
555 }
556
557 ScriptNext::ScriptNext(SWindowGUI *gui, int x, int y)
558  : BC_GenericButton(x, y, _("Next"))
559 {
560         sw_gui = gui;
561 }
562
563 ScriptNext::~ScriptNext()
564 {
565 }
566
567 int ScriptNext::handle_event()
568 {
569         sw_gui->load_next_selection();
570         return 1;
571 }
572
573 ScriptPaste::ScriptPaste(SWindowGUI *gui, int x, int y)
574  : BC_GenericButton(x, y, _("Paste"))
575 {
576         sw_gui = gui;
577 }
578
579 ScriptPaste::~ScriptPaste()
580 {
581 }
582
583 int ScriptPaste::handle_event()
584 {
585         sw_gui->paste_selection();
586         return 1;
587 }
588
589 ScriptClear::ScriptClear(SWindowGUI *gui, int x, int y)
590  : BC_GenericButton(x, y, _("Clear"))
591 {
592         sw_gui = gui;
593 }
594
595 ScriptClear::~ScriptClear()
596 {
597 }
598
599 int ScriptClear::handle_event()
600 {
601         sw_gui->clear_selection();
602         return 1;
603 }
604
605
606 ScriptLines::ScriptLines()
607 {
608         used = 0;
609         lines = 0;
610         text = new char[allocated = 256];
611         *text = 0;
612 }
613
614 ScriptLines::~ScriptLines()
615 {
616         delete [] text;
617 }
618
619 void ScriptLines::append(char *cp)
620 {
621         int len = strlen(cp);
622         if( allocated-used < len+1 ) {
623                 int isz = allocated + used + len;
624                 char *new_text = new char[isz];
625                 allocated = isz;
626                 memcpy(new_text, text, used);
627                 delete [] text;  text = new_text;
628         }
629         memcpy(text+used, cp, len);
630         text[used += len] = 0;
631         ++lines;
632 }
633
634 int ScriptLines::break_lines()
635 {
636         int line_limit = 60;
637         int limit2 = line_limit/2;
638         int limit4 = line_limit/4-2;
639         char *cp = text, *dp = cp+used;
640         int n;  char *bp, *ep, *pp, *sp;
641         for( lines=0; cp<dp; ++lines ) {
642                 // find end of line/buffer
643                 for( ep=cp; ep<dp && *ep!='\n'; ++ep );
644                 // delete trailing spaces
645                 for( sp=ep; sp>=cp && (!*sp || isspace(*sp)); --sp);
646                 ++sp;
647                 if( (n=ep-sp) > 0 ) {
648                         memmove(sp,ep,dp+1-ep);
649                         used -= n;  dp -= n;  ep -= n;
650                 }
651                 ++ep;
652                 // skip, if comment or title line
653                 if( *cp == '*' || *cp == '=' ) {
654                         cp = ep;  continue;
655                 }
656                 // delete leading spaces
657                 for( sp=cp; sp<ep && isspace(*sp); ++sp);
658                 if( (n=sp-cp) > 0 ) {
659                         memmove(cp,sp,dp+1-sp);
660                         used -= n;  dp -= n;  ep -= n;
661                 }
662                 // target about half remaining line, constrain line_limit
663                 if( (n=(ep-1-cp)/2) < limit2 || n > line_limit )
664                         n = line_limit;
665                 // search for last punct, last space before line_limit
666                 for( bp=cp, pp=sp=0; --n>=0 && cp<ep; ++cp ) {
667                         if( ispunct(*cp) && isspace(*(cp+1)) ) pp = cp;
668                         else if( isspace(*cp) ) sp = cp;
669                 }
670                 // line not empty
671                 if( cp < ep ) {
672                         // first, after punctuation
673                         if( pp && pp-bp >= limit4 )
674                                 cp = pp+1;
675                         // then, on spaces
676                         else if( sp ) {
677                                 cp = sp;
678                         }
679                         // last, on next space
680                         else {
681                                 while( cp<dp && !isspace(*cp) ) ++cp;
682                         }
683                         // add new line
684                         if( !*cp ) break;
685                         *cp++ = '\n';
686                 }
687         }
688         return lines;
689 }
690
691 int ScriptLines::get_text_rows()
692 {
693         int ret = 1, i=used;
694         for( char *cp=text; --i>=0; ++cp )
695                 if( *cp == '\n' ) ++ret;
696         return ret;
697 }
698
699 char *ScriptLines::get_text_row(int n)
700 {
701         char *cp = text;
702         if( !n ) return cp;
703         for( int i=used; --i>=0; ) {
704                 if( *cp++ != '\n' ) continue;
705                 if( --n <= 0 ) return cp;
706         }
707         return 0;
708 }
709
710 ScriptScroll::ScriptScroll(SWindowGUI *gui, int x, int y, int w)
711  : BC_ScrollBar(x, y, SCROLL_HORIZ + SCROLL_STRETCH, w, 0, 0, 0)
712 {
713         this->sw_gui = gui;
714 }
715
716 ScriptScroll::~ScriptScroll()
717 {
718 }
719
720 int ScriptScroll::handle_event()
721 {
722         int64_t pos = get_value();
723         sw_gui->set_script_pos(pos);
724         sw_gui->script_position->update(pos);
725         return 1;
726 }
727
728
729 ScriptPosition::ScriptPosition(SWindowGUI *gui, int x, int y, int w, int v, int mn, int mx)
730  : BC_TumbleTextBox(gui, v, mn, mx, x, y, w-BC_Tumbler::calculate_w())
731 {
732         this->sw_gui = gui;
733 }
734
735 ScriptPosition::~ScriptPosition()
736 {
737 }
738
739 int ScriptPosition::handle_event()
740 {
741         int64_t pos = atol(get_text());
742         sw_gui->set_script_pos(pos);
743         sw_gui->script_scroll->update_value(pos);
744         return 1;
745 }
746
747
748 ScriptEntry::ScriptEntry(SWindowGUI *gui, int x, int y, int w, int rows, char *text)
749  : BC_ScrollTextBox(gui, x, y, w, rows, text, -strlen(text))
750 {
751         this->sw_gui = gui;
752         this->ttext = text;
753 }
754
755 ScriptEntry::ScriptEntry(SWindowGUI *gui, int x, int y, int w, int rows)
756  : BC_ScrollTextBox(gui, x, y, w, rows,(char*)0, BCTEXTLEN)
757 {
758         this->sw_gui = gui;
759         this->ttext = 0;
760 }
761
762 ScriptEntry::~ScriptEntry()
763 {
764 }
765
766 void ScriptEntry::set_text(char *text, int isz)
767 {
768         if( !text || !*text ) return;
769         if( isz < 0 ) isz = strlen(text)+1;
770         BC_ScrollTextBox::set_text(text, isz);
771         ttext = text;
772 }
773
774 int ScriptEntry::handle_event()
775 {
776         if( ttext && sw_gui->get_button_down() &&
777             sw_gui->get_buttonpress() == 1 &&
778             sw_gui->get_triple_click() ) {
779                 int ibeam = get_ibeam_letter(), row = 0;
780                 const char *tp = ttext;
781                 while( *tp && tp-ttext < ibeam ) {
782                         if( *tp++ == '\n' ) ++row;
783                 }
784                 int pos = sw_gui->script_entry_no;
785                 sw_gui->load_selection(pos, row);
786         }
787         return 1;
788 }
789
790 int SWindowGUI::load_script_line(FILE *fp)
791 {
792         char line[8192];
793         for(;;) { // skip blank lines
794                 char *cp = fgets(line,sizeof(line),fp);
795                 if( !cp ) return 1;
796                 ++script_line_no;
797                 while( *cp && isspace(*cp) ) ++cp;
798                 if( *cp ) break;
799         }
800
801         ScriptLines *entry = new ScriptLines();
802         script.append(entry);
803
804         for(;;) { // load non-blank lines
805                 //int len = strlen(line);
806                 //if( line[len-1] == '\n' ) line[len-1] = ' ';
807                 entry->append(line);
808                 char *cp = fgets(line,sizeof(line),fp);
809                 if( !cp ) break;
810                 ++script_line_no;
811                 while( *cp && isspace(*cp) ) ++cp;
812                 if( !*cp ) break;
813         }
814         script_text_lines += entry->break_lines();
815         return 0;
816 }
817
818 void SWindowGUI::load_script()
819 {
820         FILE *fp = fopen(script_path,"r");
821         if( !fp ) {
822                 char string[BCTEXTLEN];
823                 sprintf(string,_("cannot open: \"%s\"\n%s"),script_path,strerror(errno));
824                 MainError::show_error(string);
825                 return;
826         }
827         load_script(fp);
828         script_text_no = -1;
829         load_selection(script_entry_no=0, 0);
830 }
831
832 void SWindowGUI::load_script(FILE *fp)
833 {
834         script.remove_all_objects();
835         script_line_no = 0;
836         script_text_lines = 0;
837         while( !load_script_line(fp) );
838         char value[64];
839         sprintf(value,"%ld",ftell(fp));
840         script_filesz->update(value);
841         sprintf(value,"%jd",script_line_no);
842         script_lines->update(value);
843         sprintf(value,"%d",script.size());
844         script_entries->update(value);
845         sprintf(value,"%jd",script_text_lines);
846         script_texts->update(value);
847         int hw = script_scroll->get_h();
848         script_scroll->update_length(script.size(), script_entry_no, hw, 0);
849         script_position->update(script_entry_no);
850         script_position->set_boundaries((int64_t)0, (int64_t)script.size()-1);
851         fclose(fp);
852 }
853
854 void SWindowGUI::save_spumux_data()
855 {
856         char filename[BCTEXTLEN], track_title[BCTEXTLEN];
857         snprintf(filename,sizeof(filename),"%s",script_path);
858         filename[sizeof(filename)-1] = 0;
859         char *bp = strrchr(filename,'/');
860         if( !bp ) bp = filename; else ++bp;
861         char *ext = strrchr(bp, '.');
862         if( !ext ) ext = bp + strlen(bp);
863         int len = sizeof(filename)-1 - (ext-filename);
864
865         Tracks *tracks = swindow->mwindow->edl->tracks;
866         for( Track *track=tracks->first; track; track=track->next ) {
867                 if( track->data_type != TRACK_SUBTITLE ) continue;
868                 if( !track->is_armed() ) continue;
869                 char *cp = track_title, *ep = cp+sizeof(track_title)-6;
870                 for( const char *bp=track->title; cp<ep && *bp!=0; ) {
871                         int wch = butf8(bp); // iswalnum(wch) broken by MS port
872                         if( !( (wch >= 'A' && wch <= 'Z') ||
873                                (wch >= 'a' && wch <= 'z') ||
874                                (wch >= '0' && wch <= '9') ) ) wch = '_';
875                         butf8(wch, cp);
876                 }
877                 const char *sfx = "";
878                 switch( sub_format ) {
879                 case SUB_FORMAT_SRT:   sfx = ".srt";   break;
880                 case SUB_FORMAT_RIP:   sfx = ".sub";   break;
881                 case SUB_FORMAT_UDVD:  sfx = ".udvd";  break;
882                 }
883                 *cp = 0;
884                 snprintf(ext,len,"-%s%s",track_title, sfx);
885                 FILE *fp = fopen(filename, "w");
886                 if( !fp ) {
887                         eprintf(_("Unable to open %s:\n%m"), filename);
888                         continue;
889                 }
890                 switch( sub_format ) {
891                 case SUB_FORMAT_RIP:
892                         fprintf(fp,"[SUBTITLE]\n"
893                                 "[COLF]&HFFFFFF,[SIZE]12,[FONT]Times New Roman\n");
894                         break;
895                 }
896                 int64_t start = 0;  int count = 0;
897                 for( Edit *edit=track->edits->first; edit; edit=edit->next ) {
898                         SEdit *sedit = (SEdit *)edit;
899                         if( !sedit->length ) continue;
900                         int64_t end = start + sedit->length;
901                         double st = sedit->track->from_units(start);
902                         int shr = st/3600; st -= shr*3600;
903                         int smn = st/60;   st -= smn*60;
904                         int ssc = st;      st -= ssc;
905                         int sms = st*1000;
906                         double et = sedit->track->from_units(end);
907                         int ehr = et/3600; et -= ehr*3600;
908                         int emn = et/60;   et -= emn*60;
909                         int esc = et;      et -= esc;
910                         int ems = et*1000;
911                         char *text = sedit->get_text();
912                         if( *text ) {
913                                 ++count;
914                                 switch( sub_format ) {
915                                 case SUB_FORMAT_SRT:
916                                         fprintf(fp, "%d\n%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d\n%s\n\n",
917                                                  count, shr, smn, ssc, sms, ehr, emn, esc, ems, text);
918                                         break;
919                                 case SUB_FORMAT_RIP:
920                                         fprintf(fp, "%02d:%02d:%02d.%02d,%02d:%02d:%02d.%02d\n%s\n\n",
921                                                  shr, smn, ssc, sms/10, ehr, emn, esc, ems/10, text);
922                                         break;
923                                 case SUB_FORMAT_UDVD:
924                                         fprintf(fp, "{%jd}{%jd}%s\n", start, end-1, text);
925                                         break;
926                                 }
927                         }
928                         start = end;
929                 }
930                 fclose(fp);
931         }
932 }
933
934 SWindowItemFormat::SWindowItemFormat(SWindowSaveFormat *save_format,
935                 const char *text, int id)
936  : BC_MenuItem(text)
937 {
938         this->save_format = save_format;
939         this->id = id;
940 }
941
942 int SWindowItemFormat::handle_event()
943 {
944         save_format->sw_gui->sub_format = id;
945         save_format->update_toggles();
946         return 1;
947 }
948
949 SWindowSaveFormat::SWindowSaveFormat(SWindowGUI *sw_gui, int x, int y)
950  : BC_PopupMenu(x, y, _("Format"))
951 {
952         this->sw_gui = sw_gui;
953 }
954
955 void SWindowSaveFormat::create_objects()
956 {
957         add_item(srt = new SWindowItemFormat(this, _("SRT"), SUB_FORMAT_SRT));
958         add_item(rip = new SWindowItemFormat(this, _("SUB"), SUB_FORMAT_RIP));
959         add_item(udvd = new SWindowItemFormat(this, _("UDVD"), SUB_FORMAT_UDVD));
960         update_toggles();
961 }
962
963 void SWindowSaveFormat::update_toggles()
964 {
965         srt->set_checked(sw_gui->sub_format == SUB_FORMAT_SRT);
966         rip->set_checked(sw_gui->sub_format == SUB_FORMAT_RIP);
967         udvd->set_checked(sw_gui->sub_format == SUB_FORMAT_UDVD);
968 }
969
970
971 SWindow::SWindow(MWindow *mwindow)
972  : Thread(1, 0, 0)
973 {
974         this->mwindow = mwindow;
975         window_lock = new Mutex("SWindow::window_lock");
976         swin_lock = new Condition(0,"SWindow::swin_lock");
977         gui = 0;
978         done = 1;
979         gui_done = 1;
980
981         start();
982 }
983
984 SWindow::~SWindow()
985 {
986         stop();
987         delete gui;
988         delete swin_lock;
989         delete window_lock;
990 }
991
992
993 void SWindow::start()
994 {
995         if( !Thread::running() ) {
996                 done = 0;
997                 Thread::start();
998         }
999 }
1000
1001 void SWindow::stop()
1002 {
1003         if( Thread::running() ) {
1004                 done = 1;
1005                 swin_lock->unlock();
1006                 window_lock->lock("SWindow::stop");
1007                 if( gui ) gui->stop(1);
1008                 window_lock->unlock();
1009                 Thread::cancel();
1010         }
1011         Thread::join();
1012 }
1013
1014 void SWindow::run()
1015 {
1016         int root_w = mwindow->gui->get_root_w(1);
1017         int root_h = mwindow->gui->get_root_h(1);
1018
1019         while( !done ) {
1020                 swin_lock->reset();
1021                 swin_lock->lock();
1022                 if( done ) break;
1023                 int x = mwindow->session->swindow_x;
1024                 int y = mwindow->session->swindow_y;
1025                 int w = mwindow->session->swindow_w;
1026                 int h = mwindow->session->swindow_h;
1027                 if( w < xS(600) ) w = xS(600);
1028                 if( h < yS(500) ) h = yS(500);
1029                 int scr_x = mwindow->gui->get_screen_x(1, -1);
1030                 int scr_w = mwindow->gui->get_screen_w(1, -1);
1031                 if( x < scr_x ) x = scr_x;
1032                 if( x > scr_x+scr_w ) x = scr_x+scr_w;
1033                 if( x+w > root_w ) x = root_w - w;
1034                 if( x < 0 ) { x = 0;  w = scr_w; }
1035                 if( y+h > root_h ) y = root_h - h;
1036                 if( y < 0 ) { y = 0;  h = root_h; }
1037                 if( y+h > root_h ) h = root_h-y;
1038                 mwindow->session->swindow_x = x;
1039                 mwindow->session->swindow_y = y;
1040                 mwindow->session->swindow_w = w;
1041                 mwindow->session->swindow_h = h;
1042
1043                 gui_done = 0;
1044                 gui = new SWindowGUI(this, x, y, w, h);
1045                 gui->lock_window("ChannelInfo::gui_create_objects");
1046                 gui->load_defaults();
1047                 gui->create_objects();
1048                 gui->set_icon(mwindow->theme->get_image("record_icon"));
1049                 gui->reposition_window(x, y);
1050                 gui->resize_event(w, h);
1051                 gui->load();
1052                 gui->show_window();
1053                 gui->unlock_window();
1054
1055                 int result = gui->run_window();
1056                 if( !result ) {
1057                         gui->save_spumux_data();
1058                 }
1059
1060                 window_lock->lock("ChannelInfo::run 1");
1061                 gui->save_defaults();
1062                 delete gui;  gui = 0;
1063                 window_lock->unlock();
1064         }
1065 }
1066
1067
1068
1069
1070 void SWindow::run_swin()
1071 {
1072         window_lock->lock("SWindow::run_swin");
1073         if( gui ) {
1074                 gui->lock_window("SWindow::run_swin");
1075                 gui->raise_window();
1076                 gui->unlock_window();
1077         }
1078         else
1079                 swin_lock->unlock();
1080         window_lock->unlock();
1081 }
1082
1083 void SWindow::paste_subttl()
1084 {
1085         window_lock->lock("SWindow::paste_subttl 1");
1086         if( gui ) {
1087                 gui->lock_window("SWindow::paste_subttl 2");
1088                 gui->paste_selection();
1089                 gui->unlock_window();
1090         }
1091         window_lock->unlock();
1092 }
1093
1094 int SWindow::update_selection()
1095 {
1096         window_lock->lock("SWindow::update_selection 1");
1097         if( gui ) {
1098                 gui->lock_window("SWindow::update_selection 2");
1099                 gui->update_selection();
1100                 gui->unlock_window();
1101         }
1102         window_lock->unlock();
1103         return 0;
1104 }
1105
1106
1107
1108 SubttlSWin::SubttlSWin(MWindow *mwindow)
1109  : BC_MenuItem(_("SubTitle..."), _("Alt-y"), 'y')
1110 {
1111         set_alt();
1112         this->mwindow = mwindow;
1113 }
1114
1115 SubttlSWin::~SubttlSWin()
1116 {
1117 }
1118
1119 int SubttlSWin::handle_event()
1120 {
1121         mwindow->gui->swindow->run_swin();
1122         return 1;
1123 };
1124