Simplified calculations a bit
[gregoa/zavai.git] / src / fisheye.vala
index 285d9cbd3c81f34e39f4bb6af3aa4efd8b379860..c40c8a73d24afc3dd400664d234f3c38572208f7 100644 (file)
@@ -25,32 +25,21 @@ public class FisheyeList : Gtk.DrawingArea
        protected Gtk.TreeModel model;
        protected Gdk.Pixmap backing_store;
 
        protected Gtk.TreeModel model;
        protected Gdk.Pixmap backing_store;
 
-       private string[] list;
-       protected int cur_el;
-
-       protected int _distortion_factor;
-       public int distortion_factor {
-               get { return _distortion_factor; }
-               set {
-                       _distortion_factor = value;
-                       focus_layout_needed = true;
-               }
-       }
-
-
        // Pango layouts cached for speed
        // Pango layouts cached for speed
-       // TODO: If you create and keep a PangoLayout using this context, you
-       // must deal with changes to the context by calling
-       // pango_layout_context_changed() on the layout in response to the
-       // "style-set" and "direction-changed" signals for the widget.
+       protected const int max_font_size = 30;
        protected Pango.Layout[] pango_cache;
 
        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;
+
        // Layout information
        protected int focus_first;
        protected int focus_end;
        protected int[] focus_starts;
        protected bool focus_locked;
        // Layout information
        protected int focus_first;
        protected int focus_end;
        protected int[] focus_starts;
        protected bool focus_locked;
-       protected int focus_centre;
        protected bool focus_layout_needed;
 
        protected int _focus_size;
        protected bool focus_layout_needed;
 
        protected int _focus_size;
@@ -60,28 +49,39 @@ public class FisheyeList : Gtk.DrawingArea
                        _focus_size = value;
                        focus_starts = new int[value+1];
                        focus_layout_needed = true;
                        _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()
        {
                model = null;
                backing_store = null;
 
        public FisheyeList()
        {
                model = null;
                backing_store = null;
 
-               list = new string[300];
-               for (int i = 0; i < 300; ++i)
-                       list[i] = "Lorem Ipsum %d".printf(i);
+               label_cache = null;
 
 
-               pango_cache = new Pango.Layout[30];
-               for (int i = 0; i < 30; ++i)
+               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;
                        pango_cache[i] = null;
 
                // Defaults for properties
                focus_size = 20;
-               distortion_factor = 30;
+               label_column = 0;
 
                cur_el = 0;
 
                cur_el = 0;
-               focus_centre = 0;
                focus_locked = false;
 
                add_events(Gdk.EventMask.POINTER_MOTION_MASK
                focus_locked = false;
 
                add_events(Gdk.EventMask.POINTER_MOTION_MASK
@@ -92,21 +92,54 @@ public class FisheyeList : Gtk.DrawingArea
        public unowned Gtk.TreeModel get_model() { return model; }
        public void set_model (Gtk.TreeModel? model)
        {
        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 = 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;
        }
 
@@ -129,7 +162,7 @@ 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)
                                focus_layout_needed = true;
                }
                        if (old_cur_el != cur_el)
                                focus_layout_needed = true;
                }
@@ -169,42 +202,82 @@ 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
-               int undy = idx * allocation.height / list.length;
                // Distorted position
                // Distorted position
-               int pos = fisheye(undy, focus_centre, distortion_factor, 0, allocation.height);
+               int pos = fisheye(idx);
                //stderr.printf("%d %f %f\n", idx, undy, pos);
                return pos;
        }
 
                //stderr.printf("%d %f %f\n", idx, undy, pos);
                return pos;
        }
 
-       protected void focus_layout()
+       protected void build_label_cache()
        {
        {
-               // Anchor point
-               focus_centre = 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)
        {
                }
                focus_layout_needed = false;
        }
 
        protected void draw(Gdk.Drawable drawable)
        {
+               if (build_label_cache_needed)
+                       build_label_cache();
                if (focus_layout_needed)
                        focus_layout();
 
                if (focus_layout_needed)
                        focus_layout();
 
@@ -252,7 +325,7 @@ public class FisheyeList : Gtk.DrawingArea
                                pc.set_font_description(fd);
                                pango_cache[size] = new Pango.Layout(pc);
                        }
                                pc.set_font_description(fd);
                                pango_cache[size] = new Pango.Layout(pc);
                        }
-                       pango_cache[size].set_text(list[idx], -1);
+                       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();
                        var layout = pango_cache[size];
                        //stderr.printf("AZAZA %p\n", layout.get_attributes());
                        //var attrlist = layout.get_attributes().copy();
@@ -270,41 +343,54 @@ public class FisheyeList : Gtk.DrawingArea
                }
        }
 
                }
        }
 
-    /*
-     * 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 x, int a, int d, int min, int max)
-    {
-        if ( d != 0 ) {
-            bool left = x<a;
-            double v;
-           int m = (left ? a-min : max-a);
-            if ( m == 0 ) m = max-min;
-            v = (double)(x - a).abs() / m;
-            v = (double)(d+1)/(d+(1/v));
-            return (int)Math.round((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
@@ -316,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);
        }
 }
 
        }
 }