PVoice Page Editor

From WxPerl wiki

Jump to: navigation, search

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>
Personal tools
Google AdSense