mirror of
https://gitlab.gnome.org/GNOME/gitg
synced 2024-10-31 04:58:58 +00:00
7ece2dfcb5
When we move a child, three childrens separators may be affected. The moved child, the one after the new position, and the one *after* the previous position (as it got a new before). However, we were looking at the one before the previous location => bug.
990 lines
28 KiB
Vala
990 lines
28 KiB
Vala
/* -*- 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;
|
|
|
|
construct {
|
|
set_can_focus (true);
|
|
set_has_window (true);
|
|
set_redraw_on_allocate (true);
|
|
|
|
selection_mode = SelectionMode.SINGLE;
|
|
|
|
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);
|
|
}
|
|
|
|
public unowned Widget? get_selected_child (){
|
|
if (selected_child != null)
|
|
return selected_child.widget;
|
|
|
|
return null;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
/****** 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 ();
|
|
}
|
|
|
|
/* 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)
|
|
select_and_activate (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) {
|
|
/* TODO: Handle null cursor child */
|
|
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));
|
|
}
|
|
}
|
|
|
|
public override void remove (Widget widget) {
|
|
bool was_visible = widget.get_visible ();
|
|
|
|
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) {
|
|
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
|
unowned ChildInfo child_info = iter.get ();
|
|
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;
|
|
}
|
|
}
|
|
}
|