Make mv's use signed chars explicitly.
[xiph/unicode.git] / sushivision / slider.c
blob0a42f42afda68521af0105e121922d44d717d845
1 /*
3 * sushivision copyright (C) 2006-2007 Monty <monty@xiph.org>
5 * sushivision is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * sushivision is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with sushivision; see the file COPYING. If not, write to the
17 * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
22 #define _GNU_SOURCE
23 #include <stdlib.h>
24 #include <math.h>
25 #include <string.h>
26 #include <gtk/gtk.h>
27 #include <gdk/gdkkeysyms.h>
28 #include "internal.h"
30 static double val_to_pixel(_sv_slider_t *s, double val);
32 static int total_slice_width(_sv_slider_t *s){
33 int i;
34 int count=0;
35 if(s->flip)
36 for(i=0;i<s->num_slices;i++)
37 count += s->slices[i]->allocation.height;
38 else
39 for(i=0;i<s->num_slices;i++)
40 count += s->slices[i]->allocation.width;
41 return count;
44 static int slice_width(_sv_slider_t *s,int slices){
45 int i;
46 int count=0;
47 if(s->flip)
48 for(i=0;i<slices;i++)
49 count += s->slices[i]->allocation.height;
50 else
51 for(i=0;i<slices;i++)
52 count += s->slices[i]->allocation.width;
53 return count;
56 static int total_slice_height(_sv_slider_t *s){
57 int i;
58 int max=0;
59 if(s->flip){
60 for(i=0;i<s->num_slices;i++)
61 if(max<s->slices[i]->allocation.width)
62 max = s->slices[i]->allocation.width;
63 }else{
64 for(i=0;i<s->num_slices;i++)
65 if(max<s->slices[i]->allocation.height)
66 max = s->slices[i]->allocation.height;
68 return max;
71 /* guess where I came from. */
72 static void rounded_rectangle (cairo_t *c,
73 double x, double y, double w, double h,
74 double radius)
76 cairo_move_to (c, x+radius, y);
77 cairo_arc (c, x+w-radius, y+radius, radius, M_PI * 1.5, M_PI * 2);
78 cairo_arc (c, x+w-radius, y+h-radius, radius, 0, M_PI * 0.5);
79 cairo_arc (c, x+radius, y+h-radius, radius, M_PI * 0.5, M_PI);
80 cairo_arc (c, x+radius, y+radius, radius, M_PI, M_PI * 1.5);
83 static double shades[] = {1.15, 0.95, 0.896, 0.82, 0.7, 0.665, 0.5, 0.45, 0.4};
85 static void bg_set(GtkWidget *w, cairo_t *c){
86 _sv_slice_t *sl = SLICE(w);
87 GdkColor *bg = &w->style->bg[sl->thumb_state?GTK_STATE_ACTIVE:GTK_STATE_NORMAL];
88 double shade_r=bg->red/65535.;
89 double shade_g=bg->green/65535.;
90 double shade_b=bg->blue/65535.;
92 cairo_set_source_rgb (c, shade_r,shade_g,shade_b);
95 static void fg_shade(GtkWidget *w, cairo_t *c, int shade){
96 _sv_slice_t *sl = SLICE(w);
97 GdkColor *fg = &w->style->fg[sl->thumb_state?GTK_STATE_ACTIVE:GTK_STATE_NORMAL];
98 double shade_r=fg->red*shades[shade]/65535;
99 double shade_g=fg->green*shades[shade]/65535;
100 double shade_b=fg->blue*shades[shade]/65535;
102 cairo_set_source_rgb (c, shade_r,shade_g,shade_b);
105 static void parent_shade(_sv_slider_t *s, cairo_t *c, int shade){
106 GtkWidget *parent=gtk_widget_get_parent(s->slices[0]);
107 GdkColor *bg = &parent->style->bg[GTK_STATE_NORMAL];
108 double shade_r=bg->red*shades[shade]/65535;
109 double shade_g=bg->green*shades[shade]/65535;
110 double shade_b=bg->blue*shades[shade]/65535;
112 cairo_set_source_rgb (c, shade_r,shade_g,shade_b);
115 static void _sv_slider_draw_background(_sv_slider_t *s){
116 if(!s->realized)return;
118 int i;
119 GtkWidget *parent=gtk_widget_get_parent(s->slices[0]);
120 GdkColor *text = &s->slices[0]->style->text[0];
121 GdkColor *bg = &parent->style->bg[0];
122 int textborder=1;
123 double textr=text->red;
124 double textg=text->green;
125 double textb=text->blue;
127 int x=0;
128 int y=0;
129 int w=s->w;
130 int h=s->h;
132 int tx=x;
133 int ty=y+s->ypad;
134 int tw=w;
135 int th=h - s->ypad*2;
136 cairo_pattern_t *pattern;
138 // prepare background
139 cairo_t *c = cairo_create(s->background);
141 // Fill with bg color
142 gdk_cairo_set_source_color(c,bg);
143 cairo_rectangle(c,0,0,w,h);
144 cairo_fill(c);
146 cairo_rectangle (c, x+1, ty, w-2, th);
147 bg_set(s->slices[0],c);
148 cairo_fill (c);
149 cairo_surface_flush(s->background);
151 // Create trough innards
152 if(s->gradient){
153 // background map gradient
154 // this happens 'under' cairo
155 u_int32_t *pixel=s->backdata+ty*s->w;
157 for(i=tx;i<tx+tw;i++){
158 _sv_lcolor_t outc = {0,0,0,0};
160 s->mapfunc(rint(val*65536.f),255,&outc);
162 return m->mixfunc( (_sv_ucolor_t)(u_int32_t)((outc.a<<24) + (outc.r<<16) + (outc.g<<8) + outc.b),
163 (_sv_ucolor_t)mix).u | 0xff000000;
165 pixel[i]=_sv_mapping_calc(s->gradient,_sv_slider_pixel_to_mapdel(s,i), pixel[i]);
167 for(i=ty+1;i<ty+th;i++){
168 memcpy(pixel+w,pixel,w*4);
169 pixel+=s->w;
171 }else{
172 // normal background
173 textborder=0;
176 // Top shadow
177 cairo_rectangle (c, tx+1, ty, tw-2, 4);
178 pattern = cairo_pattern_create_linear (0, ty-1, 0, ty+3);
179 cairo_pattern_add_color_stop_rgba (pattern, 0.0, 0., 0., 0., 0.2);
180 cairo_pattern_add_color_stop_rgba (pattern, 1.0, 0., 0., 0., 0.);
181 cairo_set_source (c, pattern);
182 cairo_fill (c);
183 cairo_pattern_destroy (pattern);
185 // Left shadow
186 cairo_rectangle (c, tx+1, ty, tx+4, th);
187 pattern = cairo_pattern_create_linear (tx, ty-1, tx+3, ty-1);
188 cairo_pattern_add_color_stop_rgba (pattern, 0.0, 0., 0., 0., 0.1);
189 cairo_pattern_add_color_stop_rgba (pattern, 1.0, 0., 0., 0., 0.);
190 cairo_set_source (c, pattern);
191 cairo_fill (c);
192 cairo_pattern_destroy (pattern);
194 // lines & labels
195 for(i=0;i<s->labels;i++){
196 int x=val_to_pixel(s,s->label_vals[i])+.5;
197 int y=s->h-s->ypad-1.5;
199 cairo_move_to(c,x+.5,s->ypad+2);
200 cairo_line_to(c,x+.5,s->h-s->ypad-2);
201 cairo_set_source_rgba(c,textr,textg,textb,.8);
202 cairo_set_line_width(c,1);
203 cairo_stroke(c);
205 if(i>0){
206 cairo_text_extents_t ex;
207 cairo_text_extents (c, s->label[i], &ex);
209 x-=2;
210 x-=ex.width;
212 }else{
213 x+=2;
216 if(textborder){
217 cairo_set_source_rgba(c,1.,1.,1.,.5);
218 cairo_set_line_width(c,2.5);
219 cairo_move_to (c, x,y);
220 cairo_text_path (c, s->label[i]);
221 cairo_stroke(c);
224 cairo_set_source_rgba(c,textr,textg,textb,1.);
225 cairo_move_to (c, x,y);
226 cairo_show_text (c, s->label[i]);
229 // Draw border
230 rounded_rectangle (c, tx+0.5, ty-0.5, tw-1, th+1, 1.5);
231 parent_shade(s,c,7);
232 cairo_set_line_width (c, 1.0);
233 cairo_stroke (c);
235 cairo_destroy(c);
238 void _sv_slider_draw(_sv_slider_t *s){
239 if(!s->realized)return;
241 int i;
242 cairo_t *c;
243 //int w=s->w;
244 int h=s->h;
245 int w=s->w;
247 c = cairo_create(s->foreground);
249 if(s->flip){
250 cairo_matrix_t m = {0.,-1., 1.,0., 0.,w};
251 cairo_set_matrix(c,&m);
254 cairo_set_source_surface(c,s->background,0,0);
255 cairo_rectangle(c,0,0,w,h);
256 cairo_fill(c);
258 // thumbs
259 for(i=0;i<s->num_slices;i++){
260 GtkWidget *sl = s->slices[i];
261 double x = rint(val_to_pixel(s,((_sv_slice_t *)(s->slices[i]))->thumb_val))+.5;
263 double rad = 2.;
265 float y = rint(h/2)+.5;
266 float xd = y*.575;
267 float rx = 1.73*rad;
268 cairo_pattern_t *pattern;
270 if(SLICE(sl)->thumb_active){
271 if ((s->num_slices == 1) || (s->num_slices == 3 && i==1)){
272 // outline
273 cairo_move_to(c,x,s->h/2);
274 cairo_arc_negative(c, x+xd-rx, rad+.5, rad, 30.*(M_PI/180.), 270.*(M_PI/180.));
275 cairo_arc_negative(c, x-xd+rx, rad+.5, rad, 270.*(M_PI/180.), 150.*(M_PI/180.));
276 cairo_close_path(c);
278 fg_shade(sl,c,2);
279 cairo_fill_preserve(c);
281 cairo_set_line_width(c,1);
282 fg_shade(sl,c,7);
284 if(((_sv_slice_t *)s->slices[i])->thumb_focus)
285 cairo_set_source_rgba(c,0,0,0,1);
287 cairo_stroke_preserve(c);
288 cairo_set_dash (c, NULL, 0, 0.);
290 // top highlight
291 pattern = cairo_pattern_create_linear (0, 0, 0, 4);
292 cairo_pattern_add_color_stop_rgba (pattern, 0.0, 1., 1., 1., 0.2);
293 cairo_pattern_add_color_stop_rgba (pattern, 1.0, 1., 1., 1., 0.);
294 cairo_set_source (c, pattern);
295 cairo_fill_preserve (c);
296 cairo_pattern_destroy (pattern);
298 // Left highlight
299 pattern = cairo_pattern_create_linear (x-xd+3, 0, x-xd+6, 0);
300 cairo_pattern_add_color_stop_rgba (pattern, 0.0, 1., 1., 1., 0.1);
301 cairo_pattern_add_color_stop_rgba (pattern, 1.0, 1., 1., 1., 0.);
302 cairo_set_source (c, pattern);
303 cairo_fill (c);
304 cairo_pattern_destroy (pattern);
306 // needle shadow
307 cairo_set_line_width(c,2);
308 cairo_move_to(c,x,s->h/2+3);
309 cairo_line_to(c,x,s->ypad/2);
310 cairo_set_source_rgba(c,0.,0.,0.,.5);
311 cairo_stroke(c);
313 // needle
314 cairo_set_line_width(c,1);
315 cairo_move_to(c,x,s->h/2+2);
316 cairo_line_to(c,x,s->ypad/2);
317 cairo_set_source_rgb(c,1.,1.,0);
318 cairo_stroke(c);
320 }else{
321 if(i==0){
322 // bracket left
324 // outline
325 cairo_move_to(c,x,s->h/2);
326 cairo_line_to(c,x-xd/2,s->h/2);
327 cairo_arc_negative(c, x-xd*3/2+rx, h-rad-.5, rad, 210.*(M_PI/180.), 90.*(M_PI/180.));
328 cairo_line_to(c, x, h-.5);
329 cairo_close_path(c);
331 fg_shade(sl,c,2);
332 cairo_set_line_width(c,1);
333 cairo_fill_preserve(c);
334 fg_shade(sl,c,7);
335 if(((_sv_slice_t *)s->slices[i])->thumb_focus)
336 cairo_set_source_rgba(c,0,0,0,1);
337 cairo_stroke_preserve(c);
339 // top highlight
340 pattern = cairo_pattern_create_linear (0, y, 0, y+4);
341 cairo_pattern_add_color_stop_rgba (pattern, 0.0, 1., 1., 1., 0.2);
342 cairo_pattern_add_color_stop_rgba (pattern, 1.0, 1., 1., 1., 0.);
343 cairo_set_source (c, pattern);
344 cairo_fill_preserve (c);
345 cairo_pattern_destroy (pattern);
347 // Left highlight
348 pattern = cairo_pattern_create_linear (x-xd*3/2+3, 0, x-xd*3/2+6, 0);
349 cairo_pattern_add_color_stop_rgba (pattern, 0.0, 1., 1., 1., 0.1);
350 cairo_pattern_add_color_stop_rgba (pattern, 1.0, 1., 1., 1., 0.);
351 cairo_set_source (c, pattern);
352 cairo_fill (c);
353 cairo_pattern_destroy (pattern);
354 }else{
356 // bracket right
358 // outline
359 cairo_move_to(c,x,s->h/2);
360 cairo_line_to(c,x+xd/2,s->h/2);
361 cairo_arc(c, x+xd*3/2-rx, h-rad-.5, rad, 330.*(M_PI/180.), 90.*(M_PI/180.));
362 cairo_line_to(c, x, h-.5);
363 cairo_close_path(c);
365 fg_shade(sl,c,2);
366 cairo_set_line_width(c,1);
367 cairo_fill_preserve(c);
368 fg_shade(sl,c,7);
369 if(((_sv_slice_t *)s->slices[i])->thumb_focus)
370 cairo_set_source_rgba(c,0,0,0,1);
371 cairo_stroke_preserve(c);
373 // top highlight
374 pattern = cairo_pattern_create_linear (0, y, 0, y+4);
375 cairo_pattern_add_color_stop_rgba (pattern, 0.0, 1., 1., 1., 0.2);
376 cairo_pattern_add_color_stop_rgba (pattern, 1.0, 1., 1., 1., 0.);
377 cairo_set_source (c, pattern);
378 cairo_fill_preserve (c);
379 cairo_pattern_destroy (pattern);
381 // Left highlight
382 pattern = cairo_pattern_create_linear (x+1, 0, x+4, 0);
383 cairo_pattern_add_color_stop_rgba (pattern, 0.0, 1., 1., 1., 0.1);
384 cairo_pattern_add_color_stop_rgba (pattern, 1.0, 1., 1., 1., 0.);
385 cairo_set_source (c, pattern);
386 cairo_fill (c);
387 cairo_pattern_destroy (pattern);
390 // needle shadow
391 cairo_set_line_width(c,2);
392 cairo_move_to(c,x,s->h/2-3);
393 cairo_line_to(c,x,h-s->ypad/2);
394 cairo_set_source_rgba(c,0.,0.,0.,.5);
395 cairo_stroke(c);
397 // needle
398 cairo_set_line_width(c,1);
399 cairo_move_to(c,x,s->h/2-2);
400 cairo_line_to(c,x,h-s->ypad/2);
401 cairo_set_source_rgb(c,1.,1.,0);
402 cairo_stroke(c);
407 cairo_destroy(c);
410 void _sv_slider_realize(_sv_slider_t *s){
411 int w = total_slice_width(s);
412 int h = total_slice_height(s);
413 if(s->background == 0 || w != s->w || h != s->h){
415 if(s->background)
416 cairo_surface_destroy(s->background);
418 if(s->foreground)
419 cairo_surface_destroy(s->foreground);
421 if(s->backdata)
422 free(s->backdata);
424 s->backdata = calloc(w*h,4);
426 s->background = cairo_image_surface_create_for_data ((unsigned char *)s->backdata,
427 CAIRO_FORMAT_RGB24,
428 w,h,w*4);
429 if(s->flip){
430 s->foreground = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
431 h,w);
432 }else{
433 s->foreground = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
434 w,h);
437 s->w=w;
438 s->h=h;
440 s->xpad=h*.45;
441 if(s->xpad<4)s->xpad=4;
442 s->realized = 1;
443 _sv_slider_draw_background(s);
444 _sv_slider_draw(s);
447 s->realized = 1;
450 static double val_to_pixel(_sv_slider_t *s,double v){
451 int j;
452 double ret=0;
453 double neg = (s->neg? -1.: 1.);
454 int tx=s->xpad;
455 int tw=s->w - tx*2;
457 v*=neg;
459 if( v<s->label_vals[0]*neg){
460 ret=0;
461 }else if(v>s->label_vals[s->labels-1]*neg){
462 ret=tw;
463 }else{
464 for(j=0;j<s->labels;j++){
465 if(v>=s->label_vals[j]*neg && v<=s->label_vals[j+1]*neg){
466 v*=neg;
467 double del=(v-s->label_vals[j])/(s->label_vals[j+1]-s->label_vals[j]);
468 double pixlo=rint((double)(j)/(s->labels-1)*tw);
469 double pixhi=rint((double)(j+1)/(s->labels-1)*tw);
470 ret=pixlo*(1.-del)+pixhi*del+tx;
471 break;
476 return ret;
479 double _sv_slider_val_to_del(_sv_slider_t *s,double v){
480 if(isnan(v))return NAN;
481 int j=s->labels-1;
483 if(s->neg){
484 while(--j)
485 if(v<=s->label_vals[j])break;
486 }else{
487 while(--j)
488 if(v>s->label_vals[j])break;
490 return (j + (v-s->label_vals[j])*s->labeldel[j])*s->labelinv;
494 double _sv_slider_val_to_mapdel(_sv_slider_t *s,double v){
495 int j=s->labels-1;
496 if(isnan(v))return NAN;
498 if(s->neg){
500 if(v > s->al)return NAN;
501 if(v >= s->lo)return 0.;
502 if(v <= s->hi)return 1.;
503 while(--j)
504 if(v<=s->label_vals[j])break;
506 }else{
508 if(v < s->al)return NAN;
509 if(v <= s->lo)return 0.;
510 if(v >= s->hi)return 1.;
511 while(--j)
512 if(v>s->label_vals[j])break;
516 return v*s->labeldelB[j] + s->labelvalB[j];
519 void _sv_slider_expose_slice(_sv_slider_t *s, int slicenum){
520 _sv_slice_t *slice = (_sv_slice_t *)(s->slices[slicenum]);
521 GtkWidget *w = GTK_WIDGET(slice);
523 if(GTK_WIDGET_REALIZED(w)){
524 cairo_t *c = gdk_cairo_create(w->window);
526 _sv_slider_realize(s);
527 if(s->flip){
528 cairo_set_source_surface(c,s->foreground,0, slice_width(s,slicenum+1)-total_slice_width(s));
529 }else{
530 cairo_set_source_surface(c,s->foreground,-slice_width(s,slicenum),0);
532 cairo_rectangle(c,0,0,w->allocation.width,w->allocation.height);
533 cairo_fill(c);
535 cairo_destroy(c);
539 void _sv_slider_expose(_sv_slider_t *s){
540 int i;
541 for(i=0;i<s->num_slices;i++)
542 _sv_slider_expose_slice(s,i);
545 void _sv_slider_size_request_slice(_sv_slider_t *s,GtkRequisition *requisition){
546 int maxx=0,x0=0,x1=0,maxy=0,i,w;
548 // need a dummy surface to find text sizes
549 cairo_surface_t *dummy=cairo_image_surface_create(CAIRO_FORMAT_RGB24,1,1);
550 cairo_t *c = cairo_create(dummy);
552 // find the widest label
553 for(i=0;i<s->labels;i++){
554 cairo_text_extents_t ex;
555 cairo_text_extents(c, s->label[i], &ex);
556 if(i==0) x0 = ex.width;
557 if(i==1) x1 = ex.width;
558 if(ex.width > maxx)maxx=ex.width;
559 if(ex.height > maxy)maxy=ex.height;
562 maxx*=1.5;
563 // also check first + second label width
564 if(x0+x1*1.2 > maxx)maxx=(x0+x1)*1.2;
566 w = (maxx+2)*s->labels+4;
567 if(w<200)w=200;
569 if(s->flip){
570 requisition->height = (w+s->num_slices-1)/s->num_slices;
571 requisition->width = maxy+4+s->ypad*2;
572 }else{
573 requisition->width = (w+s->num_slices-1)/s->num_slices;
574 requisition->height = maxy+4+s->ypad*2;
576 cairo_destroy(c);
577 cairo_surface_destroy(dummy);
580 static double slice_adjust_pixel(_sv_slider_t *s,int slicenum, double x){
581 double width = slice_width(s,slicenum);
582 return x+width;
585 static double quant(_sv_slider_t *s, double val){
586 if(s->quant_denom!=0.){
587 val *= s->quant_denom;
588 val /= s->quant_num;
590 val = rint(val);
592 val *= s->quant_num;
593 val /= s->quant_denom;
595 return val;
598 double _sv_slider_pixel_to_val(_sv_slider_t *s,double x){
599 int tx=s->xpad;
600 int tw=s->w - tx*2;
601 double del = (double)(x-tx)/tw;
602 if(del<0)
603 return quant(s,s->label_vals[0]);
604 if(del>=1.)
605 return quant(s,(s->label_vals[s->labels-1]));
606 return _sv_slider_del_to_val(s,del);
609 double _sv_slider_pixel_to_del(_sv_slider_t *s,double x){
610 int tx=s->xpad;
611 int tw=s->w - tx*2;
612 x-=tx;
614 if(x<=0){
615 return 0.;
616 }else if (x>tw){
617 return 1.;
618 }else
619 return x/tw;
622 double _sv_slider_pixel_to_mapdel(_sv_slider_t *s,double x){
623 int tx=s->xpad;
624 int tw=s->w - tx*2;
625 x = ((x-tx)/tw - s->lodel)*s->idelrange;
627 if (x<=0.) return 0.;
628 if (x>=1.) return 1.;
629 return x;
632 double _sv_slider_del_to_val(_sv_slider_t *s, double del){
633 int base;
634 if(isnan(del))return del;
636 del *= (s->labels-1);
637 base = floor(del);
638 del -= base;
640 return quant(s,( (1.-del)*s->label_vals[base] + del*s->label_vals[base+1] ));
643 void _sv_slider_vals_bound(_sv_slider_t *s,int slicenum){
644 int i,flag=-1;
645 _sv_slice_t *center = SLICE(s->slices[slicenum]);
646 double min = (s->neg ? s->label_vals[s->labels-1] : s->label_vals[0]);
647 double max = (s->neg ? s->label_vals[0] : s->label_vals[s->labels-1]);
648 int flip = (s->neg? 1: 0);
650 if(center->thumb_val < min)
651 center->thumb_val = min;
653 if(center->thumb_val > max)
654 center->thumb_val = max;
656 // now make sure other sliders have valid spacing
657 if( (s->flags & _SV_SLIDER_FLAG_INDEPENDENT_MIDDLE) &&
658 s->num_slices == 3)
659 flag=1;
661 for(i=slicenum-1; i>=0;i--){
662 int i2 = i+1;
663 if(i==flag)continue;
664 if(i2 == flag)i2++;
665 if(i2>=s->num_slices)continue;
667 _sv_slice_t *sl = SLICE(s->slices[i]);
668 _sv_slice_t *sl2 = SLICE(s->slices[i2]);
669 if((sl->thumb_val>sl2->thumb_val)^flip)
670 _sv_slice_thumb_set(sl,sl2->thumb_val);
673 for(i=slicenum+1; i<s->num_slices;i++){
674 int i2 = i-1;
675 if(i==flag)continue;
676 if(i2 == flag)i2--;
677 if(i2<0)continue;
679 _sv_slice_t *sl = SLICE(s->slices[i]);
680 _sv_slice_t *sl2 = SLICE(s->slices[i2]);
681 if((sl->thumb_val<sl2->thumb_val)^flip)
682 _sv_slice_thumb_set(sl,sl2->thumb_val);
687 static int determine_thumb(_sv_slider_t *s,int slicenum,int x,int y){
688 int i;
689 int best=-1;
690 float bestdist=s->w+1;
691 int n = s->num_slices;
693 if(s->flip){
694 _sv_slice_t *sl = SLICE(s->slices[slicenum]);
695 int temp = x;
696 x = sl->widget.allocation.height - y -1;
697 y = temp;
700 x=slice_adjust_pixel(s,slicenum,x);
701 for(i=0;i<n;i++){
702 _sv_slice_t *sl = SLICE(s->slices[i]);
703 if(sl->thumb_active){
704 float tx = val_to_pixel(s,sl->thumb_val) + i - s->num_slices/2;
705 float ty = ((n==3 && i==1) ? 0:s->h);
706 float d = hypot (x-tx,y-ty);
707 if(d<bestdist){
708 best=i;
709 bestdist=d;
713 return best;
716 static int lit_thumb(_sv_slider_t *s){
717 int i;
718 for(i=0;i<s->num_slices;i++){
719 _sv_slice_t *sl = SLICE(s->slices[i]);
720 if(sl->thumb_state)return i;
722 return -1;
725 void _sv_slider_unlight(_sv_slider_t *s){
726 int i;
727 for(i=0;i<s->num_slices;i++){
728 _sv_slice_t *sl = SLICE(s->slices[i]);
729 if(!sl->thumb_grab)
730 sl->thumb_state = 0;
734 int _sv_slider_lightme(_sv_slider_t *s,int slicenum,int x,int y){
735 int last = lit_thumb(s);
736 int best = determine_thumb(s,slicenum,x,y);
737 if(last != best){
738 _sv_slider_unlight(s);
739 if(best>-1){
740 _sv_slice_t *sl = SLICE(s->slices[best]);
741 sl->thumb_state=1;
743 return 1;
745 return 0;
748 void _sv_slider_button_press(_sv_slider_t *s,int slicenum,int x,int y){
749 int i;
750 for(i=0;i<s->num_slices;i++){
751 _sv_slice_t *sl = SLICE(s->slices[i]);
752 if(sl->thumb_state){
753 sl->thumb_grab=1;
754 sl->thumb_focus=1;
755 gtk_widget_grab_focus(GTK_WIDGET(sl));
757 if(sl->callback)sl->callback(sl->callback_data,0);
758 _sv_slider_motion(s,slicenum,x,y);
759 }else{
760 sl->thumb_grab=0;
761 sl->thumb_focus=0;
764 _sv_slider_draw(s);
765 _sv_slider_expose(s);
768 void _sv_slider_button_release(_sv_slider_t *s,int slicenum,int x,int y){
769 int i;
770 for(i=0;i<s->num_slices;i++){
771 _sv_slice_t *sl = SLICE(s->slices[i]);
773 if(sl->thumb_grab){
774 sl->thumb_grab=0;
775 if(sl->callback)sl->callback(sl->callback_data,2);
780 static void update_gradient(_sv_slider_t *s){
781 if(s->gradient && s->num_slices==3){
782 _sv_slice_t *sl = SLICE(s->slices[0]);
783 _sv_slice_t *sa = SLICE(s->slices[1]);
784 _sv_slice_t *sh = SLICE(s->slices[2]);
785 double ldel = _sv_slider_val_to_del(s,sl->thumb_val);
786 double hdel = _sv_slider_val_to_del(s,sh->thumb_val);
788 s->al = sa->thumb_val;
790 if(s->gradient->low != ldel ||
791 s->gradient->high != hdel){
792 int j;
794 s->lo = sl->thumb_val;
795 s->hi = sh->thumb_val;
796 s->idelrange = 1./(hdel-ldel);
797 s->lodel = ldel;
799 for(j=0;j<s->labels-1;j++){
800 s->labeldelB[j] = s->labeldel[j] * s->idelrange * s->labelinv;
801 s->labelvalB[j] = (j-s->label_vals[j]*s->labeldel[j]-
802 s->lodel*(s->labels-1))*s->idelrange*s->labelinv;
805 _sv_mapping_set_lo(s->gradient,ldel);
806 _sv_mapping_set_hi(s->gradient,hdel);
807 _sv_slider_draw_background(s);
808 _sv_slider_draw(s);
810 _sv_slider_expose(s);
814 void _sv_slider_motion(_sv_slider_t *s,int slicenum,int x,int y){
815 double altered[s->num_slices];
816 int i, grabflag=0;
817 _sv_slice_t *sl = SLICE(s->slices[slicenum]);
818 int px = (s->flip?sl->widget.allocation.height-y-1 : x);
820 for(i=0;i<s->num_slices;i++){
821 sl = SLICE(s->slices[i]);
822 altered[i] = sl->thumb_val;
825 /* is a thumb already grabbed? */
826 for(i=0;i<s->num_slices;i++){
827 sl = SLICE(s->slices[i]);
828 if(sl->thumb_grab){
829 sl->thumb_val=
830 _sv_slider_pixel_to_val(s,slice_adjust_pixel(s,slicenum,px));
831 _sv_slider_vals_bound(s,i);
832 grabflag = 1;
836 // did a gradient get altered?
837 update_gradient(s);
839 if(grabflag){
840 _sv_slider_draw(s);
841 _sv_slider_expose(s);
843 // call slice callbacks on all slices that were altered; value
844 // bounding might have affected slices other than the grabbed one.
846 for(i=0;i<s->num_slices;i++){
847 _sv_slice_t *sl = SLICE(s->slices[i]);
849 if(sl->thumb_val != altered[i])
850 if(sl->callback)sl->callback(sl->callback_data,1);
853 }else{
854 /* nothing grabbed right now; determine if we're in a thumb's area */
855 if(_sv_slider_lightme(s,slicenum,x,y)){
856 _sv_slider_draw(s);
857 _sv_slider_expose(s);
862 gboolean _sv_slider_key_press(_sv_slider_t *s,GdkEventKey *event,int slicenum){
863 _sv_slice_t *sl = (_sv_slice_t *)(s->slices[slicenum]);
864 int shift = (event->state&GDK_SHIFT_MASK);
865 if(event->state&GDK_MOD1_MASK) return FALSE;
866 if(event->state&GDK_CONTROL_MASK)return FALSE;
868 /* non-control keypresses */
869 switch(event->keyval){
870 case GDK_Left:
872 double x = val_to_pixel(s,sl->thumb_val);
873 while(sl->thumb_val > s->label_vals[0] &&
874 sl->thumb_val == _sv_slider_pixel_to_val(s,x))x--;
875 if(shift)
876 x-=9;
877 sl->thumb_val=_sv_slider_pixel_to_val(s,x);
878 _sv_slider_vals_bound(s,slicenum);
879 // did a gradient get altered?
880 update_gradient(s);
882 if(sl->callback){
883 sl->callback(sl->callback_data,0);
884 sl->callback(sl->callback_data,1);
885 sl->callback(sl->callback_data,2);
887 _sv_slider_draw(s);
888 _sv_slider_expose(s);
891 return TRUE;
893 case GDK_Right:
895 double x = val_to_pixel(s,sl->thumb_val);
896 while(sl->thumb_val < s->label_vals[s->labels-1] &&
897 sl->thumb_val == _sv_slider_pixel_to_val(s,x))x++;
898 if(shift)
899 x+=9;
900 sl->thumb_val=_sv_slider_pixel_to_val(s,x);
901 _sv_slider_vals_bound(s,slicenum);
902 // did a gradient get altered?
903 update_gradient(s);
905 if(sl->callback){
906 sl->callback(sl->callback_data,0);
907 sl->callback(sl->callback_data,1);
908 sl->callback(sl->callback_data,2);
910 _sv_slider_draw(s);
911 _sv_slider_expose(s);
913 return TRUE;
916 return FALSE; // keep processing
919 _sv_slider_t *_sv_slider_new(_sv_slice_t **slices, int num_slices, char **labels, double *label_vals, int num_labels,
920 unsigned flags){
921 int i;
922 _sv_slider_t *ret = calloc(1,sizeof(*ret));
924 ret->slices = (GtkWidget **)slices;
925 ret->num_slices = num_slices;
926 ret->quant_num=0.;
927 ret->quant_denom=0.;
929 ret->label = calloc(num_labels,sizeof(*ret->label));
930 for(i=0;i<num_labels;i++)
931 ret->label[i]=strdup(labels[i]);
933 ret->label_vals = calloc(num_labels,sizeof(*ret->label_vals));
934 memcpy(ret->label_vals,label_vals,sizeof(*ret->label_vals)*num_labels);
935 ret->labels = num_labels;
937 // set up each slice
938 for(i=0;i<num_slices;i++){
939 slices[i]->slider = ret;
940 slices[i]->slicenum = i;
942 ret->ypad=8;
943 ret->xpad=5;
944 //ret->minstep=minstep;
945 //ret->step=step;
947 if(label_vals[0]>label_vals[1])
948 ret->neg = 1;
950 ret->flags=flags;
951 if(flags & _SV_SLIDER_FLAG_VERTICAL) ret->flip = 1;
953 ret->labelinv = 1./(ret->labels-1);
954 ret->labeldel = calloc(ret->labels-1,sizeof(*ret->labeldel));
955 for(i=0;i<ret->labels-1;i++)
956 ret->labeldel[i] = 1./(ret->label_vals[i+1]-ret->label_vals[i]);
957 ret->lo = ret->label_vals[0];
958 ret->hi = ret->label_vals[ret->labels-1];
959 ret->lodel = 0.;
960 ret->idelrange = 1.;
962 ret->labeldelB = calloc(ret->labels-1,sizeof(*ret->labeldelB));
963 ret->labelvalB = calloc(ret->labels-1,sizeof(*ret->labelvalB));
964 for(i=0;i<ret->labels-1;i++){
965 ret->labeldelB[i] = ret->labeldel[i] * ret->labelinv;
966 ret->labelvalB[i] = (i-ret->label_vals[i]*ret->labeldel[i])*ret->labelinv;
969 return ret;
972 void _sv_slider_set_gradient(_sv_slider_t *s, _sv_mapping_t *m){
973 s->gradient = m;
974 if(s->realized){
975 _sv_slider_draw_background(s);
976 _sv_slider_draw(s);
977 _sv_slider_expose(s);
981 void _sv_slider_set_thumb_active(_sv_slider_t *s, int thumbnum, int activep){
982 _sv_slice_set_active(SLICE(s->slices[thumbnum]),activep);
985 double _sv_slider_get_value(_sv_slider_t *s, int thumbnum){
986 GtkWidget *w;
987 if(thumbnum >= s->num_slices)return 0;
988 if(thumbnum < 0)return 0;
989 w = s->slices[thumbnum];
990 return SLICE(w)->thumb_val;
993 void _sv_slider_set_value(_sv_slider_t *s, int thumbnum, double v){
994 GtkWidget *w;
995 if(thumbnum >= s->num_slices)return;
996 if(thumbnum < 0)return;
997 w = s->slices[thumbnum];
998 _sv_slice_thumb_set(SLICE(w),v);
999 update_gradient(s);
1002 void _sv_slider_set_quant(_sv_slider_t *s,double num, double denom){
1003 s->quant_num=num;
1004 s->quant_denom=denom;
1007 double _sv_slider_print_height(_sv_slider_t *s){
1008 return (s->slices[0]->allocation.height - s->ypad*2)*1.2;
1011 void _sv_slider_print(_sv_slider_t *s, cairo_t *c, int w, int h){
1012 cairo_save(c);
1013 double ypad = h*.1;
1014 double neg = (s->neg? -1.: 1.);
1016 // set clip region
1017 cairo_rectangle(c,0,ypad,w,h-ypad*2);
1018 cairo_clip(c);
1020 // determine start/end deltas
1021 // eliminate label sections that are completely unused
1022 int slices = s->num_slices;
1023 double lo = (slices>0?SLICE(s->slices[0])->thumb_val:s->label_vals[0]) * neg;
1024 double hi = (slices>0?SLICE(s->slices[slices-1])->thumb_val:s->label_vals[s->labels-1]) * neg;
1026 // alpha could push up the unused area
1027 if(slices==3 && SLICE(s->slices[1])->thumb_val*neg>lo)
1028 lo = SLICE(s->slices[1])->thumb_val*neg;
1030 // if lo>hi (due to alpha), show the whole scale empty
1031 if(lo>hi){
1032 lo = s->label_vals[0]*neg;
1033 hi = s->label_vals[s->labels-1]*neg;
1036 int firstlabel=0;
1037 int lastlabel=s->labels-1;
1038 int i;
1040 for(i=s->labels-2;i>0;i--)
1041 if(lo>s->label_vals[i]*neg){
1042 firstlabel=i;
1043 break;
1046 for(i=1;i<s->labels-1;i++)
1047 if(hi<s->label_vals[i]*neg){
1048 lastlabel=i;
1049 break;
1052 double lodel = 1. / (s->labels-1) * firstlabel;
1053 double hidel = 1. / (s->labels-1) * lastlabel;
1054 double alphadel = (slices==3 ?
1055 _sv_slider_val_to_del(s,SLICE(s->slices[1])->thumb_val):0.);
1057 // create background image
1059 cairo_surface_t *image = cairo_image_surface_create(CAIRO_FORMAT_RGB24,w,h);
1060 cairo_t *ci = cairo_create(image);
1061 int x,y;
1063 cairo_save(c);
1064 cairo_set_source_rgb (ci, .5,.5,.5);
1065 cairo_paint(ci);
1066 cairo_set_source_rgb (ci, .314,.314,.314);
1067 for(y=0;y<=h/2;y+=8){
1068 int phase = (y>>3)&1;
1069 for(x=0;x<w;x+=8){
1070 if(phase)
1071 cairo_rectangle(ci,x,y+h/2.,8.,8.);
1072 else
1073 cairo_rectangle(ci,x,h/2.-y-8,8.,8.);
1074 cairo_fill(ci);
1075 phase=!phase;
1079 for(y=0;y<h;y++){
1080 _sv_ucolor_t *line = (_sv_ucolor_t *)cairo_image_surface_get_data(image) + w*y;
1081 for(x=0;x<w;x++){
1082 double del = (hidel - lodel) / (w-1) * x + lodel;
1083 if(del>=alphadel)
1084 line[x].u = _sv_mapping_calc(s->gradient, del, line[x].u);
1088 // composite background with correct resample filter
1089 cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image);
1090 cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
1091 cairo_set_source(c,pattern);
1092 cairo_rectangle(c,0,0,w,h);
1093 cairo_fill(c);
1095 cairo_destroy(ci);
1096 cairo_pattern_destroy(pattern);
1097 cairo_surface_destroy(image);
1098 cairo_restore(c);
1101 // add labels
1102 cairo_set_font_size(c,h-ypad*2-3);
1103 for(i=firstlabel;i<=lastlabel;i++){
1104 double x = (double)(i-firstlabel) / (lastlabel - firstlabel) * (w-1);
1105 double y;
1106 cairo_text_extents_t ex;
1108 cairo_move_to(c,x+.5,ypad);
1109 cairo_line_to(c,x+.5,h-ypad);
1110 cairo_set_source_rgba(c,0,0,0,.8);
1111 cairo_set_line_width(c,1);
1112 cairo_stroke(c);
1114 cairo_text_extents (c, s->label[i], &ex);
1115 if(i>firstlabel){
1117 x-=2;
1118 x-=ex.width;
1120 }else{
1121 x+=2;
1123 y = h/2. - ex.y_bearing/2.;
1125 cairo_set_source_rgba(c,1.,1.,1.,.5);
1126 cairo_set_line_width(c,2.5);
1127 cairo_move_to (c, x,y);
1128 cairo_text_path (c, s->label[i]);
1129 cairo_stroke(c);
1131 cairo_set_source_rgba(c,0,0,0,1.);
1132 cairo_move_to (c, x,y);
1133 cairo_show_text (c, s->label[i]);
1136 cairo_restore(c);