[baobab] Use EggListBox for the location list



commit b3fe8f50b356d677ba634eb21cdc26119bdb823c
Author: Paolo Borelli <paolo borelli nice-software com>
Date:   Mon Jul 23 18:06:54 2012 +0200

    Use EggListBox for the location list
    
    The file egg-list-box.vala is simply cut and pasted from its module. We
    should use a git subtree or git submodule, but gnome-autogen script
    fails when it finds the configure.ac of the submodule and I was too lazy
    to figure out why.

 src/Makefile.am           |    1 +
 src/baobab-main-window.ui |   11 -
 src/baobab-window.vala    |   16 +-
 src/egg-list-box.vala     | 1098 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1110 insertions(+), 16 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index ba18e04..8eb7740 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -40,6 +40,7 @@ baobab_SOURCES = \
 	baobab-location-widget.vala	\
 	baobab-scanner.vala		\
 	baobab-window.vala		\
+	egg-list-box.vala		\
 	main.vala			\
 	$(BUILT_SOURCES)
 
diff --git a/src/baobab-main-window.ui b/src/baobab-main-window.ui
index f569bdf..de08e90 100644
--- a/src/baobab-main-window.ui
+++ b/src/baobab-main-window.ui
@@ -195,17 +195,6 @@
                 <property name="visible">True</property>
                 <property name="vexpand">True</property>
                 <property name="hexpand">True</property>
-                <child>
-                  <object class="GtkViewport" id="volume-viewport">
-                    <property name="visible">True</property>
-                    <child>
-                      <object class="GtkGrid" id="location-view">
-                        <property name="visible">True</property>
-                        <property name="orientation">vertical</property>
-                      </object>
-                    </child>
-                  </object>
-                </child>
               </object>
             </child>
            </object>
diff --git a/src/baobab-window.vala b/src/baobab-window.vala
index ca30887..249907c 100644
--- a/src/baobab-window.vala
+++ b/src/baobab-window.vala
@@ -32,9 +32,10 @@ namespace Baobab {
         Gtk.InfoBar infobar;
         Gtk.Label infobar_primary;
         Gtk.Label infobar_secondary;
+        Gtk.ScrolledWindow location_scroll;
+        Egg.ListBox location_list;
         Gtk.TreeView treeview;
         Gtk.Notebook chart_notebook;
-        Gtk.Grid location_view;
         Chart rings_chart;
         Chart treemap_chart;
         Gtk.Spinner spinner;
@@ -121,12 +122,13 @@ namespace Baobab {
             infobar = builder.get_object ("infobar") as Gtk.InfoBar;
             infobar_primary = builder.get_object ("infobar-primary-label") as Gtk.Label;
             infobar_secondary = builder.get_object ("infobar-secondary-label") as Gtk.Label;
+            location_scroll = builder.get_object ("volume-scrolled-window") as Gtk.ScrolledWindow;
+            location_list = builder.get_object ("location-view") as Egg.ListBox;
             treeview = builder.get_object ("treeview") as Gtk.TreeView;
             chart_notebook = builder.get_object ("chart-notebook") as Gtk.Notebook;
             rings_chart = builder.get_object ("rings-chart") as Chart;
             treemap_chart = builder.get_object ("treemap-chart") as Chart;
             spinner = builder.get_object ("spinner") as Gtk.Spinner;
-            location_view = builder.get_object ("location-view") as Gtk.Grid;
 
             setup_home_page ();
             setup_treeview (builder);
@@ -317,7 +319,7 @@ namespace Baobab {
         }
 
         void update_locations () {
-            location_view.foreach ((widget) => { widget.destroy (); });
+            location_list.foreach ((widget) => { widget.destroy (); });
 
             foreach (var location in location_monitor.get_locations ()) {
                 LocationWidget loc_widget;
@@ -338,13 +340,17 @@ namespace Baobab {
                     });
                 }
 
-                location_view.add (loc_widget);
+                location_list.add (loc_widget);
             }
 
-            location_view.show_all ();
+            location_list.show_all ();
         }
 
         void setup_home_page () {
+            location_list = new Egg.ListBox ();
+            location_scroll.add_with_viewport (location_list);
+            location_list.set_adjustment (location_scroll.get_vadjustment ());
+
             location_monitor = LocationMonitor.get ();
             location_monitor.changed.connect (() => { update_locations (); });
             update_locations ();
diff --git a/src/egg-list-box.vala b/src/egg-list-box.vala
new file mode 100644
index 0000000..aa70a9d
--- /dev/null
+++ b/src/egg-list-box.vala
@@ -0,0 +1,1098 @@
+/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2011 Alexander Larsson <alexl redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gtk;
+
+public class Egg.ListBox : Container {
+  public delegate bool FilterFunc (Widget child);
+  public delegate void UpdateSeparatorFunc (ref Widget? separator, Widget child, Widget? before);
+
+  private class ChildInfo {
+    public Widget widget;
+    public Widget? separator;
+    public SequenceIter<ChildInfo> iter;
+    public int y;
+    public int height;
+
+    public ChildInfo (Widget widget) {
+      this.widget = widget;
+    }
+  }
+
+  private Sequence<ChildInfo> children;
+  private HashTable<unowned Widget, unowned ChildInfo> child_hash;
+  private HashTable<unowned Widget, unowned ChildInfo> separator_hash;
+  private CompareDataFunc<Widget>? sort_func;
+  private FilterFunc? filter_func;
+  private UpdateSeparatorFunc? update_separator_func;
+  private unowned ChildInfo selected_child;
+  private unowned ChildInfo prelight_child;
+  private unowned ChildInfo cursor_child;
+  bool active_child_active;
+  private unowned ChildInfo active_child;
+  private SelectionMode selection_mode;
+  private Adjustment? adjustment;
+  private bool activate_single_click;
+
+  /* DnD */
+  private Widget drag_highlighted_widget;
+  private uint auto_scroll_timeout_id;
+
+  construct {
+    set_can_focus (true);
+    set_has_window (true);
+    set_redraw_on_allocate (true);
+
+    selection_mode = SelectionMode.SINGLE;
+    activate_single_click = true;
+
+    children = new Sequence<ChildInfo>();
+    child_hash = new HashTable<unowned Widget, unowned ChildInfo> (GLib.direct_hash, GLib.direct_equal);
+    separator_hash = new HashTable<unowned Widget, unowned ChildInfo> (GLib.direct_hash, GLib.direct_equal);
+  }
+
+  ~ListBox (){
+    if (auto_scroll_timeout_id != 0)
+      Source.remove (auto_scroll_timeout_id);
+  }
+
+  public unowned Widget? get_selected_child (){
+    if (selected_child != null)
+      return selected_child.widget;
+
+    return null;
+  }
+
+  public unowned Widget? get_child_at_y (int y){
+      unowned ChildInfo? child = find_child_at_y (y);
+
+      if (child == null)
+        return null;
+
+      return child.widget;
+  }
+
+  public void select_child (Widget? child) {
+    unowned ChildInfo? info = null;
+    if (child != null)
+      info = lookup_info (child);
+    update_selected (info);
+  }
+
+  public virtual signal void child_selected (Widget? child) {
+  }
+
+  public virtual signal void child_activated (Widget? child) {
+  }
+
+  public void set_adjustment (Adjustment? adjustment) {
+    this.adjustment = adjustment;
+    this.set_focus_vadjustment (adjustment);
+  }
+
+  public void add_to_scrolled (ScrolledWindow scrolled) {
+    scrolled.add_with_viewport (this);
+    this.set_adjustment (scrolled.get_vadjustment ());
+  }
+
+  public void set_selection_mode (SelectionMode mode) {
+    if (mode == SelectionMode.MULTIPLE) {
+      warning ("Multiple selections not supported");
+      return;
+    }
+    selection_mode = mode;
+    if (mode == SelectionMode.NONE)
+      update_selected (null);
+  }
+
+  public void set_filter_func (owned FilterFunc? f) {
+    filter_func = (owned)f;
+    refilter ();
+  }
+
+  public void set_separator_funcs (owned UpdateSeparatorFunc? update_separator) {
+    update_separator_func = (owned)update_separator;
+    reseparate ();
+  }
+
+  public void refilter () {
+    apply_filter_all ();
+    reseparate ();
+    queue_resize ();
+  }
+
+  public void resort () {
+    children.sort (do_sort);
+    reseparate ();
+    queue_resize ();
+  }
+
+  public void reseparate () {
+    for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
+      update_separator (iter);
+    }
+    queue_resize ();
+  }
+
+  public void set_sort_func (owned CompareDataFunc<Widget>? f) {
+    sort_func = (owned)f;
+    resort ();
+  }
+
+  public void child_changed (Widget widget) {
+    unowned ChildInfo? info = lookup_info (widget);
+    if (info == null)
+      return;
+
+    var prev_next = get_next_visible (info.iter);
+
+    if (sort_func != null) {
+      children.sort_changed (info.iter, do_sort);
+      this.queue_resize ();
+    }
+    apply_filter (info.widget);
+    if (this.get_visible ()) {
+      update_separator (info.iter);
+      update_separator (get_next_visible (info.iter));
+      update_separator (prev_next);
+    }
+
+  }
+
+  public void set_activate_on_single_click (bool single) {
+    activate_single_click = single;
+  }
+
+  /****** Implementation ***********/
+
+  private int do_sort (ChildInfo a, ChildInfo b) {
+    return sort_func (a.widget, b.widget);
+  }
+
+  [Signal (action=true)]
+  public virtual signal void activate_cursor_child () {
+    select_and_activate (cursor_child);
+  }
+
+  [Signal (action=true)]
+  public virtual signal void toggle_cursor_child () {
+    if (cursor_child == null)
+      return;
+
+    if (selection_mode == SelectionMode.SINGLE &&
+	selected_child == cursor_child)
+      update_selected (null);
+    else
+      select_and_activate (cursor_child);
+  }
+
+  [Signal (action=true)]
+  public virtual signal void move_cursor (MovementStep step, int count) {
+    Gdk.ModifierType state;
+
+    bool modify_selection_pressed = false;
+
+    if (Gtk.get_current_event_state (out state)) {
+      var modify_mod_mask =  this.get_modifier_mask (Gdk.ModifierIntent.MODIFY_SELECTION);
+      if ((state & modify_mod_mask) == modify_mod_mask)
+	modify_selection_pressed = true;
+    }
+
+    ChildInfo? child = null;
+    switch (step) {
+    case MovementStep.BUFFER_ENDS:
+      if (count < 0)
+	child = get_first_visible ();
+      else
+	child = get_last_visible ();
+      break;
+    case MovementStep.DISPLAY_LINES:
+      if (cursor_child != null) {
+	SequenceIter<ChildInfo>? iter = cursor_child.iter;
+
+	while (count < 0 && iter != null) {
+	  iter = get_previous_visible (iter);
+	  count++;
+	}
+	while (count > 0 && iter != null) {
+	  iter = get_next_visible (iter);
+	  count--;
+	}
+	if (iter != null && !iter.is_end ()) {
+	  child = iter.get ();
+	}
+      }
+      break;
+    case MovementStep.PAGES:
+      int page_size = 100;
+      if (adjustment != null)
+	page_size = (int) adjustment.get_page_increment ();
+
+      if (cursor_child != null) {
+	int start_y = cursor_child.y;
+	int end_y = start_y;
+	SequenceIter<ChildInfo>? iter = cursor_child.iter;
+
+	child = cursor_child;
+	if (count < 0) {
+	  /* Up */
+
+	  while (iter != null && !iter.is_begin ()) {
+	    iter = get_previous_visible (iter);
+	    if (iter == null)
+	      break;
+	    ChildInfo prev = iter.get ();
+	    if (prev.y < start_y - page_size)
+	      break;
+	    child = prev;
+	  }
+	} else {
+	  /* Down */
+
+	  while (iter != null && !iter.is_end ()) {
+	    iter = get_next_visible (iter);
+	    if (iter.is_end ())
+	      break;
+	    ChildInfo next = iter.get ();
+	    if (next.y > start_y + page_size)
+	      break;
+	    child = next;
+	  }
+	}
+	end_y = child.y;
+	if (end_y != start_y && adjustment != null)
+	  adjustment.value += end_y - start_y;
+
+      }
+      break;
+    default:
+      return;
+    }
+
+    if (child == null) {
+      error_bell ();
+      return;
+    }
+
+    update_cursor (child);
+    if (!modify_selection_pressed)
+      update_selected (child);
+  }
+
+  private static void add_move_binding (BindingSet binding_set, uint keyval, Gdk.ModifierType modmask,
+					MovementStep step, int count) {
+    BindingEntry.add_signal (binding_set, keyval, modmask,
+			     "move-cursor", 2,
+			     typeof (MovementStep), step,
+			     typeof (int), count);
+
+    if ((modmask & Gdk.ModifierType.CONTROL_MASK) == Gdk.ModifierType.CONTROL_MASK)
+      return;
+
+    BindingEntry.add_signal (binding_set, keyval, Gdk.ModifierType.CONTROL_MASK,
+			     "move-cursor", 2,
+			     typeof (MovementStep), step,
+			     typeof (int), count);
+  }
+
+  [CCode (cname = "klass")]
+  private static extern void *workaround_for_local_var_klass;
+  static construct {
+    unowned BindingSet binding_set = BindingSet.by_class (workaround_for_local_var_klass);
+
+    add_move_binding (binding_set, Gdk.Key.Home, 0,
+		      MovementStep.BUFFER_ENDS, -1);
+    add_move_binding (binding_set, Gdk.Key.KP_Home, 0,
+		      MovementStep.BUFFER_ENDS, -1);
+
+    add_move_binding (binding_set, Gdk.Key.End, 0,
+		      MovementStep.BUFFER_ENDS, 1);
+    add_move_binding (binding_set, Gdk.Key.KP_End, 0,
+		      MovementStep.BUFFER_ENDS, 1);
+
+    add_move_binding (binding_set, Gdk.Key.Up, Gdk.ModifierType.CONTROL_MASK,
+		      MovementStep.DISPLAY_LINES, -1);
+    add_move_binding (binding_set, Gdk.Key.KP_Up, Gdk.ModifierType.CONTROL_MASK,
+		      MovementStep.DISPLAY_LINES, -1);
+
+    add_move_binding (binding_set, Gdk.Key.Down, Gdk.ModifierType.CONTROL_MASK,
+		      MovementStep.DISPLAY_LINES, 1);
+    add_move_binding (binding_set, Gdk.Key.KP_Down, Gdk.ModifierType.CONTROL_MASK,
+		      MovementStep.DISPLAY_LINES, 1);
+
+    add_move_binding (binding_set, Gdk.Key.Page_Up, 0,
+		      MovementStep.PAGES, -1);
+    add_move_binding (binding_set, Gdk.Key.KP_Page_Up, 0,
+		      MovementStep.PAGES, -1);
+
+    add_move_binding (binding_set, Gdk.Key.Page_Down, 0,
+		      MovementStep.PAGES, 1);
+    add_move_binding (binding_set, Gdk.Key.KP_Page_Down, 0,
+		      MovementStep.PAGES, 1);
+
+    BindingEntry.add_signal (binding_set, Gdk.Key.space, Gdk.ModifierType.CONTROL_MASK,
+			     "toggle-cursor-child", 0);
+
+    activate_signal = GLib.Signal.lookup ("activate-cursor-child", typeof (ListBox));
+  }
+
+  unowned ChildInfo? find_child_at_y (int y) {
+    unowned ChildInfo? child_info = null;
+    for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
+      unowned ChildInfo info = iter.get ();
+      if (y >= info.y && y < info.y + info.height) {
+	child_info = info;
+	break;
+      }
+    }
+    return child_info;
+  }
+
+  private void update_cursor (ChildInfo? child) {
+    cursor_child = child;
+    this.grab_focus ();
+    this.queue_draw ();
+    if (child != null && adjustment != null) {
+      Allocation allocation;
+      this.get_allocation (out allocation);
+      adjustment.clamp_page (cursor_child.y + allocation.y,
+			     cursor_child.y + allocation.y + cursor_child.height);
+    }
+  }
+
+  private void update_selected (ChildInfo? child) {
+    if (child != selected_child &&
+	(child == null || selection_mode != SelectionMode.NONE)) {
+      selected_child = child;
+      child_selected (selected_child != null ? selected_child.widget : null);
+      queue_draw ();
+    }
+    if (child != null)
+      update_cursor (child);
+  }
+
+  private void select_and_activate (ChildInfo? child) {
+    unowned Widget? w = null;
+    if (child != null)
+      w = child.widget;
+    update_selected (child);
+    if (w != null)
+      child_activated (w);
+  }
+
+  private void update_prelight (ChildInfo? child) {
+    if (child != prelight_child) {
+      prelight_child = child;
+      queue_draw ();
+    }
+  }
+
+  private void update_active (ChildInfo? child) {
+    bool val = active_child == child;
+    if (active_child != null && val != active_child_active) {
+      active_child_active = val;
+      queue_draw ();
+    }
+  }
+
+  public override bool enter_notify_event (Gdk.EventCrossing event) {
+    if (event.window != get_window ())
+      return false;
+
+    unowned ChildInfo? child = find_child_at_y ((int)event.y);
+    update_prelight (child);
+    update_active (child);
+
+    return false;
+  }
+
+  public override bool leave_notify_event (Gdk.EventCrossing event) {
+    if (event.window != get_window ())
+      return false;
+
+    unowned ChildInfo? child;
+    if (event.detail != Gdk.NotifyType.INFERIOR) {
+      child = null;
+    } else {
+      child = find_child_at_y ((int)event.y);
+    }
+    update_prelight (child);
+    update_active (child);
+
+    return false;
+  }
+
+  public override bool motion_notify_event (Gdk.EventMotion event) {
+    unowned ChildInfo? child = find_child_at_y ((int)event.y);
+    update_prelight (child);
+    update_active (child);
+
+    return false;
+  }
+
+  public override bool button_press_event (Gdk.EventButton event) {
+    if (event.button == 1) {
+      unowned ChildInfo? child = find_child_at_y ((int)event.y);
+      if (child != null) {
+        active_child = child;
+        active_child_active = true;
+        queue_draw ();
+
+        if (event.type == Gdk.EventType.2BUTTON_PRESS &&
+            !activate_single_click && child.widget != null)
+          child_activated (child.widget);
+      }
+
+      /* TODO: Should mark as active while down, and handle grab breaks */
+    }
+    return false;
+  }
+
+  public override bool button_release_event (Gdk.EventButton event) {
+    if (event.button == 1) {
+      if (active_child != null && active_child_active)
+        if (activate_single_click)
+          select_and_activate (active_child);
+        else
+          update_selected (active_child);
+      active_child = null;
+      active_child_active = false;
+      queue_draw ();
+    }
+    return false;
+  }
+
+  public override void show () {
+    reseparate ();
+    base.show ();
+  }
+
+  public override bool focus (DirectionType direction) {
+    bool had_focus;
+    bool focus_into;
+    unowned Widget recurse_into = null;
+
+    focus_into = true;
+    had_focus = has_focus;
+
+    unowned ChildInfo? current_focus_child = null;
+    unowned ChildInfo? next_focus_child = null;
+
+    if (had_focus) {
+      /* If on row, going right, enter into possible container */
+      if (direction == DirectionType.RIGHT || direction == DirectionType.TAB_FORWARD) {
+        if (cursor_child != null)
+          recurse_into = cursor_child.widget;
+      }
+      current_focus_child = cursor_child;
+      /* Unless we're going up/down we're always leaving
+      the container */
+      if (direction != DirectionType.UP && direction != DirectionType.DOWN)
+        focus_into = false;
+    } else if (this.get_focus_child () != null) {
+      /* There is a focus child, always navigat inside it first */
+      recurse_into = this.get_focus_child ();
+      current_focus_child = lookup_info (recurse_into);
+
+      /* If exiting child container to the right, exit row */
+      if (direction == DirectionType.RIGHT || direction == DirectionType.TAB_FORWARD)
+        focus_into = false;
+
+      /* If exiting child container to the left, select row or out */
+      if (direction == DirectionType.LEFT || direction == DirectionType.TAB_BACKWARD) {
+        next_focus_child = current_focus_child;
+      }
+    } else {
+      /* If coming from the left, enter into possible container */
+      if (direction == DirectionType.LEFT || direction == DirectionType.TAB_BACKWARD) {
+        if (selected_child != null)
+          recurse_into = selected_child.widget;
+      }
+    }
+
+    if (recurse_into != null) {
+      if (recurse_into.child_focus (direction))
+        return true;
+    }
+
+    if (!focus_into)
+      return false; // Focus is leaving us
+
+    /* TODO: This doesn't handle up/down going into a focusable separator */
+
+    if (next_focus_child == null) {
+      if (current_focus_child != null) {
+        if (direction == DirectionType.UP) {
+          var i = get_previous_visible (current_focus_child.iter);
+          if (i != null)
+            next_focus_child = i.get ();
+        } else {
+          var i = get_next_visible (current_focus_child.iter);
+          if (!i.is_end ())
+            next_focus_child = i.get ();
+        }
+      } else {
+          switch (direction) {
+            case DirectionType.DOWN:
+            case DirectionType.TAB_FORWARD:
+              next_focus_child = get_first_visible ();
+              break;
+            case DirectionType.UP:
+            case DirectionType.TAB_BACKWARD:
+              next_focus_child = get_last_visible ();
+              break;
+            default:
+              next_focus_child = selected_child;
+              if (next_focus_child == null)
+                next_focus_child = get_first_visible ();
+              break;
+           }
+      }
+    }
+
+    if (next_focus_child == null) {
+      if (direction == DirectionType.UP || direction == DirectionType.DOWN) {
+        error_bell ();
+        return true;
+      }
+
+      return false;
+    }
+
+    bool modify_selection_pressed = false;
+    Gdk.ModifierType state;
+
+    if (Gtk.get_current_event_state (out state)) {
+      var modify_mod_mask =  this.get_modifier_mask (Gdk.ModifierIntent.MODIFY_SELECTION);
+      if ((state & modify_mod_mask) == modify_mod_mask)
+        modify_selection_pressed = true;
+    }
+
+    update_cursor (next_focus_child);
+    if (!modify_selection_pressed)
+      update_selected (next_focus_child);
+
+    return true;
+  }
+
+  private struct ChildFlags {
+    unowned ChildInfo child;
+    StateFlags state;
+
+    public static ChildFlags *find_or_add (ref ChildFlags[] array, ChildInfo to_find) {
+      for (int i = 0; i < array.length; i++) {
+	if (array[i].child == to_find)
+	  return &array[i];
+      }
+      array.resize (array.length+1);
+      array[array.length-1].child = to_find;
+      array[array.length-1].state = 0;
+      return &array[array.length-1];
+    }
+  }
+
+  public override bool draw (Cairo.Context cr) {
+    Allocation allocation;
+    this.get_allocation (out allocation);
+
+    unowned StyleContext context = this.get_style_context ();
+
+    context.render_background (cr,
+			       0, 0, allocation.width, allocation.height);
+
+    ChildFlags[] flags = {};
+
+    if (selected_child != null) {
+      var found = ChildFlags.find_or_add (ref flags, selected_child);
+      found.state |= StateFlags.SELECTED;
+    }
+
+    if (prelight_child != null) {
+      var found = ChildFlags.find_or_add (ref flags, prelight_child);
+      found.state |= StateFlags.PRELIGHT;
+    }
+
+    if (active_child != null && active_child_active) {
+      var found = ChildFlags.find_or_add (ref flags, active_child);
+      found.state |= StateFlags.ACTIVE;
+    }
+
+    foreach (unowned ChildFlags? flag in flags) {
+      context.save ();
+      context.set_state (flag.state);
+      context.render_background (cr,
+				 0, flag.child.y,
+				 allocation.width, flag.child.height);
+      context.restore ();
+    }
+
+    if (has_visible_focus() && cursor_child != null) {
+      context.render_focus (cr, 0, cursor_child.y,
+			    allocation.width, cursor_child.height);
+    }
+
+    base.draw (cr);
+
+    return true;
+  }
+
+  public override void realize () {
+    Allocation allocation;
+    get_allocation (out allocation);
+    set_realized (true);
+
+    Gdk.WindowAttr attributes = { };
+    attributes.x = allocation.x;
+    attributes.y = allocation.y;
+    attributes.width = allocation.width;
+    attributes.height = allocation.height;
+    attributes.window_type = Gdk.WindowType.CHILD;
+    attributes.event_mask = this.get_events () |
+		       Gdk.EventMask.ENTER_NOTIFY_MASK |
+		       Gdk.EventMask.LEAVE_NOTIFY_MASK |
+		       Gdk.EventMask.POINTER_MOTION_MASK |
+		       Gdk.EventMask.EXPOSURE_MASK |
+		       Gdk.EventMask.BUTTON_PRESS_MASK |
+		       Gdk.EventMask.BUTTON_RELEASE_MASK;
+
+    attributes.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT;
+    var window = new Gdk.Window (get_parent_window (), attributes,
+				 Gdk.WindowAttributesType.X |
+				 Gdk.WindowAttributesType.Y);
+    this.get_style_context ().set_background (window);
+    window.set_user_data (this);
+    this.set_window (window);
+  }
+
+  private void apply_filter (Widget child) {
+    bool do_show = true;
+    if (filter_func != null)
+      do_show = filter_func (child);
+    child.set_child_visible (do_show);
+  }
+
+  private void apply_filter_all () {
+    for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
+      unowned ChildInfo child_info = iter.get ();
+      apply_filter (child_info.widget);
+    }
+  }
+
+  private unowned ChildInfo? get_first_visible () {
+    for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
+      unowned ChildInfo child_info = iter.get ();
+      unowned Widget widget = child_info.widget;
+      if (widget.get_visible () && widget.get_child_visible ())
+	return child_info;
+    }
+    return null;
+  }
+
+  private unowned ChildInfo? get_last_visible () {
+    var iter = children.get_end_iter ();
+    while (!iter.is_begin ()) {
+      iter = iter.prev ();
+      unowned ChildInfo child_info = iter.get ();
+      unowned Widget widget = child_info.widget;
+      if (widget.get_visible () && widget.get_child_visible ())
+	return child_info;
+    }
+    return null;
+  }
+
+  private SequenceIter<ChildInfo>? get_previous_visible (SequenceIter<ChildInfo> _iter) {
+    if (_iter.is_begin())
+      return null;
+    var iter = _iter;
+
+    do {
+      iter = iter.prev ();
+
+      unowned ChildInfo child_info = iter.get ();
+      unowned Widget widget = child_info.widget;
+      if (widget.get_visible () && widget.get_child_visible ())
+	return iter;
+    } while (!iter.is_begin ());
+
+    return null;
+  }
+
+  private SequenceIter<ChildInfo>? get_next_visible (SequenceIter<ChildInfo> _iter) {
+    if (_iter.is_end())
+      return _iter;
+
+    var iter = _iter;
+    do {
+      iter = iter.next ();
+
+      if (!iter.is_end ()) {
+	unowned ChildInfo child_info = iter.get ();
+	unowned Widget widget = child_info.widget;
+	if (widget.get_visible () && widget.get_child_visible ())
+	  return iter;
+      }
+    } while (!iter.is_end ());
+
+    return iter;
+  }
+
+  private void update_separator (SequenceIter<ChildInfo>? iter) {
+    if (iter == null || iter.is_end ())
+      return;
+
+    unowned ChildInfo info = iter.get ();
+    var before_iter = get_previous_visible (iter);
+    var widget = info.widget;
+    Widget? before_widget = null;
+    if (before_iter != null) {
+      unowned ChildInfo before_info = before_iter.get ();
+      before_widget = before_info.widget;
+    }
+
+    if (update_separator_func != null &&
+	widget.get_visible () &&
+	widget.get_child_visible ()) {
+      var old_separator = info.separator;
+      update_separator_func (ref info.separator, widget, before_widget);
+      if (old_separator != info.separator) {
+	if (old_separator != null) {
+	  old_separator.unparent ();
+	  separator_hash.remove (old_separator);
+	}
+	if (info.separator != null) {
+	  separator_hash.set (info.separator, info);
+	  info.separator.set_parent (this);
+	  info.separator.show ();
+	}
+	this.queue_resize ();
+      }
+    } else if (info.separator != null) {
+      separator_hash.remove (info.separator);
+      info.separator.unparent ();
+      info.separator = null;
+      this.queue_resize ();
+    }
+  }
+
+  private unowned ChildInfo? lookup_info (Widget widget) {
+    return child_hash.get (widget);
+  }
+
+  public override void add (Widget widget) {
+    ChildInfo info = new ChildInfo (widget);
+    SequenceIter<ChildInfo> iter;
+
+    child_hash.set (widget, info);
+
+    if (sort_func != null)
+      iter = children.insert_sorted (info, do_sort);
+    else
+      iter = children.append (info);
+
+    info.iter = iter;
+    widget.set_parent (this);
+
+    apply_filter (widget);
+
+    if (this.get_visible ()) {
+      update_separator (iter);
+      update_separator (get_next_visible (iter));
+    }
+
+    widget.notify["visible"].connect (child_visibility_changed);
+  }
+
+  private void child_visibility_changed (Object object, ParamSpec pspec) {
+    if (this.get_visible ()) {
+      unowned ChildInfo? info = lookup_info (object as Widget);
+      if (info != null) {
+	update_separator (info.iter);
+	update_separator (get_next_visible (info.iter));
+      }
+    }
+  }
+
+  public override void remove (Widget widget) {
+    bool was_visible = widget.get_visible ();
+
+    widget.notify["visible"].disconnect (child_visibility_changed);
+
+    unowned ChildInfo? info = lookup_info (widget);
+    if (info == null) {
+      info = separator_hash.get (widget);
+      if (info != null) {
+	separator_hash.remove (widget);
+	info.separator = null;
+	widget.unparent ();
+	if (was_visible && this.get_visible ())
+	  this.queue_resize ();
+
+      } else
+	warning ("Tried to remove non-child %p\n", widget);
+      return;
+    }
+
+    if (info.separator != null) {
+      separator_hash.remove (info.separator);
+      info.separator.unparent ();
+      info.separator = null;
+    }
+
+    if (info == selected_child)
+      update_selected (null);
+    if (info == prelight_child)
+      prelight_child = null;
+    if (info == cursor_child)
+      cursor_child = null;
+    if (info == active_child)
+      active_child = null;
+
+    var next = get_next_visible (info.iter);
+
+    widget.unparent ();
+
+    child_hash.remove (widget);
+    children.remove (info.iter);
+
+    if (this.get_visible ())
+      update_separator (next);
+
+    if (was_visible && this.get_visible ())
+      this.queue_resize ();
+  }
+
+  public override void forall_internal (bool include_internals,
+					Gtk.Callback callback) {
+    var iter = children.get_begin_iter ();
+    while (!iter.is_end ()) {
+      unowned ChildInfo child_info = iter.get ();
+      iter = iter.next();
+      if (child_info.separator != null && include_internals)
+	callback (child_info.separator);
+      callback (child_info.widget);
+    }
+  }
+
+  public override void compute_expand_internal (out bool hexpand, out bool vexpand) {
+    base.compute_expand_internal (out hexpand, out vexpand);
+    /* We don't expand vertically beyound the minimum size */
+    vexpand = false;
+  }
+
+  public override Type child_type () {
+    return typeof (Widget);
+  }
+
+  public override Gtk.SizeRequestMode get_request_mode () {
+    return SizeRequestMode.HEIGHT_FOR_WIDTH;
+  }
+
+  public override void get_preferred_height (out int minimum_height, out int natural_height) {
+    int natural_width;
+    get_preferred_width_internal (null, out natural_width);
+    get_preferred_height_for_width_internal (natural_width, out minimum_height, out natural_height);
+  }
+
+  public override void get_preferred_height_for_width (int width, out int minimum_height, out int natural_height) {
+    minimum_height = 0;
+    unowned StyleContext context = this.get_style_context ();
+    int focus_width, focus_pad;
+    context.get_style ("focus-line-width", out focus_width,
+		       "focus-padding", out focus_pad);
+    for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
+      unowned ChildInfo child_info = iter.get ();
+      unowned Widget widget = child_info.widget;
+      int child_min;
+
+      if (!widget.get_visible () || !widget.get_child_visible ())
+	continue;
+
+      if (child_info.separator != null) {
+	child_info.separator.get_preferred_height_for_width (width, out child_min, null);
+	minimum_height += child_min;
+      }
+
+      widget.get_preferred_height_for_width (width - 2 * (focus_width + focus_pad),
+					     out child_min, null);
+      minimum_height += child_min + 2 * (focus_width + focus_pad);
+    }
+
+    /* We always allocate the minimum height, since handling
+       expanding rows is way too costly, and unlikely to
+       be used, as lists are generally put inside a scrolling window
+       anyway.
+    */
+    natural_height = minimum_height;
+  }
+
+  public override void get_preferred_width (out int minimum_width, out int natural_width) {
+    unowned StyleContext context = this.get_style_context ();
+    int focus_width, focus_pad;
+    context.get_style ("focus-line-width", out focus_width,
+		       "focus-padding", out focus_pad);
+    minimum_width = 0;
+    natural_width = 0;
+    for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
+      unowned ChildInfo child_info = iter.get ();
+      unowned Widget widget = child_info.widget;
+      int child_min, child_nat;
+
+      if (!widget.get_visible () || !widget.get_child_visible ())
+	continue;
+
+      widget.get_preferred_width (out child_min, out child_nat);
+      minimum_width = int.max (minimum_width, child_min + 2 * (focus_width + focus_pad));
+      natural_width = int.max (natural_width, child_nat + 2 * (focus_width + focus_pad));
+
+      if (child_info.separator != null) {
+	child_info.separator.get_preferred_width (out child_min, out child_nat);
+	minimum_width = int.max (minimum_width, child_min);
+	natural_width = int.max (natural_width, child_nat);
+      }
+    }
+  }
+
+  public override void get_preferred_width_for_height (int height, out int minimum_width, out int natural_width) {
+    get_preferred_width_internal (out minimum_width, out natural_width);
+  }
+
+  public override void size_allocate (Gtk.Allocation allocation) {
+    Allocation child_allocation = { 0, 0, 0, 0};
+    Allocation separator_allocation = { 0, 0, 0, 0};
+
+    set_allocation (allocation);
+
+    var window = get_window();
+    if (window != null)
+      window.move_resize (allocation.x,
+			  allocation.y,
+			  allocation.width,
+			  allocation.height);
+
+    var context = this.get_style_context ();
+    int focus_width, focus_pad;
+    context.get_style ("focus-line-width", out focus_width,
+		       "focus-padding", out focus_pad);
+
+    child_allocation.x = 0 + focus_width + focus_pad;
+    child_allocation.y = 0;
+    child_allocation.width = allocation.width - 2 * (focus_width + focus_pad);
+
+    separator_allocation.x = 0;
+    separator_allocation.width = allocation.width;
+
+    for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
+      unowned ChildInfo child_info = iter.get ();
+      unowned Widget widget = child_info.widget;
+      int child_min;
+
+      if (!widget.get_visible () || !widget.get_child_visible ()) {
+	child_info.y = child_allocation.y;
+	child_info.height = 0;
+	continue;
+      }
+
+      if (child_info.separator != null) {
+	child_info.separator.get_preferred_height_for_width (allocation.width, out child_min, null);
+	separator_allocation.height = child_min;
+	separator_allocation.y = child_allocation.y;
+
+	child_info.separator.size_allocate (separator_allocation);
+
+	child_allocation.y += child_min;
+      }
+
+      child_info.y = child_allocation.y;
+      child_allocation.y += focus_width + focus_pad;
+
+      widget.get_preferred_height_for_width (child_allocation.width, out child_min, null);
+      child_allocation.height = child_min;
+
+      child_info.height = child_allocation.height + 2 * (focus_width + focus_pad);
+      widget.size_allocate (child_allocation);
+
+      child_allocation.y += child_min + focus_width + focus_pad;
+    }
+  }
+
+  /* DnD */
+
+  public void drag_unhighlight_widget () {
+    if (drag_highlighted_widget == null)
+      return;
+
+    Gtk.drag_unhighlight (drag_highlighted_widget);
+    drag_highlighted_widget = null;
+  }
+
+  public void drag_highlight_widget (Widget widget) {
+    if (drag_highlighted_widget == widget)
+      return;
+
+    drag_unhighlight_widget ();
+
+    Gtk.drag_highlight (widget);
+    drag_highlighted_widget = widget;
+  }
+
+  public override void drag_leave (Gdk.DragContext context, uint time_) {
+    drag_unhighlight_widget ();
+
+    if (auto_scroll_timeout_id != 0) {
+      Source.remove (auto_scroll_timeout_id);
+      auto_scroll_timeout_id = 0;
+    }
+  }
+
+  public override bool drag_motion (Gdk.DragContext context, int x, int y, uint time_) {
+    /* Auto-scroll during Dnd if cursor is moving into the top/bottom portion of the
+     * box. */
+    if (auto_scroll_timeout_id != 0) {
+      Source.remove (auto_scroll_timeout_id);
+      auto_scroll_timeout_id = 0;
+    }
+
+    if (adjustment == null)
+     return false;
+
+    /* Part of the view triggering auto-scroll */
+    double size = 30;
+    int move = 0;
+
+    if (y < adjustment.value + size) {
+      /* Scroll up */
+      move = -1;
+    }
+    else if (y > (adjustment.value + adjustment.page_size) - size) {
+      /* Scroll down */
+      move = 1;
+    }
+
+    if (move == 0)
+      return false;
+
+    auto_scroll_timeout_id = Timeout.add (150, () =>
+      {
+        adjustment.value += (adjustment.step_increment * move);
+
+        return true;
+      });
+
+    return false;
+  }
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]