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