# Copyright 2007, 2008, 2009, 2010 Kevin Ryde

# This file is part of Gtk2-Ex-WidgetBits.
#
# Gtk2-Ex-WidgetBits 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 3, or (at your option) any
# later version.
#
# Gtk2-Ex-WidgetBits 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 Gtk2-Ex-WidgetBits.  If not, see <http://www.gnu.org/licenses/>.

package Gtk2::Ex::TreeModelBits;
use 5.008;
use strict;
use warnings;
use Gtk2;

use Exporter;
our @ISA = ('Exporter');
our @EXPORT_OK = qw(column_contents
                    remove_matching_rows
                    all_column_types
                    iter_prev);

our $VERSION = 32;

# uncomment this to run the ### lines
#use Smart::Comments;

sub column_contents {
  my ($model, $column) = @_;
  my @ret;

  # pre-extend, helpful for a list model style, likely to do little for an
  # actual tree
  $#ret = $model->iter_n_children(undef) - 1;

  my $pos = 0;
  $model->foreach (sub {
                     my ($model, $path, $iter) = @_;
                     $ret[$pos++] = $model->get_value ($iter, $column);
                     return 0; # keep walking
                   });
  # iterating should give n_children, trim @ret if it doesn't
  ### assert: $pos >= scalar(@ret)
  $#ret = $pos-1;

  return @ret;
}

# If a remove() might end up removing more than one row then it's expected
# to leave $iter at whatever next row then exists (at the same depth).
# A multi-remove happens for instance in Gtk2::Ex::ListModelConcat when it's
# presenting two or more copies of one submodel.
# Gtk2::Ex::TreeModelFilter::Change::remove() asks for similar from its
# child remove().
#
sub remove_matching_rows {
  my $model = shift;
  my $subr = shift;

  my @pending;
  my $iter = $model->get_iter_first;

  for (;;) {
    # undef at end of one level, pop to upper level, or finished if no upper
    $iter ||= pop @pending || last;
    ### looking at: $model->get_path($iter)->to_string

    if ($subr->($model, $iter, @_)) {
      if (! $model->remove ($iter)) {
        $iter = undef; # no more at this depth
      }
      # otherwise $iter updated to next row
      next;
    }

    my $child = $model->iter_children ($iter);
    $iter = $model->iter_next ($iter);

    if ($child) {
      ### descend to child: $model->get_path($child)->to_string
      push @pending, $iter;
      $iter = $child;
    }
  }
}

sub all_column_types {
  my ($model) = @_;
  return map { $model->get_column_type($_) } 0 .. $model->get_n_columns - 1;
}

sub iter_prev {
  my ($model, $iter) = @_;
  my $path = $model->get_path ($iter);
  return ($path->prev
          ? $model->get_iter ($path)  # path moved
          : undef); # no more nodes (last path index was 0)
}

1;
__END__

=for stopwords TreeModel ListStore Ryde Gtk2 Gtk2-Ex-WidgetBits Perl-Gtk Gtk lookup

=head1 NAME

Gtk2::Ex::TreeModelBits - miscellaneous TreeModel helpers

=head1 SYNOPSIS

 use Gtk2::Ex::TreeModelBits;

=head1 FUNCTIONS

=over 4

=item C<@types = Gtk2::Ex::TreeModelBits::all_column_types ($model)>

Return a list of all the column types in C<$model>.  For example to create
another ListStore with the same types as an existing one,

    my $new_store = Gtk2::ListStore->new
      (Gtk2::Ex::TreeModelBits::all_column_types ($old_store));

=item C<@values = Gtk2::Ex::TreeModelBits::column_contents ($model, $col)>

Return a list of all the values in column number C<$col> of a
C<Gtk2::TreeModel> object C<$model>.

Any tree structure in the model is flattened out for the return.  A parent
row's column value comes first, followed by the column values from its
children, recursively, as per C<< $model->foreach >>.

=item C<Gtk2::Ex::TreeModelBits::remove_matching_rows ($store, $subr, ...)>

Remove from C<$store> all rows passing C<$subr>.  C<$store> can be a
C<Gtk2::TreeStore>, a C<Gtk2::ListStore>, or another type with the same
style C<< $store->remove >> method.  C<$subr> is called

    $want_remove = &$subr ($store, $iter, ...)

where C<$iter> is the row being considered, and any extra arguments to
C<remove_matching_rows> are passed on to C<$subr>.  C<$subr> should return
true if it wants to remove the row.

The order rows are considered and removed is unspecified except that a
parent row is tested before its children, and the children of course are not
tested if the parent is removed.

If you use an old Gtk 2.0.x and might pass a C<Gtk2::ListStore> or
C<Gtk2::TreeStore> to C<remove_matching_rows> then get Perl-Gtk 1.240 or
higher to have the C<remove> method on those classes return a flag the same
as in Gtk 2.2 and up.  Otherwise on those stores C<remove_matching_rows>
will stop after the first row removed.

=item C<$iter = Gtk2::Ex::TreeModelBits::iter_prev ($model, $iter)>

Return a new C<Gtk2::TreeIter> which is the row preceding the given
C<$iter>, at the same depth.  If C<$iter> is the first element at its depth
then the return is C<undef>.

This is like a reverse of C<iter_next>.  Going to the previous row is not a
native operation and might be a touch slow if a model uses say a linked list
and so must chase through data for a path lookup.

=back

=head1 EXPORTS

Nothing is exported by default, but the functions can be requested in usual
C<Exporter> style,

    use Gtk2::Ex::TreeModelBits 'remove_matching_rows';
    remove_matching_rows ($store, sub { ... });

There's no C<:all> tag since this module is meant as a grab-bag of functions
and to import as-yet unknown things would be asking for name clashes.

=head1 SEE ALSO

L<Gtk2::ListStore>, L<Gtk2::TreeModel>, L<Gtk2::Ex::WidgetBits>,
L<Gtk2::Ex::TreeModel::ImplBits>

=head1 HOME PAGE

L<http://user42.tuxfamily.org/gtk2-ex-widgetbits/index.html>

=head1 LICENSE

Copyright 2007, 2008, 2009, 2010 Kevin Ryde

Gtk2-Ex-WidgetBits 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 3, or (at your option) any later
version.

Gtk2-Ex-WidgetBits 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
Gtk2-Ex-WidgetBits.  If not, see L<http://www.gnu.org/licenses/>.

=cut
