The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package iPodDB::Songlist;

=head1 NAME

iPodDB::Songlist - listing of songs in a playlist

=head1 SYNOPSIS

    my $songlist = iPodDB::Songlist->new( $frame );
    $songlist->populate( $playlist );

=head1 DESCRIPTION

This module provides a listing of the songs in an iPod playlist. It has a
few events attached to it such as: sorting the column on click, and poping up
the File menu when you right-click a row.

=cut

use base qw( Wx::ListCtrl Exporter Class::Accessor );
use Wx qw( wxLC_REPORT wxLC_VRULES wxLC_HRULES wxLIST_STATE_SELECTED wxLIST_NEXT_ALL );
use Wx::Event qw( EVT_LIST_COL_CLICK EVT_LIST_ITEM_RIGHT_CLICK EVT_LIST_ITEM_SELECTED EVT_LIST_ITEM_DESELECTED EVT_LIST_BEGIN_DRAG );
use Wx::DND;

use strict;
use warnings;

use Path::Class;

use constant ARTIST     => 0;
use constant ALBUM      => 1;
use constant TITLE      => 2;

use constant ASCENDING  => 0;
use constant DESCENDING => 1;

our $VERSION     = '0.02';
our @EXPORT_OK   = qw( song_to_path );

my @columns      = qw( artist album title );
my @column_sort  = ( ASCENDING ) x 3;
my $current_sort = ARTIST;

=head1 PROPERTIES

=head2 songs

An list of Mac::iPod::DB::Song objects

=cut

__PACKAGE__->mk_accessors( qw( songs ) );

=head1 METHODS

=head2 new( $frame )

This creates the list widget.

=cut

sub new {
    my $class  = shift;
    my $parent = shift;

    my $self   = $class->SUPER::new( $parent, -1, [ -1, -1 ], [ -1, -1 ], wxLC_REPORT | wxLC_VRULES | wxLC_HRULES );

    bless $self, $class;

    $self->InsertColumn( $_, ucfirst( $columns[ $_ ] ) ) for 0..$#columns;

    $self->EVT_LIST_COL_CLICK( $self, \&on_column_click );
    $self->EVT_LIST_ITEM_RIGHT_CLICK( $self, \&on_row_right_click );
    $self->EVT_LIST_ITEM_SELECTED( $self, \&on_select );
    $self->EVT_LIST_ITEM_DESELECTED( $self, \&on_select );
    $self->EVT_LIST_BEGIN_DRAG( $self, \&on_drag );

    return $self;
}

=head2 populate( @songs )

This will add the list of songs to the listing as well as
update the status bar.

=cut

sub populate {
    my $self   = shift;
    my @songs  = @_;
    my $status = $self->GetGrandParent->status;

    $self->DeleteAllItems;
    $status->clear;

    return unless @songs;

    my $time;
    my $filesize;
    for my $index ( 0...$#songs ) {
        my $id   = $self->InsertItem( Wx::ListItem->new );
        my $song = $songs[ $index ];

        for( 0..$#columns ) {
            my $column = $columns[ $_ ];
            $self->SetItem( $id, $_, $song->$column );
        }
        $self->SetItemData( $id, $index );

        $time     += $song->time;
        $filesize += $song->filesize;
    }

    $self->songs( \@songs );

    $status->songs( scalar @songs );
    $status->time( $time );
    $status->size( $filesize );

    $self->SetColumnWidth( $_, -1 ) for 0..$#columns;
    $self->SortItems( sub { return $self->cmp_songs( $current_sort, @_ ); } );
}

=head2 as_songobject( [ @items ] )

This function returns an array of Mac::iPod::DB::Song objects based on a list of items.
If no list is provided the currently selected items are used.

=cut

sub as_songobject {
    my $self     = shift;
    my @items    = @_;
    my @allsongs = @{ $self->songs };
    my $item     = -1;
    my @songs;

    unless( @items ) {
        while( ( $item = $self->GetNextItem( $item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED ) ) != -1 ) {
            push @items, $self->GetItemData( $item );
        }
    }

    push @songs, $allsongs[ $_ ] for @items;

    return @songs;
}

=head2 as_filedataobject( [ @items ] )

Return a Wx::FileDataObject from a list of items suitable for sending to the clipboard
or for processing in a drag and drop event. If no list is provided, the currently
selected items are used.

=cut

sub as_filedataobject {
    my $self  = shift;
    my @items = @_;
    my $path  = dir( $self->GetGrandParent->preferences->mountpoint );
    my $files = Wx::FileDataObject->new;
    
    for my $song ( $self->as_songobject( @items ) ) {
        my $file = song_to_path( $path, $song );
        $files->AddFile( $file->stringify );
    }

    return $files;
}

=head2 cmp_songs( $column, $a, $b )

This is the sorting function.

=cut

sub cmp_songs {
    my $self   = shift;
    my $column = shift;
    my $a      = shift;
    my $b      = shift;
    my @songs  = @{ $self->songs };
    my $field  = $columns[ $column ];

    return 0 unless @songs;

    if( $column_sort[ $column ] == DESCENDING ) {
        my $temp = $a;
        $a       = $b;
        $b       = $temp;
    }

    return $songs[ $a ]->$field cmp $songs[ $b ]->$field;
}

=head2 song_to_path( $directory, $song )

This utility function takes a directory (Path::Class object) and then the a song object. It then translates
the stored path to a path relative to the $directory. This method is available for export.

=cut

sub song_to_path {
    my $dir  = shift;
    my @path = split( ':', shift->path );

    for( 1..$#path ) {
        $dir = $_ == $#path ? $dir->file( $path[ $_ ] ) : $dir->subdir( $path[ $_ ] );
    }

    return $dir;
}

=head1 EVENTS

=head2 on_column_click( )

This event will sort the listing based on which column was clicked. It will
flip between ascending order and descending order on every click.

=cut

sub on_column_click {
    my $self   = shift;
    my $event  = shift;
    my $column = $event->GetColumn;

    if( $current_sort == $column ) {
        $column_sort[ $column ] ^= 1;
    }
    else {
        $current_sort = $column;
    }

    $self->SortItems( sub { return $self->cmp_songs( $column, @_ ); } );
}

=head2 on_row_right_click( )

This event will pop up the File menu as long as at least one song is selected.

=cut

sub on_row_right_click {
    my $self   = shift;
    my $event  = shift;
    my $parent = $self->GetGrandParent;

    if( $self->GetSelectedItemCount ) {
        $parent->PopupMenu( iPodDB::Menu::File->new( $parent ), $event->GetPoint );
    }
}

=head2 on_select( )

This event enables or disables options on the File menu when items are selected
or deselected.

=cut

sub on_select {
    my $self   = shift;
    my $menu   = $self->GetGrandParent->menu;
    my $enable = $self->GetSelectedItemCount ? 1 : 0;

    my @menus  = ( 'File', 'Copy To...', 'File', 'Copy' );

    while( @menus ) {
        my $item   = $menu->FindMenuItem( shift( @menus ), shift( @menus ) );
        $menu->Enable( $item, $enable );
    }
}

=head2 on_drag()

This event allows a user to drag files to a destination.

=cut

sub on_drag {
    my $self = shift;

    return unless $self->GetSelectedItemCount;

    Wx::DropSource->new( $self->as_filedataobject, $self->GetGrandParent )->DoDragDrop;
}

=head1 AUTHOR

=over 4 

=item * Brian Cassidy E<lt>bricas@cpan.orgE<gt>

=back

=head1 COPYRIGHT AND LICENSE

Copyright 2004 by Brian Cassidy

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself. 

=cut

1;