The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#########################################################################################
# Package       HiPi::Apps::Control::Panel::GPIODEV
# Description:  Base for Device panels
# Created       Wed Feb 27 23:09:33 2013
# SVN Id        $Id: GPIODEV.pm 1076 2013-03-13 08:55:10Z Mark Dootson $
# Copyright:    Copyright (c) 2013 Mark Dootson
# Licence:      This work 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 of the License, or any later 
#               version.
#########################################################################################

package HiPi::Apps::Control::Panel::GPIODEV;

#########################################################################################

use strict;
use warnings;
use parent qw( HiPi::Apps::Control::Panel::Device );
use Wx qw( :sizer :id :misc :textctrl wxTheApp);
use HiPi::Apps::Control::Data::DeviceGPIO;
use HiPi::Wx::ValidatedCheckBox;
use HiPi::Language;
use HiPi::Wx::ValidatedText;
use HiPi::Apps::Control::GroupDialog;
use HiPi::Utils;
use Wx::Event( qw( EVT_BUTTON EVT_CHECKBOX EVT_LIST_ITEM_SELECTED EVT_LIST_ITEM_DESELECTED) );
use Try::Tiny;
use HiPi::Device::GPIO;

our $VERSION = '0.22';

__PACKAGE__->create_accessors( qw( list group btnexport btnmode btnlevel btnlogic btnedge device));

sub new {
    my ($class, $parent) = @_;
    my $self = $class->SUPER::new($parent);
    
    my $vdata = HiPi::Apps::Control::Data::DeviceGPIO->new;
    $self->SetValidationData($vdata);
    
    $self->device(HiPi::Device::GPIO->new);
    
    #------------------------------------------------------
    # Controls
    #------------------------------------------------------
    my $checkdev = HiPi::Wx::ValidatedCheckBox->new($self, t('Allow Group Access To Devices'),  $vdata, 'udevactive');
    my $grp_label = Wx::StaticText->new($self, wxID_ANY, t('Group Name'));
    my $grp = HiPi::Wx::ValidatedText->new($self, '', $vdata, 'udevgroup', wxTE_READONLY|wxALIGN_RIGHT);
    $self->group($grp);
    my $grpbutton = Wx::Button->new($self, wxID_ANY, '...', wxDefaultPosition, [25,-1]);
    my $list = HiPi::Apps::Control::Panel::GPIODEV::List->new($self, $vdata);
    $self->list($list);
    
    my $buttonlabel = Wx::StaticText->new($self, wxID_ANY, t('Toggle Pin Settings'));
    
    my $btnexport = Wx::Button->new($self, wxID_ANY, 'Export', wxDefaultPosition, [60, -1]);
    $self->btnexport($btnexport);
    my $btnmode = Wx::Button->new($self, wxID_ANY, 'Mode', wxDefaultPosition, [60, -1]);
    $self->btnmode($btnmode);
    my $btnlevel = Wx::Button->new($self, wxID_ANY, 'Level', wxDefaultPosition, [60, -1]);
    $self->btnlevel($btnlevel);
    my $btnlogic = Wx::Button->new($self, wxID_ANY, 'Invert', wxDefaultPosition, [60, -1]);
    $self->btnlogic($btnlogic);
    my $btnedge = Wx::Button->new($self, wxID_ANY, 'Edge', wxDefaultPosition, [60, -1]);
    $self->btnedge($btnedge);
    
    
    #------------------------------------------------------
    # Events
    #------------------------------------------------------
    EVT_BUTTON($self, $grpbutton, \&_on_evt_manage_group);
    EVT_CHECKBOX($self, $checkdev, \&_on_evt_usegroup);
    EVT_LIST_ITEM_SELECTED($self, $list, \&_on_evt_list_selected);
    EVT_LIST_ITEM_DESELECTED($self, $list, \&_on_evt_list_deselected);
    EVT_BUTTON($self, $btnexport, \&_on_evt_button_export);
    EVT_BUTTON($self, $btnmode, \&_on_evt_button_mode);
    EVT_BUTTON($self, $btnlevel, \&_on_evt_button_level);
    EVT_BUTTON($self, $btnlogic, \&_on_evt_button_logic);
    EVT_BUTTON($self, $btnedge, \&_on_evt_button_edge);
    
    #------------------------------------------------------
    # Layout
    #------------------------------------------------------
    my $msizer   = Wx::BoxSizer->new(wxVERTICAL);
    my $topsizer = Wx::BoxSizer->new(wxHORIZONTAL);
    $topsizer->Add($checkdev, 1, wxEXPAND|wxTOP|wxRIGHT|wxBOTTOM, 5);
    $topsizer->Add($grp_label, 0, wxEXPAND|wxTOP, 8);
    $topsizer->Add($grp, 0, wxEXPAND|wxTOP|wxLEFT|wxBOTTOM, 5);
    $topsizer->Add($grpbutton, 0, wxEXPAND|wxLEFT|wxTOP|wxBOTTOM, 5);
    $msizer->Add( $topsizer, 0, wxEXPAND|wxALL, 0);
    $msizer->Add( $list, 1, wxEXPAND|wxALL, 0);
    
    my $buttonsizer = Wx::BoxSizer->new(wxHORIZONTAL);
    $buttonsizer->Add($buttonlabel,1,wxEXPAND|wxTOP, 10);
    for ( $btnexport, $btnmode, $btnlevel, $btnlogic, $btnedge ) {
        $_->Enable(0);
        $buttonsizer->Add($_, 0, wxLEFT|wxBOTTOM|wxTOP|wxEXPAND, 6);
    }
    $msizer->Add($buttonsizer, 0, wxEXPAND|wxALL, 0);
    $self->SetSizer( $msizer );
    
    return $self;
}

sub _disable_buttons {
    my $self = shift;
    $self->btnexport->Enable(0);
    $self->btnmode->Enable(0);
    $self->btnlevel->Enable(0);
    $self->btnlogic->Enable(0);
    $self->btnedge->Enable(0);
}

sub _on_evt_list_selected {
    my($self, $event) = @_;
    $event->Skip(1);
    my $index = $event->GetIndex;
    $self->_disable_buttons;
    
    if(!defined($index) || $index == -1) {
        return;
    }
    
    my $pinname = $event->GetText;
    my $pin = $event->GetEventObject->pindata->{$pinname};
    unless( $pin ) {
        Wx::Logerror(qq(Unable to find data for $pinname));
        return;
    }
    
    $self->btnexport->Enable(1);    
    return unless $pin->{exported};
    $self->btnmode->Enable(1);
    
    if( $pin->{direction} ) {
        $self->btnlevel->Enable(1);
    } else {
        $self->btnlogic->Enable(1);
        $self->btnedge->Enable(1);
    }
}

sub _get_selected_pin {
    my $self = shift;
    my $item = $self->list->GetFirstSelected;
    return undef if !defined($item) || $item == -1;
    
    my $pinname = $self->list->GetItemText($item);
    my $pin = $self->list->pindata->{$pinname};
    unless( $pin ) {
        Wx::Logerror(qq(Unable to find data for $pinname));
        return undef;
    }
    return $pin;
}

sub _on_evt_list_deselected {
    my($self, $event) = @_;
    $event->Skip(1);
    $self->_disable_buttons;
}

sub _on_evt_button_export {
    my($self, $event) = @_;
    $event->Skip(1);
    my $busy = Wx::BusyCursor->new;
    my $pin = $self->_get_selected_pin;
    return unless $pin;
    try {
        if($pin->{exported}) {
            $self->device->unexport_pin($pin->{id});
        } else {
            $self->device->export_pin($pin->{id});
        }
    } catch {
        Wx::LogError('Failed to change export status of pin %s : %s', $pin->{id}, $_ );
    };
    $self->_disable_buttons;
    $self->RefreshValidatedPanel;
}

sub _on_evt_button_mode {
    my($self, $event) = @_;
    $event->Skip(1);
    my $busy = Wx::BusyCursor->new;
    my $pindata = $self->_get_selected_pin;
    return unless $pindata;
    
    my $newmode = ($pindata->{direction}) ? 0 : 1;
    {
        my $pin = $self->device->get_pin( $pindata->{id} );
        $pin->mode($newmode);
    }
    $self->_disable_buttons;
    $self->RefreshValidatedPanel;
}

sub _on_evt_button_level {
    my($self, $event) = @_;
    $event->Skip(1);
    my $busy = Wx::BusyCursor->new;
    my $pindata = $self->_get_selected_pin;
    return unless $pindata;
    
    my $newval = ($pindata->{value}) ? 0 : 1;
    {
        my $pin = $self->device->get_pin( $pindata->{id} );
        $pin->value($newval);
    }
    $self->_disable_buttons;
    $self->RefreshValidatedPanel;
}

sub _on_evt_button_logic {
    my($self, $event) = @_;
    $event->Skip(1);
    my $pindata = $self->_get_selected_pin;
    return unless $pindata;
    my $newval = ($pindata->{activelow}) ? 0 : 1;
    {
        my $pin = $self->device->get_pin( $pindata->{id} );
        $pin->active_low($newval);
    }
    $self->_disable_buttons;
    $self->RefreshValidatedPanel;
}

sub _on_evt_button_edge {
    my($self, $event) = @_;
    $event->Skip(1);
    my $pindata = $self->_get_selected_pin;
    return unless $pindata;
    
    try {
        my $edge = Wx::GetSingleChoice( t('Select the type of edge detection to apply to pin %s.', $pindata->{id}), wxTheApp->GetAppDisplayName,
                [qw(none rising falling both)], $self );
        
        my $pin = $self->device->get_pin( $pindata->{id} );
        
        if($edge) {
            $pin->interrupt($edge);
        } else {
            $pin->interrupt('none');
        }
    } catch {
        Wx::LogError($_);
        Wx::LogError(t('Failed to set interrupts for GPIO %s', $pindata->{id}));
    };
    
    $self->_disable_buttons;
    $self->RefreshValidatedPanel;
}

sub _on_evt_manage_group {
    my($self, $event) = @_;
    $event->Skip(1);
    my $group = $self->group->GetValue;
    my $dialog = HiPi::Apps::Control::GroupDialog->new($self, t('Manage Group - (%s)', $group), $group);
    $dialog->ShowModal();
    $dialog->Destroy;
}

sub _on_evt_usegroup {
    my($self, $event) = @_;
    $event->Skip(1);
    my $busy = Wx::BusyCursor->new;
    try {
        my $val = ( $event->IsChecked ) ? 1 : 0;
        my $rh = HiPi::Utils::parse_udev_rule;
        $rh->{gpio}->{active} = $val;
        HiPi::Utils::set_udev_rules( $rh );
        Wx::LogMessage('Applied new group access settings.');
    } catch {
        Wx::LogError($_);
        Wx::LogError('Failed to apply new group access settings.');
    };
    
    $self->RefreshValidatedPanel;
}

#########################################################################################

package HiPi::Apps::Control::Panel::GPIODEV::List;

#########################################################################################
use strict;
use warnings;
use Wx qw( :listctrl :id :misc);
use base qw( Wx::ListView HiPi::Class );

__PACKAGE__->create_accessors( qw( pindata ) );

sub new {
    my($class, $parent, $vdata) = @_;
    my $self = $class->SUPER::new($parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_SINGLE_SEL );
    
    # cols name exported mode value reverse interrupt
    $self->InsertColumn(0, 'Pin', wxLIST_FORMAT_LEFT, 75);
    $self->InsertColumn(1, 'RPi', wxLIST_FORMAT_LEFT, 100);
    $self->InsertColumn(2, 'Export', wxLIST_FORMAT_LEFT, 50);
    $self->InsertColumn(3, 'Mode', wxLIST_FORMAT_LEFT, 50);
    $self->InsertColumn(4, 'Level', wxLIST_FORMAT_LEFT, 50);
    $self->InsertColumn(5, 'Invert', wxLIST_FORMAT_LEFT, 50);
    $self->InsertColumn(6, 'Edge', wxLIST_FORMAT_LEFT, 100);
    
    $self->SetValidator( HiPi::Apps::Control::Panel::GPIODEV::ListValidator->new($vdata, 'pindata'));
    
    return $self;
}

#########################################################################################

package HiPi::Apps::Control::Panel::GPIODEV::ListValidator;

#########################################################################################

use strict;
use warnings;
use parent qw( HiPi::Wx::Validator );
use HiPi::Constant qw( :raspberry );

sub new {
    my $class = shift;
    my $self = $class->SUPER::new( @_ );
    return $self;
}

sub GetWindowValue {
    my $self = shift;
    $self->GetWindow->pindata;
}

sub SetWindowValue {
    my ($self, $data) = @_;
    my $list = $self->GetWindow;
    $list->pindata($data);
    $list->Freeze;
    $list->DeleteAllItems;
    
    my $index = 0;
    
    for my $pinname ( sort keys(%$data) ) {
        my $pin = $data->{$pinname};
        my $id = $list->InsertImageStringItem( $index, $pinname, -1);
        $list->SetItemData($id, $pin->{id});
        $list->SetItem($id,1, $pin->{rpi}, -1);
        
        if( $pin->{exported} ) {
            
            my $edge = 'None';
            if( $pin->{edge} == RPI_INT_BOTH ) {
                $edge = 'Both';
            } elsif( $pin->{edge} == RPI_INT_FALL ) {
                $edge = 'Falling';
            } elsif( $pin->{edge} == RPI_INT_RISE ) {
                $edge = 'Rising';
            } else {
                $edge = 'None';
            }
            $list->SetItem($id,2, 'Yes', -1);
            $list->SetItem($id,3, ($pin->{direction}) ? 'Output' : 'Input', -1);
            $list->SetItem($id,4, ($pin->{value}) ? 'High' : 'Low', -1);
            $list->SetItem($id,5, ($pin->{activelow}) ? 'Yes' : 'No', -1);
            $list->SetItem($id,6, $edge, -1);
        } else {
            $list->SetItem($id,2, 'No', -1);
            $list->SetItem($id,3, '-', -1);
            $list->SetItem($id,4, '-', -1);
            $list->SetItem($id,5, '-', -1);
            $list->SetItem($id,6, '-', -1);
        }
        
        $index ++;
    }
    $list->Thaw;
    
}

1;