1 /*  dvdisaster: Additional error correction for optical media.
2  *  Copyright (C) 2004-2015 Carsten Gnoerlich.
3  *
4  *  Email: carsten@dvdisaster.org  -or-  cgnoerlich@fsfe.org
5  *  Project homepage: http://www.dvdisaster.org
6  *
7  *  This file is part of dvdisaster.
8  *
9  *  dvdisaster is free software: you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation, either version 3 of the License, or
12  *  (at your option) any later version.
13  *
14  *  dvdisaster is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with dvdisaster. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "dvdisaster.h"
24 
25 /***
26  *** Spiral drawing and updating
27  ***/
28 
29 static long long int readable, correctable, missing;
30 static int percent,min_required;
31 static GdkColor *footer_color;
32 
33 #define REDRAW_TITLE      1<<0
34 #define REDRAW_SUBTITLE   1<<1
35 #define REDRAW_PROGRESS   1<<2
36 #define REDRAW_ERRORMSG   1<<3
37 
draw_text(GdkDrawable * d,PangoLayout * l,char * text,int x,int y,GdkColor * color,int redraw)38 static int draw_text(GdkDrawable *d, PangoLayout *l, char *text, int x, int y, GdkColor *color, int redraw)
39 {  static GdkPixmap *pixmap;
40    static int pixmap_width, pixmap_height;
41    int w,h,pw;
42    int erase_to = Closure->readAdaptiveSpiral->mx - Closure->readAdaptiveSpiral->diameter/2;
43 
44    SetText(l, text, &w, &h);
45 
46    pw = erase_to-x;
47    if(pw > pixmap_width || h > pixmap_height)
48    {  if(pixmap) g_object_unref(pixmap);
49 
50       pixmap = gdk_pixmap_new(d, pw, h, -1);
51       pixmap_width = pw;
52       pixmap_height = h;
53    }
54 
55 
56    if(redraw)  /* redraw using double buffering to prevent flicker */
57    {  gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->background);
58       gdk_draw_rectangle(pixmap, Closure->drawGC, TRUE, 0, 0, pw, h);
59 
60       gdk_gc_set_rgb_fg_color(Closure->drawGC, color);
61       gdk_draw_layout(pixmap, Closure->drawGC, 0, 0, l);
62       gdk_draw_drawable(d, Closure->drawGC, pixmap, 0, 0, x, y, pw, h);
63    }
64 
65    return h;
66 }
67 
redraw_labels(GtkWidget * widget,int erase_mask)68 static void redraw_labels(GtkWidget *widget, int erase_mask)
69 {  GdkDrawable *d = Closure->readAdaptiveDrawingArea->window;
70    char buf[256];
71    int x,y,w,h;
72 
73    /* Draw the labels */
74 
75    x = 10;
76    gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);
77 
78    y = Closure->readAdaptiveSpiral->my - Closure->readAdaptiveSpiral->diameter/2;
79    h = draw_text(d, Closure->readLinearCurve->layout,
80 		 _("Adaptive reading:"), x, y, Closure->foreground, erase_mask & REDRAW_TITLE);
81 
82    y += h+h/2;
83    if(Closure->readAdaptiveSubtitle)
84    {  char *c = Closure->readAdaptiveSubtitle + strlen(Closure->readAdaptiveSubtitle)/2;
85 
86       while(*c && *c != ' ')  /* find point to split text in middle */
87         c++;
88 
89       if(c)                   /* split text into two lines */
90       {  *c = 0;
91 	 h = draw_text(d, Closure->readLinearCurve->layout,
92 		       Closure->readAdaptiveSubtitle, x, y, Closure->foreground,
93 		       erase_mask & REDRAW_SUBTITLE);
94 	 h = draw_text(d, Closure->readLinearCurve->layout,
95 			c+1, x, y+h, Closure->foreground,
96 			erase_mask & REDRAW_SUBTITLE);
97 	 *c = ' ';
98       }
99       else                    /* draw text in one line */
100       {  h = draw_text(d, Closure->readLinearCurve->layout,
101 		       Closure->readAdaptiveSubtitle, x, y, Closure->foreground,
102 		       erase_mask & REDRAW_SUBTITLE);
103       }
104    }
105 
106    y += 4*h;
107    h = draw_text(d, Closure->readLinearCurve->layout,
108 		 _("Sectors processed"), x, y, Closure->foreground, erase_mask & REDRAW_TITLE);
109 
110    y += h;
111    snprintf(buf, 255, "  %s: %lld", _("readable"), readable);
112    h = draw_text(d, Closure->readLinearCurve->layout, buf, x, y, Closure->foreground, erase_mask & REDRAW_PROGRESS);
113 
114    y += h;
115    snprintf(buf, 255, "  %s: %lld", _("correctable"), correctable);
116    h = draw_text(d, Closure->readLinearCurve->layout, buf, x, y, Closure->foreground, erase_mask & REDRAW_PROGRESS);
117 
118    y += h;
119    snprintf(buf, 255, "  %s: %lld", _("missing"), missing);
120    h = draw_text(d, Closure->readLinearCurve->layout, buf, x, y, Closure->foreground, erase_mask & REDRAW_PROGRESS);
121 
122    if(min_required > 0 && readable > 0)
123    {  int percent = round(((1000*readable)/(readable+correctable+missing)));
124 
125       if(!missing)                /* Make sure target percentage is reached */
126 	percent = min_required;   /* in spite of rounding errors            */
127 
128       y += h;
129       snprintf(buf, 255, _("Readable: %d.%d%% / %d.%d%% required"),
130 	       percent/10, percent%10,
131 	       min_required/10, min_required%10);
132       h = draw_text(d, Closure->readLinearCurve->layout, buf, x, y, Closure->foreground, erase_mask & REDRAW_PROGRESS);
133    }
134 
135    y += h;
136    snprintf(buf, 255, _("Total recoverable: %d.%d%%"), percent/10, percent%10);
137    h = draw_text(d, Closure->readLinearCurve->layout, buf, x, y, Closure->foreground, erase_mask & REDRAW_PROGRESS);
138 
139 
140    if(Closure->readAdaptiveErrorMsg && erase_mask & REDRAW_ERRORMSG)
141    {  gdk_gc_set_rgb_fg_color(Closure->drawGC, footer_color);
142 
143       SetText(Closure->readLinearCurve->layout, Closure->readAdaptiveErrorMsg, &w, &h);
144       y = Closure->readAdaptiveSpiral->my + Closure->readAdaptiveSpiral->diameter/2 - h;
145       gdk_draw_layout(d, Closure->drawGC, x, y, Closure->readLinearCurve->layout);
146    }
147 }
148 
redraw_spiral(GtkWidget * widget)149 static void redraw_spiral(GtkWidget *widget)
150 {
151    DrawSpiral(Closure->readAdaptiveSpiral);
152 }
153 
154 /* Calculate the geometry of the spiral */
155 
update_geometry(GtkWidget * widget)156 static void update_geometry(GtkWidget *widget)
157 {  GtkAllocation *a = &widget->allocation;
158 
159    Closure->readAdaptiveSpiral->mx = a->width - 15 - Closure->readAdaptiveSpiral->diameter / 2;
160    Closure->readAdaptiveSpiral->my = a->height / 2;
161 }
162 
163 /* Expose event handler */
164 
expose_cb(GtkWidget * widget,GdkEventExpose * event,gpointer data)165 static gboolean expose_cb(GtkWidget *widget, GdkEventExpose *event, gpointer data)
166 {
167    SetSpiralWidget(Closure->readAdaptiveSpiral, widget);
168 
169    if(event->count) /* Exposure compression */
170      return TRUE;
171 
172    update_geometry(widget);
173    redraw_labels(widget, ~0);
174    redraw_spiral(widget);
175 
176    return TRUE;
177 }
178 
179 /*
180  * Clip the spiral. Simply remove the clipping elements to avoid flicker.
181  */
182 
clip_idle_func(gpointer data)183 static gboolean clip_idle_func(gpointer data)
184 {  Spiral *spiral = Closure->readAdaptiveSpiral;
185    int i;
186 
187    if(spiral->segmentClipping < spiral->segmentCount)
188    {  GdkColor *outline = spiral->outline;
189       int clipping = spiral->segmentClipping;
190 
191       spiral->outline = Closure->background;
192       spiral->segmentClipping = spiral->segmentCount;
193 
194       for(i=clipping; i < spiral->segmentCount; i++)
195 	DrawSpiralSegment(spiral, Closure->background, i);
196 
197       spiral->outline = outline;
198       spiral->segmentClipping = clipping;
199 
200       /* Now redraw the last turn */
201 
202       for(i=ADAPTIVE_READ_SPIRAL_SIZE-300; i<=clipping; i++)
203 	DrawSpiralSegment(spiral, Closure->background, i);
204    }
205 
206    return FALSE;
207 }
208 
ClipReadAdaptiveSpiral(int segments)209 void ClipReadAdaptiveSpiral(int segments)
210 {
211    Closure->readAdaptiveSpiral->segmentClipping = segments;
212 
213    g_idle_add(clip_idle_func, NULL);
214 }
215 
216 /*
217  * Change the segment color.
218  * Segment numbers are passed with an offset of 100,
219  * since another routine is occasionally doing an
220  * g_idle_remove_by_data(GINT_TO_POINTER(REDRAW_PROGRESS)),
221  * with REDRAW_PROGRESS being 4 which would make segment 4 fail to redraw.
222  * One of the many places where the Gtk+ API is not well thought out.
223  */
224 
segment_idle_func(gpointer data)225 static gboolean segment_idle_func(gpointer data)
226 {  int segment = GPOINTER_TO_INT(data);
227 
228    segment-=100;
229    DrawSpiralSegment(Closure->readAdaptiveSpiral,
230 		     Closure->readAdaptiveSpiral->segmentColor[segment],
231 		     segment);
232 
233    return FALSE;
234 }
235 
ChangeSegmentColor(GdkColor * color,int segment)236 void ChangeSegmentColor(GdkColor *color, int segment)
237 {
238    Closure->readAdaptiveSpiral->segmentColor[segment] = color;
239    if(Closure->readAdaptiveSpiral->cursorPos == segment)
240         Closure->readAdaptiveSpiral->colorUnderCursor = color;
241    else g_idle_add(segment_idle_func, GINT_TO_POINTER(100+segment));
242 }
243 
244 /*
245  * Remove the white markers drawn during the fill operation
246  */
247 
remove_fill_idle_func(gpointer data)248 static gboolean remove_fill_idle_func(gpointer data)
249 {  Spiral *spiral = Closure->readAdaptiveSpiral;
250    int i;
251 
252    for(i=0; i<spiral->segmentCount; i++)
253      if(spiral->segmentColor[i] == Closure->whiteSector)
254        DrawSpiralSegment(spiral, Closure->background, i);
255 
256    return FALSE;
257 }
258 
RemoveFillMarkers()259 void RemoveFillMarkers()
260 {
261    g_idle_add(remove_fill_idle_func, NULL);
262 }
263 
264 /***
265  *** Redraw the label in our window
266  ***/
267 
label_redraw_idle_func(gpointer data)268 static gboolean label_redraw_idle_func(gpointer data)
269 {  int erase_mask = GPOINTER_TO_INT(data);
270 
271    redraw_labels(Closure->readAdaptiveDrawingArea, erase_mask);
272 
273    return FALSE;
274 }
275 
SetAdaptiveReadSubtitle(char * title)276 void SetAdaptiveReadSubtitle(char *title)
277 {
278    if(Closure->readAdaptiveSubtitle)
279      g_free(Closure->readAdaptiveSubtitle);
280 
281    Closure->readAdaptiveSubtitle = g_strdup(title);
282 
283    g_idle_add(label_redraw_idle_func, GINT_TO_POINTER(REDRAW_SUBTITLE));
284 }
285 
SetAdaptiveReadFootline(char * msg,GdkColor * color)286 void SetAdaptiveReadFootline(char *msg, GdkColor *color)
287 {
288    if(Closure->readAdaptiveErrorMsg)
289      g_free(Closure->readAdaptiveErrorMsg);
290 
291    Closure->readAdaptiveErrorMsg = g_strdup(msg);
292    footer_color = color;
293 
294    g_idle_add(label_redraw_idle_func, GINT_TO_POINTER(REDRAW_ERRORMSG));
295 }
296 
UpdateAdaptiveResults(gint64 r,gint64 c,gint64 m,int p)297 void UpdateAdaptiveResults(gint64 r, gint64 c, gint64 m, int p)
298 {  readable = r;
299    correctable = c;
300    missing = m;
301    percent = p;
302 
303    g_idle_remove_by_data(GINT_TO_POINTER(REDRAW_PROGRESS));
304    g_idle_add(label_redraw_idle_func, GINT_TO_POINTER(REDRAW_PROGRESS));
305 }
306 
307 /***
308  *** Reset the notebook contents for new read action
309  ***/
310 
ResetAdaptiveReadWindow()311 void ResetAdaptiveReadWindow()
312 {  FillSpiral(Closure->readAdaptiveSpiral, Closure->background);
313    //   DrawSpiral(Closure->readAdaptiveSpiral);
314 
315    if(Closure->readAdaptiveSubtitle)
316      g_free(Closure->readAdaptiveSubtitle);
317 
318    if(Closure->readAdaptiveErrorMsg)
319      g_free(Closure->readAdaptiveErrorMsg);
320 
321    Closure->readAdaptiveSubtitle = NULL;
322    Closure->readAdaptiveErrorMsg = NULL;
323 
324    readable = correctable = missing = 0;
325    percent = min_required = 0;
326 
327    if(Closure->readAdaptiveDrawingArea->window)
328    {  static GdkRectangle rect;
329       GtkAllocation *a = &Closure->readAdaptiveDrawingArea->allocation;
330 
331       rect.x = rect.y = 0;
332       rect.width  = a->width;
333       rect.height = a->height;
334 
335       gdk_window_clear(Closure->readAdaptiveDrawingArea->window);
336       gdk_window_invalidate_rect(Closure->readAdaptiveDrawingArea->window, &rect, FALSE);
337    }
338 }
339 
340 /*
341  * Set the minimum required data recovery value
342  */
343 
SetAdaptiveReadMinimumPercentage(int value)344 void SetAdaptiveReadMinimumPercentage(int value)
345 {  min_required = value;
346 }
347 
348 /***
349  *** Create the notebook contents for the reading and scanning action
350  ***/
351 
CreateAdaptiveReadWindow(GtkWidget * parent)352 void CreateAdaptiveReadWindow(GtkWidget *parent)
353 {  GtkWidget *sep,*d_area;
354 
355    Closure->readAdaptiveHeadline = gtk_label_new(NULL);
356    gtk_misc_set_alignment(GTK_MISC(Closure->readAdaptiveHeadline), 0.0, 0.0);
357    gtk_misc_set_padding(GTK_MISC(Closure->readAdaptiveHeadline), 5, 0);
358    gtk_label_set_ellipsize(GTK_LABEL(Closure->readAdaptiveHeadline), PANGO_ELLIPSIZE_END);
359    gtk_box_pack_start(GTK_BOX(parent), Closure->readAdaptiveHeadline, FALSE, FALSE, 3);
360 
361    sep = gtk_hseparator_new();
362    gtk_box_pack_start(GTK_BOX(parent), sep, FALSE, FALSE, 0);
363 
364    sep = gtk_hseparator_new();
365    gtk_box_pack_start(GTK_BOX(parent), sep, FALSE, FALSE, 0);
366 
367    d_area = Closure->readAdaptiveDrawingArea = gtk_drawing_area_new();
368    gtk_box_pack_start(GTK_BOX(parent), d_area, TRUE, TRUE, 0);
369    g_signal_connect(G_OBJECT(d_area), "expose_event", G_CALLBACK(expose_cb), NULL);
370 
371    Closure->readAdaptiveSpiral = CreateSpiral(Closure->grid, Closure->background, 10, 5,
372 					      ADAPTIVE_READ_SPIRAL_SIZE);
373 
374    gtk_widget_set_size_request(d_area, -1, Closure->readAdaptiveSpiral->diameter);
375 }
376 
377