PVoice Page Editor
From WxPerl wiki
This is still work in progress, but I'm posting a preview here. You will need data to get this to work for the moment (you could use the sample data that you can download from http://www.pvoice.org), plus you need the Microsoft Agent software (you can download that for free too from http://www.microsoft.com/msagent).
When I finish this script, I'll post links to all needed components here too.
<code>
#!/usr/bin/perl
use strict;
use warnings;
package EditorFrame;
use IO::File;
use Wx qw(:everything);
use Wx::Image;
use Wx::Event qw(EVT_TREE_SEL_CHANGED EVT_BUTTON EVT_MENU);
use Win32::OLE;
use Win32::OLE::Variant;
# TTS id for Dutch language for MS Agent
our $DUTCH = 0x0413;
use base qw(Wx::Frame);
our $PVOICEDATA = "c:/docume~1/jouke/mydocu~1/pvoice~1.0/page-e~1/data";
sub new {
my $class = shift;
# Call the superclass' constructor
my $self = $class->SUPER::new( undef, # parent
-1, # id
"pVoice Page Editor", # label
wxDefaultPosition, # position
wxDefaultSize); # size
$self->{UPID} = 10_000;
$self->{NEXTID} = 10_001;
$self->{PREVID} = 10_002;
$self->{TREEID} = 10_003;
$self->{UPID2} = 10_004;
# This frame has a split window, with a tree on the left and a
# panel on the right
$self->{split} = Wx::SplitterWindow->new($self, -1);
$self->{tree} = Wx::TreeCtrl->new($self->{split}, $self->{TREEID});
$self->{panel} = Wx::Panel->new($self->{split}, -1);
$self->{panel}->SetBackgroundColour(wxWHITE);
$self->{panel}->{tls} = Wx::GridSizer->new(4,4,1,1);
# tell we want automatic layout
$self->{panel}->SetAutoLayout( 1 );
$self->{panel}->SetSizer( $self->{panel}->{tls} );
$self->{panel}->{buttons} = {};
$self->{MAX_X} = 125;
$self->{MAX_Y} = 125;
$self->{currentpage} = 1;
# Read the categoryfile, translate that into the tree and define the split
$self->{cat} = $self->readcatfile("$PVOICEDATA", "cat.txt");
$self->populate_tree_list($self->{tree}, $self->{cat});
$self->{split}->SplitVertically( $self->{tree}, $self->{panel}, 150 );
# Create three menus
my $mfile = Wx::Menu->new();
my $mhelp = Wx::Menu->new();
my $medit = Wx::Menu->new();
# With one item each
my( $ID_ABOUT, $ID_DELETE, $ID_EXIT ) = ( 1..100);
$mhelp->Append( $ID_ABOUT, "&About...\tCtrl-A", "Show about dialog" );
$medit->Append( $ID_DELETE, "&Delete...\tCtrl-D", "Delete this" );
$mfile->Append( $ID_EXIT, "E&xit\tAlt-X", "Quit this program" );
# Create the menubar and append the individual menus
my $mbar = Wx::MenuBar->new();
$mbar->Append($mfile, "&File");
$mbar->Append($medit, "&Edit");
$mbar->Append($mhelp, "&Help");
# Assign the menu to our frame
$self->SetMenuBar($mbar);
# declare the events
EVT_MENU($self, $ID_EXIT, \&OnQuit);
EVT_MENU($self, $ID_ABOUT, \&OnAbout);
EVT_MENU($self, $ID_DELETE, \&OnDelete);
EVT_TREE_SEL_CHANGED( $self, $self->{TREEID}, \&OnSelChanged );
# Set up MS Agent
Win32::OLE->Initialize(Win32::OLE::COINIT_MULTITHREADED);
$self->{agent} = Win32::OLE->new('Agent.Control.2') or die Win32::OLE->LastError();
$self->{agent}->{Connected} = Variant(VT_BOOL, 1);
# Choose the character
$self->{agent}->Characters->Load('Buttercup', 'buttercup.acs');
$self->{char} = $self->{agent}->Characters('buttercup');
# Choose the language
$self->{char}->{LanguageID} = $DUTCH;
# And the female voice
$self->{char}->{TTSModeID} = '{A0DDCA40-A92C-11d1-B17B-0020AFED142E}';
# And show the agent
$self->{char}->Show();
$self->{char}->Play("Greet");
return $self;
}
sub OnQuit {
my ($self, $event) = @_;
# Let the MS Agent character wave to the user
$self->{char}->Play("Wave");
$self->{char}->Speak("\\Emp\\Doei Krista!");
sleep(4);
# and close
$self->Close( 1 );
}
sub OnDelete {
my ($self, $event) = @_;
}
sub OnAbout {
my ($self, $event) = @_;
# display a simple about box
Wx::MessageBox( "pVoice PageEditor\n" .
"Using wxPerl " . $Wx::VERSION . "\n" .
wxVERSION_STRING,
"About pVoice PageEditor", wxOK | wxICON_INFORMATION,
$self );
}
sub readcatfile
# the catfile looks like this:
#pagenumber\tindexonpage\tname
# Where pagenumber starts with 1 (pVoice 0.x supports only page 1)
# And indexonpage starts with 0 and has a highest value of 15
# And name can be any text, but a name.jpg should be available to display the image
# button and a directory called name should also be available containing the categoryindex.
{
my $self = shift;
my $datadir = shift;
my $catfile = shift;
my $cfh = IO::File->new("$datadir/$catfile", "r") || warn "Can't open $datadir/$catfile: $!";
my $cat;
while (<$cfh>)
{
chomp;
my ($page, $idx, $name) = split(/\t/);
$cat->{$page}->{$idx} = $name;
}
return $cat;
}
sub readidxfile
# The indexfile looks like this:
#pagenumber\tindexonpage\tname
# Where pagenumber starts with 1
# And indexonpage starts with 0 and has a highest value of 13
# And name can be any text as long as name.jpg and name.wav exist in that directory.
{
my $self = shift;
my $catdatadir = shift;
my $idxfile = shift;
my $ifh = IO::File->new("$catdatadir/$idxfile", "r") || warn "Can't open $catdatadir/$idxfile: $!";
my $catidx;
while (<$ifh>)
{
chomp;
my ($page, $idx, $name) = split(/\t/);
$catidx->{$page}->{$idx} = $name;
}
return $catidx;
}
sub showpage
{
my $self = shift;
# read the indexfile for this category
$self->{cidx} = $self->readidxfile("$PVOICEDATA/$self->{cat}", "index.txt");
# Remove the buttons from the top level sizer
while ($self->{panel}->{tls}->Remove(0)){}
# And destroy the buttons
foreach (keys %{$self->{panel}->{buttons}})
{
$self->{panel}->{buttons}->{$_}->Destroy();
}
# Empty the buttons hash
$self->{panel}->{buttons}={};
my $i = 1000;
my ($bmp, $button);
if ($self->{currentpage} == 1)
{
# Add the 'up' button
$button = Wx::BitmapButton->new( $self->{panel}, # parent
$self->{UPID}, # id
$self->getimgfile("$PVOICEDATA/omhoog.jpg"), #bitmap
wxDefaultPosition, # position
wxDefaultSize, # size
wxSUNKEN_BORDER); # style
$self->{panel}->{tls}->Add($button, 0, wxGROW|wxALL, 5);
$self->{panel}->{buttons}->{$self->{UPID}} = $button;
$self->{selectedbutton} = $self->{UPID};
}
else
{
# Add the 'prev' button
$button = Wx::BitmapButton->new( $self->{panel}, # parent
$self->{PREVID}, # id
$self->getimgfile("$PVOICEDATA/vorige.jpg"), #bitmap
wxDefaultPosition, # position
wxDefaultSize, # size
wxSUNKEN_BORDER); # style
$self->{panel}->{tls}->Add($button, 0, wxGROW|wxALL, 5);
$self->{panel}->{buttons}->{$self->{PREVID}} = $button;
$self->{selectedbutton} = $self->{PREVID};
}
EVT_BUTTON($self, $button, \&OnButton);
# Make the first button the selected button
$button->SetBackgroundColour(Wx::Colour->new(0,155,0));
$i++;
# draw the 'word' buttons
foreach my $idx (0..13)
{
# check if there actually is an image for the position
if (exists $self->{cidx}->{$self->{currentpage}}->{$idx})
{
# get the bitmap from the image
$bmp = $self->getimgfile("$PVOICEDATA/$self->{cat}/$self->{cidx}->{$self->{currentpage}}->{$idx}.jpg");
if ($bmp)
{
# If there is a bitmap, continue
$button = Wx::BitmapButton->new( $self->{panel}, # parent
$i, # id
$bmp, # bitmap
wxDefaultPosition, # position
wxDefaultSize, # size
wxSUNKEN_BORDER); # style
$self->{woordjes}->{$i} = $self->{cidx}->{$self->{currentpage}}->{$idx};
EVT_BUTTON($self, $button, \&OnButton);
$self->{panel}->{buttons}->{$i} = $button;
# if the creation of the button was successful...
if ($button)
{
# we add it to the top level sizer
$self->{panel}->{tls}->Add($button, 0, wxGROW|wxALL, 5);
}
else
{
# otherwise we add an empty spacer
$self->{panel}->{tls}->Add(10,10,0,wxGROW|wxALL, 5);
}
$i++;
undef $button;
}
else
{
# if the bitmap could not be created we add an emtpy spacer
$self->{panel}->{tls}->Add(10,10,0,wxGROW|wxALL, 5);
}
}
else
{
# if there is no definition for word here, add an emtpy spacer
$self->{panel}->{tls}->Add(10,10,0,wxGROW|wxALL, 5);
}
}
if (exists $self->{cidx}->{$self->{currentpage}+1})
{
# Place the last button
$button = Wx::BitmapButton->new( $self->{panel}, # parent
$self->{NEXTID}, # id
$self->getimgfile("$PVOICEDATA/volgende.jpg"), # bitmap
wxDefaultPosition, # position
wxDefaultSize, # size
wxSUNKEN_BORDER); # style
$self->{panel}->{tls}->Add($button, 0, wxGROW|wxALL, 5);
$self->{panel}->{buttons}->{$self->{NEXTID}} = $button;
}
else
{
# Add the 'up' button
$button = Wx::BitmapButton->new( $self->{panel}, # parent
$self->{UPID2}, # id
$self->getimgfile("$PVOICEDATA/omhoog.jpg"), #bitmap
wxDefaultPosition, # position
wxDefaultSize, # size
wxSUNKEN_BORDER); # style
$self->{panel}->{tls}->Add($button, 0, wxGROW|wxALL, 5);
$self->{panel}->{buttons}->{$self->{UPID2}} = $button;
}
EVT_BUTTON($self, $button, \&OnButton);
# size the window optimally and set its minimal size
$self->{panel}->{tls}->Fit( $self->{panel} );
$self->{panel}->{tls}->SetSizeHints( $self->{panel} );
# A little trick to do the updating of the panel. Don't ask why, it just
# won't update otherwise
$self->{split}->SetSashPosition($self->{split}->GetSashPosition());
}
sub OnButton
{
my ($self, $event) = @_;
# give the right button the selected status
$self->{panel}->{buttons}->{$self->{selectedbutton}}->SetBackgroundColour(wxWHITE);
$self->{panel}->{buttons}->{$event->GetId}->SetBackgroundColour(Wx::Colour->new(0,155,0));
$self->{selectedbutton} = $event->GetId;
if (($self->{selectedbutton} == $self->{UPID}) || ($self->{selectedbutton} == $self->{UPID2}))
{
# Remove the buttons from the top level sizer
while ($self->{panel}->{tls}->Remove(0)){}
# And destroy the buttons
foreach (keys %{$self->{panel}->{buttons}})
{
$self->{panel}->{buttons}->{$_}->Destroy();
}
# Empty the buttons hash
$self->{panel}->{buttons}={};
$self->{tree}->SelectItem($self->{root_id});
return;
}
if ($self->{selectedbutton} == $self->{NEXTID})
{
$self->{currentpage}++;
$self->showpage();
return;
}
if ($self->{selectedbutton} == $self->{PREVID})
{
$self->{currentpage}--;
$self->showpage();
return;
}
# Play the corresponding word
play($self, $self->{woordjes}->{$event->GetId()});
}
sub play
{
my ($self, $woord) = @_;
# The mapping done here should actually be defined in the indexfile, but
# that's something for later
$woord = '\Map="runÈ"="renÈ"\\' if $woord =~/^rene$/i;
$woord = "muriÎl" if $woord =~/^muriel$/i;
# Let the MS Agent Character speak it!
$self->{char}->Speak("$woord");
}
sub getimgfile
{
my $self = shift;
my $file = shift;
# if there is no file, return nothing
return undef if not $file;
# open the file
if (my $fh = IO::File->new("$file", "r"))
{
# it's an image, so it's binary....duh!
binmode $fh;
# We need an image handler
my $ihandler = Wx::JPEGHandler->new();
# And a new image object
my $image = Wx::Image->new();
# Load the $fh into $image, using $ihandler
$ihandler->LoadFile($image, $fh);
# resize the image
my ($x,$y) = ($image->GetWidth, $image->GetHeight);
if ($x >= $y)
{
my $factor = $x/$self->{MAX_X};
$image = $image->Scale($self->{MAX_X},$y/$factor);
}
else
{
my $factor = $y/$self->{MAX_Y};
$image = $image->Scale($x/$factor, $self->{MAX_Y});
}
#and return the image
return $image->ConvertToBitmap();
}
else
{
# if we can't open the file, warn and return nothing
warn "Can't open $file: $!";
return undef;
}
}
sub populate_tree_list
{
my $self = shift;
my $tree = shift;
my $treelist = shift;
# Add a root for the tree
$self->{root_id} = $tree->AddRoot( "pVoice", # label
-1, # image
-1, # selected image
Wx::TreeItemData->new(undef) ); # no item data
my $id;
# process each page
foreach my $page (sort {$a <=> $b} keys %{$treelist} )
{
# Append it under the root item
$id = $tree->AppendItem( $self->{root_id}, # parent
$page, # label
-1, # image
-1, # selected image
Wx::TreeItemData->new(undef) ); # no item data either
# process each item on each page
foreach my $item (sort {$a <=> $b} keys %{$treelist->{$page}})
{
# append them under the page-item
$tree->AppendItem( $id, # parent
"$item $treelist->{$page}->{$item}", # label
-1, # image
-1, # selected image
Wx::TreeItemData->new($treelist->{$page}->{$item})); # item data is the item itself
}
$tree->Expand($id);
}
$tree->Expand( $self->{root_id} );
}
sub OnSelChanged
{
my($self, $event) = @_;
# determine which item was selected
my $id = $event->GetItem;
my $cat = $self->{tree}->GetPlData( $id );
# if we don't have a category, don't do anything
return unless $cat ;
# make this the new category
$self->{cat} = $cat;
$self->{currentpage} = 1;
$self->showpage();
}
package PageEditor;
use base qw(Wx::App);
sub OnInit {
my $self = shift;
$self->SetAppName("Page Editor");
$self->SetVendorName("pVoice");
my $frame = EditorFrame->new();
my $config = Wx::ConfigBase::Get();
my $x = $config->ReadInt("X", 50);
my $y = $config->ReadInt("Y", 50);
my $w = $config->ReadInt("Width", 800);
my $h = $config->ReadInt("height", 600);
$frame->SetSize($x, $y, $w, $h);
$frame->Show(1);
return 1;
}
package main;
my $pe = new PageEditor();
$pe->MainLoop();
</code>
