The LCD, Angular Meter, and Linear Meter are all now in module format using Class::Accessor. Sure makes changes easier/quicker. There is also an additional process control example displaying 4 linear meters and 2 angular(round) meters. The updated code will be(has been) posted to github http://github.com/jmlynesjr/wxPerl-Module-Examples in a couple of days. For now, enjoy the clock code below.
Update1: Need to add another flag to signal 12/24 hour display format. And light one of the decimal points on am/pm. Creeping elegance..:).
Update2: Made the fix for 12/24 hour display and realised that I have polluted the LCDdisplay class with data that is only needed by the LCDClock program. I need to derive a sub-class of LDCdisplay to hold this data, It's always something...
Update3: Derived class version done and will be the version posted to github when I can get to it.
Update4: Alarm Clock version completed. Uses a wxMediaControl within an AudibleAlarm.pm class to play a selected MP3 file as the wakeup alarm sound.
Update5: Latest code now posted to GitHub. A new repository was created, so the URL above was changed.
#! /home/pete/CitrusPerl/perl/bin/perl
# LCDClock.pl
#
# Uses LCDdisplay.pm module to display an LCD Clock
# Includes a flashing colon(:) to indicate seconds, if enabled
# LCDdisplay.pm can display 0-F, :, -, _, and a few other special char
+acters
# Up/down count, display your favorite nationa debt, etc.
#
# James M. Lynes, Jr
# Last Modified: February 4, 2013
#
package main;
use strict;
use warnings;
my $app = App->new();
$app->MainLoop;
package App;
use strict;
use warnings;
use base 'Wx::App';
sub OnInit {
my $frame = Frame->new();
$frame->Show(1);
}
package Frame;
use strict;
use warnings;
use Wx qw(:everything);
use base qw(Wx::Frame);
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_TIMER);
use LCDdisplay;
use Data::Dumper;
sub new {
my($self) = @_;
$self = $self->SUPER::new(undef, -1, "LCD Clock",
wxDefaultPosition, [400, 200]);
EVT_PAINT( $self, \&onPaint ); # Initialize event h
+andlers
EVT_SIZE( $self, \&onSize );
EVT_TIMER( $self, -1, \&onTimer);
$self->{LCD} = LCDdisplay::Data->new(); # Create an LCD
+ object
$self->{LCD}->mBlinkFlag(1); # Blink colon ? 1->yes
$self->{LCD}->mNumberDigits(5); # 5 digit display i
+ncluding :
$self->{LCD}->mValue(""); # Initialize the disp
+lay
$self->SetBackgroundColour($self->{LCD}->mDefaultColour); # Bla
+ck background
my $timer = Wx::Timer->new($self); # Initialize 1 s
+econd timer
$timer->Start(1000);
return $self;
}
1;
#
# Dismiss a size event
#
sub onSize{
my($self, $event) = @_;
$event->Skip();
}
#
# Draw the LCD
#
sub onPaint {
my($self, $event) = @_;
LCDdisplay->Draw($self, $self->{LCD});
}
#
# Format the time for display
#
sub onTimer {
my($self, $event) = @_;
my($min, $hour) = (localtime)[1,2];
my $colon = ":";
if($self->{LCD}->mBlinkFlag) { # Blink the colon?
if($self->{LCD}->mToggle) { # yes, toggle the :
$self->{LCD}->mToggle(0);
$colon = ":";
}
else {
$self->{LCD}->mToggle(1);
$colon = " ";
}
}
my $minstr = sprintf("%02d", $min);
my $hourstr = sprintf("%02d", $hour);
$self->{LCD}->mValue($hourstr . $colon . $minstr);
$self->Refresh(0);
}
#! /home/pete/CitrusPerl/perl/bin/perl
#
# LCDdisplay.pm
# This script draws a simulated seven segment LCD Display.
# Segments are drawn as 4 or 6 sided polygons
# The colon(:) is drawn as 2 ellipses and takes up a character space
# The decimal(.) is drawn as 1 ellipse and does not take a character s
+pace
# A leading decimal(.) must be prefaced by a 0 as 0.1234
# The string value to be displayed is stored in mValue
#
# Written in wxPerl. Tested on Citrus Perl 5.16 with wxWidgets 2.8.x.
# Ported by: James M. Lynes. Jr.
# Last Modified Date: February 4, 2013
#
# Adapted from LCDWindow.cpp by Marco Cavallini
# based in part(mostly) on the work of
# the KWIC project (http://www.koansoftware.com/kwic/index.htm).
# Referenced on pg 596 of the "Wx Book" -
# "Cross-Platform GUI Programming with wxWidgets", Smart, Hock, & Csom
+or
#
#
package LCDdisplay;
use 5.010; # Using Given/When
use strict;
use warnings;
use Wx qw(:everything);
use Data::Dumper;
my %defaults = ( # Object initialization Data
mSegmentLen => 40,
mSegmentWidth => 10,
mSpace => 5,
mNumberDigits => 6, # width of mValue includ
+ing . or :
mValue => "", # Default string to be d
+isplayed
LCD_Number_Segments => 8, # Segment 7 is the decim
+al point
mLightColour => Wx::Colour->new(0, 255, 0), # Brigh
+t green
mGrayColour => Wx::Colour->new(0, 64, 0), # Dim gr
+een
mDefaultColour => Wx::Colour->new(0, 0, 0), # Black
mCounter => 0, # use for up/down c
+ounter
mToggle => 0, # use to toggle the :
mBlinkFlag => 0, # use to enable blinking
+the :
);
my %wxDigitData = ( # Actual string to be displ
+ayed
value => "",
decimal => 0, # 1=true turns on the deci
+mal point for digit
);
my %ctbl = ( # Map character to segments -
0 => 0x3F, # Not defined in the 7-segme
+nt "abcdefg" format
1 => 0x14,
2 => 0x6D, # ***0*** Bit Num
+bers -654 3210
3 => 0x75, # * *
4 => 0x56, # 1 2
5 => 0x73, # * *
6 => 0x7B, # ***6***
7 => 0x15, # * *
8 => 0x7F, # 3 4
9 => 0x77, # * *
A => 0x5F, # ***5*** 7
B => 0x7A,
C => 0x2B,
D => 0x7C,
E => 0x6B,
F => 0x4B,
'-' => 0x40,
'_' => 0x20,
'^' => 0x47, # code for degree symbol
'=' => 0x61, # code for undefined symbo
+l
' ' => 0x00, # code for a space
); # Other options could be added
#
# Paint the simulated LCD Display ------------------------------------
+----------------------
#
sub Draw {
my($class, $self, $lcd) = @_;
my $disp = Wx::PaintDC->new($self); # Create paint
+device context
my( $dw, $dh) = $disp->GetSizeWH(); # Calculate dis
+play scaling
my $bw = GetBitmapWidth();
my $bh = GetBitmapHeight();
my $xs = $dw/$bw;
my $ys = $dh/$bh;
my $as = $xs > $ys ? $ys : $xs;
$disp->SetUserScale($as, $as);
$disp->SetDeviceOrigin((($dw-$bw*$as)/2), (($dh-$bh*$as)/2));
DoDrawing($disp, $lcd); # Paint the display
}
sub DoDrawing {
my($dc, $lcd) = @_;
my @cbuf = reverse(split(//,$lcd->mValue)); # Process o
+ne character at a time
my $cbuflen = @cbuf;
if($cbuflen > $lcd->mNumberDigits) { # Truncate string
+to max display width
$cbuflen = $lcd->mNumberDigits;
}
my $ctr = 0;
my $seg = 0;
while($ctr < $cbuflen) {
if($cbuf[$ctr] ne '.') { # Skip decimal point,
+not drawn as a character
$wxDigitData{value} = $cbuf[$ctr];
$wxDigitData{decimal} = 0;
if($cbuf[$ctr-1] eq '.') {$wxDigitData{decimal} = 1;} # Turn o
+n decimal point for this digit
DrawDigit($dc, $seg, $lcd);
$seg++;
}
$ctr++
}
}
sub DrawDigit {
my($dc, $digit, $lcd) = @_;
my $value = $wxDigitData{value};
my $dec = Decode($value);
if($value eq ':') { # Draw a colon(:)
DrawTwoDots($dc, $digit, $lcd);
}
else {
my $ctr = 0;
while($ctr < $lcd->LCD_Number_Segments-1) {
DrawSegment($dc, $digit, $ctr, ($dec>>$ctr)&1, $lcd);
$ctr++;
}
DrawSegment($dc, $digit, 7, $wxDigitData{decimal}, $lcd); #
+ Draw the decimal point
}
}
sub DrawTwoDots { # Draws a colon(:)
my($dc, $digit, $lcd) = @_;
my $sl = $lcd->mSegmentLen;
my $sw = $lcd->mSegmentWidth;
my $sp = $lcd->mSpace;
my $x = DigitX($digit);
my $y = DigitY($digit);
$x += ($sl/2)-$sw;
$y += ($sl/2)-$sw;
my $brushOn = Wx::Brush->new($lcd->mLightColour, wxSOLID);
$dc->SetBrush($brushOn);
$dc->SetPen(Wx::Pen->new($lcd->mDefaultColour, 1, wxSOLID));
$dc->DrawEllipse($x, $y, $sw*2, $sw*2);
$y += $sl;
$dc->DrawEllipse($x, $y, $sw*2, $sw*2);
}
sub DrawSegment {
my($dc, $digit, $segment, $state, $lcd) = @_;
my $sl = $lcd->mSegmentLen;
my $sw = $lcd->mSegmentWidth;
my $x = DigitX($digit);
my $y = DigitY($digit);
my $brushOn = Wx::Brush->new($lcd->mLightColour, wxSOLID);
my $brushOff = Wx::Brush->new($lcd->mGrayColour, wxSOLID);
if($state) { # bit set for On segment
$dc->SetBrush($brushOn); # Bright color for On
+segment
}
else { # bit cleared for Off segment
$dc->SetBrush($brushOff); # Dim color for Off s
+egment
}
$dc->SetPen(Wx::Pen->new($lcd->mDefaultColour, 1, wxSOLID));
my @points; # Verticies for 4 sided seg
+ments
my @p6; # Verticies for the 6 sided seg
+ment
given($segment) {
when(0) {
$points[0] = Wx::Point->new($x, $y);
$points[1] = Wx::Point->new($x+$sl, $y);
$points[2] = Wx::Point->new($x+$sl-$sw, $y+$sw);
$points[3] = Wx::Point->new($x+$sw, $y+$sw);
}
when(1) {
$points[0] = Wx::Point->new($x, $y);
$points[1] = Wx::Point->new($x, $y+$sl);
$points[2] = Wx::Point->new($x+$sw, $y+$sl-$sw/2);
$points[3] = Wx::Point->new($x+$sw, $y+$sw);
}
when(2) {
$x += $sl-$sw;
$points[0] = Wx::Point->new($x,$y+$sw);
$points[1] = Wx::Point->new($x+$sw, $y);
$points[2] = Wx::Point->new($x+$sw, $y+$sl);
$points[3] = Wx::Point->new($x, $y+$sl-$sw/2);
}
when(3) {
$y += $sl;
$points[0] = Wx::Point->new($x, $y);
$points[1] = Wx::Point->new($x, $y+$sl);
$points[2] = Wx::Point->new($x+$sw, $y+$sl-$sw);
$points[3] = Wx::Point->new($x+$sw, $y+$sw-$sw/2);
}
when(4) {
$x += $sl-$sw;
$y += $sl;
$points[0] = Wx::Point->new($x, $y+$sw/2);
$points[1] = Wx::Point->new($x+$sw, $y);
$points[2] = Wx::Point->new($x+$sw, $y+$sl);
$points[3] = Wx::Point->new($x, $y+$sl-$sw);
}
when(5) {
$y += 2*$sl-$sw;
$points[0] = Wx::Point->new($x+$sw, $y);
$points[1] = Wx::Point->new($x+$sl-$sw, $y);
$points[2] = Wx::Point->new($x+$sl, $y+$sw);
$points[3] = Wx::Point->new($x, $y+$sw);
}
when(6) {
$y += $sl-$sw/2;
$p6[0] = Wx::Point->new($x, $y+$sw/2);
$p6[1] = Wx::Point->new($x+$sw, $y);
$p6[2] = Wx::Point->new($x+$sl-$sw, $y);
$p6[3] = Wx::Point->new($x+$sl, $y+$sw/2);
$p6[4] = Wx::Point->new($x+$sl-$sw, $y+$sw);
$p6[5] = Wx::Point->new($x+$sw, $y+$sw);
}
default {}
}
if($segment < 6) { # Draw the 4 sided segme
+nts(0-5)
$dc->DrawPolygon(\@points, 0, 0);
}
elsif($segment == 6) { # Draw the 6 sided segme
+nt(6)
$dc->DrawPolygon(\@p6, 0, 0);
}
else { # Draw the decimal point(7)
$y += 2*$sl;
$x += $sl;
$dc->DrawEllipse($x+1, $y-$sw, $sw, $sw);
}
}
sub Decode { $ctbl{$_[0]} // $ctbl{'='}} # Table lookup
# for character translation
#
# Support subs -------------------------------------------------------
+---------------------
#
sub GetDigitWidth {
return $defaults{mSegmentLen} + $defaults{mSegmentWidth} + $defaul
+ts{mSpace};
}
sub GetDigitHeight {
return ($defaults{mSegmentLen}*2) + ($defaults{mSpace}*2);
}
sub GetBitmapWidth {
return ($defaults{mNumberDigits}*GetDigitWidth()) + $defaults{mSpa
+ce};
}
sub GetBitmapHeight {
return GetDigitHeight();
}
sub DigitX {
my($digit) = @_;
return GetBitmapWidth()-(($digit+1)*GetDigitWidth());
}
sub DigitY {
my($digit) = @_;
return $defaults{mSpace};
}
sub SetNumberDigits {
my $ndigits = @_;
$defaults{mNumberDigits} = $ndigits;
}
sub SetValue {
my $value = @_;
$defaults{mValue} = $value;
}
sub GetValue {
return $defaults{mValue};
}
sub GetNumberDigits {
return $defaults{mNumberDigits};
}
sub SetLightColour {
my($ref) = @_;
$defaults{mLightColour} = $ref;
}
sub SetGrayColour {
my($ref) = @_;
$defaults{mGrayColour} = $ref;
}
sub GetLightColour {
return $defaults{mLightColour};
}
sub GetGrayColour {
return $defaults{mGrayColour};
}
sub GetDigitsNeeded {
my($string) = @_;
$string =~ s/\.//g;
return strlen($string);
}
package LCDdisplay::Data;
use strict;
use warnings;
use Class::Accessor::Fast;
use base qw(Class::Accessor::Fast);
__PACKAGE__->mk_accessors(qw(mSegmentLen mSegmentWidth mSpace mNumberD
+igits
mValue LCD_Number_Segments mLightColour m
+GrayColour
mDefaultColour mCounter mToggle mBlinkFla
+g));
sub new {shift->SUPER::new(@_, \%defaults);}
1;