CUFP
Jouke
Since I've mentioned the project so many times in the chatterbox
and many people have shown interest, I thought it would be
fair to let you all see what I have up till now. Updates will
come later.<br><br>
<b>Update: </b>I'm posting next versions on [id://83879|this node]. <br />
<b>Update II:</b> Changed the link to www.pvoice.org<br />
If you want to know more about it, check out <a href="http://www.pvoice.org">http://www.pvoice.org</a><br><br>
<code>
#!/usr/bin/perl -w
#
# pVoice
#
# pVoice is a simple speech-application for disabled people. It displays buttons
# with images, and by selecting them, the corresponding word will be spoken.
# The images have to be in JPEG fileformat and will be resized to fit in the
# defined $MAX_HEIGHT and $MAX_WIDTH.
#
# Words are grouped together in categories. These categories are defined in the
# file $SOUNDDIR/$CATFILE which has the following format:
# <pagenumber><tab><categorynumber><tab><categoryname><newline>
# Pagenumber can currently only be '1', categorynumbers start at '0', and
# categoryname is a self-defineable name for the category. There has to be a
# $SOUNDDIR/<categoryname>.$IMG_EXT and a directory $SOUNDDIR/<categoryname>.
# For example:
# if $SOUNDDIR is './data', and $IMG_EXT is set to 'jpg' and if the first
# category is named 'people', there has to be a file ./data/people.jpg and
# a directory ./data/people .
#
# In the directory <categoryname> there is a file $INDEXFILE, in which the
# words of that category are defined. The $INDEXFILE has the following format:
# <pagenumber><tab><wordnumber><tab><word><newline>
# Pagenumbers start at 1, wordnumbers start at 0. The word must correspond with
# a <word>.$SOUND_EXT and <word>.$IMG_EXT. For example:
# if $SOUNDDIR is './data/, $IMG_EXT is 'jpg', $SOUND_EXT is 'wav' and the
# current category is 'people', then if a word 'teacher' is listed in the
# $INDEXFILE, there has to be a file ./data/people/teacher.jpg (the image) and
# a file ./data/people/teacher.wav (the sound)
#
# There are three neccesary 'navigation-images', namely $UP_IMG, $NEXT_IMG and
# $PREV_IMG, which have to reside in the $SOUNDDIR. They are used for
# -respectively- going back to the category-page, going to the next wordpage and
# going to the previous wordpage.
#
# To operate the program only two 'actions' are needed. A left mousebuttonclick
# and a right mousebuttonclick. Disabled people (like my daughter) can generate
# these mouseevents with various devices like a headsupport, which generates
# a left click when the head goes left, and a right click when the head goes
# right.
# One thing to keep in mind is that the mousepointer itself should stay inside
# the program-window, but pointed at the window-background - not at an image.
# To browse through the images, keep clicking right. To activate an image
# (either a word, a category or an up/left/right arrow), click left.
#
# It works on both Win32 platforms (tested on Win98) and Unix platforms (tested
# on SuSE Linux 6.4 and 7.0) but requires the following modules:
use Tk 800.017;
use Tk::JPEG;
use GD 1.27;
use MIME::Base64;
# And on unix-systems it requires the system command 'play' to play the .wav
# files, and on Win32 systems, it requires Win32::Sound for the same purpose.
#
# Author: Jouke Visser
# Last modification: March 15, 2001
#
# Copyright (c) 2001, Jouke Visser
# pVoice may be distributed under the terms of Perl itself (either using the
# Artistic License or the GNU Public License)
require Win32::Sound if $^O eq "MSWin32";
use strict;
</code>
<readmore>
<code>
# These variables are global, so they don't have to be passed through to every
# subroutine
use vars qw (
$PROGRAM_TITLE
$BGCOLOR
$ACTIVE_BGCOLOR
$SOUNDDIR
$CURRENT_CATEGORY
$CURRENT_PAGE
$INDEXFILE
$CATFILE
$SOUND_EXT
$IMG_EXT
$NEXT_IMG
$PREV_IMG
$UP_IMG
$MAX_HEIGHT
$MAX_WIDTH
$BORDER_WIDTH
$SELECTED_BUTTON
@BUTTONS
);
#------------------------------------------------------------------------------
# Configuration-stuff
$PROGRAM_TITLE = "pVoice";
$BGCOLOR = 'white';
$ACTIVE_BGCOLOR = 'red';
$SOUNDDIR = "./data";
$CURRENT_CATEGORY = "";
$CURRENT_PAGE = 1;
$INDEXFILE = "index.txt";
$CATFILE = "cat.txt";
$SOUND_EXT = "wav";
$IMG_EXT = "jpg";
$NEXT_IMG = "volgende.$IMG_EXT"; # next-image
$PREV_IMG = "vorige.$IMG_EXT"; # previous-image
$UP_IMG = "omhoog.$IMG_EXT"; # up-image
$MAX_HEIGHT = 120;
$MAX_WIDTH = 100;
$BORDER_WIDTH = 10;
$SELECTED_BUTTON = 0;
@BUTTONS = ();
# Create the main window
my $main = MainWindow->new(-background => $BGCOLOR);
# Maximize the window
my ($screenw, $screenh) = ($main->screenwidth, $main->screenheight);
$main->geometry($screenw."x".$screenh);
# Write the title of the window
$main->title($PROGRAM_TITLE);
#create the window for the images
my $mainframe = $main->Frame(-background => $BGCOLOR)->pack();
# -fill=>'both',
# -expand=>1
# );
#configure the grid to constant cellsizes
$mainframe->gridColumnconfigure(0, -minsize => $MAX_WIDTH + 2*$BORDER_WIDTH,
-weight => 0, -pad => 4);
$mainframe->gridColumnconfigure(1, -minsize => $MAX_WIDTH + 2*$BORDER_WIDTH,
-weight => 0, -pad => 4);
$mainframe->gridColumnconfigure(2, -minsize => $MAX_WIDTH + 2*$BORDER_WIDTH,
-weight => 0, -pad => 4);
$mainframe->gridColumnconfigure(3, -minsize => $MAX_WIDTH + 2*$BORDER_WIDTH,
-weight => 0, -pad => 4);
$mainframe->gridRowconfigure (0, -weight => 0, -pad => 4);
$mainframe->gridRowconfigure (1, -minsize => $MAX_HEIGHT + 2*$BORDER_WIDTH,
-weight => 0, -pad => 4);
$mainframe->gridRowconfigure (2, -minsize => $MAX_HEIGHT + 2*$BORDER_WIDTH,
-weight => 0, -pad => 4);
$mainframe->gridRowconfigure (3, -minsize => $MAX_HEIGHT + 2*$BORDER_WIDTH,
-weight => 0, -pad => 4);
$mainframe->gridRowconfigure (4, -minsize => $MAX_HEIGHT + 2*$BORDER_WIDTH,
-weight => 0, -pad => 4);
# Write the header
my $label=$main->Label(-text => $PROGRAM_TITLE, -background => $BGCOLOR);
$label->grid(
-in => $mainframe,
-column => 0,
-row => 0,
-columnspan=> 4
);
# Read the categoryfile and draw the first page with category-buttons
my @categories = readcategoryfile();
drawcatpage($mainframe, \@categories);
# Make the first button active
$BUTTONS[$SELECTED_BUTTON]->configure(-state => 'active');
# At present time only left and right mousebuttons are used...
$main->bind('<Button-3>',
sub {
$BUTTONS[$SELECTED_BUTTON]->configure(
-state => 'normal');
if ($SELECTED_BUTTON < @BUTTONS -1)
{$SELECTED_BUTTON++}
else {$SELECTED_BUTTON=0}
$BUTTONS[$SELECTED_BUTTON]->configure(
-state => 'active');
}
);
$main->bind('<Button-1>', sub {
$BUTTONS[$SELECTED_BUTTON]->invoke;
}
);
MainLoop;
#------------------------------------------------------------------------------
sub play
# Play the sound
{
my ($label) = @_;
my $file = "$SOUNDDIR/$CURRENT_CATEGORY/$label.$SOUND_EXT";
warn "File does not exist ($file)" unless (-f $file);
return sub {system('play', "$file")} if $^O ne 'MSWin32';
return sub {Win32::Sound::Play($file)} if $^O eq 'MSWin32';
}
#------------------------------------------------------------------------------
sub readcurrentcategoryindex
{
my (@categorycontents, $page, $index, $file);
# Parse index.txt in the category directory and open the files
open (INDEX, "$SOUNDDIR/$CURRENT_CATEGORY/$INDEXFILE") || die "Can't open $SOUNDDIR/$CURRENT_CATEGORY/$INDEXFILE: $!\n";
my @indexfile = <INDEX>;
close(INDEX);
foreach(@indexfile)
{
next if !($_);
chop;
($page, $index, $file) = split(/\t/);
$categorycontents[$page]->[$index]=$file;
}
return @categorycontents;
}
#------------------------------------------------------------------------------
sub readcategoryfile
{
my (@categoryfile, $page, $index, $dir);
# Parse cat.txt
open (INDEX, "$SOUNDDIR/$CATFILE") || die "Can't open $SOUNDDIR/$CATFILE: $!\n";
my @catfile = <INDEX>;
close(INDEX);
foreach(@catfile)
{
next if !($_);
chop;
($page, $index, $dir) = split(/\t/);
$categoryfile[$page]->[$index]=$dir;
}
return @categoryfile;
}
#------------------------------------------------------------------------------
sub drawpage
{
my ($mainframe, $categorycontentsref) = @_;
my @categorycontents = @{$categorycontentsref};
my ($j, @images);
@BUTTONS = ();
die "No words found\n" unless @{$categorycontents[$CURRENT_PAGE]};
if ($CURRENT_PAGE eq 1)
{
addupbutton($mainframe, \@categorycontents);
}
else {
addprevbutton($mainframe, \@categorycontents);
}
my $y=1;
#Draw the images of the current category (first page)
for ($j=1; $j<15; $j++)
{
next if not defined($categorycontents[$CURRENT_PAGE]->[$j-1]);
my $scaledimage = $mainframe->Photo(
"button$y",
-data => scaleimage("$SOUNDDIR/$CURRENT_CATEGORY/$categorycontents[$CURRENT_PAGE]->[$j-1].$IMG_EXT"),
-format => 'jpeg'
);
my $callback = \&play($categorycontents[$CURRENT_PAGE]->[$j-1]);
$BUTTONS[$y] = $mainframe->Button(
-activebackground => $ACTIVE_BGCOLOR,
-borderwidth => $BORDER_WIDTH,
-image => "button$y",
-command => $callback,
-relief => 'flat'
);
$BUTTONS[$y]->grid(
-in => $mainframe,
-column => ($j%4),
-row => int($j/4)+1
);
$y++;
}
}
#------------------------------------------------------------------------------
sub scaleimage
{
my ($file) = @_;
open(GDFILE, $file) || die "File could not be opened ($file) : $!\n";
my $im = GD::Image->newFromJpeg(\*GDFILE);
close (GDFILE);
my ($width, $height) = $im->getBounds();
# Create an empty image with the desired dimensions
my $resizedim = $width/($height/$MAX_HEIGHT)<$MAX_WIDTH ? new GD::Image($width/($height/$MAX_HEIGHT),$MAX_HEIGHT) : new GD::Image($MAX_WIDTH, $height/($width/$MAX_WIDTH));
# Copy everything from $im and resize it into $resizedim
$resizedim->copyResized($im,0,0,0,0,$resizedim->getBounds(),$width, $height);
# encode the jpeg-output of the $resizedim
return encode_base64($resizedim->jpeg(100));
}
#------------------------------------------------------------------------------
sub drawcatpage
{
my ($mainframe, $categoryref) = @_;
my @categories = @{$categoryref};
my ($j, @images, $x);
@BUTTONS = ();
die "No categories found\n" unless $#{$categories[$CURRENT_PAGE]};
$x = 0;
#Put the images of the categories on the screen
for ($j=1; $j<=@{$categories[$CURRENT_PAGE]}; $j++)
{
my $idx = $CURRENT_PAGE eq 1 ? $x : $j;
my $cat = $categories[$CURRENT_PAGE]->[$x];
my $scaledimage = $mainframe->Photo("button$idx",
-data => scaleimage("$SOUNDDIR/$cat.$IMG_EXT"),
-format => 'jpeg'
);
my $callback = sub {
#retrieve the categorycontents
$CURRENT_CATEGORY=$cat;
my @categorycontent = readcurrentcategoryindex();
foreach (@BUTTONS)
{
$_->destroy()
}
@BUTTONS = ();
$CURRENT_PAGE=1;
drawpage($mainframe, \@categorycontent);
if ($CURRENT_PAGE < $#categorycontent) {addlastbutton($mainframe, \@categorycontent);}
else {addupbutton($mainframe, \@categorycontent);}
$SELECTED_BUTTON=0;
$BUTTONS[$SELECTED_BUTTON]->configure(-state => 'active');
};
$BUTTONS[$idx] = $mainframe->Button(
-activebackground => $ACTIVE_BGCOLOR,
-borderwidth => $BORDER_WIDTH,
-image => "button$idx",
-command => $callback,
-relief => 'flat'
);
$BUTTONS[$idx]->grid(
-in => $mainframe,
-column => ($idx%4),
-row => int($idx/4)+1
);
$x++;
}
}
#------------------------------------------------------------------------------
sub addupbutton
{
my ($mainframe, $categorycontentref) = @_;
my @categorycontent = @$categorycontentref;
my $i = @BUTTONS;
my $y = $CURRENT_PAGE == $#{$categorycontentref} ? 15 : 0;
my $scaledimage = $mainframe->Photo("button$i",
-data => scaleimage("$SOUNDDIR/$UP_IMG"),
-format => 'jpeg'
);
my $callback = sub {
my @categories = readcategoryfile();
foreach (@BUTTONS)
{
$_->destroy()
}
@BUTTONS = ();
$CURRENT_PAGE=1;
drawcatpage($mainframe, \@categories);
$SELECTED_BUTTON=0;
$BUTTONS[$SELECTED_BUTTON]->configure(-state => 'active');
};
$BUTTONS[$i] = $mainframe->Button(
-activebackground => $ACTIVE_BGCOLOR,
-borderwidth => $BORDER_WIDTH,
-image => "button$i",
-command => $callback,
-relief => 'flat'
);
$BUTTONS[$i]->grid(
-in => $mainframe,
-column => $y%4,
-row => int($y/4)+1
);
}
#------------------------------------------------------------------------------
sub addlastbutton
{
my ($mainframe, $categorycontentref) = @_;
my @categorycontent = @$categorycontentref;
my $y = 15;
my $j = @BUTTONS;
my $scaledimage = $mainframe->Photo("button$j",
-data => scaleimage("$SOUNDDIR/$NEXT_IMG"),
-format => 'jpeg'
);
my $callback = sub {
foreach (@BUTTONS)
{
$_->destroy()
}
@BUTTONS = ();
$CURRENT_PAGE++;
drawpage($mainframe, \@categorycontent);
if ($CURRENT_PAGE < $#categorycontent) {addlastbutton($mainframe, \@categorycontent);}
else {addupbutton($mainframe, \@categorycontent);}
# Make the first button active
$SELECTED_BUTTON=0;
$BUTTONS[$SELECTED_BUTTON]->configure(-state => 'active');
};
$BUTTONS[$j] = $mainframe->Button(
-activebackground => $ACTIVE_BGCOLOR,
-borderwidth => $BORDER_WIDTH,
-image => "button$j",
-command => $callback,
-relief => 'flat'
);
$BUTTONS[$j]->grid(
-in => $mainframe,
-column => ($y%4),
-row => int($y/4)+1
);
}
#------------------------------------------------------------------------------
sub addprevbutton
{
my ($mainframe, $categorycontentref) = @_;
my @categorycontent = @$categorycontentref;
my $j = @BUTTONS;
my $scaledimage = $mainframe->Photo("button$j",
-data => scaleimage("$SOUNDDIR/$PREV_IMG"),
-format => 'jpeg'
);
my $callback = sub {
foreach (@BUTTONS)
{
$_->destroy()
}
@BUTTONS = ();
$CURRENT_PAGE--;
drawpage($mainframe, \@categorycontent);
if ($CURRENT_PAGE < $#categorycontent) {addlastbutton($mainframe, \@categorycontent);}
else {addupbutton($mainframe, \@categorycontent);}
$SELECTED_BUTTON=0;
$BUTTONS[$SELECTED_BUTTON]->configure(-state => 'active');
};
$BUTTONS[$j] = $mainframe->Button(
-activebackground => $ACTIVE_BGCOLOR,
-borderwidth => $BORDER_WIDTH,
-image => "button$j",
-command => $callback,
-relief => 'flat'
);
$BUTTONS[$j]->grid(
-in => $mainframe,
-column => ($j%4),
-row => int($j/4) +1
);
}
</code>
<br><br>
<i>Jouke Visser, Perl 'Adept'</i>