Simplified calculations a bit
[gregoa/zavai.git] / src / fisheye.vala
index caa124096291b814ffdd273d3d7496c5c0dad0a8..c40c8a73d24afc3dd400664d234f3c38572208f7 100644 (file)
@@ -22,53 +22,124 @@ using GLib;
 
 public class FisheyeList : Gtk.DrawingArea
 {
 
 public class FisheyeList : Gtk.DrawingArea
 {
+       protected Gtk.TreeModel model;
        protected Gdk.Pixmap backing_store;
 
        protected Gdk.Pixmap backing_store;
 
-       private string[] list;
+       // Pango layouts cached for speed
+       protected const int max_font_size = 30;
+       protected Pango.Layout[] pango_cache;
+
+       // Labels to show, extracted from the model
+       protected string[] label_cache;
+       protected bool build_label_cache_needed;
+
        protected int cur_el;
        protected int cur_el;
-       protected double distortion_factor;
 
 
-       // Number of items shown before and after the focus element
+       // Layout information
        protected int focus_first;
        protected int focus_end;
        protected int focus_first;
        protected int focus_end;
-       protected int focus_size;
        protected int[] focus_starts;
        protected bool focus_locked;
        protected int[] focus_starts;
        protected bool focus_locked;
-       protected double focus_centre;
+       protected bool focus_layout_needed;
+
+       protected int _focus_size;
+       public int focus_size {
+               get { return _focus_size; }
+               set {
+                       _focus_size = value;
+                       focus_starts = new int[value+1];
+                       focus_layout_needed = true;
+                       queue_draw();
+               }
+       }
+
+       protected int _label_column;
+       public int label_column {
+               get { return _label_column; }
+               set {
+                       _label_column = value;
+                       build_label_cache_needed = true;
+                       queue_draw();
+               }
+       }
+
+       //public virtual signal void cursor_changed ();
+       public signal void row_activated(Gtk.TreePath path);
 
        public FisheyeList()
        {
 
        public FisheyeList()
        {
+               model = null;
                backing_store = null;
 
                backing_store = null;
 
-               list = new string[300];
-               for (int i = 0; i < 300; ++i)
-                       list[i] = "Antani %d".printf(i);
+               label_cache = null;
 
 
-               cur_el = 0;
-               focus_centre = 0;
+               pango_cache = new Pango.Layout[max_font_size];
+               for (int i = 0; i < pango_cache.length; ++i)
+                       pango_cache[i] = null;
+
+               // Defaults for properties
                focus_size = 20;
                focus_size = 20;
-               focus_starts = new int[focus_size + 1];
+               label_column = 0;
+
+               cur_el = 0;
                focus_locked = false;
                focus_locked = false;
-               distortion_factor = 30;
-               focus_layout();
 
                add_events(Gdk.EventMask.POINTER_MOTION_MASK
                         | Gdk.EventMask.BUTTON_PRESS_MASK
                         | Gdk.EventMask.BUTTON_RELEASE_MASK);
        }
 
 
                add_events(Gdk.EventMask.POINTER_MOTION_MASK
                         | Gdk.EventMask.BUTTON_PRESS_MASK
                         | Gdk.EventMask.BUTTON_RELEASE_MASK);
        }
 
+       public unowned Gtk.TreeModel get_model() { return model; }
+       public void set_model (Gtk.TreeModel? model)
+       {
+               if (this.model != null)
+               {
+                       this.model.row_changed -= on_row_changed;
+                       this.model.row_deleted -= on_row_deleted;
+                       this.model.row_has_child_toggled -= on_row_has_child_toggled;
+                       this.model.row_inserted -= on_row_inserted;
+                       this.model.rows_reordered -= on_rows_reordered;
+               }
+               this.model = model;
+               this.model.row_changed += on_row_changed;
+               this.model.row_deleted += on_row_deleted;
+               this.model.row_has_child_toggled += on_row_has_child_toggled;
+               this.model.row_inserted += on_row_inserted;
+               this.model.rows_reordered += on_rows_reordered;
+               build_label_cache_needed = true;
+               queue_draw();
+       }
+
+       private void on_row_changed(Gtk.TreePath path, Gtk.TreeIter iter) { build_label_cache_needed = true; }
+       private void on_row_deleted(Gtk.TreePath path) { build_label_cache_needed = true; }
+       private void on_row_has_child_toggled(Gtk.TreePath path, Gtk.TreeIter iter) { build_label_cache_needed = true; }
+       private void on_row_inserted(Gtk.TreePath path, Gtk.TreeIter iter) { build_label_cache_needed = true; }
+       private void on_rows_reordered(Gtk.TreePath path, Gtk.TreeIter iter, void* new_order) { build_label_cache_needed = true; }
+
        /* Mouse button got pressed over widget */
        /* Mouse button got pressed over widget */
+       /*
        public override bool button_press_event(Gdk.EventButton event)
        {
        public override bool button_press_event(Gdk.EventButton event)
        {
-               stderr.printf("Mouse pressed on %d %s\n", cur_el, list[cur_el]);
+               stderr.printf("Mouse pressed on %d %s\n", cur_el, label_cache[cur_el]);
                return false;
        }
                return false;
        }
+       */
 
        /* Mouse button got released */
        public override bool button_release_event(Gdk.EventButton event)
        {
 
        /* Mouse button got released */
        public override bool button_release_event(Gdk.EventButton event)
        {
-               stderr.printf("Mouse released on %d %s\n", cur_el, list[cur_el]);
-               // ...
+               stderr.printf("Mouse released on %d %s\n", cur_el, label_cache[cur_el]);
+
+               // Emit row_activated if applicable
+               if (model != null)
+               {
+                       Gtk.TreeIter iter;
+                       if (model.iter_nth_child(out iter, null, cur_el))
+                       {
+                               Gtk.TreePath path = model.get_path(iter);
+                               row_activated(path);
+                       }
+               }
                return false;
        }
 
                return false;
        }
 
@@ -79,7 +150,7 @@ public class FisheyeList : Gtk.DrawingArea
                int x = (int)event.x;
                int y = (int)event.y;
 
                int x = (int)event.x;
                int y = (int)event.y;
 
-               focus_locked = x < allocation.width/2 && y >= focus_starts[0] && y < focus_starts[focus_end - focus_first];
+               focus_locked = !focus_layout_needed && x < allocation.width/2 && y >= focus_starts[0] && y < focus_starts[focus_end - focus_first];
 
                if (focus_locked)
                {
 
                if (focus_locked)
                {
@@ -91,9 +162,9 @@ public class FisheyeList : Gtk.DrawingArea
                                }
 
                } else {
                                }
 
                } else {
-                       cur_el = y * list.length / allocation.height;
+                       cur_el = y * label_cache.length / allocation.height;
                        if (old_cur_el != cur_el)
                        if (old_cur_el != cur_el)
-                               focus_layout();
+                               focus_layout_needed = true;
                }
 
                //stderr.printf("MOTION %f %f CE %d\n", event.x, event.y, cur_el);
                }
 
                //stderr.printf("MOTION %f %f CE %d\n", event.x, event.y, cur_el);
@@ -108,7 +179,7 @@ public class FisheyeList : Gtk.DrawingArea
        public override bool configure_event (Gdk.EventConfigure event)
        {
                backing_store = new Gdk.Pixmap(window, allocation.width, allocation.height, -1);
        public override bool configure_event (Gdk.EventConfigure event)
        {
                backing_store = new Gdk.Pixmap(window, allocation.width, allocation.height, -1);
-               focus_layout();
+               focus_layout_needed = true;
                queue_draw();
                return false;
        }
                queue_draw();
                return false;
        }
@@ -131,53 +202,86 @@ public class FisheyeList : Gtk.DrawingArea
                return false;
        }
 
                return false;
        }
 
+       public override void style_set(Gtk.Style? previous_style)
+       {
+               // Reset the pango cache if the pango context changes
+               for (int i = 0; i < pango_cache.length; ++i)
+                       pango_cache[i] = null;
+       }
+       public override void direction_changed(Gtk.TextDirection previous_direction)
+       {
+               // Reset the pango cache if the pango context changes
+               for (int i = 0; i < pango_cache.length; ++i)
+                       pango_cache[i] = null;
+       }
+
        protected int el_y(int idx)
        {
        protected int el_y(int idx)
        {
-               // Undistorted Y
-               double undy = (double)idx * allocation.height / list.length;
                // Distorted position
                // Distorted position
-               double pos = fisheye(undy, focus_centre, distortion_factor, 0, allocation.height);
+               int pos = fisheye(idx);
                //stderr.printf("%d %f %f\n", idx, undy, pos);
                //stderr.printf("%d %f %f\n", idx, undy, pos);
-               return (int)Math.round(pos);
+               return pos;
        }
 
        }
 
-       protected void focus_layout()
+       protected void build_label_cache()
        {
        {
-               // Anchor point
-               focus_centre = (double)cur_el*allocation.height/list.length;
+               if (model == null)
+               {
+                       label_cache = new string[0];
+               } else {
+                       Gtk.TreeIter iter;
+                       if (!model.get_iter_first(out iter))
+                       {
+                               label_cache = new string[0];
+                       }
+                       else
+                       {
+                               int count = model.iter_n_children(null);
+                               label_cache = new string[count];
+
+                               int i = 0;
+                               do {
+                                       string val;
+                                       model.get(iter, _label_column, out val, -1);
+                                       label_cache[i] = val;
+                                       ++i;
+                               } while (model.iter_next(ref iter));
+                       }
+               }
 
 
-               focus_first = cur_el > focus_size/2 ? cur_el-focus_size/2 : 0;
-               focus_end = focus_first + focus_size;
-               if (focus_end >= list.length) focus_end = list.length;
+               build_label_cache_needed = false;
+               focus_layout_needed = true;
+       }
 
 
-               // Compute starting positions for all items in focus
-               for (int idx = focus_first; idx < focus_end; ++idx)
+       protected void focus_layout()
+       {
+               if (label_cache.length == 0)
                {
                {
-                       int posprev = idx == 0 ? 0 : el_y(idx-1);
-                       int pos = el_y(idx);
-                       int posnext = idx == list.length-1 ? 1 : el_y(idx+1);
-                       int y0 = (pos+posprev)/2;
-                       int y1 = (pos+posnext)/2;
-
-                       focus_starts[idx - focus_first] = y0;
-                       focus_starts[idx - focus_first + 1] = y1;
+                       focus_first = 0;
+                       focus_end = 0;
+                       focus_starts[0] = 0;
+               } else {
+                       focus_first = cur_el > _focus_size/2 ? cur_el-_focus_size/2 : 0;
+                       focus_end = focus_first + _focus_size;
+                       if (focus_end >= label_cache.length) focus_end = label_cache.length;
+
+                       // Compute starting positions for all items in focus
+                       for (int idx = focus_first; idx <= focus_end; ++idx)
+                       {
+                               focus_starts[idx - focus_first] = el_y(idx);
+                       }
                }
                }
+               focus_layout_needed = false;
        }
 
        protected void draw(Gdk.Drawable drawable)
        {
        }
 
        protected void draw(Gdk.Drawable drawable)
        {
-                Gtk.Style style = get_style();
-       /*
-       public enum StateType {
-               NORMAL,
-               ACTIVE,
-               PRELIGHT,
-               SELECTED,
-               INSENSITIVE
-       }
-               style.bg[Gtk.StateType.NORMAL];
-       */
+               if (build_label_cache_needed)
+                       build_label_cache();
+               if (focus_layout_needed)
+                       focus_layout();
 
 
+                Gtk.Style style = get_style();
 
                // Background
                drawable.draw_rectangle(style.bg_gc[Gtk.StateType.NORMAL], true, 0, 0, allocation.width, allocation.height);
 
                // Background
                drawable.draw_rectangle(style.bg_gc[Gtk.StateType.NORMAL], true, 0, 0, allocation.width, allocation.height);
@@ -208,12 +312,21 @@ public class FisheyeList : Gtk.DrawingArea
                                        0, y0, allocation.width, y1-y0);
                        }
 
                                        0, y0, allocation.width, y1-y0);
                        }
 
-                       var layout = create_pango_layout(list[idx]);
-
-                       var fd = style.font_desc.copy();
-                       //fd.set_size((y1-y0)*Pango.SCALE);
-                       fd.set_absolute_size((y1-y0)*Pango.SCALE);
-                       layout.set_font_description(fd);
+               
+                       // TODO: cache pango contexts instead of fontdescs
+                       int size = (y1-y0)*80/100;
+                       if (size <= 0) size = 1;
+                       if (size >= pango_cache.length) size = pango_cache.length - 1;
+                       if (pango_cache[size] == null)
+                       {
+                               var fd = style.font_desc.copy();
+                               fd.set_absolute_size(size*Pango.SCALE);
+                               var pc = create_pango_context();
+                               pc.set_font_description(fd);
+                               pango_cache[size] = new Pango.Layout(pc);
+                       }
+                       pango_cache[size].set_text(label_cache[idx], -1);
+                       var layout = pango_cache[size];
                        //stderr.printf("AZAZA %p\n", layout.get_attributes());
                        //var attrlist = layout.get_attributes().copy();
                        //stderr.printf("AL %p\n", attrlist);
                        //stderr.printf("AZAZA %p\n", layout.get_attributes());
                        //var attrlist = layout.get_attributes().copy();
                        //stderr.printf("AL %p\n", attrlist);
@@ -224,45 +337,60 @@ public class FisheyeList : Gtk.DrawingArea
                        //attrlist.change(new Pango.AttrSize(y1-y0));
                        //layout.set_attributes(attrlist);
                        //layout.set_height(y1-y0);
                        //attrlist.change(new Pango.AttrSize(y1-y0));
                        //layout.set_attributes(attrlist);
                        //layout.set_height(y1-y0);
+                       //int w, h;
+                       //layout.get_pixel_size(out w, out h);
                        Gdk.draw_layout(drawable, style.fg_gc[itemState], 0, y0, layout);
                }
        }
 
                        Gdk.draw_layout(drawable, style.fg_gc[itemState], 0, y0, layout);
                }
        }
 
-    /*
-     * The following function is adapted from Prefuse's FisheyeDistortion.java.
-     *
-     * A relevant annotation from Prefuse:
-     *
-     * For more details on this form of transformation, see Manojit Sarkar and 
-     * Marc H. Brown, "Graphical Fisheye Views of Graphs", in Proceedings of 
-     * CHI'92, Human Factors in Computing Systems, p. 83-91, 1992. Available
-     * online at <a href="http://citeseer.ist.psu.edu/sarkar92graphical.html">
-     * http://citeseer.ist.psu.edu/sarkar92graphical.html</a>. 
-     */
-
-    /*
-     * Distorts an item's coordinate.
-     * @param x the undistorted coordinate
-     * @param coordinate of the anchor or focus point
-     * @param d disortion factor
-     * @param min the beginning of the display
-     * @param max the end of the display
-     * @return the distorted coordinate
-     */
-    private double fisheye(double x, double a, double d, double min, double max)
-    {
-        if ( d != 0 ) {
-            bool left = x<a;
-            double v;
-           double m = (left ? a-min : max-a);
-            if ( m == 0 ) m = max-min;
-            v = Math.fabs(x - a) / m;
-            v = (d+1)/(d+(1/v));
-            return (left?-1:1)*m*v + a;
-        } else {
-            return x;
-        }
-    }
+       /*
+        * The following function is adapted from Prefuse's FisheyeDistortion.java.
+        *
+        * A relevant annotation from Prefuse:
+        *
+        * For more details on this form of transformation, see Manojit Sarkar and 
+        * Marc H. Brown, "Graphical Fisheye Views of Graphs", in Proceedings of 
+        * CHI'92, Human Factors in Computing Systems, p. 83-91, 1992. Available
+        * online at <a href="http://citeseer.ist.psu.edu/sarkar92graphical.html">
+        * http://citeseer.ist.psu.edu/sarkar92graphical.html</a>. 
+        */
+
+       /*
+        * Distorts an item's coordinate.
+        * @param x the undistorted coordinate
+        * @param coordinate of the anchor or focus point
+        * @param d disortion factor
+        * @param min the beginning of the display
+        * @param max the end of the display
+        * @return the distorted coordinate
+        */
+       private int fisheye(int idx)
+       {
+               // Autocompute distortion factor
+               // 20 is the pixel size of the item at centre of focus
+               double d = label_cache.length * 20 / allocation.height;
+               if ( d <= 1 )
+                       return idx * allocation.height / label_cache.length;
+
+               double a = (double)cur_el * allocation.height / label_cache.length;
+               double x = (double)idx * allocation.height / label_cache.length;
+               double max = (double)allocation.height;
+
+               if (idx < cur_el)
+               {
+                       double m = a;
+                       if ( m == 0 ) m = max;
+                       double v = (double)(a - x) / m;
+                       v = (double)(d+1)/(d+(1/v));
+                       return (int)Math.round(a - m*v);
+               } else {
+                       double m = max-a;
+                       if ( m == 0 ) m = max;
+                       double v = (double)(x - a) / m;
+                       v = (double)(d+1)/(d+(1/v));
+                       return (int)Math.round(a + m*v);
+               }
+       }
 }
 
 public class Fisheye : Gtk.Window
 }
 
 public class Fisheye : Gtk.Window
@@ -274,6 +402,30 @@ public class Fisheye : Gtk.Window
 
                var list = new FisheyeList();
                add(list);
 
                var list = new FisheyeList();
                add(list);
+
+               var store = new Gtk.ListStore(1, typeof(string));
+               Gtk.TreeIter iter;
+               var infd = FileStream.open("/tmp/names", "r");
+               if (infd == null)
+               {
+                       for (int i = 0; i < 300; ++i)
+                       {
+                               store.append(out iter);
+                               store.set(iter, 0, "Antani %d".printf(i), -1);
+                       }
+               } else {
+                       char buf[255];
+                       while (true)
+                       {
+                               string line = infd.gets(buf);
+                               if (line == null)
+                                       break;
+                               store.append(out iter);
+                               store.set(iter, 0, line, -1);
+                       }
+               }
+
+               list.set_model(store);
        }
 }
 
        }
 }