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, &param %p\n", in_param, &param);
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