[gnome-contacts/new-design] Add new Column.Row container to nicer handle the content pane



commit 172267f0e49f0afbce5731973b9178bb054c42ec
Author: Alexander Larsson <alexl redhat com>
Date:   Fri Dec 9 14:44:18 2011 +0100

    Add new Column.Row container to nicer handle the content pane
    
    This will also be useful later for the list.

 src/contacts-contact-pane.vala |  135 +++++++++--
 src/contacts-row.vala          |  489 ++++++++++++++++++++++++++++++++++++----
 2 files changed, 551 insertions(+), 73 deletions(-)
---
diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala
index 0369253..0b15299 100644
--- a/src/contacts-contact-pane.vala
+++ b/src/contacts-contact-pane.vala
@@ -413,11 +413,93 @@ public class Contacts.AvatarMenu : Menu {
   }
 }
 
+public class Contacts.FieldRow : Contacts.Row {
+  int start;
+
+  public FieldRow(ContactPane pane) {
+    base (3);
+
+    start = 0;
+
+    set_column_min_width (0, 32);
+    set_column_min_width (1, 400);
+    set_column_max_width (1, 450);
+    set_column_min_width (2, 32);
+    set_column_spacing (0, 8);
+    set_column_spacing (1, 8);
+  }
+
+  public void pack (Widget w) {
+    this.attach (w, 1, start++);
+  }
+
+  public void label (string s) {
+    var l = new Label (s);
+    l.set_halign (Align.START);
+    l.get_style_context ().add_class ("dim-label");
+    pack (l);
+  }
+
+  public void header (string s) {
+    var l = new Label (s);
+    l.set_markup (
+      "<span font='24px'>%s</span>".printf (s));
+    l.set_halign (Align.START);
+    pack (l);
+  }
+
+  public void text (string s, bool wrap = false) {
+    var l = new Label (s);
+    if (wrap) {
+      l.set_line_wrap (true);
+      l.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
+    } else {
+      l.set_ellipsize (Pango.EllipsizeMode.END);
+    }
+    l.set_halign (Align.START);
+    pack (l);
+  }
+
+  public void text_detail (string text, string detail, bool wrap = false) {
+    var grid = new Grid ();
+
+    var l = new Label (text);
+    l.set_hexpand (true);
+    l.set_halign (Align.START);
+    if (wrap) {
+      l.set_line_wrap (true);
+      l.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
+    } else {
+      l.set_ellipsize (Pango.EllipsizeMode.END);
+    }
+    grid.add (l);
+
+    l = new Label (detail);
+    l.set_halign (Align.END);
+    l.get_style_context ().add_class ("dim-label");
+
+    grid.set_halign (Align.FILL);
+    grid.add (l);
+
+    pack (grid);
+  }
+
+  public void left_add (Widget widget) {
+    this.attach (widget, 0, 0);
+    widget.set_halign (Align.END);
+  }
+
+  public void right_add (Widget widget) {
+    this.attach (widget, 2, 0);
+    widget.set_halign (Align.START);
+  }
+}
+
 public class Contacts.PersonaSheet : Grid {
   ContactPane pane;
   Persona persona;
-  Row header;
-  Row footer;
+  FieldRow header;
+  FieldRow footer;
 
   abstract class Field : Grid {
     public class string label_name;
@@ -425,14 +507,14 @@ public class Contacts.PersonaSheet : Grid {
     public PersonaSheet sheet { get; construct; }
     public int row_nr { get; construct; }
     public bool added;
-    Row label_row;
+    FieldRow label_row;
 
     public abstract void populate ();
 
     construct {
       this.set_orientation (Orientation.VERTICAL);
 
-      label_row = new Row (sheet.pane);
+      label_row = new FieldRow (sheet.pane);
       this.add (label_row);
       label_row.label (label_name);
     }
@@ -453,8 +535,8 @@ public class Contacts.PersonaSheet : Grid {
       }
     }
 
-    public Row new_row () {
-      var row = new Row (sheet.pane);
+    public FieldRow new_row () {
+      var row = new FieldRow (sheet.pane);
       this.add (row);
       return row;
     }
@@ -480,7 +562,7 @@ public class Contacts.PersonaSheet : Grid {
 	var button = new Button();
 	button.set_relief (ReliefStyle.NONE);
 	button.add (image);
-	row.right.add (button);
+	row.right_add (button);
       }
     }
   }
@@ -496,8 +578,7 @@ public class Contacts.PersonaSheet : Grid {
       var emails = Contact.sort_fields<EmailFieldDetails>(details.email_addresses);
       foreach (var email in emails) {
 	var row = new_row ();
-	row.text (email.value);
-	row.detail (TypeSet.general.format_type (email));
+	row.text_detail (email.value, TypeSet.general.format_type (email));
       }
     }
   }
@@ -513,8 +594,7 @@ public class Contacts.PersonaSheet : Grid {
       var phone_numbers = Contact.sort_fields<PhoneFieldDetails>(details.phone_numbers);
       foreach (var phone in phone_numbers) {
 	var row = new_row ();
-	row.text (phone.value);
-	row.detail (TypeSet.phone.format_type (phone));
+	row.text_detail (phone.value, TypeSet.phone.format_type (phone));
       }
     }
   }
@@ -560,7 +640,7 @@ public class Contacts.PersonaSheet : Grid {
 	var button = new Button();
 	button.set_relief (ReliefStyle.NONE);
 	button.add (image);
-	row.right.add (button);
+	row.right_add (button);
       }
     }
   }
@@ -608,10 +688,13 @@ public class Contacts.PersonaSheet : Grid {
 
       foreach (var addr in details.postal_addresses) {
 	var row = new_row ();
-	row.detail (TypeSet.general.format_type (addr));
 	string[] strs = Contact.format_address (addr.value);
+	int i = 0;
 	foreach (var s in strs) {
-	  row.text (s, true);
+	  if (i++ == 0)
+	    row.text_detail (s, TypeSet.general.format_type (addr), true);
+	  else
+	    row.text (s, true);
 	}
       }
     }
@@ -650,20 +733,17 @@ public class Contacts.PersonaSheet : Grid {
       Contact.persona_has_writable_property (persona, "postal-addresses");
 
     if (!persona.store.is_primary_store) {
-      header = new Row (pane);
+      header = new FieldRow (pane);
       this.attach (header, 0, row_nr++, 1, 1);
 
-      var label = new Label ("");
-      label.set_markup (
-	"<span font='24px'>%s</span>".printf (Contact.format_persona_store_name (persona.store)));
-      header.pack_start (label);
+      header.header (Contact.format_persona_store_name (persona.store));
 
       if (!editable) {
 	var image = new Image.from_icon_name ("changes-prevent-symbolic", IconSize.MENU);
 
 	image.get_style_context ().add_class ("dim-label");
-	header.left.add (image);
-	header.left.set (1.0f, 0.5f, 0, 1.0f);
+	image.set_valign (Align.CENTER);
+	header.left_add (image);
       }
     }
 
@@ -676,10 +756,13 @@ public class Contacts.PersonaSheet : Grid {
     }
 
     if (editable) {
-      footer = new Row (pane);
+      footer = new FieldRow (pane);
       this.attach (footer, 0, row_nr++, 1, 1);
+
       var b = new Button.with_label ("Add detail...");
-      footer.pack_start (b);
+      b.set_halign (Align.START);
+
+      footer.pack (b);
     }
 
   }
@@ -847,11 +930,11 @@ public class Contacts.ContactPane : ScrolledWindow {
 
     this.get_child().get_style_context ().add_class ("contact-pane");
 
-    var top_row = new Row (this);
-    top_row.left.set_size_request (32, -1);
+    var top_row = new FieldRow (this);
     top_grid.add (top_row);
     card_grid = new Grid ();
-    top_row.pack_start (card_grid, Align.FILL);
+    card_grid.set_vexpand (false);
+    top_row.pack (card_grid);
 
     personas_grid = new Grid ();
     personas_grid.set_orientation (Orientation.VERTICAL);
diff --git a/src/contacts-row.vala b/src/contacts-row.vala
index df16b15..3361105 100644
--- a/src/contacts-row.vala
+++ b/src/contacts-row.vala
@@ -18,69 +18,464 @@
 
 using Gtk;
 
-public class Contacts.Row : Grid {
-  public Alignment left;
-  public Grid content;
-  public Alignment right;
-  int start;
+public class Contacts.Row : Container {
+  struct RowInfo {
+    int min_height;
+    int nat_height;
+    bool expand;
+  }
 
-  public Row (ContactPane pane) {
-    this.set_orientation (Orientation.HORIZONTAL);
-    this.set_column_spacing (8);
+  struct ColumnInfo {
+    int min_width;
+    int nat_width;
+    int max_width;
+    int prio;
+    int spacing;
+  }
+  struct Child {
+    Widget? widget;
+  }
 
-    this.set_hexpand (true);
-    this.set_vexpand (false);
+  int n_columns;
+  int n_rows;
+  ColumnInfo[] column_info;
+  Child[,] row_children;
 
-    left = new Alignment (1,0,0,0);
-    left.set_hexpand (true);
-    pane.border_size_group.add_widget (left);
+  int[] cached_widths;
+  int cached_widths_for_width;
 
-    content = new Grid ();
-    content.set_size_request (450, -1);
+  public Row (int n_columns) {
+    int i;
 
-    right = new Alignment (0,0,0,0);
-    right.set_hexpand (true);
-    pane.border_size_group.add_widget (right);
+    set_has_window (false);
+    set_redraw_on_allocate (false);
+    set_hexpand (true);
 
-    this.attach (left, 0, 0, 1, 1);
-    this.attach (content, 1, 0, 1, 1);
-    this.attach (right, 2, 0, 1, 1);
-    this.show_all ();
+    this.n_columns = n_columns;
+    n_rows = 1;
+    row_children = new Child[n_columns, n_rows];
+    column_info = new ColumnInfo[n_columns];
+    for (i = 0; i < n_columns; i++) {
+      column_info[i].min_width = 0;
+      column_info[i].nat_width = -1;
+      column_info[i].max_width = -1;
+      column_info[i].prio = 0;
+    }
   }
 
-  public void pack_start (Widget w, Align align = Align.START) {
-    content.attach (w, 0, start++, 1, 1);
-    w.set_hexpand (true);
-    w.set_halign (align);
+  public void set_column_priority (int column, int priority) {
+    if (column >= n_columns)
+      return;
+
+    column_info[column].prio = priority;
+
+    cached_widths = null;
+    if (this.get_visible ())
+      this.queue_resize ();
   }
 
-  public void pack_end (Widget w) {
-    content.attach (w, 1, 0, 1, 1);
-    w.set_hexpand (false);
-    w.set_halign (Align.END);
+  public void set_column_min_width (int column, int min_width) {
+    if (column >= n_columns)
+      return;
+
+    column_info[column].min_width = min_width;
+    cached_widths = null;
+    if (this.get_visible ())
+      this.queue_resize ();
   }
 
-  public void label (string s) {
-    var l = new Label (s);
-    l.get_style_context ().add_class ("dim-label");
-    pack_start (l);
+  public void set_column_max_width (int column, int max_width) {
+    if (column >= n_columns)
+      return;
+
+    column_info[column].max_width = max_width;
+    cached_widths = null;
+    if (this.get_visible ())
+      this.queue_resize ();
+  }
+
+  public void set_column_spacing (int column, int spacing) {
+    if (column >= n_columns)
+      return;
+
+    column_info[column].spacing = spacing;
+    cached_widths = null;
+    if (this.get_visible ())
+      this.queue_resize ();
   }
 
-  public void text (string s, bool wrap = false) {
-    var l = new Label (s);
-    if (wrap) {
-      l.set_line_wrap (true);
-      l.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
-    } else {
-      l.set_ellipsize (Pango.EllipsizeMode.END);
+  public void attach (Widget widget, int attach_col, int attach_row) {
+    if (attach_col >= n_columns || attach_row >= n_rows) {
+      int old_n_columns = n_columns;
+      int old_n_rows = n_rows;
+
+      if (attach_col >= n_columns) {
+	n_columns = attach_col + 1;
+
+	var old_column_info = (owned)column_info;
+	column_info = new ColumnInfo[n_columns];
+
+	for (int i = 0; i < n_columns; i++) {
+	  if (i < old_n_columns)
+	    column_info[i] = old_column_info[i];
+	  else {
+	    column_info[i].min_width = 0;
+	    column_info[i].nat_width = -1;
+	    column_info[i].max_width = -1;
+	    column_info[i].prio = 0;
+	  }
+	}
+      }
+
+      if (attach_row >= n_rows)
+	n_rows = attach_row + 1;
+
+      var old_row_children = (owned)row_children;
+      row_children = new Child[n_columns, n_rows];
+
+      for (int row = 0; row < n_rows; row++) {
+	for (int col = 0; col < n_columns; col++) {
+	  if (row < old_n_rows &&
+	      col < old_n_columns) {
+	    row_children[col, row] = (owned)old_row_children[col, row];
+	  }
+	}
+      }
     }
-    pack_start (l);
+
+    Child *child_info = &row_children[attach_col, attach_row];
+    if (child_info.widget != null) {
+      remove (child_info.widget);
+    }
+
+    child_info.widget = widget;
+    widget.set_parent (this);
   }
 
-  public void detail (string s) {
-    var l = new Label (s);
-    l.get_style_context ().add_class ("dim-label");
-    pack_end (l);
+  public override void add (Widget widget) {
+    for (int row = 0; row < n_rows; row++) {
+      for (int col = 0; col < n_columns; col++) {
+	Child *child_info = &row_children[col, row];
+	if (child_info.widget == null) {
+	  attach (widget, col, row);
+	  return;
+	}
+      }
+    }
+    attach (widget, 0, n_rows);
   }
-}
 
+  public override void remove (Widget widget) {
+    for (int row = 0; row < n_rows; row++) {
+      for (int col = 0; col < n_columns; col++) {
+	Child *child_info = &row_children[col, row];
+	if (child_info.widget == widget) {
+          bool was_visible = widget.get_visible ();
+
+          widget.unparent ();
+
+	  child_info.widget = null;
+
+          if (was_visible && this.get_visible ())
+            this.queue_resize ();
+
+          return;
+	}
+      }
+    }
+  }
+
+  public override void forall_internal (bool include_internals,
+					Gtk.Callback callback) {
+    for (int row = 0; row < n_rows; row++) {
+      for (int col = 0; col < n_columns; col++) {
+	Child *child_info = &row_children[col, row];
+	if (child_info.widget != null) {
+	  callback (child_info.widget);
+	}
+      }
+    }
+  }
+
+  public override void compute_expand_internal (out bool hexpand, out bool vexpand) {
+    hexpand = false;
+    for (int i = 0; i < n_columns; i++) {
+      var info = &column_info[i];
+      if (info.max_width == -1 &&
+	  info.max_width !=  info.min_width) {
+	hexpand = true;
+	break;
+      }
+    }
+
+    vexpand = false;
+    for (int row = 0; row < n_rows; row++) {
+      for (int col = 0; col < n_columns; col++) {
+	Child *child_info = &row_children[col, row];
+	if (child_info.widget != null) {
+	  vexpand |= child_info.widget.compute_expand (Orientation.VERTICAL);
+	}
+      }
+    }
+  }
+
+  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 (null, out natural_width);
+    get_preferred_height_for_width (natural_width, out minimum_height, out natural_height);
+  }
+
+  int[] distribute_widths (int width) {
+    if (cached_widths != null &&
+	cached_widths_for_width == width)
+      return cached_widths;
+
+    int max_prio = 0;
+    var widths = new int[n_columns];
+
+    /* First distribute the min widths */
+    for (int i = 0; i < n_columns; i++) {
+      ColumnInfo *info = &column_info[i];
+
+      if (info->prio > max_prio)
+	max_prio = info->prio;
+
+      if (width > info.min_width) {
+	widths[i] = info.min_width;
+	width -= info.min_width;
+      } else if (width > 0) {
+	widths[i] = width;
+	width = 0;
+      } else {
+	widths[i] = 0;
+      }
+      width -= info.spacing;
+    }
+
+    /* Distribute remaining width equally among
+       children with same priority, up to max */
+    for (int prio = max_prio; width > 0 && prio >= 0; prio--) {
+      int n_children;
+
+      while (width > 0) {
+	n_children = 0;
+	int max_extra = width;
+
+	for (int i = 0; i < n_columns; i++) {
+	  ColumnInfo *info = &column_info[i];
+
+	  if (info.prio == prio &&
+	      (info.max_width < 0 ||
+	       widths[i] < info.max_width)) {
+	    n_children++;
+
+	    if (info.max_width >= 0 &&
+		info.max_width - widths[i] < max_extra)
+	      max_extra = info.max_width - widths[i];
+	  }
+	}
+
+	if (n_children == 0)
+	  break; // No more unsatisfied children on this prio
+
+	int distribute = int.min (width, max_extra * n_children);
+	width -= distribute;
+
+	int per_child = distribute / n_children;
+	int per_child_extra = distribute % n_children;
+	int per_child_extra_sum = 0;
+
+	for (int i = 0; i < n_columns; i++) {
+	  ColumnInfo *info = &column_info[i];
+
+	  if (info.prio == prio &&
+	      (info.max_width < 0 ||
+	       widths[i] < info.max_width)) {
+	    widths[i] += per_child;
+	    per_child_extra_sum += per_child_extra;
+	    if (per_child_extra_sum > distribute) {
+	      widths[i] += 1;
+	      per_child_extra_sum -= distribute;
+	    }
+	  }
+	}
+      }
+    }
+
+    cached_widths = widths;
+    cached_widths_for_width = width;
+    return widths;
+  }
+
+  int[] distribute_heights (int height, RowInfo[] row_info) {
+    var heights = new int[n_rows];
+
+    /* First distribute the min heights */
+    for (int i = 0; i < n_rows; i++) {
+      RowInfo *info = &row_info[i];
+
+      if (height > info.min_height) {
+	heights[i] = info.min_height;
+	height -= info.min_height;
+      } else if (height > 0) {
+	heights[i] = height;
+	height = 0;
+      } else {
+	heights[i] = 0;
+      }
+    }
+
+    /* Distribute remaining height equally among
+       children that have not filled up their natural size */
+    int n_children;
+
+    while (height > 0) {
+      n_children = 0;
+      int max_extra = height;
+
+      for (int i = 0; i < n_rows; i++) {
+	RowInfo *info = &row_info[i];
+
+	if (info.expand || heights[i] < info.nat_height) {
+	  n_children++;
+
+	  if (info.nat_height < heights[i] &&
+	      info.nat_height - heights[i] < max_extra)
+	    max_extra = info.nat_height - heights[i];
+	}
+      }
+
+      if (n_children == 0)
+	break;
+
+      int distribute = int.min (height, max_extra * n_children);
+      height -= distribute;
+
+      int per_child = distribute / n_children;
+      int per_child_extra = distribute % n_children;
+      int per_child_extra_sum = 0;
+
+      for (int i = 0; i < n_rows; i++) {
+	RowInfo *info = &row_info[i];
+
+	if (heights[i] < info.nat_height ||
+	    n_children == n_rows) {
+	  heights[i] += per_child;
+	  per_child_extra_sum += per_child_extra;
+	  if (per_child_extra_sum > distribute) {
+	    heights[i] += 1;
+	    per_child_extra_sum -= distribute;
+	  }
+	}
+      }
+    }
+
+    return heights;
+  }
+
+  private RowInfo[] get_row_heights_for_widths (int[] widths) {
+    RowInfo[] info = new RowInfo[n_rows];
+
+    for (int row = 0; row < n_rows; row++) {
+      int row_min = 0, row_nat = 0;
+      bool first = true;
+      bool expand = false;
+
+      for (int col = 0; col < n_columns; col++) {
+	Child *child_info = &row_children[col, row];
+	if (child_info.widget != null) {
+	  int child_min, child_nat;
+
+	  child_info.widget.get_preferred_height_for_width (widths[col], out child_min, out child_nat);
+
+	  expand |= child_info.widget.compute_expand (Orientation.VERTICAL);
+
+	  if (first) {
+	    first = false;
+	    row_min = child_min;
+	    row_nat = child_nat;
+	  } else {
+	    row_min = int.max (row_min, child_min);
+	    row_nat = int.max (row_nat, child_nat);
+	  }
+	}
+      }
+      info[row].min_height = row_min;
+      info[row].nat_height = row_nat;
+      info[row].expand = expand;
+    }
+    return info;
+  }
+
+  public override void get_preferred_height_for_width (int width, out int minimum_height, out int natural_height) {
+    var widths = distribute_widths (width);
+    var row_info =  get_row_heights_for_widths (widths);
+
+    minimum_height = 0;
+    natural_height = 0;
+
+    for (int row = 0; row < n_rows; row++) {
+      minimum_height += row_info[row].min_height;
+      natural_height += row_info[row].nat_height;
+    }
+  }
+
+  public override void get_preferred_width (out int minimum_width, out int natural_width) {
+    minimum_width = 0;
+    natural_width = 0;
+    for (int i = 0; i < n_columns; i++) {
+      minimum_width += column_info[i].min_width;
+      if (column_info[i].nat_width >= 0)
+	natural_width += column_info[i].nat_width;
+      else if (column_info[i].max_width >= 0)
+	natural_width += column_info[i].max_width;
+      else
+	natural_width += column_info[i].min_width;
+      minimum_width += column_info[i].spacing;
+      natural_width += column_info[i].spacing;
+    }
+  }
+
+  public override void get_preferred_width_for_height (int height, out int minimum_width, out int natural_width) {
+    get_preferred_width (out minimum_width, out natural_width);
+  }
+
+  public override void size_allocate (Gtk.Allocation allocation) {
+    var widths = distribute_widths (allocation.width);
+    var row_info = get_row_heights_for_widths (widths);
+    var heights = distribute_heights (allocation.height, row_info);
+
+    set_allocation (allocation);
+
+    int y = 0;
+    for (int row = 0; row < n_rows; row ++) {
+      int x = 0;
+      for (int col = 0; col < n_columns; col++) {
+	Child *child_info = &row_children[col, row];
+	if (child_info.widget != null) {
+	  Allocation child_allocation = { 0, 0, 0, 0};
+
+	  child_allocation.width = widths[col]; // calculate_child_width (child_info.widget, widths[col]);
+	  child_allocation.height = heights[row];
+
+	  if (get_direction () == TextDirection.RTL)
+	    child_allocation.x = allocation.x + allocation.width - x - child_allocation.width;
+	  else
+	    child_allocation.x = allocation.x + x;
+	  child_allocation.y = allocation.y + y;
+	  child_info.widget.size_allocate (child_allocation);
+	}
+	x += widths[col] + column_info[col].spacing;
+      }
+      y += heights[row];
+    }
+  }
+}



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