1 /***************************************************************************
2 DIA_eraser.cpp - configuration dialog for
3 Eraser filter
4 -------------------
5 Chris MacGregor, December 2007
6 chris-avidemux@bouncingdog.com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "ADM_toolkitGtk.h"
19 #include "prefs.h"
20
21 #include "avi_vars.h"
22
23 #ifdef HAVE_ENCODER
24
25 #include <algorithm>
26
27 #include "ADM_editor/ADM_edit.hxx"
28 #include "ADM_videoFilter.h"
29 #include "ADM_videoFilter_internal.h"
30 #include "ADM_videoFilter/ADM_vidEraser.h"
31
32 #include "DIA_fileSel.h"
33
34 #include "DIA_flyDialog.h"
35 #include "DIA_flyEraser.h"
36 #include "DIA_factory.h"
37
38
39 using namespace std;
40
41 #undef _
42 #define _(_s) QT_TR_NOOP(_s)
43
44 /********************************************************************/
45 static GtkWidget * create_eraser_dialog (void);
46 static GtkWidget * dialog = 0;
47
48 static gboolean gui_draw (GtkWidget * widget,
49 GdkEventExpose * event, gpointer user_data);
50 static void gui_update (GtkObject * button, gpointer user_data);
51 static void frame_changed (GtkRange *, gpointer user_data);
52
53 static volatile int lock = 0;
54
55 static const int MAX_FRAME_NUM = 99999999;
56
57 /********************************************************************/
58
59 class flyEraserGtk : public flyEraser
60 {
61 protected:
62 GtkWidget * dialog;
63 GtkListStore * list_store;
64 GtkTreeModel * tree_model;
65 GtkWidget * tree_view_widget;
66 GtkTreeView * tree_view;
67 public:
68 GtkTreeSelection * tree_sel;
69 bool warned_about_scaling;
70
71 public:
72 uint8_t download();
73 uint8_t upload();
74 uint8_t upload_masklist (bool set_preview_frame);
75 void wipeOutputBuffer();
76
flyEraserGtk(uint32_t width,uint32_t height,AVDMGenericVideoStream * in,void * canvas,void * slider,GtkWidget * dialog,ADMVideoEraser * eraserp,ERASER_PARAM * in_param,const MenuMapping * menu_mapping,uint32_t menu_mapping_count)77 flyEraserGtk (uint32_t width, uint32_t height,
78 AVDMGenericVideoStream * in,
79 void * canvas, void * slider, GtkWidget * dialog,
80 ADMVideoEraser * eraserp, ERASER_PARAM * in_param,
81 const MenuMapping * menu_mapping,
82 uint32_t menu_mapping_count)
83 : flyEraser (width, height, in, canvas, slider, dialog,
84 eraserp, in_param, menu_mapping, menu_mapping_count),
85 dialog (dialog),
86 list_store (gtk_list_store_new (2, G_TYPE_INT, G_TYPE_INT)),
87 tree_model (GTK_TREE_MODEL (list_store)),
88 tree_view_widget (WID (rangeListTreeview)),
89 tree_view (GTK_TREE_VIEW (tree_view_widget)),
90 tree_sel (gtk_tree_view_get_selection (tree_view)),
91 warned_about_scaling (false)
92 {
93 // printf ("flyEraserGtk::flyEraserGtk: "
94 // "in_param %p, ¶m %p\n", in_param, ¶m);
95
96 gtk_tree_view_set_model (tree_view, tree_model);
97 gtk_tree_view_columns_autosize ((tree_view));
98 gtk_tree_selection_set_mode (tree_sel, GTK_SELECTION_BROWSE);
99 };
100
~flyEraserGtk()101 ~flyEraserGtk ()
102 {
103 g_object_unref (list_store);
104 }
105
106 void selection_changed ();
107 };
108
previewSomeEvent(GtkWidget * widget,uint8_t button,float fx,float fy,guint state,gpointer data)109 static gboolean previewSomeEvent (GtkWidget * widget,
110 uint8_t button, float fx, float fy,
111 guint state, gpointer data)
112 {
113 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (data);
114
115 uint32_t x, y;
116
117 float zoom = myFly->getZoom();
118 if (zoom == 1.0)
119 {
120 x = static_cast <uint32_t> (fx);
121 y = static_cast <uint32_t> (fy);
122 }
123 else
124 {
125 if (!myFly->warned_about_scaling)
126 {
127 fprintf (stderr, "******* WARNING: you are erasing in a "
128 "scaled-down window (%.5f) - all pixel positions are "
129 "therefore APPROXIMATE!!!\n", zoom);
130 myFly->warned_about_scaling = true;
131 }
132
133 zoom = 1 / zoom;
134 x = static_cast <uint32_t> (fx * zoom + .5);
135 y = static_cast <uint32_t> (fy * zoom + .5);
136 }
137
138 bool erasing = (button == 1) ? myFly->param.brush_mode
139 : !myFly->param.brush_mode;
140 ADMImage * image = myFly->getOutputImage();
141
142 uint16_t width = image->_width;
143 uint16_t height = image->_height;
144
145 if (x >= width || y >= height)
146 return FALSE;
147
148 uint8_t brush_size = myFly->param.brush_size;
149 if (state & GDK_SHIFT_MASK && brush_size > 0)
150 --brush_size;
151 else if (state & GDK_CONTROL_MASK)
152 ++brush_size;
153 brush_size = brush_size * 2 + 1;
154
155 uint8_t halfrange = brush_size / 2;
156
157 uint32_t xmin = (x > halfrange) ? (x - halfrange) : 0;
158 uint32_t ymin = (y > halfrange) ? (y - halfrange) : 0;
159
160 uint32_t xmax = x + halfrange;
161 uint32_t ymax = y + halfrange;
162
163 if (xmax >= width)
164 xmax = width - 1;
165 brush_size = xmax - xmin + 1;
166 if (ymax >= height)
167 ymax = height - 1;
168
169 Eraser::LineVec & lines = myFly->current_mask->lines;
170 Eraser::LineVec::iterator lineit = lines.begin();
171
172 // HERE: We could improve performance by doing some kind of binary search
173 // to find the line with the first y. After that it probably wouldn't
174 // help all that much, if at all. However, unless they're masking a lot
175 // of pixels, and tweaking them frequently and/or on slow machines, it may
176 // not be worth the trouble.
177
178 for (y = ymin; y <= ymax; y++)
179 {
180 while (lineit != lines.end() && lineit->y < y)
181 ++lineit;
182
183 if (erasing)
184 {
185 bool need_insert = true;
186 while (lineit != lines.end() && lineit->y == y)
187 {
188 if (xmax + 1 < lineit->x)
189 {
190 // This line is to the right of and is not touching the new
191 // line. Since the lines are sorted and we haven't stopped
192 // before this, that means that there are no lines with which
193 // we can connect. Break out and insert a separate new line.
194 break;
195 }
196
197 Eraser::Line & line = *lineit++;
198
199 uint16_t currlinexmaxplus1 = line.x + line.count;
200 if (currlinexmaxplus1 < xmin)
201 {
202 // This line is to the left of and is not touching the new
203 // line. Skip it and continue looking.
204 continue;
205 }
206
207 // We get here whenever, and only if, the new line touches the
208 // current line in some way. It may overlap the head (left end),
209 // or the tail (right end), or both, or neither (in which case no
210 // change is made); in any case, there is no need to insert a new
211 // line object. If we overlap the tail of the current line, then
212 // it is possible that we also will overlap (and can connect with,
213 // and thus delete) the following line. (It is also possible that
214 // we extend the tail of it, and perhaps even connect to the
215 // following one, etc., so we iterate to be sure we handle all
216 // such cases. Having trouble picturing how that could happen?
217 // Here's one way:
218 // |0 5
219 // |..*.*.*.*..|
220 //
221 // Now imagine a 9x9 brush covering coordinates 1 through 9.)
222
223 if (xmin < line.x)
224 {
225 // The new line overlaps (and extends) the beginning of this
226 // line. No insertion or deletion is required.
227 line.x = xmin;
228 line.count = currlinexmaxplus1 - xmin;
229 }
230
231 if (xmax >= currlinexmaxplus1)
232 {
233 // The new line overlaps (and extends) the end of this line.
234 line.count = xmax - line.x + 1;
235
236 // Possibly we can also merge with the head of the next line,
237 // in which case we can delete that line. As described above,
238 // there may be more than one such, and so we iterate until
239 // there are none.
240
241 while (lineit != lines.end() && xmax + 1 >= lineit->x
242 && lineit->y == y)
243 {
244 // Okay, here we go. We fold the next line into the
245 // current line, and delete that line.
246 line.count = max (lineit->x + lineit->count,
247 int (xmax + 1))
248 - line.x;
249 lineit = lines.erase (lineit);
250 }
251 }
252
253 need_insert = false;
254 break;
255 }
256
257 if (need_insert)
258 lineit = lines.insert (lineit,
259 Eraser::Line (xmin, y, brush_size));
260 }
261 else // if (!erasing) (unerasing)
262 {
263 while (lineit != lines.end() && lineit->y == y)
264 {
265 Eraser::Line & line = *lineit;
266
267 if (xmax < line.x)
268 {
269 // This line is to the right of and does not overlap the
270 // area we are un-erasing. Since the lines are sorted and
271 // we haven't stopped before this, that means that there
272 // is nothing (more) for us to delete, so we break out.
273 break;
274 }
275
276 uint16_t currlinexmaxplus1 = line.x + line.count;
277 if (currlinexmaxplus1 <= xmin)
278 {
279 // This line is to the left of and does not overlap the
280 // area we are un-erasing. Skip it and continue looking.
281 ++lineit;
282 continue;
283 }
284
285 // We get here whenever, and only if, we are un-erasing some
286 // part of the current line. We may be un-erasing the head
287 // (left end), or the tail (right end), or both (the entire
288 // line, in which case we delete it from the list). If we are
289 // un-erasing the middle of the current line (but not either
290 // end), then we will need to split it into two separate lines
291 // (thus inserting one new one). If we are un-erasing the
292 // tail (or more) of the current line, then it is possible
293 // that we also will need to do something with the following
294 // line, and perhaps also the one after that, etc., so we
295 // allow the loop to continue to be sure we handle all such
296 // cases. Having trouble picturing how that could happen?
297 // Here's one way:
298 //
299 // |0 5
300 // |..*.*.*.*..|
301 //
302 // Now imagine a 9x9 brush covering coordinates 1 through 9.)
303
304 if (xmax + 1 >= currlinexmaxplus1)
305 {
306 // The area to erase overlaps (at least) the tail of the
307 // current line. Let's check the beginning...
308
309 if (xmin <= line.x)
310 {
311 // We overlap the head and the tail, so we can just
312 // delete this entire line and continue on to examine
313 // the next one.
314 lineit = lines.erase (lineit);
315 continue;
316 }
317
318 // We're just making this line shorter. However, we might
319 // overlap the following line(s), too, so we need to keep
320 // looking.
321
322 line.count = xmin - line.x;
323 }
324 else if (xmin <= line.x)
325 {
326 // We're erasing the head of this line, but not the tail.
327 // We know that we don't need to look at the following
328 // line, either.
329 line.x = xmax + 1;
330 line.count = currlinexmaxplus1 - line.x;
331 break;
332 }
333 else
334 {
335 // We must be erasing a chunk of the middle of this line,
336 // which means we need to split it (inserting one new
337 // segment). We know that we don't need to look at the
338 // following line, either.
339
340 Eraser::Line new_line (line.x, y, xmin - line.x);
341 line.x = xmax + 1;
342 line.count = currlinexmaxplus1 - line.x;
343 lineit = lines.insert (lineit, new_line);
344 break;
345 }
346
347 ++lineit;
348 }
349 }
350 }
351
352 if (myFly->param.debug & 0x40)
353 {
354 printf ("---------------------------------------------\n");
355 for (Eraser::LineVec::const_iterator lineit = lines.begin();
356 lineit != lines.end();
357 ++lineit)
358 {
359 printf ("line: %d %d %d\n", lineit->x, lineit->y, lineit->count);
360 }
361 }
362
363 myFly->update();
364
365 return TRUE;
366 }
367
368 /********************************************************************/
369
previewButtonEvent(GtkWidget * widget,GdkEventButton * event,gpointer data)370 static gboolean previewButtonEvent (GtkWidget * widget,
371 GdkEventButton * event,
372 gpointer data)
373 {
374 if (event->type != GDK_BUTTON_PRESS)
375 return FALSE;
376
377 return previewSomeEvent (widget, event->button,
378 event->x, event->y, event->state, data);
379 }
380
381 /********************************************************************/
382
previewMotionEvent(GtkWidget * widget,GdkEventMotion * event,gpointer data)383 static gboolean previewMotionEvent (GtkWidget * widget,
384 GdkEventMotion * event,
385 gpointer data)
386 {
387 if (event->type != GDK_MOTION_NOTIFY)
388 return FALSE;
389
390 uint8_t button;
391 if (event->state & GDK_BUTTON1_MASK)
392 button = 1;
393 else if (event->state & GDK_BUTTON2_MASK)
394 button = 2;
395 else if (event->state & GDK_BUTTON3_MASK)
396 button = 3;
397 else
398 return FALSE;
399
400 return previewSomeEvent (widget, button, event->x, event->y,
401 event->state, data);
402 }
403
404 /********************************************************************/
405
browse_button_clicked(GtkButton *,gpointer user_data)406 static void browse_button_clicked (GtkButton *, gpointer user_data)
407 {
408 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
409
410 // First, determine a default output file if we don't already have an
411 // output file.
412
413 const char * filename
414 = gtk_entry_get_text (GTK_ENTRY(WID(eraserDataFileEntry)));
415 const int MAX_SEL = 2048;
416 char buffer [MAX_SEL + 1];
417 const char * lastfilename;
418 const char * defaultSuffix = "eraser_data";
419 if ((!filename || !*filename)
420 && prefs->get (LASTFILES_FILE1, (ADM_filename **)&lastfilename))
421 {
422 strcpy (buffer, lastfilename);
423 char * cptr = buffer + strlen (buffer);
424 while (cptr > buffer)
425 {
426 if (*cptr == '.')
427 {
428 strcpy (cptr + 1, defaultSuffix);
429 filename = buffer;
430 printf ("Default output filename is %s based on %s + %s\n",
431 filename, lastfilename, defaultSuffix);
432 break;
433 }
434 --cptr;
435 }
436 }
437 else if (!filename)
438 filename = "";
439
440 if (FileSel_SelectWrite ("Store Eraser Data in File",
441 buffer, MAX_SEL, filename))
442 {
443 gtk_entry_set_text (GTK_ENTRY(WID(eraserDataFileEntry)), buffer);
444 }
445 }
446
447 /********************************************************************/
448
jump_first_button_clicked(GtkButton *,gpointer user_data)449 static void jump_first_button_clicked (GtkButton *, gpointer user_data)
450 {
451 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
452
453 myFly->sliderSet (myFly->current_mask->first_frame);
454 myFly->sliderChanged();
455 }
456
457 /********************************************************************/
458
jump_last_button_clicked(GtkButton *,gpointer user_data)459 static void jump_last_button_clicked (GtkButton *, gpointer user_data)
460 {
461 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
462
463 myFly->sliderSet (myFly->current_mask->last_frame);
464 myFly->sliderChanged();
465 }
466
467 /********************************************************************/
468
selection_changed()469 void flyEraserGtk::selection_changed ()
470 {
471 if (lock)
472 return;
473
474 uint32_t sel_num = 99999;
475 if (!getSelectionNumber (eraserp->getMasks().size(),
476 GTK_WIDGET (tree_view), list_store, &sel_num))
477 {
478 printf ("ugh, failed to determine which row is selected!!\n");
479 sel_num = 0; // best we can do
480 }
481 else if (param.debug)
482 printf ("row %d is selected\n", sel_num);
483
484 ++lock;
485
486 uint32_t current_sel = current_mask - eraserp->getMasks().begin();
487 if (sel_num != current_sel)
488 {
489 current_mask = eraserp->getMasks().begin() + sel_num;
490 gtk_spin_button_set_value
491 (GTK_SPIN_BUTTON(WID(frameRangeFirstSpinButton)),
492 current_mask->first_frame);
493 gtk_spin_button_set_value
494 (GTK_SPIN_BUTTON(WID(frameRangeLastSpinButton)),
495 current_mask->last_frame);
496 }
497
498 uint32_t middle_frame = (current_mask->first_frame
499 + current_mask->last_frame) / 2;
500 if (middle_frame >= _in->getInfo()->nb_frames)
501 middle_frame = current_mask->first_frame;
502
503 if (sliderGet() != middle_frame)
504 {
505 sliderSet (middle_frame);
506 sliderChanged();
507 }
508
509 --lock;
510 }
511
512 /********************************************************************/
513
treeview_selection_changed(GtkTreeSelection * sel,gpointer user_data)514 static void treeview_selection_changed (GtkTreeSelection * sel,
515 gpointer user_data)
516 {
517 if (lock)
518 return;
519
520 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
521
522 myFly->selection_changed();
523 }
524
525 /********************************************************************/
526
frame_range_changed(GtkSpinButton * spinbutton,gpointer user_data)527 static void frame_range_changed (GtkSpinButton * spinbutton,
528 gpointer user_data)
529 {
530 if (lock)
531 return;
532
533 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
534
535 bool changed = false;
536
537 Eraser::MaskVec::iterator & current_mask = myFly->current_mask;
538 Eraser::MaskVec & masks = myFly->eraserp->getMasks();
539 typedef Eraser::MaskVec::iterator MaskIter;
540 typedef Eraser::MaskVec::reverse_iterator MaskRiter;
541
542 int32_t value = gtk_spin_button_get_value_as_int
543 (GTK_SPIN_BUTTON(WID(frameRangeFirstSpinButton)));
544 if (value != current_mask->first_frame)
545 {
546 current_mask->first_frame = value;
547 changed = true;
548 int32_t curr_value = value;
549 for (MaskRiter maskit (current_mask); maskit != masks.rend(); ++maskit)
550 {
551 if (maskit->last_frame >= curr_value)
552 maskit->last_frame = max (curr_value - 1, 0);
553 else
554 break;
555
556 if (maskit->first_frame >= curr_value)
557 maskit->first_frame = max (curr_value - 1, 0);
558 else
559 break;
560
561 curr_value = max (curr_value - 1, 0);
562 }
563
564 // we deliberately include the current mask in the following loop
565
566 curr_value = value;
567 for (MaskIter maskit (current_mask); maskit != masks.end(); ++maskit)
568 {
569 if (maskit->last_frame < curr_value)
570 maskit->last_frame = curr_value;
571 else
572 break;
573
574 if (maskit->first_frame < curr_value)
575 maskit->first_frame = curr_value;
576 else
577 break;
578
579 ++curr_value;
580 }
581 }
582
583 value = gtk_spin_button_get_value_as_int
584 (GTK_SPIN_BUTTON(WID(frameRangeLastSpinButton)));
585 if (value != myFly->current_mask->last_frame)
586 {
587 myFly->current_mask->last_frame = value;
588 changed = true;
589
590 // we deliberately include the current mask in the following loop
591
592 int32_t curr_value = value;
593 for (MaskRiter maskit (current_mask + 1);
594 maskit != masks.rend();
595 ++maskit)
596 {
597 if (maskit->first_frame > curr_value)
598 maskit->first_frame = curr_value;
599 else
600 break;
601
602 if (maskit->last_frame > curr_value)
603 maskit->last_frame = curr_value;
604 else
605 break;
606
607 curr_value = max (curr_value - 1, 0);
608 }
609
610 curr_value = value;
611 for (MaskIter maskit (current_mask + 1);
612 maskit != masks.end();
613 ++maskit)
614 {
615 if (maskit->first_frame <= curr_value)
616 maskit->first_frame = curr_value + 1;
617 else
618 break;
619
620 if (maskit->last_frame <= curr_value)
621 maskit->last_frame = curr_value + 1;
622 else
623 break;
624
625 ++curr_value;
626 }
627 }
628
629 if (!changed)
630 return;
631
632 myFly->upload_masklist (false);
633 gui_update (GTK_OBJECT (spinbutton), user_data);
634 }
635
636 /********************************************************************/
637
insert_button_clicked(GtkButton * button,gpointer user_data)638 static void insert_button_clicked (GtkButton * button, gpointer user_data)
639 {
640 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
641
642 bool current_changed = false;
643
644 Eraser::MaskVec::iterator & current_mask = myFly->current_mask;
645 Eraser::MaskVec & masks = myFly->eraserp->getMasks();
646 typedef Eraser::MaskVec::iterator MaskIter;
647
648 int32_t frame = current_mask->last_frame + 1;
649 if (button == GTK_BUTTON (WID (duplicateButton)))
650 current_mask = masks.insert (current_mask + 1,
651 Eraser::Mask (frame, frame,
652 current_mask->lines));
653 else
654 current_mask = masks.insert (current_mask + 1,
655 Eraser::Mask (frame, frame));
656
657 for (MaskIter maskit (current_mask + 1); maskit != masks.end(); ++maskit)
658 {
659 if (maskit->first_frame <= frame)
660 maskit->first_frame = frame + 1;
661 else
662 break;
663
664 if (maskit->last_frame <= frame)
665 maskit->last_frame = frame + 1;
666 else
667 break;
668
669 ++frame;
670 }
671
672 myFly->upload_masklist (true);
673 gui_update (GTK_OBJECT (button), user_data);
674 }
675
676 /********************************************************************/
677
delete_button_clicked(GtkButton * button,gpointer user_data)678 static void delete_button_clicked (GtkButton * button, gpointer user_data)
679 {
680 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
681
682 bool current_changed = false;
683
684 Eraser::MaskVec::iterator & current_mask = myFly->current_mask;
685 Eraser::MaskVec & masks = myFly->eraserp->getMasks();
686
687 current_mask = masks.erase (current_mask);
688 if (current_mask == masks.end())
689 {
690 if (masks.empty())
691 masks.push_back (Eraser::Mask (0, MAX_FRAME_NUM));
692 current_mask = masks.end() - 1;
693 }
694
695 myFly->upload_masklist (true);
696 gui_update (GTK_OBJECT (button), user_data);
697 }
698
699 /********************************************************************/
700
previewOutputMenuChange(GtkComboBox * combo,gpointer user_data)701 static void previewOutputMenuChange (GtkComboBox * combo, gpointer user_data)
702 {
703 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
704 uint32_t index = gtk_combo_box_get_active (combo);
705 uint32_t filter_count;
706 FILTER * filters = getCurrentVideoFilterList (&filter_count);
707 FILTER * filter = filters + index;
708 VF_FILTERS tag = filter->tag;
709
710 gchar * activestr = gtk_combo_box_get_active_text (combo);
711
712 printf ("user selected preview of #%d = %s (%d) @%p (was %p)\n",
713 index, activestr, tag, filter->filter, myFly->getSource());
714
715 if (strncmp (activestr, "XX ", 3) == 0)
716 {
717 printf ("selected preview source has different dimensions - "
718 "forcing selection to current filter\n");
719 gtk_combo_box_set_active (combo, myFly->this_filter_index);
720 }
721 else
722 {
723 flyEraserGtk::PreviewMode mode;
724 if (index == myFly->this_filter_index)
725 mode = flyEraserGtk::PREVIEWMODE_THIS_FILTER;
726 else if (index > myFly->this_filter_index)
727 mode = flyEraserGtk::PREVIEWMODE_LATER_FILTER;
728 else // if (index < myFly->this_filter_index)
729 mode = flyEraserGtk::PREVIEWMODE_EARLIER_FILTER;
730
731 myFly->changeSource (filter->filter, mode);
732 }
733
734 g_free (activestr);
735 }
736
737 /********************************************************************/
738
preview_video_configured(GtkWidget * widget,GdkEventConfigure * event,gpointer user_data)739 gboolean preview_video_configured (GtkWidget * widget, GdkEventConfigure * event,
740 gpointer user_data)
741 {
742 fprintf (stderr, "preview_configured: now %dx%d @ +%d+%d\n",
743 event->width, event->height, event->x, event->y);
744
745 flyEraserGtk * myFly = static_cast <flyEraserGtk *> (user_data);
746 myFly->recomputeSize();
747
748 return FALSE;
749 }
750
751 /********************************************************************/
752
DIA_eraser(AVDMGenericVideoStream * in,ADMVideoEraser * eraserp,ERASER_PARAM * param,const MenuMapping * menu_mapping,uint32_t menu_mapping_count)753 uint8_t DIA_eraser (AVDMGenericVideoStream * in, ADMVideoEraser * eraserp,
754 ERASER_PARAM * param, const MenuMapping * menu_mapping,
755 uint32_t menu_mapping_count)
756 {
757 // Allocate space for preview video
758 uint32_t width = in->getInfo()->width;
759 uint32_t height = in->getInfo()->height;
760
761 // We never have an empty list - there is always at least one mask,
762 // and if we create it the frame range is the entire video.
763
764 Eraser::MaskVec & masks = eraserp->getMasks();
765 if (masks.empty())
766 {
767 masks.push_back (Eraser::Mask (0, MAX_FRAME_NUM));
768 }
769
770 dialog = create_eraser_dialog();
771 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog),
772 GTK_RESPONSE_OK,
773 GTK_RESPONSE_CANCEL,
774 -1);
775 gtk_register_dialog (dialog);
776
777 // Fix up a bunch of things that Glade can't do. This is less efficient
778 // than just editing the Glade output, but it's not that big a deal and
779 // doing it this way makes it MUCH easier to use Glade to tweak the layout
780 // later.
781
782 // 1. Connect the slider/spinner pairs by making them share a
783 // GtkAdjustment.
784
785 #define JOIN_SPINPAIR(_basename) \
786 gtk_range_set_adjustment \
787 (GTK_RANGE(WID(_basename ## Slider)), \
788 gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(WID(_basename ## Spinner))))
789
790 JOIN_SPINPAIR (color);
791
792 // 2. Create the flyDialog.
793
794 gtk_widget_show (dialog);
795
796 flyEraserGtk * myDialog
797 = new flyEraserGtk (width, height, in,
798 WID(previewVideo), WID(previewSlider),
799 dialog, eraserp, param,
800 menu_mapping, menu_mapping_count);
801
802 g_signal_connect (GTK_OBJECT (WID(previewVideo)), "configure-event",
803 GTK_SIGNAL_FUNC (preview_video_configured),
804 gpointer (myDialog));
805
806 myDialog->upload();
807 myDialog->sliderChanged();
808
809 // 3. Set up the treeview stuff. This is waaaay more complicated than I
810 // needed for my simple little list, but that's just how it is in GTK, I
811 // guess.
812
813 GtkCellRenderer * renderer = gtk_cell_renderer_text_new();
814
815 gtk_tree_view_append_column
816 (GTK_TREE_VIEW(WID(rangeListTreeview)),
817 gtk_tree_view_column_new_with_attributes (_("First Frame"),
818 renderer, "text", 0,
819 NULL));
820 gtk_tree_view_append_column
821 (GTK_TREE_VIEW(WID(rangeListTreeview)),
822 gtk_tree_view_column_new_with_attributes (_("Last Frame"),
823 renderer, "text", 1,
824 NULL));
825
826 g_signal_connect (G_OBJECT(myDialog->tree_sel), "changed",
827 GTK_SIGNAL_FUNC(treeview_selection_changed),
828 gpointer(myDialog));
829
830 // 4. Connect up all the other signals and stuff.
831
832 g_signal_connect (GTK_OBJECT(WID(eraserDataFileBrowseButton)), "clicked",
833 GTK_SIGNAL_FUNC(browse_button_clicked),
834 gpointer(myDialog));
835
836 #define CNX(_widg,_signame) \
837 g_signal_connect (GTK_OBJECT(WID(_widg)), _signame, \
838 GTK_SIGNAL_FUNC(gui_update), gpointer(myDialog));
839
840 CNX (brushModeMenu, "changed");
841 CNX (brushSizeMenu, "changed");
842 CNX (colorSpinner, "value_changed");
843 // CNX (frameRangeFirstSpinButton, "value_changed");
844 // CNX (frameRangeLastSpinButton, "value_changed");
845
846 g_signal_connect (GTK_OBJECT(WID(frameRangeFirstSpinButton)),
847 "value_changed",
848 GTK_SIGNAL_FUNC(frame_range_changed),
849 gpointer(myDialog));
850 g_signal_connect (GTK_OBJECT(WID(frameRangeLastSpinButton)),
851 "value_changed",
852 GTK_SIGNAL_FUNC(frame_range_changed),
853 gpointer(myDialog));
854
855 g_signal_connect (GTK_OBJECT(WID(insertButton)), "clicked",
856 GTK_SIGNAL_FUNC(insert_button_clicked),
857 gpointer(myDialog));
858 g_signal_connect (GTK_OBJECT(WID(duplicateButton)), "clicked",
859 GTK_SIGNAL_FUNC(insert_button_clicked),
860 gpointer(myDialog));
861 g_signal_connect (GTK_OBJECT(WID(deleteButton)), "clicked",
862 GTK_SIGNAL_FUNC(delete_button_clicked),
863 gpointer(myDialog));
864
865 // preview stuff:
866
867 g_signal_connect (GTK_OBJECT(WID(previewSlider)), "value_changed",
868 GTK_SIGNAL_FUNC(frame_changed), gpointer(myDialog));
869 g_signal_connect (GTK_OBJECT(WID(previewVideo)), "expose_event",
870 GTK_SIGNAL_FUNC(gui_draw), gpointer(myDialog));
871
872 g_signal_connect (GTK_OBJECT(WID(previewVideo)), "button_press_event",
873 GTK_SIGNAL_FUNC(previewButtonEvent),
874 gpointer(myDialog));
875 g_signal_connect (GTK_OBJECT(WID(previewVideo)), "motion_notify_event",
876 GTK_SIGNAL_FUNC(previewMotionEvent),
877 gpointer(myDialog));
878
879 g_signal_connect (GTK_OBJECT(WID(previewJumpFirstButton)), "clicked",
880 GTK_SIGNAL_FUNC(jump_first_button_clicked),
881 gpointer(myDialog));
882 g_signal_connect (GTK_OBJECT(WID(previewJumpLastButton)), "clicked",
883 GTK_SIGNAL_FUNC(jump_last_button_clicked),
884 gpointer(myDialog));
885
886 GtkWidget * previewOutputMenu = WID(previewOutputMenu);
887 uint32_t filter_count;
888 FILTER * filters = getCurrentVideoFilterList (&filter_count);
889 int32_t active = -1;
890
891 // The " + (active < 0)" below is a bit of a hack. We know that in
892 // on_action() in gui_filtermanager.cpp, case A_ADD, the new filter-to-be
893 // is added to the filter list without incrementing nb_active_filter yet.
894 // So if we get to the end of the list and haven't yet found the filter
895 // that we're configuring, we know it's a new one and therefore that it is
896 // one past the apparent end of the list. It's not a clean solution, but
897 // it seems like the cleanEST solution.
898
899 for (uint32_t i = 0; i < filter_count + (active < 0); i++)
900 {
901 const char * name
902 = (i == 0) ? "(input)" : filterGetNameFromTag (filters [i].tag);
903 bool free_name = false;
904
905 FILTER * filter = filters + i;
906 AVDMGenericVideoStream * source = filter->filter;
907 uint32_t w = source->getInfo()->width;
908 uint32_t h = source->getInfo()->height;
909 if (w != width || h != height)
910 {
911 name = g_strconcat ("XX ", name, " XX", NULL);
912 free_name = true;
913 }
914
915 printf ("filter [%d] = %s (%d) @ %p; %dx%d\n",
916 i, name, filter->tag, source, w, h);
917 gtk_combo_box_append_text (GTK_COMBO_BOX (previewOutputMenu), name);
918 if (filter->filter == myDialog->getSource())
919 {
920 gtk_combo_box_set_active (GTK_COMBO_BOX (previewOutputMenu), i);
921 printf ("\tfilter [%d] is being configured now\n", i);
922 active = i;
923 }
924
925 if (free_name)
926 g_free (const_cast <char *> (name));
927 }
928
929 ADM_assert (active >= 0);
930 myDialog->this_filter_index = active;
931
932 g_signal_connect (GTK_OBJECT(previewOutputMenu), "changed",
933 GTK_SIGNAL_FUNC(previewOutputMenuChange),
934 gpointer(myDialog));
935
936 uint8_t ret = 0;
937 int response = gtk_dialog_run(GTK_DIALOG(dialog));
938
939 if (response == GTK_RESPONSE_OK)
940 {
941 // Don't let them leave without asking for a filename - if they leave
942 // with no filename selected, they will lose all their mask data!
943
944 const char * filename =
945 gtk_entry_get_text (GTK_ENTRY(WID(eraserDataFileEntry)));
946 if (filename == NULL || *filename == '\0')
947 browse_button_clicked (NULL, gpointer (myDialog));
948
949 myDialog->download();
950 myDialog->pushParam();
951 ret = 1;
952 }
953 else
954 myDialog->restoreParam();
955
956 gtk_unregister_dialog(dialog);
957 gtk_widget_destroy(dialog);
958
959 delete myDialog;
960
961 return ret;
962 }
963
frame_changed(GtkRange *,gpointer user_data)964 void frame_changed (GtkRange *, gpointer user_data)
965 {
966 flyEraserGtk * myDialog = static_cast <flyEraserGtk *> (user_data);
967
968 myDialog->sliderChanged();
969 }
970
gui_update(GtkObject *,gpointer user_data)971 void gui_update (GtkObject *, gpointer user_data)
972 {
973 if (lock)
974 return;
975
976 flyEraserGtk * myDialog = static_cast <flyEraserGtk *> (user_data);
977 myDialog->update();
978 }
979
gui_draw(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)980 gboolean gui_draw (GtkWidget * widget, GdkEventExpose * event, gpointer user_data)
981 {
982 flyEraserGtk * myDialog
983 = static_cast <flyEraserGtk *> (user_data);
984 myDialog->display();
985 return TRUE;
986 }
987
988 /**************************************/
989
upload(void)990 uint8_t flyEraserGtk::upload (void)
991 {
992 lock++;
993
994 gtk_entry_set_text (GTK_ENTRY(WID(eraserDataFileEntry)),
995 param.data_file.c_str());
996
997 gtk_spin_button_set_value
998 (GTK_SPIN_BUTTON(WID(colorSpinner)), param.output_color);
999
1000 gtk_spin_button_set_value
1001 (GTK_SPIN_BUTTON(WID(debugSpinButton)), param.debug);
1002
1003 lock--;
1004
1005 return upload_masklist (true);
1006 }
1007
upload_masklist(bool set_preview_frame)1008 uint8_t flyEraserGtk::upload_masklist (bool set_preview_frame)
1009 {
1010 lock++;
1011
1012 Eraser::MaskVec & masks = eraserp->getMasks();
1013
1014 gtk_tree_view_set_model (tree_view, NULL);
1015
1016 // There may be a more efficient way, but for the likely numbers of rows
1017 // we'll be dealing with, I don't know that it's worth the trouble to
1018 // implement it.
1019 gtk_list_store_clear (list_store);
1020
1021 for (Eraser::MaskVec::const_iterator maskit = masks.begin();
1022 maskit != masks.end();
1023 ++maskit)
1024 {
1025 const Eraser::Mask & mask = *maskit;
1026
1027 GtkTreeIter iter;
1028 gtk_list_store_append (list_store, &iter);
1029 gtk_list_store_set (list_store, &iter, 0, mask.first_frame,
1030 1, mask.last_frame, -1);
1031 // printf ("uploaded mask %d (%d - %d) to treeview\n",
1032 // maskit - masks.begin(), mask.first_frame, mask.last_frame);
1033 }
1034
1035 gtk_tree_view_set_model (tree_view, tree_model);
1036
1037 setSelectionNumber (masks.size(), GTK_WIDGET (tree_view), list_store,
1038 current_mask - masks.begin());
1039
1040 gtk_spin_button_set_value
1041 (GTK_SPIN_BUTTON(WID(frameRangeFirstSpinButton)),
1042 current_mask->first_frame);
1043 gtk_spin_button_set_value
1044 (GTK_SPIN_BUTTON(WID(frameRangeLastSpinButton)),
1045 current_mask->last_frame);
1046
1047 if (set_preview_frame)
1048 {
1049 uint32_t middle_frame = (current_mask->first_frame
1050 + current_mask->last_frame) / 2;
1051 if (middle_frame >= _in->getInfo()->nb_frames)
1052 middle_frame = current_mask->first_frame;
1053 sliderSet (middle_frame);
1054 sliderChanged();
1055 }
1056
1057 lock--;
1058 return 1;
1059 }
1060
download(void)1061 uint8_t flyEraserGtk::download (void)
1062 {
1063 getMenuValues();
1064
1065 const char * filename =
1066 gtk_entry_get_text (GTK_ENTRY(WID(eraserDataFileEntry)));
1067
1068 // Note that the documentation states clearly that the result of
1069 // gtk_entry_get_text() must NOT be freed, modified, or stored. This is
1070 // different than gtk_file_chooser_get_filename(), which returns a pointer
1071 // which must eventually be passed to g_free().
1072
1073 if (filename)
1074 param.data_file = filename;
1075 else
1076 param.data_file = "";
1077
1078 param.output_color
1079 = gtk_spin_button_get_value_as_int
1080 (GTK_SPIN_BUTTON(WID(colorSpinner)));
1081
1082 param.debug
1083 = gtk_spin_button_get_value_as_int
1084 (GTK_SPIN_BUTTON(WID(debugSpinButton)));
1085
1086 return 1;
1087 }
1088
wipeOutputBuffer()1089 void flyEraserGtk::wipeOutputBuffer ()
1090 {
1091 // printf ("wiping output buffer\n");
1092 uint32_t pixelcount = _w * _h;
1093 uint32_t * outp = reinterpret_cast <uint32_t *>
1094 (YPLANE (_yuvBufferOut) + pixelcount);
1095 uint32_t intcount = pixelcount / 4 + 1;
1096 uint32_t value = 0x60;
1097 while (--intcount)
1098 {
1099 *--outp = value;
1100 value = value >> 8 | ((value & 0xff) << 24);
1101 }
1102
1103 memset (UPLANE (_yuvBufferOut), 128, pixelcount >> 2);
1104 memset (VPLANE (_yuvBufferOut), 128, pixelcount >> 2);
1105 }
1106
1107 // The following was generated by glade from eraser.glade once upon a time (in
1108 // fact, several times :-). Thus far, I've avoided editing it, so
1109 // regenerating the code from Glade (I used 2.12.1) and swapping it in here
1110 // should not lose anything. All the tweaking and adjusting and
1111 // signal-connecting is done in code above, which is maintained by hand, and
1112 // which has much knowledge of the names and layout of the widgets (so be
1113 // cautious about renaming things!).
1114
1115 GtkWidget*
create_eraser_dialog(void)1116 create_eraser_dialog (void)
1117 {
1118 GtkWidget *eraser_dialog;
1119 GtkWidget *dialogVbox;
1120 GtkWidget *dialogHbox;
1121 GtkWidget *allSettingsVbox;
1122 GtkWidget *settingsOuterHbox;
1123 GtkWidget *settingsOuterVbox;
1124 GtkWidget *brushSettingsHbox;
1125 GtkWidget *brushModeHbox;
1126 GtkWidget *brushModeLabel;
1127 GtkWidget *brushModeMenu;
1128 GtkWidget *brushSizeHbox;
1129 GtkWidget *brushSizeLabel;
1130 GtkWidget *brushSizeMenu;
1131 GtkWidget *colorVbox;
1132 GtkWidget *colorLabel;
1133 GtkWidget *colorHbox;
1134 GtkWidget *colorSlider;
1135 GtkObject *colorSpinner_adj;
1136 GtkWidget *colorSpinner;
1137 GtkWidget *eraserDataFileHbox;
1138 GtkWidget *eraserDataFileLabel;
1139 GtkWidget *eraserDataFileEntry;
1140 GtkWidget *debugHbox;
1141 GtkWidget *eraserDataFileBrowseButton;
1142 GtkWidget *browse_debug_spacer;
1143 GtkWidget *debugLabel;
1144 GtkObject *debugSpinButton_adj;
1145 GtkWidget *debugSpinButton;
1146 GtkWidget *frameRangeHbox;
1147 GtkWidget *frameRangeStartHbox;
1148 GtkWidget *frameRangeStartLabel;
1149 GtkObject *frameRangeFirstSpinButton_adj;
1150 GtkWidget *frameRangeFirstSpinButton;
1151 GtkWidget *frameRangeLastHbox;
1152 GtkWidget *frameRangeLastLabel;
1153 GtkObject *frameRangeLastSpinButton_adj;
1154 GtkWidget *frameRangeLastSpinButton;
1155 GtkWidget *rangeListScrolledWindow;
1156 GtkWidget *rangeListTreeview;
1157 GtkWidget *rangeListHButtonBox;
1158 GtkWidget *insertButton;
1159 GtkWidget *duplicateButton;
1160 GtkWidget *deleteButton;
1161 GtkWidget *previewVboxOuter;
1162 GtkWidget *previewJumpButtonHbox;
1163 GtkWidget *previewJumpLastButton;
1164 GtkWidget *previewJumpFirstButton;
1165 GtkWidget *previewJumpLabel;
1166 GtkWidget *previewFrame;
1167 GtkWidget *previewAlignment;
1168 GtkWidget *previewVbox;
1169 GtkWidget *previewControlHbox;
1170 GtkWidget *previewOutputMenu;
1171 GtkWidget *previewSlider;
1172 GtkWidget *previewVideo;
1173 GtkWidget *previewLabel;
1174 GtkWidget *dialogButtonBox;
1175 GtkWidget *cancelButton;
1176 GtkWidget *okButton;
1177 GtkTooltips *tooltips;
1178
1179 tooltips = gtk_tooltips_new ();
1180
1181 eraser_dialog = gtk_dialog_new ();
1182 // NO NEED TO "FIX" THAT _("...")!!
1183 // see handy macros near top of file.
1184 gtk_window_set_title (GTK_WINDOW (eraser_dialog), _("Eraser Configuration"));
1185 gtk_window_set_type_hint (GTK_WINDOW (eraser_dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
1186
1187 dialogVbox = GTK_DIALOG (eraser_dialog)->vbox;
1188 gtk_widget_show (dialogVbox);
1189
1190 dialogHbox = gtk_hbox_new (FALSE, 0);
1191 gtk_widget_show (dialogHbox);
1192 gtk_box_pack_start (GTK_BOX (dialogVbox), dialogHbox, TRUE, TRUE, 0);
1193
1194 allSettingsVbox = gtk_vbox_new (FALSE, 12);
1195 gtk_widget_show (allSettingsVbox);
1196 gtk_box_pack_start (GTK_BOX (dialogHbox), allSettingsVbox, TRUE, TRUE, 0);
1197 gtk_container_set_border_width (GTK_CONTAINER (allSettingsVbox), 8);
1198
1199 settingsOuterHbox = gtk_hbox_new (FALSE, 0);
1200 gtk_widget_show (settingsOuterHbox);
1201 gtk_box_pack_start (GTK_BOX (allSettingsVbox), settingsOuterHbox, TRUE, TRUE, 0);
1202
1203 settingsOuterVbox = gtk_vbox_new (FALSE, 12);
1204 gtk_widget_show (settingsOuterVbox);
1205 gtk_box_pack_start (GTK_BOX (settingsOuterHbox), settingsOuterVbox, TRUE, TRUE, 0);
1206
1207 brushSettingsHbox = gtk_hbox_new (FALSE, 15);
1208 gtk_widget_show (brushSettingsHbox);
1209 gtk_box_pack_start (GTK_BOX (settingsOuterVbox), brushSettingsHbox, FALSE, TRUE, 0);
1210
1211 brushModeHbox = gtk_hbox_new (FALSE, 0);
1212 gtk_widget_show (brushModeHbox);
1213 gtk_box_pack_start (GTK_BOX (brushSettingsHbox), brushModeHbox, TRUE, TRUE, 0);
1214
1215 brushModeLabel = gtk_label_new_with_mnemonic (_("Brush _Mode: "));
1216 gtk_widget_show (brushModeLabel);
1217 gtk_box_pack_start (GTK_BOX (brushModeHbox), brushModeLabel, FALSE, FALSE, 0);
1218
1219 brushModeMenu = gtk_combo_box_new_text ();
1220 gtk_widget_show (brushModeMenu);
1221 gtk_box_pack_start (GTK_BOX (brushModeHbox), brushModeMenu, TRUE, TRUE, 0);
1222
1223 brushSizeHbox = gtk_hbox_new (FALSE, 0);
1224 gtk_widget_show (brushSizeHbox);
1225 gtk_box_pack_start (GTK_BOX (brushSettingsHbox), brushSizeHbox, TRUE, TRUE, 0);
1226
1227 brushSizeLabel = gtk_label_new_with_mnemonic (_("Brush _Size: "));
1228 gtk_widget_show (brushSizeLabel);
1229 gtk_box_pack_start (GTK_BOX (brushSizeHbox), brushSizeLabel, FALSE, FALSE, 0);
1230
1231 brushSizeMenu = gtk_combo_box_new_text ();
1232 gtk_widget_show (brushSizeMenu);
1233 gtk_box_pack_start (GTK_BOX (brushSizeHbox), brushSizeMenu, TRUE, TRUE, 0);
1234
1235 colorVbox = gtk_vbox_new (FALSE, 0);
1236 gtk_widget_show (colorVbox);
1237 gtk_box_pack_start (GTK_BOX (settingsOuterVbox), colorVbox, FALSE, TRUE, 0);
1238
1239 colorLabel = gtk_label_new_with_mnemonic (_("Output \"_Color\" for all masked pixels:"));
1240 gtk_widget_show (colorLabel);
1241 gtk_box_pack_start (GTK_BOX (colorVbox), colorLabel, FALSE, FALSE, 0);
1242 gtk_misc_set_alignment (GTK_MISC (colorLabel), 0, 0.5);
1243
1244 colorHbox = gtk_hbox_new (FALSE, 5);
1245 gtk_widget_show (colorHbox);
1246 gtk_box_pack_start (GTK_BOX (colorVbox), colorHbox, TRUE, TRUE, 0);
1247
1248 colorSlider = gtk_hscale_new (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 255, 1, 1, 0)));
1249 gtk_widget_show (colorSlider);
1250 gtk_box_pack_start (GTK_BOX (colorHbox), colorSlider, TRUE, TRUE, 0);
1251 gtk_scale_set_draw_value (GTK_SCALE (colorSlider), FALSE);
1252 gtk_scale_set_value_pos (GTK_SCALE (colorSlider), GTK_POS_LEFT);
1253 gtk_scale_set_digits (GTK_SCALE (colorSlider), 0);
1254
1255 colorSpinner_adj = gtk_adjustment_new (0, 0, 255, 1, 1, 0);
1256 colorSpinner = gtk_spin_button_new (GTK_ADJUSTMENT (colorSpinner_adj), 1, 0);
1257 gtk_widget_show (colorSpinner);
1258 gtk_box_pack_start (GTK_BOX (colorHbox), colorSpinner, FALSE, TRUE, 0);
1259 gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (colorSpinner), TRUE);
1260
1261 eraserDataFileHbox = gtk_hbox_new (FALSE, 5);
1262 gtk_widget_show (eraserDataFileHbox);
1263 gtk_box_pack_start (GTK_BOX (settingsOuterVbox), eraserDataFileHbox, FALSE, TRUE, 0);
1264
1265 eraserDataFileLabel = gtk_label_new_with_mnemonic (_("Eraser _Data File:"));
1266 gtk_widget_show (eraserDataFileLabel);
1267 gtk_box_pack_start (GTK_BOX (eraserDataFileHbox), eraserDataFileLabel, FALSE, FALSE, 0);
1268
1269 eraserDataFileEntry = gtk_entry_new ();
1270 gtk_widget_show (eraserDataFileEntry);
1271 gtk_box_pack_start (GTK_BOX (eraserDataFileHbox), eraserDataFileEntry, TRUE, TRUE, 0);
1272 gtk_tooltips_set_tip (tooltips, eraserDataFileEntry, _("File in which eraser mask information (the list below and the pixels erased for each range) will be stored"), NULL);
1273 gtk_entry_set_invisible_char (GTK_ENTRY (eraserDataFileEntry), 8226);
1274 gtk_entry_set_width_chars (GTK_ENTRY (eraserDataFileEntry), 35);
1275
1276 debugHbox = gtk_hbox_new (FALSE, 0);
1277 gtk_widget_show (debugHbox);
1278 gtk_box_pack_start (GTK_BOX (settingsOuterVbox), debugHbox, FALSE, TRUE, 0);
1279
1280 eraserDataFileBrowseButton = gtk_button_new_with_mnemonic (_("_Browse..."));
1281 gtk_widget_show (eraserDataFileBrowseButton);
1282 gtk_box_pack_start (GTK_BOX (debugHbox), eraserDataFileBrowseButton, FALSE, FALSE, 0);
1283
1284 browse_debug_spacer = gtk_label_new ("");
1285 gtk_widget_show (browse_debug_spacer);
1286 gtk_box_pack_start (GTK_BOX (debugHbox), browse_debug_spacer, TRUE, FALSE, 0);
1287
1288 debugLabel = gtk_label_new_with_mnemonic (_("_Debugging settings (bits): "));
1289 gtk_widget_show (debugLabel);
1290 gtk_box_pack_start (GTK_BOX (debugHbox), debugLabel, FALSE, FALSE, 0);
1291
1292 debugSpinButton_adj = gtk_adjustment_new (0, 0, 16777215, 1, 10, 0);
1293 debugSpinButton = gtk_spin_button_new (GTK_ADJUSTMENT (debugSpinButton_adj), 1, 0);
1294 gtk_widget_show (debugSpinButton);
1295 gtk_box_pack_start (GTK_BOX (debugHbox), debugSpinButton, FALSE, TRUE, 0);
1296 gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (debugSpinButton), TRUE);
1297
1298 frameRangeHbox = gtk_hbox_new (FALSE, 32);
1299 gtk_widget_show (frameRangeHbox);
1300 gtk_box_pack_start (GTK_BOX (settingsOuterVbox), frameRangeHbox, FALSE, TRUE, 0);
1301
1302 frameRangeStartHbox = gtk_hbox_new (FALSE, 0);
1303 gtk_widget_show (frameRangeStartHbox);
1304 gtk_box_pack_start (GTK_BOX (frameRangeHbox), frameRangeStartHbox, TRUE, TRUE, 0);
1305
1306 frameRangeStartLabel = gtk_label_new_with_mnemonic (_("_First frame: "));
1307 gtk_widget_show (frameRangeStartLabel);
1308 gtk_box_pack_start (GTK_BOX (frameRangeStartHbox), frameRangeStartLabel, FALSE, FALSE, 0);
1309
1310 frameRangeFirstSpinButton_adj = gtk_adjustment_new (0, 0, 100000000, 1, 10, 0);
1311 frameRangeFirstSpinButton = gtk_spin_button_new (GTK_ADJUSTMENT (frameRangeFirstSpinButton_adj), 1, 0);
1312 gtk_widget_show (frameRangeFirstSpinButton);
1313 gtk_box_pack_start (GTK_BOX (frameRangeStartHbox), frameRangeFirstSpinButton, TRUE, TRUE, 0);
1314 gtk_tooltips_set_tip (tooltips, frameRangeFirstSpinButton, _("First frame to which currently selected eraser mask applies; 0 means first frame of video"), NULL);
1315 gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (frameRangeFirstSpinButton), TRUE);
1316
1317 frameRangeLastHbox = gtk_hbox_new (FALSE, 0);
1318 gtk_widget_show (frameRangeLastHbox);
1319 gtk_box_pack_start (GTK_BOX (frameRangeHbox), frameRangeLastHbox, TRUE, TRUE, 0);
1320
1321 frameRangeLastLabel = gtk_label_new_with_mnemonic (_("_Last frame: "));
1322 gtk_widget_show (frameRangeLastLabel);
1323 gtk_box_pack_start (GTK_BOX (frameRangeLastHbox), frameRangeLastLabel, FALSE, FALSE, 0);
1324
1325 frameRangeLastSpinButton_adj = gtk_adjustment_new (100000000, 0, 100000000, 1, 10, 0);
1326 frameRangeLastSpinButton = gtk_spin_button_new (GTK_ADJUSTMENT (frameRangeLastSpinButton_adj), 1, 0);
1327 gtk_widget_show (frameRangeLastSpinButton);
1328 gtk_box_pack_start (GTK_BOX (frameRangeLastHbox), frameRangeLastSpinButton, TRUE, TRUE, 0);
1329 gtk_tooltips_set_tip (tooltips, frameRangeLastSpinButton, _("Last frame to which currently selected eraser mask applies; use e.g. 99999999 to represent the last frame of video"), NULL);
1330 gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (frameRangeLastSpinButton), TRUE);
1331
1332 rangeListScrolledWindow = gtk_scrolled_window_new (NULL, NULL);
1333 gtk_widget_show (rangeListScrolledWindow);
1334 gtk_box_pack_start (GTK_BOX (settingsOuterVbox), rangeListScrolledWindow, TRUE, TRUE, 0);
1335 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (rangeListScrolledWindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1336 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (rangeListScrolledWindow), GTK_SHADOW_IN);
1337
1338 rangeListTreeview = gtk_tree_view_new ();
1339 gtk_widget_show (rangeListTreeview);
1340 gtk_container_add (GTK_CONTAINER (rangeListScrolledWindow), rangeListTreeview);
1341 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (rangeListTreeview), TRUE);
1342 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (rangeListTreeview), FALSE);
1343
1344 rangeListHButtonBox = gtk_hbutton_box_new ();
1345 gtk_widget_show (rangeListHButtonBox);
1346 gtk_box_pack_start (GTK_BOX (settingsOuterVbox), rangeListHButtonBox, FALSE, TRUE, 0);
1347
1348 insertButton = gtk_button_new_from_stock ("gtk-new");
1349 gtk_widget_show (insertButton);
1350 gtk_container_add (GTK_CONTAINER (rangeListHButtonBox), insertButton);
1351 GTK_WIDGET_SET_FLAGS (insertButton, GTK_CAN_DEFAULT);
1352 gtk_button_set_focus_on_click (GTK_BUTTON (insertButton), FALSE);
1353
1354 duplicateButton = gtk_button_new_with_mnemonic (_("Duplicate"));
1355 gtk_widget_show (duplicateButton);
1356 gtk_container_add (GTK_CONTAINER (rangeListHButtonBox), duplicateButton);
1357 GTK_WIDGET_SET_FLAGS (duplicateButton, GTK_CAN_DEFAULT);
1358 gtk_tooltips_set_tip (tooltips, duplicateButton, _("Make a copy of the currently selected eraser mask and insert it as the following row"), NULL);
1359 gtk_button_set_focus_on_click (GTK_BUTTON (duplicateButton), FALSE);
1360
1361 deleteButton = gtk_button_new_from_stock ("gtk-delete");
1362 gtk_widget_show (deleteButton);
1363 gtk_container_add (GTK_CONTAINER (rangeListHButtonBox), deleteButton);
1364 GTK_WIDGET_SET_FLAGS (deleteButton, GTK_CAN_DEFAULT);
1365 gtk_button_set_focus_on_click (GTK_BUTTON (deleteButton), FALSE);
1366
1367 previewVboxOuter = gtk_vbox_new (FALSE, 0);
1368 gtk_widget_show (previewVboxOuter);
1369 gtk_box_pack_start (GTK_BOX (dialogHbox), previewVboxOuter, FALSE, TRUE, 0);
1370
1371 previewJumpButtonHbox = gtk_hbox_new (FALSE, 0);
1372 gtk_widget_show (previewJumpButtonHbox);
1373 gtk_box_pack_start (GTK_BOX (previewVboxOuter), previewJumpButtonHbox, FALSE, TRUE, 5);
1374
1375 previewJumpLastButton = gtk_button_new_with_mnemonic (_("Last Frame in Range"));
1376 gtk_widget_show (previewJumpLastButton);
1377 gtk_box_pack_end (GTK_BOX (previewJumpButtonHbox), previewJumpLastButton, FALSE, FALSE, 0);
1378 gtk_button_set_focus_on_click (GTK_BUTTON (previewJumpLastButton), FALSE);
1379
1380 previewJumpFirstButton = gtk_button_new_with_mnemonic (_("First Frame in Range"));
1381 gtk_widget_show (previewJumpFirstButton);
1382 gtk_box_pack_end (GTK_BOX (previewJumpButtonHbox), previewJumpFirstButton, FALSE, FALSE, 0);
1383 gtk_button_set_focus_on_click (GTK_BUTTON (previewJumpFirstButton), FALSE);
1384
1385 previewJumpLabel = gtk_label_new (_("Jump to: "));
1386 gtk_widget_show (previewJumpLabel);
1387 gtk_box_pack_end (GTK_BOX (previewJumpButtonHbox), previewJumpLabel, FALSE, FALSE, 0);
1388
1389 previewFrame = gtk_frame_new (NULL);
1390 gtk_widget_show (previewFrame);
1391 gtk_box_pack_start (GTK_BOX (previewVboxOuter), previewFrame, FALSE, TRUE, 0);
1392
1393 previewAlignment = gtk_alignment_new (0.5, 0.5, 1, 1);
1394 gtk_widget_show (previewAlignment);
1395 gtk_container_add (GTK_CONTAINER (previewFrame), previewAlignment);
1396 gtk_alignment_set_padding (GTK_ALIGNMENT (previewAlignment), 0, 8, 6, 8);
1397
1398 previewVbox = gtk_vbox_new (FALSE, 5);
1399 gtk_widget_show (previewVbox);
1400 gtk_container_add (GTK_CONTAINER (previewAlignment), previewVbox);
1401
1402 previewControlHbox = gtk_hbox_new (FALSE, 5);
1403 gtk_widget_show (previewControlHbox);
1404 gtk_box_pack_start (GTK_BOX (previewVbox), previewControlHbox, TRUE, TRUE, 0);
1405
1406 previewOutputMenu = gtk_combo_box_new_text ();
1407 gtk_widget_show (previewOutputMenu);
1408 gtk_box_pack_start (GTK_BOX (previewControlHbox), previewOutputMenu, FALSE, TRUE, 0);
1409
1410 previewSlider = gtk_hscale_new (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 99, 1, 1, 0)));
1411 gtk_widget_show (previewSlider);
1412 gtk_box_pack_start (GTK_BOX (previewControlHbox), previewSlider, TRUE, TRUE, 0);
1413 gtk_scale_set_digits (GTK_SCALE (previewSlider), 0);
1414
1415 previewVideo = gtk_drawing_area_new ();
1416 gtk_widget_show (previewVideo);
1417 gtk_box_pack_start (GTK_BOX (previewVbox), previewVideo, TRUE, TRUE, 0);
1418 gtk_widget_set_size_request (previewVideo, 30, 30);
1419 gtk_widget_set_events (previewVideo, GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK | GDK_BUTTON_PRESS_MASK);
1420
1421 previewLabel = gtk_label_new (_("Preview"));
1422 gtk_widget_show (previewLabel);
1423 gtk_frame_set_label_widget (GTK_FRAME (previewFrame), previewLabel);
1424
1425 dialogButtonBox = GTK_DIALOG (eraser_dialog)->action_area;
1426 gtk_widget_show (dialogButtonBox);
1427 gtk_button_box_set_layout (GTK_BUTTON_BOX (dialogButtonBox), GTK_BUTTONBOX_END);
1428
1429 cancelButton = gtk_button_new_from_stock ("gtk-cancel");
1430 gtk_widget_show (cancelButton);
1431 gtk_dialog_add_action_widget (GTK_DIALOG (eraser_dialog), cancelButton, GTK_RESPONSE_CANCEL);
1432 GTK_WIDGET_SET_FLAGS (cancelButton, GTK_CAN_DEFAULT);
1433
1434 okButton = gtk_button_new_from_stock ("gtk-ok");
1435 gtk_widget_show (okButton);
1436 gtk_dialog_add_action_widget (GTK_DIALOG (eraser_dialog), okButton, GTK_RESPONSE_OK);
1437 GTK_WIDGET_SET_FLAGS (okButton, GTK_CAN_DEFAULT);
1438
1439 gtk_label_set_mnemonic_widget (GTK_LABEL (eraserDataFileLabel), eraserDataFileEntry);
1440 gtk_label_set_mnemonic_widget (GTK_LABEL (debugLabel), debugSpinButton);
1441 gtk_label_set_mnemonic_widget (GTK_LABEL (frameRangeStartLabel), debugSpinButton);
1442 gtk_label_set_mnemonic_widget (GTK_LABEL (frameRangeLastLabel), debugSpinButton);
1443
1444 /* Store pointers to all widgets, for use by lookup_widget(). */
1445 GLADE_HOOKUP_OBJECT_NO_REF (eraser_dialog, eraser_dialog, "eraser_dialog");
1446 GLADE_HOOKUP_OBJECT_NO_REF (eraser_dialog, dialogVbox, "dialogVbox");
1447 GLADE_HOOKUP_OBJECT (eraser_dialog, dialogHbox, "dialogHbox");
1448 GLADE_HOOKUP_OBJECT (eraser_dialog, allSettingsVbox, "allSettingsVbox");
1449 GLADE_HOOKUP_OBJECT (eraser_dialog, settingsOuterHbox, "settingsOuterHbox");
1450 GLADE_HOOKUP_OBJECT (eraser_dialog, settingsOuterVbox, "settingsOuterVbox");
1451 GLADE_HOOKUP_OBJECT (eraser_dialog, brushSettingsHbox, "brushSettingsHbox");
1452 GLADE_HOOKUP_OBJECT (eraser_dialog, brushModeHbox, "brushModeHbox");
1453 GLADE_HOOKUP_OBJECT (eraser_dialog, brushModeLabel, "brushModeLabel");
1454 GLADE_HOOKUP_OBJECT (eraser_dialog, brushModeMenu, "brushModeMenu");
1455 GLADE_HOOKUP_OBJECT (eraser_dialog, brushSizeHbox, "brushSizeHbox");
1456 GLADE_HOOKUP_OBJECT (eraser_dialog, brushSizeLabel, "brushSizeLabel");
1457 GLADE_HOOKUP_OBJECT (eraser_dialog, brushSizeMenu, "brushSizeMenu");
1458 GLADE_HOOKUP_OBJECT (eraser_dialog, colorVbox, "colorVbox");
1459 GLADE_HOOKUP_OBJECT (eraser_dialog, colorLabel, "colorLabel");
1460 GLADE_HOOKUP_OBJECT (eraser_dialog, colorHbox, "colorHbox");
1461 GLADE_HOOKUP_OBJECT (eraser_dialog, colorSlider, "colorSlider");
1462 GLADE_HOOKUP_OBJECT (eraser_dialog, colorSpinner, "colorSpinner");
1463 GLADE_HOOKUP_OBJECT (eraser_dialog, eraserDataFileHbox, "eraserDataFileHbox");
1464 GLADE_HOOKUP_OBJECT (eraser_dialog, eraserDataFileLabel, "eraserDataFileLabel");
1465 GLADE_HOOKUP_OBJECT (eraser_dialog, eraserDataFileEntry, "eraserDataFileEntry");
1466 GLADE_HOOKUP_OBJECT (eraser_dialog, debugHbox, "debugHbox");
1467 GLADE_HOOKUP_OBJECT (eraser_dialog, eraserDataFileBrowseButton, "eraserDataFileBrowseButton");
1468 GLADE_HOOKUP_OBJECT (eraser_dialog, browse_debug_spacer, "browse_debug_spacer");
1469 GLADE_HOOKUP_OBJECT (eraser_dialog, debugLabel, "debugLabel");
1470 GLADE_HOOKUP_OBJECT (eraser_dialog, debugSpinButton, "debugSpinButton");
1471 GLADE_HOOKUP_OBJECT (eraser_dialog, frameRangeHbox, "frameRangeHbox");
1472 GLADE_HOOKUP_OBJECT (eraser_dialog, frameRangeStartHbox, "frameRangeStartHbox");
1473 GLADE_HOOKUP_OBJECT (eraser_dialog, frameRangeStartLabel, "frameRangeStartLabel");
1474 GLADE_HOOKUP_OBJECT (eraser_dialog, frameRangeFirstSpinButton, "frameRangeFirstSpinButton");
1475 GLADE_HOOKUP_OBJECT (eraser_dialog, frameRangeLastHbox, "frameRangeLastHbox");
1476 GLADE_HOOKUP_OBJECT (eraser_dialog, frameRangeLastLabel, "frameRangeLastLabel");
1477 GLADE_HOOKUP_OBJECT (eraser_dialog, frameRangeLastSpinButton, "frameRangeLastSpinButton");
1478 GLADE_HOOKUP_OBJECT (eraser_dialog, rangeListScrolledWindow, "rangeListScrolledWindow");
1479 GLADE_HOOKUP_OBJECT (eraser_dialog, rangeListTreeview, "rangeListTreeview");
1480 GLADE_HOOKUP_OBJECT (eraser_dialog, rangeListHButtonBox, "rangeListHButtonBox");
1481 GLADE_HOOKUP_OBJECT (eraser_dialog, insertButton, "insertButton");
1482 GLADE_HOOKUP_OBJECT (eraser_dialog, duplicateButton, "duplicateButton");
1483 GLADE_HOOKUP_OBJECT (eraser_dialog, deleteButton, "deleteButton");
1484 GLADE_HOOKUP_OBJECT (eraser_dialog, previewVboxOuter, "previewVboxOuter");
1485 GLADE_HOOKUP_OBJECT (eraser_dialog, previewJumpButtonHbox, "previewJumpButtonHbox");
1486 GLADE_HOOKUP_OBJECT (eraser_dialog, previewJumpLastButton, "previewJumpLastButton");
1487 GLADE_HOOKUP_OBJECT (eraser_dialog, previewJumpFirstButton, "previewJumpFirstButton");
1488 GLADE_HOOKUP_OBJECT (eraser_dialog, previewJumpLabel, "previewJumpLabel");
1489 GLADE_HOOKUP_OBJECT (eraser_dialog, previewFrame, "previewFrame");
1490 GLADE_HOOKUP_OBJECT (eraser_dialog, previewAlignment, "previewAlignment");
1491 GLADE_HOOKUP_OBJECT (eraser_dialog, previewVbox, "previewVbox");
1492 GLADE_HOOKUP_OBJECT (eraser_dialog, previewControlHbox, "previewControlHbox");
1493 GLADE_HOOKUP_OBJECT (eraser_dialog, previewOutputMenu, "previewOutputMenu");
1494 GLADE_HOOKUP_OBJECT (eraser_dialog, previewSlider, "previewSlider");
1495 GLADE_HOOKUP_OBJECT (eraser_dialog, previewVideo, "previewVideo");
1496 GLADE_HOOKUP_OBJECT (eraser_dialog, previewLabel, "previewLabel");
1497 GLADE_HOOKUP_OBJECT_NO_REF (eraser_dialog, dialogButtonBox, "dialogButtonBox");
1498 GLADE_HOOKUP_OBJECT (eraser_dialog, cancelButton, "cancelButton");
1499 GLADE_HOOKUP_OBJECT (eraser_dialog, okButton, "okButton");
1500 GLADE_HOOKUP_OBJECT_NO_REF (eraser_dialog, tooltips, "tooltips");
1501
1502 return eraser_dialog;
1503 }
1504
1505 #endif
1506