Merge r14152 from theora-exp.
[xiph/unicode.git] / postfish / multibar.c
bloba304ebea30b74e2c0fd103545b8c6c175c2ca562
1 /*
3 * postfish
4 *
5 * Copyright (C) 2002-2005 Monty
7 * Postfish is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
12 * Postfish is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Postfish; see the file COPYING. If not, write to the
19 * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include <stdlib.h>
25 #include <math.h>
26 #include <string.h>
27 #include <gdk/gdkkeysyms.h>
28 #include "multibar.h"
30 static GdkBitmap *stipple=NULL;
31 static GdkBitmap *stippleB=NULL;
33 static float compute_dampening(float width, float target,float current,float delta,int zerodamp){
34 float raw_delta=target-current;
35 float testdelta=delta+(raw_delta*.2);
37 if(target<0 && !zerodamp){
38 if(current>0)
39 raw_delta=target-current;
40 }else if(current<0 && !zerodamp){
41 raw_delta=target-current;
42 }else if(raw_delta<0){
43 if(delta>0){
44 raw_delta=0.;
45 }else{
46 if(raw_delta<testdelta)raw_delta=testdelta;
48 }else{
49 if(delta<0){
50 raw_delta=0.;
51 }else{
52 if(raw_delta>testdelta)raw_delta=testdelta;
55 return raw_delta;
59 /* call me roughly 10-20fps */
60 static void compute(Multibar *m,float *lowvals, float *highvals, int n, int draw){
61 int i,j,xpad;
62 GtkWidget *widget=GTK_WIDGET(m);
63 float max=-400;
64 int height=widget->allocation.height;
65 int width=widget->allocation.width;
67 /* figure out the x padding */
68 if(m->thumbs<1){
69 xpad=1;
70 }else{
71 xpad=height;
74 m->xpad=xpad;
76 if(m->readout){
77 if(n>m->bars){
78 if(!m->bartrackers)
79 m->bartrackers=calloc(n,sizeof(*m->bartrackers));
80 else{
81 m->bartrackers=realloc(m->bartrackers,
82 n*sizeof(*m->bartrackers));
83 memset(m->bartrackers+m->bars,0,
84 sizeof(*m->bartrackers)*(n-m->bars));
87 for(i=m->bars;i<n;i++){
88 m->bartrackers[i].pixelposlo=-400;
89 m->bartrackers[i].pixelposhi=-400;
90 m->bartrackers[i].pixeldeltalo=0;
91 m->bartrackers[i].pixeldeltahi=0;
94 m->bars=n;
95 }else if(n<m->bars)
96 m->bars=n;
98 for(i=0;i<n;i++)
99 if(highvals[i]>max)max=highvals[i];
101 if(m->clipdelay>0)
102 m->clipdelay--;
103 else
104 m->clipdelay=0;
106 if(m->peakdelay>0)
107 m->peakdelay--;
108 else{
109 m->peakdelay=0;
110 m->peakdelta--;
111 m->peak+=m->peakdelta;
114 if(max>m->peak){
115 m->peak=max;
116 m->peakdelay=15*2; /* ~2 second hold */
117 m->peakdelta=0;
120 if(draw){
121 int *pixhi=alloca(n*sizeof(*pixhi));
122 int *pixlo=alloca(n*sizeof(*pixlo));
124 for(i=0;i<n;i++){
125 pixlo[i]=-1;
126 for(j=0;j<=m->labels;j++)
127 if(lowvals[i]>=m->levels[j]){
128 if(lowvals[i]<=m->levels[j+1]){
129 float del=(lowvals[i]-m->levels[j])/(m->levels[j+1]-m->levels[j]);
130 pixlo[i]=(j+del)/m->labels*(widget->allocation.width-xpad*2-1)+xpad;
131 break;
132 }else if(j==m->labels){
133 pixlo[i]=widget->allocation.width-xpad+1;
135 }else
136 break;
138 pixhi[i]=pixlo[i];
139 for(;j<=m->labels;j++)
140 if(highvals[i]>=m->levels[j]){
141 if(highvals[i]<=m->levels[j+1]){
142 float del=(highvals[i]-m->levels[j])/(m->levels[j+1]-m->levels[j]);
143 pixhi[i]=(j+del)/m->labels*(widget->allocation.width-xpad*2-1)+xpad;
144 break;
145 }else if(j==m->labels){
146 pixhi[i]=widget->allocation.width-xpad+1;
148 }else
149 break;
152 /* dampen movement according to setup */
154 for(i=0;i<n;i++){
155 float trackhi=m->bartrackers[i].pixelposhi;
156 float tracklo=m->bartrackers[i].pixelposlo;
157 float delhi=m->bartrackers[i].pixeldeltahi;
158 float dello=m->bartrackers[i].pixeldeltalo;
160 /* hi */
161 delhi = compute_dampening(width-xpad*2-1,pixhi[i],trackhi,delhi,m->dampen_flags & ZERO_DAMP);
163 if(pixhi[i]>trackhi){
164 if(m->dampen_flags & HI_ATTACK)pixhi[i]=trackhi+delhi;
165 }else{
166 if(m->dampen_flags & HI_DECAY)pixhi[i]=trackhi+delhi;
168 m->bartrackers[i].pixelposhi=pixhi[i];
169 m->bartrackers[i].pixeldeltahi=delhi;
171 /* lo */
172 dello = compute_dampening(width-xpad*2-1,pixlo[i],tracklo,dello,m->dampen_flags & ZERO_DAMP);
173 if(pixlo[i]>tracklo){
174 if(m->dampen_flags & LO_ATTACK)pixlo[i]=tracklo+dello;
175 }else{
176 if(m->dampen_flags & LO_DECAY)pixlo[i]=tracklo+dello;
178 m->bartrackers[i].pixelposlo=pixlo[i];
179 m->bartrackers[i].pixeldeltalo=dello;
186 static void draw(GtkWidget *widget,int n){
187 int i,j,x=-1;
188 Multibar *m=MULTIBAR(widget);
189 int xpad=m->xpad,upad=2,lpad=2;
190 int height=widget->allocation.height;
191 GtkWidget *parent=gtk_widget_get_parent(widget);
193 if(m->thumbs>0){
194 int leftover=height-widget->requisition.height+3;
195 if(leftover<height/4)
196 lpad+=leftover;
197 else
198 lpad+=height/4;
201 if(!m->boxcolor){
202 m->boxcolor=gdk_gc_new(m->backing);
203 gdk_gc_copy(m->boxcolor,widget->style->black_gc);
206 if(m->readout && widget->state!=GTK_STATE_INSENSITIVE){
207 /* draw the pixel positions */
208 while(x<widget->allocation.width){
209 int r=0xffff,g=0xffff,b=0xffff;
210 GdkColor rgb={0,0,0,0};
211 int next=widget->allocation.width;
212 for(i=0;i<n;i++){
213 if(m->bartrackers[i].pixelposlo>x && m->bartrackers[i].pixelposlo<next)
214 next=m->bartrackers[i].pixelposlo;
215 if(m->bartrackers[i].pixelposhi>x && m->bartrackers[i].pixelposhi<next)
216 next=m->bartrackers[i].pixelposhi;
219 for(i=0;i<n;i++){
220 if(m->bartrackers[i].pixelposlo<=x && m->bartrackers[i].pixelposhi>=next){
221 switch(i%8){
222 case 0:
223 r*=.65;
224 g*=.65;
225 b*=.65;
226 break;
227 case 1:
228 r*=1.;
229 g*=.5;
230 b*=.5;
231 break;
232 case 2:
233 r*=.6;
234 g*=.6;
235 b*=1.;
236 break;
237 case 3:
238 r*=.4;
239 g*=.9;
240 b*=.4;
241 break;
242 case 4:
243 r*=.7;
244 g*=.6;
245 b*=.3;
246 break;
247 case 5:
248 r*=.7;
249 g*=.4;
250 b*=.8;
251 break;
252 case 6:
253 r*=.3;
254 g*=.7;
255 b*=.7;
256 break;
257 case 7:
258 r*=.4;
259 g*=.4;
260 b*=.4;
261 break;
265 rgb.red=r;
266 rgb.green=g;
267 rgb.blue=b;
268 gdk_gc_set_rgb_fg_color(m->boxcolor,&rgb);
270 gdk_draw_rectangle(m->backing,m->boxcolor,1,x+1,upad+1,next-x,widget->allocation.height-lpad-upad-3);
272 x=next;
275 gdk_draw_line (m->backing,
276 widget->style->white_gc,
277 xpad, widget->allocation.height-lpad-1,
278 widget->allocation.width-1-xpad,
279 widget->allocation.height-lpad-1);
281 if(m->clipdelay){
282 gdk_draw_line (m->backing,
283 widget->style->fg_gc[1],
284 xpad, upad, widget->allocation.width-1-xpad, upad);
286 gdk_draw_line (m->backing,
287 widget->style->fg_gc[1],
288 xpad, widget->allocation.height-lpad-2,
289 widget->allocation.width-1-xpad,
290 widget->allocation.height-lpad-2);
291 }else{
292 gdk_draw_line (m->backing,
293 widget->style->white_gc,
294 xpad, upad, widget->allocation.width-1-xpad, upad);
296 gdk_draw_line (m->backing,
297 widget->style->white_gc,
298 xpad, widget->allocation.height-lpad-2,
299 widget->allocation.width-1-xpad,
300 widget->allocation.height-lpad-2);
303 /* peak follower */
304 if(m->dampen_flags & PEAK_FOLLOW){
305 int x=-10;
306 for(j=0;j<=m->labels+1;j++)
307 if(m->peak>=m->levels[j]){
308 if(m->peak<=m->levels[j+1]){
309 float del=(m->peak-m->levels[j])/(m->levels[j+1]-m->levels[j]);
310 x=(j+del)/m->labels*(widget->allocation.width-xpad*2-1)+xpad;
311 break;
312 }else if (j==m->labels){
313 x=widget->allocation.width-xpad+1;
315 }else
316 break;
318 for(j=0;j<n;j++)
319 if(x<m->bartrackers[j].pixelposhi)
320 x=m->bartrackers[j].pixelposhi;
323 int y=widget->allocation.height-lpad-1;
325 gdk_draw_line(m->backing,widget->style->fg_gc[0],
326 x-3,upad,x+3,upad);
327 gdk_draw_line(m->backing,widget->style->fg_gc[0],
328 x-2,upad+1,x+2,upad+1);
329 gdk_draw_line(m->backing,widget->style->fg_gc[0],
330 x-1,upad+2,x+1,upad+2);
331 gdk_draw_line(m->backing,widget->style->fg_gc[0],
332 x-3,y-1,x+3,y-1);
333 gdk_draw_line(m->backing,widget->style->fg_gc[0],
334 x-2,y-2,x+2,y-2);
335 gdk_draw_line(m->backing,widget->style->fg_gc[0],
336 x-1,y-3,x+1,y-3);
338 gdk_draw_line(m->backing,widget->style->fg_gc[1],
339 x,upad+1,x,y-2);
342 }else{
344 int width=widget->allocation.width-xpad;
345 int height=widget->allocation.height;
346 GdkGC *gc=parent->style->bg_gc[0];
348 /* blank scale to bg of parent */
349 gdk_draw_rectangle(m->backing,gc,1,xpad,0,width-xpad+1,height-lpad);
353 for(i=0;i<=m->labels;i++){
354 int x=rint(((float)i)/m->labels*(widget->allocation.width-xpad*2-1))+xpad;
355 int y=widget->allocation.height-lpad-upad;
356 int px,py;
357 int gc=0;
359 if(m->levels[i]>0.)gc=1;
361 if(m->readout)
362 gdk_draw_line (m->backing,
363 widget->style->text_gc[gc],
364 x, upad, x, y+upad);
365 else
366 gdk_draw_line (m->backing,
367 widget->style->text_gc[gc],
368 x, y/4+upad, x, y+upad);
370 pango_layout_get_pixel_size(m->layout[i],&px,&py);
372 if(i==0){
373 x+=2;
374 y-=py;
375 y/=2;
376 }else{
377 x-=px+2;
378 y-=py;
379 y/=2;
381 gdk_draw_layout (m->backing,
382 widget->style->text_gc[gc],
383 x, y+upad,
384 m->layout[i]);
388 /* draw frame */
390 int width=widget->allocation.width;
391 int height=widget->allocation.height;
392 GdkGC *gc=parent->style->bg_gc[0];
393 GdkGC *light_gc=parent->style->light_gc[0];
394 GdkGC *dark_gc=parent->style->dark_gc[0];
395 GdkGC *mid_gc=widget->style->bg_gc[GTK_STATE_ACTIVE];
397 /* blank side padding to bg of parent */
398 gdk_draw_rectangle(m->backing,gc,1,0,0,xpad,height);
399 gdk_draw_rectangle(m->backing,gc,1,width-xpad,0,xpad,height);
401 /* blank sides of trough */
402 gdk_draw_rectangle(m->backing,gc,1,
404 height-lpad,
405 m->thumblo_x,
406 lpad);
407 gdk_draw_rectangle(m->backing,gc,1,
408 m->thumbhi_x+xpad+xpad,
409 height-lpad,
410 width-xpad-xpad-m->thumbhi_x,
411 lpad);
413 /* frame */
414 if(m->readout){
415 gdk_draw_line(m->backing,dark_gc,0,0,width-2,0);
416 gdk_draw_line(m->backing,dark_gc,0,0,0,height-lpad);
417 gdk_draw_line(m->backing,dark_gc,1,height-lpad,width-2,height-lpad);
418 gdk_draw_line(m->backing,dark_gc,width-2,height-lpad,width-2,1);
420 gdk_draw_line(m->backing,light_gc,0,height-lpad+1,
421 width-1,height-lpad+1);
422 gdk_draw_line(m->backing,light_gc,width-1,0,width-1,height-lpad+1);
423 gdk_draw_line(m->backing,light_gc,1,1,width-3,1);
424 gdk_draw_line(m->backing,light_gc,1,1,1,height-lpad-1);
428 /* dark trough */
429 if(lpad>2 || m->readout==0){
430 if(lpad>2){
431 gdk_draw_rectangle(m->backing,mid_gc,1,
432 m->thumblo_x+1,height-lpad+1,
433 m->thumbhi_x-m->thumblo_x+xpad*2,lpad-1);
435 gdk_draw_line(m->backing,dark_gc,
436 m->thumblo_x,height-lpad,
437 m->thumblo_x,height-1);
441 gdk_draw_line(m->backing,light_gc,
442 m->thumblo_x,height-1,
443 m->thumbhi_x+xpad*2,height-1);
445 dark_gc=widget->style->dark_gc[GTK_STATE_ACTIVE];
447 gdk_draw_line(m->backing,dark_gc,
448 m->thumblo_x,height-lpad,
449 m->thumbhi_x+xpad*2-1,height-lpad);
451 if(lpad>2)
452 gdk_draw_line(m->backing,light_gc,
453 m->thumbhi_x+xpad*2,height-1,
454 m->thumbhi_x+xpad*2,height-lpad+1);
457 if(m->readout==0)
458 gdk_draw_point(m->backing,light_gc,width-1,height-lpad);
464 /* draw slider thumbs */
465 if(m->thumbs){
466 int height=widget->allocation.height,i,j;
467 GdkGC *black_gc=widget->style->black_gc;
468 int y0=height/3-1;
469 int y1=height-3;
471 int outer=height-1;
472 int inner=(y1+y0)/2-y0;
474 int A[3]={outer,outer,outer};
475 int B[3]={inner,inner,inner};
476 int C[3]={inner,inner,inner};
477 int D[3]={outer,outer,outer};
479 GdkColor yellow={0,0xff00,0xd000,0};
481 if(m->thumbs==2){
482 /* adjustment required for overlapping thumbs? */
483 int mid=m->thumbpixel[1]-m->thumbpixel[0]-1;
484 int midA=(mid<0?0:mid)/2;
485 int midB=(mid<0?0:mid)-midA;
487 if(midA<D[0])D[0]=midA;
488 if(midA<C[0])C[0]=midA;
489 if(midB<A[1])A[1]=midB;
490 if(midB<B[1])B[1]=midB;
493 if(m->thumbs==3){
494 /* adjust for 0,1 overlap if any; different from the 2 case */
495 int mid=m->thumbpixel[1]-m->thumbpixel[0]-1;
496 int midA=(mid<0?0:mid)/2;
497 int midB=(mid<0?0:mid)-midA;
499 if(midA<D[0])D[0]=midA;
500 if(D[0]<C[0]+2)D[0]=C[0]+2;
501 if(midB<A[1])A[1]=midB;
502 if(A[1]<B[1]+2)A[1]=B[1]+2;
504 /* adjust for 1,2 overlap if any */
506 mid=m->thumbpixel[2]-m->thumbpixel[1]-1;
507 midA=(mid<0?0:mid)/2;
508 midB=(mid<0?0:mid)-midA;
510 if(midA<D[1])D[1]=midA;
511 if(D[1]<C[1]+2)D[1]=C[1]+2;
512 if(midB<A[2])A[2]=midB;
513 if(A[2]<B[2]+2)A[2]=B[2]+2;
516 for(i=0;i<m->thumbs;i++){
517 if(m->thumbs==3){
518 /* in the three-thumb case, the middle thumb is drawn last */
519 switch(i){
520 case 0:
521 j=0;
522 break;
523 case 1:
524 j=2;
525 break;
526 case 2:
527 j=1;
528 break;
530 }else
531 j=i;
534 int x=m->thumbpixel[j]+xpad;
535 GdkPoint p[8]={ {x+D[j],y0+C[j]},
536 {x+C[j],y0+C[j]},
537 {x ,y0 },
538 {x-B[j],y0+B[j]},
539 {x-A[j],y0+B[j]},
540 {x-A[j],y1+1 },
541 {x+D[j],y1+1 },
542 {x+D[j],y0+C[j]}};
544 GdkPoint d[3]={ {x-A[j]+1,y1},
545 {x+D[j]-1,y1},
546 {x+D[j]-1,y0+C[j]+1}};
548 GdkGC *gc=widget->style->bg_gc[m->thumbstate[j]];
549 GdkGC *light_gc=widget->style->light_gc[m->thumbstate[j]];
550 GdkGC *dark_gc=widget->style->dark_gc[m->thumbstate[j]];
552 gdk_draw_polygon(m->backing,gc,TRUE,p,7);
553 gdk_draw_lines(m->backing,dark_gc,d,3);
555 if(m->thumbfocus==j && m->widgetfocus){
556 if(x&1)
557 gdk_gc_set_stipple(black_gc,stipple);
558 else
559 gdk_gc_set_stipple(black_gc,stippleB);
561 gdk_gc_set_fill(black_gc,GDK_STIPPLED);
562 gdk_draw_polygon(m->backing,black_gc,TRUE,p,7);
563 gdk_gc_set_fill(black_gc,GDK_SOLID);
566 gdk_draw_lines(m->backing,light_gc,p,6);
567 gdk_draw_lines(m->backing,black_gc,p+5,3);
569 gdk_gc_set_rgb_fg_color(m->boxcolor,&yellow);
570 gdk_draw_line(m->backing,m->boxcolor,x,y1-1,x,0);
576 static void draw_and_expose(GtkWidget *widget){
577 Multibar *m=MULTIBAR(widget);
578 if(!GDK_IS_DRAWABLE(m->backing))return;
579 draw(widget,m->bars);
580 if(!GTK_WIDGET_DRAWABLE(widget))return;
581 if(!GDK_IS_DRAWABLE(widget->window))return;
582 gdk_draw_drawable(widget->window,
583 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
584 m->backing,
585 0, 0,
586 0, 0,
587 widget->allocation.width,
588 widget->allocation.height);
591 static gboolean expose( GtkWidget *widget, GdkEventExpose *event ){
592 Multibar *m=MULTIBAR(widget);
593 gdk_draw_drawable(widget->window,
594 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
595 m->backing,
596 event->area.x, event->area.y,
597 event->area.x, event->area.y,
598 event->area.width, event->area.height);
600 return FALSE;
603 static void size_request (GtkWidget *widget,GtkRequisition *requisition){
604 int i,maxx=0,maxy=0,x,y,xpad;
605 Multibar *m=MULTIBAR(widget);
607 for(i=0;i<=m->labels;i++){
608 pango_layout_get_pixel_size(m->layout[i],&x,&y);
610 if(x>maxx)maxx=x;
611 if(y>maxy)maxy=y;
614 maxy+=4;
616 if(m->thumbs==0){
617 xpad=1;
618 }else{
619 maxy+=3;
620 xpad=maxy;
623 requisition->width = (maxx*1.5+2)*m->labels+xpad*2;
624 requisition->height = maxy;
628 static int transition_thumbfocus=0;
629 static gboolean multibar_focus (GtkWidget *widget,
630 GtkDirectionType direction){
631 Multibar *m=MULTIBAR(widget);
632 int ret=TRUE;
634 if(m->thumbs==0)return FALSE;
635 if(!m->widgetfocus)m->thumbfocus=-1;
637 switch(direction){
638 case GTK_DIR_TAB_FORWARD:
639 case GTK_DIR_RIGHT:
640 if(m->thumbfocus+1>=m->thumbs){
641 m->thumbfocus=-1;
642 ret=FALSE;
643 }else
644 m->thumbfocus++;
645 break;
647 case GTK_DIR_TAB_BACKWARD:
648 case GTK_DIR_LEFT:
649 if(m->thumbfocus==-1)
650 m->thumbfocus=m->thumbs-1;
651 else{
652 if(m->thumbfocus-1<0){
653 m->thumbfocus=-1;
654 ret=FALSE;
655 }else
656 m->thumbfocus--;
658 break;
660 case GTK_DIR_UP:
661 case GTK_DIR_DOWN:
662 if(m->thumbfocus==-1){
663 if(transition_thumbfocus>=0 && transition_thumbfocus<m->thumbs)
664 m->thumbfocus=transition_thumbfocus;
665 else
666 m->thumbfocus=0;
667 ret=TRUE;
668 }else{
669 transition_thumbfocus=m->thumbfocus;
670 m->thumbfocus=-1;
671 ret=FALSE;
673 break;
675 default:
676 m->thumbfocus=-1;
677 ret=FALSE;
680 if(ret==TRUE) gtk_widget_grab_focus(widget);
681 draw_and_expose(widget);
683 return ret;
686 static gint determine_thumb(Multibar *m,int ix, int iy){
687 GtkWidget *widget=GTK_WIDGET(m);
688 int max=widget->allocation.height;
689 float distances[3]={-1,-1,-1};
690 int thumb=-1;
692 /* center thumb */
693 if(m->thumbs==1 || m->thumbs>2){
694 int num=(m->thumbs==1?0:1);
695 int x= m->thumbpixel[num]-ix;
696 distances[num]=fabs(x);
699 /* left thumb */
700 if(m->thumbs>1){
701 int x= m->thumbpixel[0]-ix;
702 distances[0]=fabs(x-.1);
705 /* right thumb */
706 if(m->thumbs>1){
707 int num=(m->thumbs==2?1:2);
708 int x= m->thumbpixel[num]-ix;
709 distances[num]=fabs(x+.1);
712 if(m->thumbs && distances[0]<max)thumb=0;
713 if(m->thumbs>1 && distances[1]<max)
714 if(thumb == -1 || distances[1]<distances[0])thumb=1;
715 if(m->thumbs>2 && distances[2]<max)
716 if(thumb == -1 || (distances[2]<distances[0] &&
717 distances[2]<distances[1]))thumb=2;
718 if(m->thumbs>2 && distances[1]<max/2)thumb=1;
720 return thumb;
723 static int pixel_bound(Multibar *m,int x){
724 GtkWidget *w=GTK_WIDGET(m);
725 if(x<0)return 0;
726 if(x>w->allocation.width-m->xpad*2-1)
727 return w->allocation.width-m->xpad*2-1;
728 return x;
731 static float pixel_to_val(Multibar *m,int x){
732 GtkWidget *w=GTK_WIDGET(m);
733 int j;
735 for(j=0;j<=m->labels;j++){
736 int pixlo=rint((float)j/m->labels*(w->allocation.width-m->xpad*2-1));
737 int pixhi=rint((float)(j+1)/m->labels*(w->allocation.width-m->xpad*2-1));
739 if(x>=pixlo && x<=pixhi){
740 if(pixlo==pixhi)return m->levels[j];
741 float del=(float)(x-pixlo)/(pixhi-pixlo);
742 return (1.-del)*m->levels[j] + del*m->levels[j+1];
745 return 0.;
748 static int val_to_pixel(Multibar *m,float v){
749 GtkWidget *w=GTK_WIDGET(m);
750 int j,ret=0;
752 if(v<m->levels[0]){
753 ret=0;
754 }else if(v>m->levels[m->labels]){
755 ret=w->allocation.width-m->xpad*2-1;
756 }else{
757 for(j=0;j<=m->labels;j++){
758 if(v>=m->levels[j] && v<=m->levels[j+1]){
759 float del=(v-m->levels[j])/(m->levels[j+1]-m->levels[j]);
760 int pixlo=rint((float)j/m->labels*(w->allocation.width-m->xpad*2-1));
761 int pixhi=rint((float)(j+1)/m->labels*(w->allocation.width-m->xpad*2-1));
762 ret=rint(pixlo*(1.-del)+pixhi*del);
763 break;
768 ret=pixel_bound(m,ret);
769 return ret;
772 static gboolean configure(GtkWidget *widget, GdkEventConfigure *event){
773 Multibar *m=MULTIBAR(widget);
774 int i;
776 if (m->backing)
777 g_object_unref(m->backing);
779 m->backing = gdk_pixmap_new(widget->window,
780 widget->allocation.width,
781 widget->allocation.height,
782 -1);
783 gdk_draw_rectangle(m->backing,widget->style->white_gc,1,0,0,widget->allocation.width,
784 widget->allocation.height);
786 compute(m,0,0,0,1);
787 for(i=0;i<m->thumbs;i++)
788 m->thumbpixel[i]=val_to_pixel(m,m->thumbval[i]);
789 m->thumblo_x=val_to_pixel(m,m->thumblo);
790 m->thumbhi_x=val_to_pixel(m,m->thumbhi);
792 draw_and_expose(widget);
794 return TRUE;
797 static void partial_vals_bound(Multibar *m){
798 int i;
800 if(m->thumbsmall>0 && m->thumblarge>0)
801 for(i=0;i<m->thumbs;i++)
802 m->thumbval[i]=rint(m->thumbval[i]/m->thumbsmall)*m->thumbsmall;
804 for(i=0;i<m->thumbs;i++){
805 if(m->thumbval[i]<m->thumblo)m->thumbval[i]=m->thumblo;
806 if(m->thumbval[i]>m->thumbhi)m->thumbval[i]=m->thumbhi;
807 m->thumbpixel[i]=val_to_pixel(m,m->thumbval[i]);
811 static void vals_bound(Multibar *m){
812 partial_vals_bound(m);
814 if(m->thumbfocus>=0){
815 float v=m->thumbval[m->thumbfocus];
816 int x=m->thumbpixel[m->thumbfocus];
818 if(m->thumbfocus==2){
819 if(m->thumbpixel[1]>x){
820 m->thumbpixel[1]=x;
821 m->thumbval[1]=v;
823 if(m->thumbpixel[0]>x){
824 m->thumbpixel[0]=x;
825 m->thumbval[0]=v;
829 if(m->thumbfocus==1){
830 if(m->thumbpixel[2]<x){
831 m->thumbpixel[2]=x;
832 m->thumbval[2]=v;
834 if(m->thumbpixel[0]>x){
835 m->thumbpixel[0]=x;
836 m->thumbval[0]=v;
840 if(m->thumbfocus==0){
841 if(m->thumbpixel[2]<x){
842 m->thumbpixel[2]=x;
843 m->thumbval[2]=v;
845 if(m->thumbpixel[1]<x){
846 m->thumbpixel[1]=x;
847 m->thumbval[1]=v;
853 static gint lightme(GtkWidget *w,gint x,gint y){
854 Multibar *m=MULTIBAR(w);
855 int thumb=determine_thumb(m,x,y);
856 GtkStateType thumbstate[3];
857 thumbstate[0]=GTK_STATE_NORMAL;
858 thumbstate[1]=GTK_STATE_NORMAL;
859 thumbstate[2]=GTK_STATE_NORMAL;
860 if(thumb>=0)thumbstate[thumb]=GTK_STATE_PRELIGHT;
862 if(thumbstate[0]!=m->thumbstate[0] ||
863 thumbstate[1]!=m->thumbstate[1] ||
864 thumbstate[2]!=m->thumbstate[2]){
865 m->thumbstate[0]=thumbstate[0];
866 m->thumbstate[1]=thumbstate[1];
867 m->thumbstate[2]=thumbstate[2];
869 draw_and_expose(w);
871 return TRUE;
874 static gint multibar_motion(GtkWidget *w,
875 GdkEventMotion *event){
876 Multibar *m=MULTIBAR(w);
878 /* is a thumb already grabbed? */
879 if(m->thumbgrab>=0){
881 int x=event->x+m->thumbx;
882 float v;
884 x=pixel_bound(m,x);
885 m->thumbval[m->thumbgrab]=pixel_to_val(m,x);
886 vals_bound(m);
887 v=m->thumbval[m->thumbgrab];
888 x=m->thumbpixel[m->thumbgrab]=val_to_pixel(m,v);
890 if(m->callback)m->callback(GTK_WIDGET(m),m->callbackp);
891 draw_and_expose(w);
893 }else{
894 /* nothing grabbed right now; determine if we're in a a thumb's area */
895 lightme(w,event->x-m->xpad,event->y);
898 return TRUE;
901 static gint multibar_enter(GtkWidget *w,
902 GdkEventCrossing *event){
903 Multibar *m=MULTIBAR(w);
904 lightme(w,event->x-m->xpad,event->y);
905 return TRUE;
908 static gint multibar_leave(GtkWidget *widget,
909 GdkEventCrossing *event){
910 Multibar *m=MULTIBAR(widget);
912 if(m->thumbgrab<0){
913 if(m->thumbstate[0] ||
914 m->thumbstate[1] ||
915 m->thumbstate[2]){
916 m->thumbstate[0]=0;
917 m->thumbstate[1]=0;
918 m->thumbstate[2]=0;
920 draw_and_expose(widget);
923 return TRUE;
926 static gboolean button_press (GtkWidget *widget,
927 GdkEventButton *event){
928 Multibar *m=MULTIBAR(widget);
929 int thumb=determine_thumb(m,event->x-m->xpad,event->y);
931 if(thumb==0){
932 gtk_widget_grab_focus(widget);
933 m->thumbgrab=m->thumbfocus=0;
934 m->thumbx=m->thumbpixel[0]-event->x;
935 }else if(thumb==1){
936 gtk_widget_grab_focus(widget);
937 m->thumbgrab=m->thumbfocus=1;
938 m->thumbx=m->thumbpixel[1]-event->x;
939 }else if(thumb==2){
940 gtk_widget_grab_focus(widget);
941 m->thumbgrab=m->thumbfocus=2;
942 m->thumbx=m->thumbpixel[2]-event->x;
944 draw_and_expose(widget);
945 return TRUE;
948 static gboolean button_release (GtkWidget *widget,
949 GdkEventButton *event){
950 Multibar *m=MULTIBAR(widget);
951 m->thumbgrab=-1;
952 draw_and_expose(widget);
953 return TRUE;
956 static gboolean unfocus(GtkWidget *widget,
957 GdkEventFocus *event){
958 Multibar *m=MULTIBAR(widget);
959 m->widgetfocus=0;
960 draw_and_expose(widget);
961 return TRUE;
964 static gboolean refocus(GtkWidget *widget,
965 GdkEventFocus *event){
966 Multibar *m=MULTIBAR(widget);
967 m->widgetfocus=1;
968 draw_and_expose(widget);
969 return TRUE;
972 gboolean key_press(GtkWidget *w,GdkEventKey *event){
973 Multibar *m=MULTIBAR(w);
974 int x;
975 if(event->state&GDK_MOD1_MASK) return FALSE;
976 if(event->state&GDK_CONTROL_MASK) return FALSE;
978 if(m->thumbfocus>=0){
979 if(m->thumbsmall>0 && m->thumblarge>0){
981 switch(event->keyval){
982 case GDK_minus:
983 m->thumbval[m->thumbfocus]-=m->thumbsmall;
984 break;
985 case GDK_underscore:
986 m->thumbval[m->thumbfocus]-=m->thumblarge;
987 break;
988 case GDK_equal:
989 m->thumbval[m->thumbfocus]+=m->thumbsmall;
990 break;
991 case GDK_plus:
992 m->thumbval[m->thumbfocus]+=m->thumblarge;
993 break;
994 default:
995 return FALSE;
998 vals_bound(m);
999 x=val_to_pixel(m,m->thumbval[m->thumbfocus]);
1000 x=pixel_bound(m,x);
1001 m->thumbpixel[m->thumbfocus]=x;
1003 }else{
1005 switch(event->keyval){
1006 case GDK_minus:
1007 x=m->thumbpixel[m->thumbfocus]-1;
1008 break;
1009 case GDK_underscore:
1010 x=m->thumbpixel[m->thumbfocus]-10;
1011 break;
1012 case GDK_equal:
1013 x=m->thumbpixel[m->thumbfocus]+1;
1014 break;
1015 case GDK_plus:
1016 x=m->thumbpixel[m->thumbfocus]+10;
1017 break;
1018 default:
1019 return FALSE;
1022 x=pixel_bound(m,x);
1023 m->thumbpixel[m->thumbfocus]=x;
1024 m->thumbval[m->thumbfocus]=pixel_to_val(m,x);
1025 vals_bound(m);
1029 if(m->callback)m->callback(GTK_WIDGET(m),m->callbackp);
1031 draw_and_expose(w);
1032 return TRUE;
1034 return FALSE;
1038 static GtkDrawingAreaClass *parent_class = NULL;
1041 static void state_changed(GtkWidget *w,GtkStateType ps){
1042 draw_and_expose(w);
1045 static void multibar_class_init (MultibarClass *class){
1046 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
1047 parent_class = g_type_class_peek_parent (class);
1049 widget_class->expose_event = expose;
1050 widget_class->configure_event = configure;
1051 widget_class->size_request = size_request;
1052 widget_class->focus=multibar_focus;
1053 widget_class->key_press_event = key_press;
1054 widget_class->button_press_event = button_press;
1055 widget_class->button_release_event = button_release;
1056 widget_class->enter_notify_event = multibar_enter;
1057 widget_class->leave_notify_event = multibar_leave;
1058 widget_class->motion_notify_event = multibar_motion;
1059 widget_class->focus_out_event = unfocus;
1060 widget_class->focus_in_event = refocus;
1061 widget_class->state_changed = state_changed;
1063 stipple=gdk_bitmap_create_from_data(NULL,"\125\352",2,2);
1064 stippleB=gdk_bitmap_create_from_data(NULL,"\352\125",2,2);
1068 static void multibar_init (Multibar *m){
1069 m->layout=0;
1070 m->peakdelay=0;
1071 m->clipdelay=0;
1072 m->peak=-400;
1075 GType multibar_get_type (void){
1076 static GType m_type = 0;
1077 if (!m_type){
1078 static const GTypeInfo m_info={
1079 sizeof (MultibarClass),
1080 NULL, /* base_init */
1081 NULL, /* base_finalize */
1082 (GClassInitFunc) multibar_class_init,
1083 NULL, /* class_finalize */
1084 NULL, /* class_data */
1085 sizeof (Multibar),
1087 (GInstanceInitFunc) multibar_init,
1091 m_type = g_type_register_static (GTK_TYPE_DRAWING_AREA, "Multibar", &m_info, 0);
1094 return m_type;
1097 GtkWidget* multibar_new (int n, char **labels, float *levels, int thumbs,
1098 int flags){
1099 int i;
1100 GtkWidget *ret= GTK_WIDGET (g_object_new (multibar_get_type (), NULL));
1101 Multibar *m=MULTIBAR(ret);
1103 /* not the *proper* way to do it, but this is a one-shot */
1104 m->levels=calloc((n+1),sizeof(*m->levels));
1105 m->labels=n-1;
1106 memcpy(m->levels,levels,n*sizeof(*levels));
1108 m->layout=calloc(n,sizeof(*m->layout));
1109 for(i=0;i<n;i++)
1110 m->layout[i]=gtk_widget_create_pango_layout(ret,labels[i]);
1112 m->dampen_flags=flags;
1113 m->thumbfocus=-1;
1114 m->thumbgrab=-1;
1115 m->thumblo=levels[0];
1116 m->thumbhi=levels[n-1];
1117 m->thumblo_x=val_to_pixel(m,m->thumblo);
1118 m->thumbhi_x=val_to_pixel(m,m->thumbhi);
1120 if(thumbs<0)thumbs=0;
1121 if(thumbs>3)thumbs=3;
1122 m->thumbs=thumbs;
1123 if(m->thumbs!=0) GTK_WIDGET_SET_FLAGS (m, GTK_CAN_FOCUS);
1126 int events=gtk_widget_get_events(ret);
1127 gtk_widget_set_events(ret, events|
1128 GDK_POINTER_MOTION_MASK|
1129 GDK_BUTTON_PRESS_MASK |
1130 GDK_BUTTON_RELEASE_MASK|
1131 GDK_KEY_PRESS_MASK |
1132 GDK_KEY_RELEASE_MASK |
1133 GDK_ENTER_NOTIFY_MASK |
1134 GDK_LEAVE_NOTIFY_MASK |
1135 GDK_FOCUS_CHANGE_MASK );
1138 m->readout=1;
1139 return ret;
1142 GtkWidget* multibar_slider_new (int n, char **labels, float *levels,
1143 int thumbs){
1144 GtkWidget *ret= multibar_new(n,labels,levels,thumbs,0);
1145 Multibar *m=MULTIBAR(ret);
1146 m->readout=0;
1147 gtk_widget_set_name(ret,"Multislide");
1149 return ret;
1152 void multibar_set(Multibar *m,float *lo, float *hi, int n, int draw){
1153 GtkWidget *widget=GTK_WIDGET(m);
1154 compute(m,lo,hi,n,draw);
1155 if(draw)draw_and_expose(widget);
1158 void multibar_thumb_set(Multibar *m,float v, int n){
1159 GtkWidget *w=GTK_WIDGET(m);
1160 int x;
1162 if(n<0)return;
1163 if(n>=m->thumbs)return;
1166 m->thumbval[n]=v;
1167 partial_vals_bound(m);
1168 v=m->thumbval[n];
1169 x=m->thumbpixel[n]=val_to_pixel(m,v);
1170 m->thumbval[n]=v;
1172 if(n==0){
1173 if(m->thumbpixel[1]<x){
1174 m->thumbval[1]=v;
1175 m->thumbpixel[1]=x;
1177 if(m->thumbpixel[2]<x){
1178 m->thumbval[2]=v;
1179 m->thumbpixel[2]=x;
1183 if(n==1){
1184 if(m->thumbpixel[0]>x){
1185 m->thumbval[0]=v;
1186 m->thumbpixel[0]=x;
1188 if(m->thumbpixel[2]<x){
1189 m->thumbval[2]=v;
1190 m->thumbpixel[2]=x;
1194 if(n==2){
1195 if(m->thumbpixel[0]>x){
1196 m->thumbval[0]=v;
1197 m->thumbpixel[0]=x;
1199 if(m->thumbpixel[1]>x){
1200 m->thumbval[1]=v;
1201 m->thumbpixel[1]=x;
1205 if(m->callback)m->callback(GTK_WIDGET(m),m->callbackp);
1206 draw_and_expose(w);
1209 void multibar_reset(Multibar *m){
1210 m->peak=-400;
1211 m->peakdelta=0;
1212 m->peakdelay=0;
1213 m->clipdelay=0;
1214 multibar_set(m,NULL,NULL,0,1);
1217 void multibar_setwarn(Multibar *m,int draw){
1218 GtkWidget *widget=GTK_WIDGET(m);
1219 if(!m->clipdelay){
1220 m->clipdelay=15*10;
1222 if(draw)draw_and_expose(widget);
1223 }else
1224 m->clipdelay=15*10; /* ~ ten second hold */
1227 /* because closures are ludicrously complex for doing something this simple */
1228 void multibar_callback(Multibar *m,void (*callback)(GtkWidget *,gpointer),
1229 gpointer p){
1230 m->callback=callback;
1231 m->callbackp=p;
1234 float multibar_get_value(Multibar *m,int n){
1235 if(n<0)return 0.;
1236 if(n>m->thumbs)return 0.;
1237 return m->thumbval[n];
1240 void multibar_thumb_bounds(Multibar *m,float lo, float hi){
1241 GtkWidget *w=GTK_WIDGET(m);
1242 if(lo>hi)return;
1244 if(lo<m->levels[0])lo=m->levels[0];
1245 if(hi<m->levels[0])hi=m->levels[0];
1246 if(lo>m->levels[m->labels])lo=m->levels[m->labels];
1247 if(hi>m->levels[m->labels])hi=m->levels[m->labels];
1249 m->thumblo=lo;
1250 m->thumbhi=hi;
1252 m->thumblo_x=val_to_pixel(m,lo);
1253 m->thumbhi_x=val_to_pixel(m,hi);
1255 vals_bound(m);
1256 if(m->callback)m->callback(GTK_WIDGET(m),m->callbackp);
1257 draw_and_expose(w);
1260 void multibar_thumb_increment(Multibar *m,float small, float large){
1261 GtkWidget *w=GTK_WIDGET(m);
1262 if(small>large)return;
1264 m->thumbsmall=small;
1265 m->thumblarge=large;
1267 vals_bound(m);
1268 if(m->callback)m->callback(GTK_WIDGET(m),m->callbackp);
1269 draw_and_expose(w);
1272 int multibar_thumb_grab_p(Multibar *m){
1273 return m->widgetfocus;
1276 int multibar_thumb_focus(Multibar *m){
1277 return m->thumbfocus;