2 * zavai - simple interface to the OpenMoko (or to the FSO stack)
4 * Copyright (C) 2009 Enrico Zini <enrico@enricozini.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 public class FisheyeList : Gtk.DrawingArea
25 protected Gtk.TreeModel model;
26 protected Gdk.Pixmap backing_store;
28 // Pango layouts cached for speed
29 protected const int max_font_size = 30;
30 protected Pango.Layout[] pango_cache;
32 // Labels to show, extracted from the model
33 protected string[] label_cache;
34 protected bool build_label_cache_needed;
39 protected int focus_first;
40 protected int focus_end;
41 protected int[] focus_starts;
42 protected bool focus_locked;
43 protected int focus_centre;
44 protected bool focus_layout_needed;
46 protected int _focus_size;
47 public int focus_size {
48 get { return _focus_size; }
51 focus_starts = new int[value+1];
52 focus_layout_needed = true;
57 protected int _distortion_factor;
58 public int distortion_factor {
59 get { return _distortion_factor; }
61 _distortion_factor = value;
62 focus_layout_needed = true;
67 protected int _label_column;
68 public int label_column {
69 get { return _label_column; }
71 _label_column = value;
72 build_label_cache_needed = true;
77 //public virtual signal void cursor_changed ();
78 public signal void row_activated(Gtk.TreePath path);
87 pango_cache = new Pango.Layout[max_font_size];
88 for (int i = 0; i < pango_cache.length; ++i)
89 pango_cache[i] = null;
91 // Defaults for properties
93 distortion_factor = 30;
100 add_events(Gdk.EventMask.POINTER_MOTION_MASK
101 | Gdk.EventMask.BUTTON_PRESS_MASK
102 | Gdk.EventMask.BUTTON_RELEASE_MASK);
105 public unowned Gtk.TreeModel get_model() { return model; }
106 public void set_model (Gtk.TreeModel? model)
108 if (this.model != null)
110 this.model.row_changed -= on_row_changed;
111 this.model.row_deleted -= on_row_deleted;
112 this.model.row_has_child_toggled -= on_row_has_child_toggled;
113 this.model.row_inserted -= on_row_inserted;
114 this.model.rows_reordered -= on_rows_reordered;
117 this.model.row_changed += on_row_changed;
118 this.model.row_deleted += on_row_deleted;
119 this.model.row_has_child_toggled += on_row_has_child_toggled;
120 this.model.row_inserted += on_row_inserted;
121 this.model.rows_reordered += on_rows_reordered;
122 build_label_cache_needed = true;
126 private void on_row_changed(Gtk.TreePath path, Gtk.TreeIter iter) { build_label_cache_needed = true; }
127 private void on_row_deleted(Gtk.TreePath path) { build_label_cache_needed = true; }
128 private void on_row_has_child_toggled(Gtk.TreePath path, Gtk.TreeIter iter) { build_label_cache_needed = true; }
129 private void on_row_inserted(Gtk.TreePath path, Gtk.TreeIter iter) { build_label_cache_needed = true; }
130 private void on_rows_reordered(Gtk.TreePath path, Gtk.TreeIter iter, void* new_order) { build_label_cache_needed = true; }
132 /* Mouse button got pressed over widget */
134 public override bool button_press_event(Gdk.EventButton event)
136 stderr.printf("Mouse pressed on %d %s\n", cur_el, label_cache[cur_el]);
141 /* Mouse button got released */
142 public override bool button_release_event(Gdk.EventButton event)
144 stderr.printf("Mouse released on %d %s\n", cur_el, label_cache[cur_el]);
146 // Emit row_activated if applicable
150 if (model.iter_nth_child(out iter, null, cur_el))
152 Gtk.TreePath path = model.get_path(iter);
159 /* Mouse pointer moved over widget */
160 public override bool motion_notify_event(Gdk.EventMotion event)
162 int old_cur_el = cur_el;
163 int x = (int)event.x;
164 int y = (int)event.y;
166 focus_locked = !focus_layout_needed && x < allocation.width/2 && y >= focus_starts[0] && y < focus_starts[focus_end - focus_first];
170 for (int idx = focus_first; idx < focus_end; ++idx)
171 if (y < focus_starts[idx-focus_first+1])
178 cur_el = y * label_cache.length / allocation.height;
179 if (old_cur_el != cur_el)
180 focus_layout_needed = true;
183 //stderr.printf("MOTION %f %f CE %d\n", event.x, event.y, cur_el);
184 if (old_cur_el != cur_el)
192 public override bool configure_event (Gdk.EventConfigure event)
194 backing_store = new Gdk.Pixmap(window, allocation.width, allocation.height, -1);
195 focus_layout_needed = true;
200 /* Widget is asked to draw itself */
201 public override bool expose_event (Gdk.EventExpose event)
203 if (backing_store == null)
208 window.draw_drawable(
209 get_style().fg_gc[Gtk.StateType.NORMAL],
211 event.area.x, event.area.y,
212 event.area.x, event.area.y,
213 event.area.width, event.area.height);
218 public override void style_set(Gtk.Style? previous_style)
220 // Reset the pango cache if the pango context changes
221 for (int i = 0; i < pango_cache.length; ++i)
222 pango_cache[i] = null;
224 public override void direction_changed(Gtk.TextDirection previous_direction)
226 // Reset the pango cache if the pango context changes
227 for (int i = 0; i < pango_cache.length; ++i)
228 pango_cache[i] = null;
231 protected int el_y(int idx)
234 int undy = idx * allocation.height / label_cache.length;
235 // Distorted position
236 int pos = fisheye(undy, focus_centre, _distortion_factor, 0, allocation.height);
237 //stderr.printf("%d %f %f\n", idx, undy, pos);
241 protected void build_label_cache()
245 label_cache = new string[0];
248 if (!model.get_iter_first(out iter))
250 label_cache = new string[0];
254 int count = model.iter_n_children(null);
255 label_cache = new string[count];
260 model.get(iter, _label_column, out val, -1);
261 label_cache[i] = val;
263 } while (model.iter_next(ref iter));
267 build_label_cache_needed = false;
268 focus_layout_needed = true;
271 protected void focus_layout()
273 if (label_cache.length == 0)
281 focus_centre = cur_el*allocation.height/label_cache.length;
283 focus_first = cur_el > _focus_size/2 ? cur_el-_focus_size/2 : 0;
284 focus_end = focus_first + _focus_size;
285 if (focus_end >= label_cache.length) focus_end = label_cache.length;
287 // Compute starting positions for all items in focus
288 for (int idx = focus_first; idx < focus_end; ++idx)
290 int posprev = idx == 0 ? 0 : el_y(idx-1);
292 int posnext = idx == label_cache.length-1 ? 1 : el_y(idx+1);
293 int y0 = (pos+posprev)/2;
294 int y1 = (pos+posnext)/2;
296 focus_starts[idx - focus_first] = y0;
297 focus_starts[idx - focus_first + 1] = y1;
300 focus_layout_needed = false;
303 protected void draw(Gdk.Drawable drawable)
305 if (build_label_cache_needed)
307 if (focus_layout_needed)
310 Gtk.Style style = get_style();
313 drawable.draw_rectangle(style.bg_gc[Gtk.StateType.NORMAL], true, 0, 0, allocation.width, allocation.height);
316 drawable.draw_rectangle(style.bg_gc[Gtk.StateType.ACTIVE], true,
317 0, focus_starts[0], allocation.width/2, focus_starts[focus_end - focus_first]);
319 // Focus movement area
320 drawable.draw_rectangle(style.bg_gc[Gtk.StateType.INSENSITIVE], true,
321 allocation.width/2, 0, allocation.width, allocation.height);
323 // Create a Cairo context
324 //var context = Gdk.cairo_create (drawable);
326 // Paint items around focus
327 //context.select_font_face(style.font_desc.get_family(), Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL);
328 for (int idx = focus_first; idx < focus_end; ++idx)
330 int y0 = focus_starts[idx - focus_first];
331 int y1 = focus_starts[idx - focus_first + 1];
333 Gtk.StateType itemState = Gtk.StateType.NORMAL;
336 itemState = Gtk.StateType.SELECTED;
337 drawable.draw_rectangle(style.bg_gc[itemState], true,
338 0, y0, allocation.width, y1-y0);
342 // TODO: cache pango contexts instead of fontdescs
343 int size = (y1-y0)*80/100;
344 if (size <= 0) size = 1;
345 if (size >= pango_cache.length) size = pango_cache.length - 1;
346 if (pango_cache[size] == null)
348 var fd = style.font_desc.copy();
349 fd.set_absolute_size(size*Pango.SCALE);
350 var pc = create_pango_context();
351 pc.set_font_description(fd);
352 pango_cache[size] = new Pango.Layout(pc);
354 pango_cache[size].set_text(label_cache[idx], -1);
355 var layout = pango_cache[size];
356 //stderr.printf("AZAZA %p\n", layout.get_attributes());
357 //var attrlist = layout.get_attributes().copy();
358 //stderr.printf("AL %p\n", attrlist);
359 //var attrlist = new Pango.AttrList();
360 //stderr.printf("SIZE %d\n", y1-y0);
361 //attrlist.insert(new Pango.AttrSize(y1-y0));
362 //var attrlist = layout.get_attributes();
363 //attrlist.change(new Pango.AttrSize(y1-y0));
364 //layout.set_attributes(attrlist);
365 //layout.set_height(y1-y0);
367 //layout.get_pixel_size(out w, out h);
368 Gdk.draw_layout(drawable, style.fg_gc[itemState], 0, y0, layout);
373 * The following function is adapted from Prefuse's FisheyeDistortion.java.
375 * A relevant annotation from Prefuse:
377 * For more details on this form of transformation, see Manojit Sarkar and
378 * Marc H. Brown, "Graphical Fisheye Views of Graphs", in Proceedings of
379 * CHI'92, Human Factors in Computing Systems, p. 83-91, 1992. Available
380 * online at <a href="http://citeseer.ist.psu.edu/sarkar92graphical.html">
381 * http://citeseer.ist.psu.edu/sarkar92graphical.html</a>.
385 * Distorts an item's coordinate.
386 * @param x the undistorted coordinate
387 * @param coordinate of the anchor or focus point
388 * @param d disortion factor
389 * @param min the beginning of the display
390 * @param max the end of the display
391 * @return the distorted coordinate
393 private int fisheye(int x, int a, int d, int min, int max)
398 int m = (left ? a-min : max-a);
399 if ( m == 0 ) m = max-min;
400 v = (double)(x - a).abs() / m;
401 v = (double)(d+1)/(d+(1/v));
402 return (int)Math.round((left?-1:1)*m*v + a);
409 public class Fisheye : Gtk.Window
414 destroy += Gtk.main_quit;
416 var list = new FisheyeList();
419 var store = new Gtk.ListStore(1, typeof(string));
421 var infd = FileStream.open("/tmp/names", "r");
424 for (int i = 0; i < 300; ++i)
426 store.append(out iter);
427 store.set(iter, 0, "Antani %d".printf(i), -1);
433 string line = infd.gets(buf);
436 store.append(out iter);
437 store.set(iter, 0, line, -1);
441 list.set_model(store);
445 static int main (string[] args) {
448 var fe = new Fisheye();
449 fe.set_size_request(200, 300);