1 /*
2  * Copyright (C) 2002 2003 2004 2005 2007 2008 2009 2010, Magnus Hjorth
3  *
4  * This file is part of mhWaveEdit.
5  *
6  * mhWaveEdit is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * mhWaveEdit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with mhWaveEdit; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 
22 #include <config.h>
23 
24 
25 
26 #include <math.h>
27 #include "chunkview.h"
28 #include "main.h"
29 #include "inifile.h"
30 #include "player.h"
31 #include "mainloop.h"
32 
33 static GtkObjectClass *parent_class;
34 static guint font_height=0,font_width=0;
35 
36 enum { VIEW_CHANGED_SIGNAL, SELECTION_CHANGED_SIGNAL, CURSOR_CHANGED_SIGNAL,
37        CHUNK_CHANGED_SIGNAL, DOUBLE_CLICK_SIGNAL, LAST_SIGNAL };
38 static guint chunk_view_signals[LAST_SIGNAL] = { 0 };
39 
40 static void chunk_view_update_image(ChunkView *view, guint xs, guint xe);
41 static void chunk_view_redraw_samples(ChunkView *cv, off_t start, off_t end);
42 static gint calc_x(ChunkView *cv, off_t sample, off_t width);
43 static int chunk_view_autoscroll(gpointer timesource, GTimeVal *current_time,
44 				 gpointer user_data);
45 
chunk_view_changed(Document * d,gpointer user_data)46 static void chunk_view_changed(Document *d, gpointer user_data)
47 {
48      ChunkView *cv = CHUNKVIEW(user_data);
49      if (cv->image != NULL)
50 	  chunk_view_update_image(cv,0,cv->image_width-1);
51      gtk_widget_queue_draw(GTK_WIDGET(cv));
52 }
53 
chunk_view_selection_changed(Document * d,gpointer user_data)54 static void chunk_view_selection_changed(Document *d, gpointer user_data)
55 {
56      off_t os,oe,ns,ne;
57 
58      ChunkView *cv = CHUNKVIEW(user_data);
59 
60      /* Calculate the user-visible part of the old selection */
61      os = MAX(d->viewstart, d->old_selstart);
62      oe = MIN(d->viewend,   d->old_selend);
63 
64      /* Calculate user-visible part of the new selection */
65      ns = MAX(d->viewstart, d->selstart);
66      ne = MIN(d->viewend,   d->selend);
67 
68      /* Different cases:
69       *  1. Neither the old nor the new selection is in view.
70       *  2. The old selection wasn't in view but the new is..
71       *  3. The new selection is in view but the old wasn't..
72       *  4. Both are in view and overlap
73       *  5. Both are in view and don't overlap
74       */
75 
76      if (os >= oe && ns >= ne) {
77 	  /* Case 1 - Do nothing */
78      } else if (os >= oe) {
79 	  /* Case 2 - Draw the entire new sel. */
80 	  chunk_view_redraw_samples(cv,ns,ne);
81      } else if (ns >= ne) {
82 	  /* Case 3 - Redraw the old sel. */
83 	  chunk_view_redraw_samples(cv,os,oe);
84      } else if ((ns >= os && ns < oe) || (os >= ns && os < ne)) {
85 	  /* Case 4 - Redraw the changes on both sides */
86 	  if (os != ns)
87 	       chunk_view_redraw_samples(cv,MIN(os,ns),MAX(os,ns));
88 	  if (oe != ne)
89 	       chunk_view_redraw_samples(cv,MIN(oe,ne),MAX(oe,ne));
90      } else {
91 	  /* Case 5 - Redraw both selections */
92 	  chunk_view_redraw_samples(cv,os,oe);
93 	  chunk_view_redraw_samples(cv,ns,ne);
94      }
95 
96 }
97 
chunk_view_cursor_changed(Document * d,gboolean rolling,gpointer user_data)98 static void chunk_view_cursor_changed(Document *d, gboolean rolling,
99 				      gpointer user_data)
100 {
101      ChunkView *cv = CHUNKVIEW(user_data);
102      off_t pos[4];
103      int npos,pix[4],npix,i,p,j, w=GTK_WIDGET(cv)->allocation.width;
104 
105      npos = 0;
106      pos[npos++] = d->old_cursorpos;
107      pos[npos++] = d->cursorpos;
108      if (cv->show_bufpos) {
109 	  pos[npos++] = d->old_playbufpos;
110 	  pos[npos++] = d->playbufpos;
111      }
112      npix = npos;
113      for (i=0; i<npos; i++)
114 	  pix[i]=calc_x(cv,pos[i],w);
115      while (npix > 0 && pix[0] == pix[1]) {
116 	  pix[0] = pix[2]; pix[1] = pix[3];
117 	  npix -= 2;
118      }
119      for (i=0; i<npix; i++) {
120 	  if (pix[i] > -1 && pix[i] < w)
121 	       gtk_widget_queue_draw_area(GTK_WIDGET(cv),pix[i],0,1,
122 					  cv->image_height);
123      }
124 }
125 
chunk_view_destroy(GtkObject * object)126 static void chunk_view_destroy (GtkObject *object)
127 {
128      ChunkView *cv = CHUNKVIEW(object);
129      if (cv->image) {
130 	  gdk_pixmap_unref(cv->image);
131 	  cv->image = NULL;
132      }
133      if (cv->doc != NULL) {
134 	  gtk_signal_disconnect_by_data(GTK_OBJECT(cv->doc),cv);
135 	  gtk_object_unref(GTK_OBJECT(cv->doc));
136      }
137      cv->doc = NULL;
138      parent_class->destroy(object);
139      if (cv->cache) view_cache_free(cv->cache);
140      cv->cache = NULL;
141 }
142 
calc_x(ChunkView * cv,off_t sample,off_t width)143 static gint calc_x(ChunkView *cv, off_t sample, off_t width)
144 {
145      Document *d = cv->doc;
146      gfloat f,g;
147      if (sample < d->viewstart)
148 	  return -1;
149      if (sample > d->viewend )
150 	  return width;
151      else {
152 	  f = sample - d->viewstart;
153 	  g = d->viewend - d->viewstart;
154 	  return width * (f/g);
155      }
156 }
157 
calc_x_noclamp(ChunkView * cv,off_t sample,off_t width)158 static gint calc_x_noclamp(ChunkView *cv, off_t sample, off_t width)
159 {
160      Document *d = cv->doc;
161      gfloat f,g;
162 
163      f = sample - d->viewstart;
164      g = d->viewend - d->viewstart;
165      return width * (f/g);
166 }
167 
168 
chunk_view_update_image_main(ChunkView * cv,GdkDrawable * image,guint xs,guint xe)169 static void chunk_view_update_image_main(ChunkView *cv, GdkDrawable *image,
170 					 guint xs, guint xe)
171 {
172      int i,j,y;
173      gint w,h;
174      GtkWidget *wid = GTK_WIDGET(cv);
175      Document *d = cv->doc;
176      w = wid->allocation.width;
177      h = wid->allocation.height;
178      if (cv->timescale) h-=font_height;
179      if (xe < xs) return;
180      /* Svart bakgrund */
181      gdk_draw_rectangle( image, get_gc(BACKGROUND,wid), TRUE, xs, 0,
182 			 1+xe-xs,h);
183      /* Om det inte finns n�gon sampling laddad, stanna h�r. */
184      if (d == NULL || d->chunk->length == 0) return;
185      /* Rita ut markeringen med speciell f�rg */
186      if (d->selstart != d->selend && d->selstart < d->viewend &&
187 	 d->selend > d->viewstart) {
188 	  if (d->selstart > d->viewstart)
189 	       i = calc_x( cv, d->selstart, w );
190 	  else
191 	       i = 0;
192 	  if (i < xs) i=xs;
193 	  if (i <= xe) {
194 	       if (d->selend < d->viewend)
195 		    j = calc_x( cv, d->selend, w );
196 	       else
197 		    j = xe;
198 	       if (j > xe) j = xe;
199 	       if (j >= xs) {
200 		    gdk_draw_rectangle( image, get_gc(SELECTION,wid),TRUE,
201 					i, 0, 1+j-i, h );
202 	       }
203 	  }
204 
205 	  // printf("i = %d, j = %d\n",i,j);
206      }
207      /* Rita gr�a streck */
208      j = h/d->chunk->format.channels;
209      for (i=0; i<d->chunk->format.channels; i++) {
210 	  y = j/2 + i*j;
211 	  gdk_draw_line(image,get_gc(BARS,wid),xs,y,xe,y);
212      }
213      /* Drawing one pixel to the left of xs is a workaround. Some part of the
214       * code seems to draw one pixel wrong to the left. */
215      view_cache_draw_part(cv->cache, image, (xs==0)?0:xs-1, xe, h,
216 			  GTK_WIDGET(cv),
217 			  cv->scale_factor);
218 }
219 
chunk_view_update_image(ChunkView * cv,guint xs,guint xe)220 static void chunk_view_update_image(ChunkView *cv, guint xs, guint xe)
221 {
222 #if GTK_MAJOR_VERSION < 2
223      GtkWidget *wid = GTK_WIDGET(cv);
224      gint w,h;
225      /* printf("chunk_view_update_image: %d->%d\n",xs,xe); */
226      /* gint cx,v1,v2,pv1,pv2; */
227 
228      /* Skapa pixmap */
229      w = wid->allocation.width;
230      h = wid->allocation.height;
231      if (cv->timescale) h-=font_height;
232      if (cv->image!=NULL && (w != cv->image_width || h != cv->image_height)) {
233 	  gdk_pixmap_unref(cv->image);
234 	  cv->image = NULL;
235      }
236      if (cv->image == NULL) {
237 	  cv->image = gdk_pixmap_new(wid->window, w, h, -1);
238 	  cv->image_width = w;
239 	  cv->image_height = h;
240 	  xs = 0;
241 	  xe = w-1;
242      }
243      chunk_view_update_image_main(cv,cv->image,xs,xe);
244 #endif
245 }
246 
247 struct draw_mark_data {
248      ChunkView *view;
249      GdkEventExpose *event;
250 };
251 
draw_mark(gchar * label,off_t position,gpointer user_data)252 static void draw_mark(gchar *label, off_t position, gpointer user_data)
253 {
254      struct draw_mark_data *dd = (struct draw_mark_data *)user_data;
255      ChunkView *cv = dd->view;
256      GdkEventExpose *event = dd->event;
257      GtkWidget *widget = GTK_WIDGET(cv);
258      Document *d = cv->doc;
259      guint i;
260 
261      if ( position >= d->viewstart && position <= d->viewend ) {
262 	  i = calc_x( cv, position, widget->allocation.width );
263 	  if (event->area.x <= i+10 && event->area.x+event->area.width > i) {
264 	       gdk_draw_line( widget->window, get_gc(MARK,widget), i,
265 			      event->area.y, i,
266 			      event->area.y+event->area.height-1);
267 	       /* Render the text */
268 	       gdk_gc_set_foreground(widget->style->fg_gc[0],get_color(MARK));
269 	       gtk_draw_string( widget->style, widget->window,
270 				GTK_STATE_NORMAL, i+2, font_height+2, label );
271 	       gdk_gc_set_foreground(widget->style->fg_gc[0],get_color(BLACK));
272 /*	       gdk_draw_text( widget->window, widget->style->font,
273 			      gc_mark(widget), i+2, 12, (gchar *)key,
274 			      strlen(key) ); */
275 	  }
276      }
277 }
278 
draw_time_bars(ChunkView * view,GdkEventExpose * event,off_t * points,int npoints,off_t * ignpoints,int nignpoints,gboolean small,gint text)279 static void draw_time_bars(ChunkView *view, GdkEventExpose *event,
280 			   off_t *points, int npoints,
281 			   off_t *ignpoints, int nignpoints,
282 			   gboolean small, gint text)
283 {
284      char buf[32];
285      gint c,i,j;
286 #if GTK_MAJOR_VERSION == 2
287      PangoLayout *pl;
288 #endif
289      GtkWidget *widget = GTK_WIDGET(view);
290      Document *d = view->doc;
291      char *s;
292      int ignctr = 0;
293 
294      for (c=0; c<npoints; c++) {
295 
296 	  while (ignctr < nignpoints && ignpoints[ignctr] < points[c])
297 	       ignctr++;
298 	  if (ignctr < nignpoints && ignpoints[ignctr] == points[c])
299 	       continue;
300 
301 	  j = calc_x_noclamp(view, points[c], widget->allocation.width);
302 
303 	  /* printf("draw_time_bars: f=%f, h=%f\n",f,h); */
304 	  gdk_draw_line( widget->window, widget->style->white_gc, j,
305 			 widget->allocation.height-font_height-(small?3:7),
306 			 j, widget->allocation.height-font_height);
307 
308 	  if (text >= 0) {
309 
310 	       if (text > 0)
311 		    s = get_time_tail(view->doc->chunk->format.samplerate,
312 				      points[c], d->chunk->length,
313 				      buf, default_timescale_mode);
314 	       else
315 		    s = get_time_head(d->chunk->format.samplerate,
316 				      points[c], d->chunk->length,
317 				      buf, default_timescale_mode);
318 
319 	       if (s == NULL) continue;
320 
321 
322 #if GTK_MAJOR_VERSION == 1
323 	       i = gdk_string_width( widget->style->font, buf ) / 2;
324 	       gtk_draw_string( widget->style, widget->window,
325 				GTK_STATE_NORMAL, j-i,
326 				widget->allocation.height-1, buf );
327 #else
328 	       /* puts(buf); */
329 	       pl = gtk_widget_create_pango_layout( widget, buf );
330 	       pango_layout_get_pixel_size(pl, &i, NULL);
331 	       gdk_draw_layout( widget->window, widget->style->fg_gc[GTK_STATE_NORMAL],
332 				j-i/2, widget->allocation.height-font_height,
333 				pl);
334 	       g_object_unref(G_OBJECT(pl));
335 #endif
336 	  }
337      }
338 }
339 
draw_timescale(ChunkView * view,GdkEventExpose * event,gboolean text)340 static void draw_timescale(ChunkView *view, GdkEventExpose *event, gboolean text)
341 {
342      GtkWidget *widget = GTK_WIDGET(view);
343      Document *d = view->doc;
344      guint i;
345 
346      off_t *points,*midpoints,*minorpoints;
347      int npoints,nmidpoints,nminorpoints;
348 
349      guint midtext,minortext;
350 
351      if (text) gdk_window_clear_area(widget->window, event->area.x,
352 				     view->image_height,event->area.width,
353 				     font_height);
354 
355 
356      /* Draw the horizontal line */
357      i = widget->allocation.height-font_height;
358      gdk_draw_line( widget->window, widget->style->white_gc, event->area.x,
359 		    i, event->area.x+event->area.width-1, i);
360      /* pixels/sec */
361 
362      /* We want at least font_width pixels/major point
363       * and 8 pixels/minor point*/
364      npoints = widget->allocation.width / font_width + 1;
365      if (npoints < 3) npoints = 3;
366      nmidpoints = npoints;
367      nminorpoints = widget->allocation.width / 8 + 1;
368      if (nminorpoints < npoints) nminorpoints = npoints;
369      points = g_malloc(npoints * sizeof(off_t));
370      midpoints = g_malloc(nmidpoints * sizeof(off_t));
371      minorpoints = g_malloc(nminorpoints * sizeof(off_t));
372 
373      minortext = find_timescale_points(d->chunk->format.samplerate,
374 				       d->viewstart, d->viewend,
375 				       points, &npoints,
376 				       midpoints, &nmidpoints,
377 				       minorpoints, &nminorpoints,
378 				       default_timescale_mode);
379 
380      /*
381      printf("npoints: %d, pixels/point: %d\n",npoints,
382 	    widget->allocation.width / npoints);
383      printf("nmidpoints: %d, pixels/midpoint: %d\n",nmidpoints,
384 	    (nmidpoints > 0) ? widget->allocation.width / nmidpoints : 0);
385      printf("nminorpoints: %d, pixels/minorpoint: %d\n",nminorpoints,
386 	    (nminorpoints > 0) ? widget->allocation.width / nminorpoints : 0);
387      printf("font_width: %d\n",font_width);
388      */
389 
390      midtext = minortext;
391 
392      if (nminorpoints > 0 &&
393 	 (widget->allocation.width / nminorpoints) < font_width)
394 	  minortext = -1;
395      else
396 	  midtext = -1;
397 
398      draw_time_bars(view,event,points,npoints,NULL,0,FALSE,text ? 0 : -1);
399 
400      if (midtext >= 0) {
401 	  draw_time_bars(view,event,midpoints,nmidpoints,points,npoints,FALSE,
402 			 text ? midtext : -1);
403      }
404 
405      draw_time_bars(view,event,minorpoints,nminorpoints,points,npoints,TRUE,
406 		    text ? minortext : -1);
407 
408      g_free(points);
409      g_free(minorpoints);
410 }
411 
chunk_view_expose(GtkWidget * widget,GdkEventExpose * event)412 static gint chunk_view_expose(GtkWidget *widget, GdkEventExpose *event)
413 {
414      ChunkView *cv = CHUNKVIEW(widget);
415      Document *d = cv->doc;
416      guint i;
417      struct draw_mark_data dd;
418      gboolean expose_timescale=FALSE, expose_text=FALSE;
419 
420      /* printf("Expose: (%d,%d)+(%d,%d)\n",event->area.x,event->area.y,
421 	event->area.width,event->area.height); */
422 #if GTK_MAJOR_VERSION < 2
423      /* This call creates the pixmap if it doesn't exist,
424       * otherwise does nothing (because xe<xs) */
425      chunk_view_update_image(cv,1,0);
426 
427      gdk_draw_pixmap ( widget->window, get_gc(BLACK,widget), cv->image,
428 		       event->area.x, event->area.y, event->area.x,
429 		       event->area.y, event->area.width,
430 		       event->area.height );
431 #else
432      chunk_view_update_image_main( cv, widget->window, event->area.x,
433 				   event->area.x + event->area.width - 1 );
434 #endif
435 
436      if (d == NULL) return FALSE;
437 
438      /* Determine if the time scale or text/bars at the bottom needs
439       * redrawing */
440      if (cv->timescale)
441 	  if (event->area.y+event->area.height > cv->image_height-7) {
442 	       expose_timescale=TRUE;
443 	       if (event->area.y+event->area.height > cv->image_height) {
444 		    expose_text = TRUE;
445 		    event->area.height = cv->image_height-event->area.y;
446 	       }
447 	  }
448 
449      /* Draw playback indicator */
450      if ( (cv->show_bufpos) &&
451 	  d->playbufpos >= d->viewstart && d->playbufpos <= d->viewend ) {
452 	  i = calc_x( cv, d->playbufpos, widget->allocation.width );
453 	  if (event->area.x <= i && event->area.x+event->area.width > i) {
454 	       gdk_draw_line( widget->window, get_gc(BUFPOS,widget), i,
455 			      event->area.y, i,
456 			      event->area.y+event->area.height-1 );
457 	  }
458      }
459      /* Draw the marks */
460      dd.view = cv;
461      dd.event = event;
462      document_foreach_mark( d, draw_mark, &dd );
463      /* Draw the cursor */
464      if ( d->cursorpos >= d->viewstart &&
465 	  d->cursorpos <= d->viewend ) {
466 	  i = calc_x( cv, d->cursorpos, widget->allocation.width );
467 	  if (event->area.x <= i && event->area.x+event->area.width > i) {
468 	       gdk_draw_line( widget->window, get_gc(CURSOR,widget), i,
469 			      event->area.y, i,
470 			      event->area.y+event->area.height-1 );
471 	  }
472      }
473      /* Draw the time scale */
474      if (d->chunk && cv->timescale && expose_timescale)
475 	  draw_timescale( cv, event, expose_text );
476      return FALSE;
477 }
478 
479 
chunk_view_size_request(GtkWidget * widget,GtkRequisition * req)480 static void chunk_view_size_request(GtkWidget *widget, GtkRequisition *req)
481 {
482      req->width = 300;
483      req->height = 50;
484 }
485 
chunk_view_size_allocate(GtkWidget * widget,GtkAllocation * all)486 static void chunk_view_size_allocate(GtkWidget *widget, GtkAllocation *all)
487 {
488      ChunkView *cv = CHUNKVIEW(widget);
489      Document *d = cv->doc;
490      GTK_WIDGET_CLASS(parent_class)->size_allocate(widget,all);
491      if (cv->image == NULL) { 	  /* Hack */
492 	  cv->image_width = all->width;
493 	  cv->image_height = all->height;
494 	  if (cv->timescale) cv->image_height -= font_height;
495      }
496      if (d == NULL) return;
497      /* Call view_cache_update to make sure the view cache knows the new
498       * size. */
499      view_cache_update(cv->cache,d->chunk,d->viewstart,d->viewend,
500 		       widget->allocation.width,NULL,NULL);
501 }
502 
calc_sample(ChunkView * cv,gfloat x,gfloat xmax)503 static off_t calc_sample(ChunkView *cv, gfloat x, gfloat xmax)
504 {
505      return cv->doc->viewstart + (x/xmax) *
506 	  (cv->doc->viewend-cv->doc->viewstart);
507 }
508 
509 static gboolean dragmode = FALSE;
510 static off_t dragstart;
511 static off_t dragend;
512 static gboolean autoscroll = FALSE;
513 static gpointer autoscroll_source = NULL;
514 static ChunkView *autoscroll_view;
515 static gfloat autoscroll_amount;
516 static GTimeVal autoscroll_start;
517 
scroll_wheel(GtkWidget * widget,gdouble mouse_x,int direction)518 static gint scroll_wheel(GtkWidget *widget, gdouble mouse_x, int direction)
519 {
520      ChunkView *cv = CHUNKVIEW(widget);
521      Document *d = cv->doc;
522      gfloat zf;
523      off_t ui1,ui2;
524      if (d == NULL) return FALSE;
525      dragstart = dragend =
526 	  calc_sample( cv, mouse_x, (gfloat)widget->allocation.width);
527 
528      if (direction == -1)	// Scroll wheel down
529      {
530        if (d->viewend - d->viewstart == widget->allocation.width)
531 	    return FALSE;
532        zf = 1.0/1.4;
533      }
534      else if (d->viewend - d->viewstart < 2)
535 	  zf = 4.0;             // Special case to avoid locking in maximum zoom
536      else zf = 1.4;		// Scroll wheel up
537      ui1 = dragstart-(off_t)((gfloat)(dragstart-d->viewstart))*zf;
538      ui2 = dragstart+(off_t)((gfloat)(d->viewend-dragstart))*zf;
539      if (ui1 < 0) ui1 = 0;
540      if (ui2 < dragstart || ui2 > d->chunk->length) ui2 = d->chunk->length;
541      if (ui2 == ui1) {
542 	  if (ui2 < d->chunk->length)
543 	       ui2++;
544 	  else if (ui1 > 0)
545 	       ui1--;
546      }
547      document_set_view(d,ui1,ui2);
548      return FALSE;
549 }
550 
551 #if GTK_MAJOR_VERSION == 2
chunk_view_scrollwheel(GtkWidget * widget,GdkEventScroll * event)552 static gint chunk_view_scrollwheel(GtkWidget *widget, GdkEventScroll *event)
553 {
554      if (event->direction == GDK_SCROLL_DOWN) return scroll_wheel(widget, event->x, -1);
555      else return scroll_wheel(widget, event->x, 1);
556 }
557 #endif
558 
chunk_view_button_press(GtkWidget * widget,GdkEventButton * event)559 static gint chunk_view_button_press(GtkWidget *widget, GdkEventButton *event)
560 {
561      ChunkView *cv = CHUNKVIEW(widget);
562      gdouble sp,ep;
563      off_t o;
564      Document *d = cv->doc;
565      if (d == NULL) return FALSE;
566      if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
567 	  o = calc_sample(cv,event->x,(gfloat)widget->allocation.width);
568 	  gtk_signal_emit(GTK_OBJECT(cv),
569 			  chunk_view_signals[DOUBLE_CLICK_SIGNAL],&o);
570 	  return FALSE;
571      }
572      dragstart = dragend =
573 	  calc_sample( cv, event->x, (gfloat)widget->allocation.width);
574 
575      /* Snap to sel start/end */
576      sp = (gdouble)calc_x(cv,d->selstart,widget->allocation.width);
577      ep = (gdouble)calc_x(cv,d->selend,widget->allocation.width);
578      if (fabs(event->x - sp) < 3.0)
579 	  dragstart = dragend = d->selstart;
580      else if (fabs(event->x - ep) < 3.0)
581 	  dragstart = dragend = d->selend;
582 
583      if ((event->state & GDK_SHIFT_MASK) != 0 && d->selend != d->selstart) {
584 
585 	  dragmode = TRUE;
586 
587 	  /* The right selection endpoint is nearest to dragstart
588 	   * <=> dragstart > (selstart+selend)/2
589 	   * <=> 2*dragstart > selstart+selend
590 	   * <=> dragstart+dragstart > selstart+selend
591 	   * <=> dragstart-selend > selstart-dragstart
592 	   * This should be overflow-safe for large values
593 	   */
594 	  if ((event->button == 1 &&
595 	       dragstart-d->selend > d->selstart-dragstart) ||
596 	      (event->button != 1 &&
597 	       dragstart-d->selend < d->selstart-dragstart) ) {
598 	       /* Drag right endpoint */
599 	       dragstart = d->selstart;
600 	  } else {
601 	       /* Drag left endpoint */
602 	       dragstart = d->selend;
603 	  }
604 	  document_set_selection( d, dragstart, dragend );
605 
606      } else if (event->button == 1 || event->button == 2) {
607 	  dragmode = TRUE;
608 	  if (d->selend != d->selstart) {
609 	       if (d->selstart >= d->viewstart &&
610 		   dragstart == d->selstart){
611 		    dragstart = d->selend;
612 		    dragend = d->selstart;
613 		    return FALSE;
614 	       } else if (d->selend <= d->viewend &&
615 			  dragstart == d->selend) {
616 		    dragstart = d->selstart;
617 		    dragend = d->selend;
618 		    return FALSE;
619 	       }
620 	  }
621 	  document_set_selection( d, dragstart, dragend );
622      } else if (event->button == 3) {
623 	  document_set_cursor(d,dragstart);
624      } else if (event->button == 4 || event->button == 5) {
625 	  if (event->button == 5) scroll_wheel(widget, event->x, -1);
626 	  else scroll_wheel(widget, event->x, 1);
627      }
628      return FALSE;
629 }
630 
chunk_view_motion_notify(GtkWidget * widget,GdkEventMotion * event)631 static gint chunk_view_motion_notify(GtkWidget *widget, GdkEventMotion *event)
632 {
633      gint x1,x2;
634      static GdkCursor *arrows=NULL;
635      ChunkView *cv = CHUNKVIEW(widget);
636      Document *d = cv->doc;
637      if (d == NULL) return FALSE;
638      if (dragmode) {
639 	  if (event->x < widget->allocation.width)
640 	       dragend = calc_sample ( cv, (event->x > 0.0) ? event->x : 0.0,
641 				       (gfloat)widget->allocation.width );
642 	  else
643 	       dragend = d->viewend;
644 	  document_set_selection ( d, dragstart, dragend );
645 	  if (event->x < 0.0 || event->x > widget->allocation.width) {
646 	       if (!autoscroll) {
647 		    autoscroll = TRUE;
648 		    autoscroll_view = cv;
649 		    g_get_current_time(&autoscroll_start);		    		       }
650 	       if (event->x < 0.0) autoscroll_amount = event->x;
651 	       else autoscroll_amount = event->x - widget->allocation.width;
652 	       chunk_view_autoscroll(NULL,&autoscroll_start,NULL);
653 	  } else {
654 	       autoscroll = FALSE;
655 	  }
656      } else if (d->selstart != d->selend) {
657 	  if (d->selstart >= d->viewstart)
658 	       x1 = calc_x(cv,d->selstart,widget->allocation.width);
659 	  else
660 	       x1 = -500;
661 	  if (d->selend <= d->viewend)
662 	       x2 = calc_x(cv,d->selend,widget->allocation.width);
663 	  else
664 	       x2 = -500;
665 	  if (fabs(event->x-(double)x1)<3.0 ||
666 	      fabs(event->x-(double)x2)<3.0) {
667 	       if (arrows == NULL)
668 		    arrows = gdk_cursor_new(GDK_SB_H_DOUBLE_ARROW);
669 	       gdk_window_set_cursor(widget->window,arrows);
670 	  } else {
671 	       gdk_window_set_cursor(widget->window,NULL);
672 	  }
673      }
674      return FALSE;
675 }
676 
677 #define FLOAT(x) ((gfloat)(x))
678 
chunk_view_autoscroll(gpointer timesource,GTimeVal * current_time,gpointer user_data)679 static int chunk_view_autoscroll(gpointer timesource, GTimeVal *current_time,
680 				 gpointer user_data)
681 {
682      GTimeVal diff,new_time;
683      gfloat smp;
684      off_t ismp;
685 
686      /* puts("chunk_view_autoscroll"); */
687 
688      g_assert(timesource == NULL || timesource == autoscroll_source);
689 
690      if (!autoscroll) return 0;
691      timeval_subtract(&diff,current_time,&autoscroll_start);
692 
693      /* printf("diff: %d,%d\n",diff.tv_sec,diff.tv_usec); */
694      if (diff.tv_sec > 0 || diff.tv_usec > 10000) {
695 
696 	  memcpy(&autoscroll_start,current_time,sizeof(autoscroll_start));
697 
698 	  /* Convert diff -> smp */
699 	  smp = 0.2 * FLOAT(diff.tv_usec/10000 + diff.tv_sec*100) *
700 	       autoscroll_amount *
701 	       FLOAT(autoscroll_view->doc->viewend-autoscroll_view->doc->viewstart)/
702 	       FLOAT(GTK_WIDGET(autoscroll_view)->allocation.width);
703 	  /* Convert smp->ismp, update view and selection */
704 	  if (smp < 0.0) {
705 	       ismp = (off_t)(-smp);
706 	       if (ismp > dragend) {
707 		    ismp = dragend;
708 		    autoscroll = FALSE;
709 	       }
710 	       dragend -= ismp;
711 	       document_set_selection(autoscroll_view->doc,dragend,dragstart);
712 	       document_set_view(autoscroll_view->doc, dragend,
713 				 autoscroll_view->doc->viewend-ismp);
714 	  } else {
715 	       ismp = (off_t)smp;
716 	       if (dragend+ismp > autoscroll_view->doc->chunk->length) {
717 		    ismp = autoscroll_view->doc->chunk->length - dragend;
718 		    autoscroll = FALSE;
719 	       }
720 	       dragend += ismp;
721 	       document_set_selection(autoscroll_view->doc,dragstart,dragend);
722 	       document_set_view(autoscroll_view->doc,
723 				 autoscroll_view->doc->viewstart+ismp,
724 				 dragend);
725 	  }
726      }
727 
728      if (autoscroll) {
729 	  memcpy(&new_time,current_time,sizeof(new_time));
730 	  new_time.tv_usec = ((new_time.tv_usec / 25000) + 1) * 25000;
731 	  if (new_time.tv_usec >= 1000000) {
732 	       new_time.tv_sec += 1;
733 	       new_time.tv_usec -= 1000000;
734 	  }
735 	  if (autoscroll_source == NULL)
736 	       autoscroll_source =
737 		    mainloop_time_source_add(&new_time,chunk_view_autoscroll,
738 					     NULL);
739 	  else
740 	       mainloop_time_source_restart(autoscroll_source,&new_time);
741      }
742 
743      return 0;
744 }
745 
chunk_view_button_release(GtkWidget * widget,GdkEventButton * event)746 static gint chunk_view_button_release(GtkWidget *widget, GdkEventButton *event)
747 {
748      ChunkView *cv = CHUNKVIEW(widget);
749      if (event->button == 2 && cv->doc != NULL) {
750 	  document_play_selection(cv->doc,FALSE,1.0);
751      }
752      autoscroll = FALSE;
753      dragmode = FALSE;
754      return FALSE;
755 }
756 
chunk_view_class_init(GtkObjectClass * klass)757 static void chunk_view_class_init(GtkObjectClass *klass)
758 {
759      GtkWidgetClass *wc = GTK_WIDGET_CLASS(klass);
760      ChunkViewClass *cvc = CHUNKVIEW_CLASS(klass);
761      parent_class = gtk_type_class( gtk_drawing_area_get_type() );
762 
763      klass->destroy = chunk_view_destroy;
764      wc->expose_event = chunk_view_expose;
765      wc->size_request = chunk_view_size_request;
766      wc->size_allocate = chunk_view_size_allocate;
767      wc->button_press_event = chunk_view_button_press;
768      wc->motion_notify_event = chunk_view_motion_notify;
769      wc->button_release_event = chunk_view_button_release;
770 #if GTK_MAJOR_VERSION == 2
771      wc->scroll_event = chunk_view_scrollwheel;
772 #endif
773      cvc->double_click = NULL;
774 
775      chunk_view_signals[DOUBLE_CLICK_SIGNAL] =
776          gtk_signal_new("double-click", GTK_RUN_FIRST, GTK_CLASS_TYPE(klass),
777                         GTK_SIGNAL_OFFSET(ChunkViewClass,double_click),
778                         gtk_marshal_NONE__POINTER, GTK_TYPE_NONE, 1,
779 			GTK_TYPE_POINTER );
780 
781      gtk_object_class_add_signals(klass,chunk_view_signals,LAST_SIGNAL);
782 }
783 
chunk_view_init(GtkObject * obj)784 static void chunk_view_init(GtkObject *obj)
785 {
786      ChunkView *cv;
787      cv = CHUNKVIEW(obj);
788      cv->doc = NULL;
789      cv->image = NULL;
790      cv->timescale = TRUE;
791      cv->scale_factor = 1.0;
792      cv->cache = view_cache_new();
793      gtk_widget_add_events( GTK_WIDGET(obj), GDK_BUTTON_MOTION_MASK |
794 			    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
795 			    GDK_POINTER_MOTION_MASK );
796      if (!font_height) {
797 #if GTK_MAJOR_VERSION == 1
798 	  font_height = gdk_string_height( GTK_WIDGET(obj)->style->font,
799 					   "0123456789")+3;
800 	  font_width = gdk_string_width( GTK_WIDGET(obj)->style->font,
801 					 "0123456789")+3;
802 #else
803 	  PangoLayout *pl;
804 	  pl = gtk_widget_create_pango_layout( GTK_WIDGET(obj), "0123456789" );
805 	  pango_layout_get_pixel_size(pl, (gint *)&font_width,
806 				      (gint *)&font_height);
807 	  g_object_unref(pl);
808 #endif
809      }
810 }
811 
chunk_view_get_type(void)812 GtkType chunk_view_get_type(void)
813 {
814      static GtkType id = 0;
815      if (!id) {
816 	  GtkTypeInfo info = {
817 	       "ChunkView",
818 	       sizeof(ChunkView),
819 	       sizeof(ChunkViewClass),
820 	       (GtkClassInitFunc) chunk_view_class_init,
821 	       (GtkObjectInitFunc) chunk_view_init
822 	  };
823 	  id = gtk_type_unique( gtk_drawing_area_get_type(), &info );
824      }
825      return id;
826 }
827 
chunk_view_new(void)828 GtkWidget *chunk_view_new(void)
829 {
830      return GTK_WIDGET(gtk_type_new(chunk_view_get_type()));
831 }
832 
chunk_view_set_document(ChunkView * cv,Document * doc)833 void chunk_view_set_document(ChunkView *cv, Document *doc)
834 {
835      if (cv->doc == doc) return;
836      if (cv->doc != NULL) {
837 	  gtk_signal_disconnect_by_data(GTK_OBJECT(cv->doc),cv);
838 	  gtk_object_unref(GTK_OBJECT(cv->doc));
839      }
840      cv->doc = doc;
841      if (doc != NULL) {
842 	  gtk_object_ref(GTK_OBJECT(doc));
843 	  gtk_object_sink(GTK_OBJECT(doc));
844 	  gtk_signal_connect(GTK_OBJECT(doc),"view_changed",
845 			     GTK_SIGNAL_FUNC(chunk_view_changed),cv);
846 	  gtk_signal_connect(GTK_OBJECT(doc),"state_changed",
847 			     GTK_SIGNAL_FUNC(chunk_view_changed),cv);
848 	  gtk_signal_connect(GTK_OBJECT(doc),"selection_changed",
849 			     GTK_SIGNAL_FUNC(chunk_view_selection_changed),cv);
850 	  gtk_signal_connect(GTK_OBJECT(doc),"cursor_changed",
851 			     GTK_SIGNAL_FUNC(chunk_view_cursor_changed),cv);
852      }
853      chunk_view_changed(cv->doc,cv);
854 }
855 
chunk_view_redraw_samples(ChunkView * cv,off_t start,off_t end)856 static void chunk_view_redraw_samples(ChunkView *cv, off_t start, off_t end)
857 {
858      guint startpix,endpix;
859 
860      startpix = calc_x(cv,start,GTK_WIDGET(cv)->allocation.width);
861      endpix = calc_x(cv,end,GTK_WIDGET(cv)->allocation.width);
862 
863      if (cv->image)
864 	  chunk_view_update_image(cv,startpix,endpix);
865 
866      /* printf("Calling queue_draw on: (%d,%d)+(%d,%d)\n",startpix,0,
867 	    1+endpix-startpix,GTK_WIDGET(cv)->allocation.height-
868 	    (cv->timescale?font_height:0)); */
869 
870      gtk_widget_queue_draw_area(GTK_WIDGET(cv),startpix,0,1+endpix-startpix,
871 				GTK_WIDGET(cv)->allocation.height-
872 				(cv->timescale?font_height:0));
873 }
874 
chunk_view_set_timescale(ChunkView * cv,gboolean scale_visible)875 void chunk_view_set_timescale(ChunkView *cv, gboolean scale_visible)
876 {
877      if (cv->timescale == scale_visible) return;
878      cv->timescale = scale_visible;
879      chunk_view_changed(cv->doc,cv);
880 }
881 
chunk_view_update_cache(ChunkView * view)882 gboolean chunk_view_update_cache(ChunkView *view)
883 {
884      gboolean b;
885      gint i,j;
886      b = view_cache_update(view->cache, view->doc->chunk, view->doc->viewstart,
887 			   view->doc->viewend, view->image_width, &i, &j);
888 
889      if (!b && view->need_redraw_left != -1) {
890 	  gtk_widget_queue_draw_area(GTK_WIDGET(view),
891 				     view->need_redraw_left,
892 				     0,view->need_redraw_right-
893 				     view->need_redraw_left,
894 				     view->image_height);
895 	  view->need_redraw_left = -1;
896      }
897 
898      if (b && i!=j) {
899 	  if (view->need_redraw_left == -1) {
900 	       view->need_redraw_left = i;
901 	       view->need_redraw_right = j;
902 	  } else {
903 	       if (i < view->need_redraw_left) view->need_redraw_left=i;
904 	       if (j > view->need_redraw_right) view->need_redraw_right=j;
905 	  }
906 	  chunk_view_update_image(view,i,j);
907 	  /* Repaint after 20 pixels have been updated or once per second
908 	   * or we're just finished. */
909 	  if (view->need_redraw_right-view->need_redraw_left > 20 ||
910 	      (view->last_redraw_time != time(0)) ||
911 	      view_cache_uptodate(view->cache)) {
912 	       gtk_widget_queue_draw_area(GTK_WIDGET(view),
913 					  view->need_redraw_left,
914 					  0,view->need_redraw_right-
915 					  view->need_redraw_left,
916 					  view->image_height);
917 	       view->last_redraw_time = time(0);
918 	       view->need_redraw_left = -1;
919 	  }
920      }
921      return b;
922 }
923 
chunk_view_force_repaint(ChunkView * cv)924 void chunk_view_force_repaint(ChunkView *cv)
925 {
926      chunk_view_changed(cv->doc,cv);
927 }
928 
chunk_view_set_scale(ChunkView * cv,gfloat scale)929 void chunk_view_set_scale(ChunkView *cv, gfloat scale)
930 {
931      cv->scale_factor = scale;
932      chunk_view_changed(cv->doc,cv);
933 }
934