<?xml version="1.0" encoding="windows-1252"?>
<node id="1016180" title="wxPerl Process Control Example" created="2013-01-30 18:41:01" updated="2013-01-30 18:41:01">
<type id="1042">
CUFP</type>
<author id="982107">
jmlynesjr</author>
<data>
<field name="doctext">
&lt;p&gt;What do you do with a Simulated Linear Panel Meter? You modify it into a module and build a simulated process control screen around it.&lt;/p&gt;

&lt;p&gt;Need a break from "Select From...."? Give this a try.&lt;/p&gt;

&lt;p&gt;Left mouse click to select/deselect a meter. Right mouse click to change a meter's limit&lt;/p&gt;

&lt;p&gt;Many thanks to Mark Dootson for many excellent comments, not all of which are implemented yet in this example. All blame is mine.&lt;/p&gt;

&lt;readmore&gt;&lt;code&gt;#! /home/pete/CitrusPerl/perl/bin/perl

# LM3.pl - wxPerl Process Control Example
#          uses the LinearMeter3.pm modular meter
#
# Last modified by James M. Lynes, Jr - January 30,2013
#
# Draws and animates 8 Linear Meters, uses LinearMeter3.pm
# Meters change green/red to indicate limit violations
# Creates a 1 second timer to update the animation
# Left click selects/deselects the meter and sets the border to yellow/blue
# Right click on a selected meter pops a limit change dialog
# Right click on a deselected meter pops an error messsage box
# Multiple meters may be selected at the same time - may want to limit
#   this in the future.
#
# The panel is created 40 units longer than the meter to allow space
# for drawing the meter label
#

package main;
use strict;
use warnings;
my $app = App-&gt;new();
$app-&gt;MainLoop;

package App;
use strict;
use warnings;
use base 'Wx::App';
sub OnInit {
    my $frame = Frame-&gt;new();
    $frame-&gt;Show(1);
}

package Frame;
use strict;
use warnings;
use Wx qw(:everything);
use base qw(Wx::Frame);
use LinearMeter3;
use Data::Dumper;
use Wx::Event qw(EVT_PAINT EVT_TIMER EVT_LEFT_DOWN EVT_RIGHT_DOWN);

sub new {
    my ($self) = @_;
    my($meters, $width, $height) = (8, 100, 550);
    $self = $self-&gt;SUPER::new(undef, -1, "wxPerl Process Control Example", wxDefaultPosition,
                              [(($meters)+1)*10+20 + (($meters)*$width),($height+90)]);
    my $font = Wx::Font-&gt;new(12, wxFONTFAMILY_SWISS, wxNORMAL, wxBOLD);
    $self-&gt;SetFont($font);
    Wx::StaticText-&gt;new($self, -1, "Boiler 1", [190, 15], wxDefaultSize, wxALIGN_LEFT);
    Wx::StaticText-&gt;new($self, -1, "Boiler 2", [650, 15], wxDefaultSize, wxALIGN_LEFT);
 
# Create 8 panels to hold 8 meters - single row layout
    $self-&gt;{MP1} = Wx::Panel-&gt;new($self, wxID_ANY, [10 ,40], [$width, $height+40]);
    $self-&gt;{MP2} = Wx::Panel-&gt;new($self, wxID_ANY, [($width*1)+20 ,40], [$width, $height+40]);
    $self-&gt;{MP3} = Wx::Panel-&gt;new($self, wxID_ANY, [($width*2)+30 ,40], [$width, $height+40]);
    $self-&gt;{MP4} = Wx::Panel-&gt;new($self, wxID_ANY, [($width*3)+40 ,40], [$width, $height+40]);
    $self-&gt;{MP5} = Wx::Panel-&gt;new($self, wxID_ANY, [($width*4)+70 ,40], [$width, $height+40]);
    $self-&gt;{MP6} = Wx::Panel-&gt;new($self, wxID_ANY, [($width*5)+80 ,40], [$width, $height+40]);
    $self-&gt;{MP7} = Wx::Panel-&gt;new($self, wxID_ANY, [($width*6)+90 ,40], [$width, $height+40]);
    $self-&gt;{MP8} = Wx::Panel-&gt;new($self, wxID_ANY, [($width*7)+100 ,40], [$width, $height+40]);

# Create 8 meter objects - Override some default values
    $self-&gt;{LM1} = LinearMeter-&gt;new();
        $self-&gt;{LM1}-&gt;InitialValue(73);
        $self-&gt;{LM1}-&gt;Limit(76);
        $self-&gt;{LM1}-&gt;Label("Temp 1");
        $self-&gt;{LM1}-&gt;MeterHeight($height);
        $self-&gt;{LM1}-&gt;MeterWidth($width);
    $self-&gt;{LM2} = LinearMeter-&gt;new();
        $self-&gt;{LM2}-&gt;InitialValue(28);
        $self-&gt;{LM2}-&gt;Limit(31);
        $self-&gt;{LM2}-&gt;Label("Flow 1");
        $self-&gt;{LM2}-&gt;MeterHeight($height);
        $self-&gt;{LM2}-&gt;MeterWidth($width);
    $self-&gt;{LM3} = LinearMeter-&gt;new();
        $self-&gt;{LM3}-&gt;InitialValue(42);
        $self-&gt;{LM3}-&gt;Limit(46);
        $self-&gt;{LM3}-&gt;Label("Pressure 1");
        $self-&gt;{LM3}-&gt;MeterHeight($height);
        $self-&gt;{LM3}-&gt;MeterWidth($width);
    $self-&gt;{LM4} = LinearMeter-&gt;new();
        $self-&gt;{LM4}-&gt;InitialValue(62);
        $self-&gt;{LM4}-&gt;Limit(66);
        $self-&gt;{LM4}-&gt;Label("Level 1");
        $self-&gt;{LM4}-&gt;MeterHeight($height);
        $self-&gt;{LM4}-&gt;MeterWidth($width);
    $self-&gt;{LM5} = LinearMeter-&gt;new();
        $self-&gt;{LM5}-&gt;InitialValue(24);
        $self-&gt;{LM5}-&gt;Limit(28);
        $self-&gt;{LM5}-&gt;Label("Temp 2");
        $self-&gt;{LM5}-&gt;MeterHeight($height);
        $self-&gt;{LM5}-&gt;MeterWidth($width);
    $self-&gt;{LM6} = LinearMeter-&gt;new();
        $self-&gt;{LM6}-&gt;InitialValue(63);
        $self-&gt;{LM6}-&gt;Limit(66);
        $self-&gt;{LM6}-&gt;Label("Flow 2");
        $self-&gt;{LM6}-&gt;MeterHeight($height);
        $self-&gt;{LM6}-&gt;MeterWidth($width);
    $self-&gt;{LM7} = LinearMeter-&gt;new();
        $self-&gt;{LM7}-&gt;InitialValue(33);
        $self-&gt;{LM7}-&gt;Limit(37);
        $self-&gt;{LM7}-&gt;Label("Pressure 2");
        $self-&gt;{LM7}-&gt;MeterHeight($height);
        $self-&gt;{LM7}-&gt;MeterWidth($width);
    $self-&gt;{LM8} = LinearMeter-&gt;new();
        $self-&gt;{LM8}-&gt;InitialValue(81);
        $self-&gt;{LM8}-&gt;Limit(85);
        $self-&gt;{LM8}-&gt;Label("Level 2");
        $self-&gt;{LM8}-&gt;MeterHeight($height);
        $self-&gt;{LM8}-&gt;MeterWidth($width);
#
# Set up Event Handlers -------------------------------------------------------
#
# Timer
    my $timer = Wx::Timer-&gt;new( $self );
    $timer-&gt;Start( 1000 );					# 1 second period
    EVT_TIMER($self, -1, \&amp;onTimer);
# Paint
    EVT_PAINT($self, \&amp;onPaint);
# Mouse
    EVT_LEFT_DOWN($self-&gt;{MP1}, sub{$self-&gt;_evt_left_down( $self-&gt;{LM1}, @_);});
    EVT_LEFT_DOWN($self-&gt;{MP2}, sub{$self-&gt;_evt_left_down( $self-&gt;{LM2}, @_);});
    EVT_LEFT_DOWN($self-&gt;{MP3}, sub{$self-&gt;_evt_left_down( $self-&gt;{LM3}, @_);});
    EVT_LEFT_DOWN($self-&gt;{MP4}, sub{$self-&gt;_evt_left_down( $self-&gt;{LM4}, @_);});
    EVT_LEFT_DOWN($self-&gt;{MP5}, sub{$self-&gt;_evt_left_down( $self-&gt;{LM5}, @_);});
    EVT_LEFT_DOWN($self-&gt;{MP6}, sub{$self-&gt;_evt_left_down( $self-&gt;{LM6}, @_);});
    EVT_LEFT_DOWN($self-&gt;{MP7}, sub{$self-&gt;_evt_left_down( $self-&gt;{LM7}, @_);});
    EVT_LEFT_DOWN($self-&gt;{MP8}, sub{$self-&gt;_evt_left_down( $self-&gt;{LM8}, @_);});

    EVT_RIGHT_DOWN($self-&gt;{MP1}, sub{$self-&gt;_evt_right_down( $self-&gt;{LM1}, @_);});
    EVT_RIGHT_DOWN($self-&gt;{MP2}, sub{$self-&gt;_evt_right_down( $self-&gt;{LM2}, @_);});
    EVT_RIGHT_DOWN($self-&gt;{MP3}, sub{$self-&gt;_evt_right_down( $self-&gt;{LM3}, @_);});
    EVT_RIGHT_DOWN($self-&gt;{MP4}, sub{$self-&gt;_evt_right_down( $self-&gt;{LM4}, @_);});
    EVT_RIGHT_DOWN($self-&gt;{MP5}, sub{$self-&gt;_evt_right_down( $self-&gt;{LM5}, @_);});
    EVT_RIGHT_DOWN($self-&gt;{MP6}, sub{$self-&gt;_evt_right_down( $self-&gt;{LM6}, @_);});
    EVT_RIGHT_DOWN($self-&gt;{MP7}, sub{$self-&gt;_evt_right_down( $self-&gt;{LM7}, @_);});
    EVT_RIGHT_DOWN($self-&gt;{MP8}, sub{$self-&gt;_evt_right_down( $self-&gt;{LM8}, @_);});
    return $self;
}
1;
#
# Right Mouse Pressed Event - Change the Selected Meter's Limit -----------------
#
sub _evt_right_down {
    my($frame, $meter, $panel, $event) = @_;
    if($meter-&gt;Selected()) {
        my $label = $meter-&gt;Label();
        my $dialog = Wx::TextEntryDialog-&gt;new( $frame,
                     "Select a New Limit", "Change the  $label  Alarm Limit",
                     $meter-&gt;Limit());
        if($dialog-&gt;ShowModal == wxID_CANCEL) {
            $meter-&gt;BorderColour(wxBLUE);
            $meter-&gt;Selected(0);
            return;
        };
        $meter-&gt;Limit($dialog-&gt;GetValue());
        $meter-&gt;BorderColour(wxBLUE);
        $meter-&gt;Selected(0);
    }
    else {
        my $msg = Wx::MessageBox("No Meter Selected\nLeft Click a Meter to Select",
                 "Meter Limit Entry Error", wxICON_ERROR, $frame);    
    }
    $event-&gt;Skip(1);
}
#
# Left Mouse Pressed Event - Selects a Meter - Selection will Toggle -----------
#
sub _evt_left_down {
    my($frame, $meter, $panel, $event) = @_;
    if($meter-&gt;Selected()) {
        $meter-&gt;BorderColour(wxBLUE);
        $meter-&gt;Selected(0);
    }
    else {
    $meter-&gt;BorderColour(Wx::Colour-&gt;new("yellow"));        
    $meter-&gt;Selected(1);
    }
    $event-&gt;Skip(1);
}

# Simple version of Event Handler Closure
#    More complex version fits better for this application
#    EVT_LEFT_DOWN($self-&gt;{MP1}, sub{
#        my($panel, $event) = @_;
#        $self-&gt;{LM1}-&gt;BordorColour(Wx::Colour-&gt;new("yellow"));
#        $event-&gt;Skip(1);
#    });

#
# 1 second timer to simulate meter movement ---------------------------------
#
sub onTimer {
    my($self, $event) = @_;
								# Randomize for each meter
								# for a more natural look
    my $dir = (rand 10) &lt; 5 ? -1 : 1;
    my $inc = (rand 1) * $dir;
    $self-&gt;{LM1}-&gt;InitialValue($self-&gt;{LM1}-&gt;InitialValue() + $inc);
    LinearMeter-&gt;Draw($self-&gt;{MP1}, $self-&gt;{LM1});

    $dir = (rand 10) &lt; 5 ? -1 : 1;
    $inc = (rand 1) * $dir;
    $self-&gt;{LM2}-&gt;InitialValue($self-&gt;{LM2}-&gt;InitialValue() + $inc);
    LinearMeter-&gt;Draw($self-&gt;{MP2}, $self-&gt;{LM2});

    $dir = (rand 10) &lt; 5 ? -1 : 1;
    $inc = (rand 1) * $dir;
    $self-&gt;{LM3}-&gt;InitialValue($self-&gt;{LM3}-&gt;InitialValue() + $inc);
    LinearMeter-&gt;Draw($self-&gt;{MP3}, $self-&gt;{LM3});

    $dir = (rand 10) &lt; 5 ? -1 : 1;
    $inc = (rand 1) * $dir;
    $self-&gt;{LM4}-&gt;InitialValue($self-&gt;{LM4}-&gt;InitialValue() + $inc);
    LinearMeter-&gt;Draw($self-&gt;{MP4}, $self-&gt;{LM4});

    $dir = (rand 10) &lt; 5 ? -1 : 1;
    $inc = (rand 1) * $dir;
    $self-&gt;{LM5}-&gt;InitialValue($self-&gt;{LM5}-&gt;InitialValue() + $inc);
    LinearMeter-&gt;Draw($self-&gt;{MP5}, $self-&gt;{LM5});

    $dir = (rand 10) &lt; 5 ? -1 : 1;
    $inc = (rand 1) * $dir;
    $self-&gt;{LM6}-&gt;InitialValue($self-&gt;{LM6}-&gt;InitialValue() + $inc);
    LinearMeter-&gt;Draw($self-&gt;{MP6}, $self-&gt;{LM6});

    $dir = (rand 10) &lt; 5 ? -1 : 1;
    $inc = (rand 1) * $dir;
    $self-&gt;{LM7}-&gt;InitialValue($self-&gt;{LM7}-&gt;InitialValue() + $inc);
    LinearMeter-&gt;Draw($self-&gt;{MP7}, $self-&gt;{LM7});

    $dir = (rand 10) &lt; 5 ? -1 : 1;
    $inc = (rand 1) * $dir;
    $self-&gt;{LM8}-&gt;InitialValue($self-&gt;{LM8}-&gt;InitialValue() + $inc);
    LinearMeter-&gt;Draw($self-&gt;{MP8}, $self-&gt;{LM8});
}
#
# Paint the Meters ---------------------------------------------------------------------
#
sub onPaint {
    my($self, $event) = @_;
# Draw the 8 meters
    LinearMeter-&gt;Draw($self-&gt;{MP1}, $self-&gt;{LM1});
    LinearMeter-&gt;Draw($self-&gt;{MP2}, $self-&gt;{LM2});
    LinearMeter-&gt;Draw($self-&gt;{MP3}, $self-&gt;{LM3});
    LinearMeter-&gt;Draw($self-&gt;{MP4}, $self-&gt;{LM4});
    LinearMeter-&gt;Draw($self-&gt;{MP5}, $self-&gt;{LM5});
    LinearMeter-&gt;Draw($self-&gt;{MP6}, $self-&gt;{LM6});
    LinearMeter-&gt;Draw($self-&gt;{MP7}, $self-&gt;{LM7});
    LinearMeter-&gt;Draw($self-&gt;{MP8}, $self-&gt;{LM8});
} &lt;/code&gt;&lt;/readmore&gt;

&lt;readmore&gt;&lt;code&gt;# LinearMeter3.pm - Linear Meter Object
#
# Last modified by James M. Lynes, Jr - January 30,2013
#
# Creates a Linear Panel Meter
# Can be drawn vertical or horizontal
# Modified LinearMeter.pl into an object that can be used to create multiple meters
# Adapted from LinearMeter.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, &amp; Csomor
#
# Added high-limit/alarm processing- red/green color change
# Added label display between the bottom of the meter and the bottom of the panel
# Set an initial value, high-limit, and tic array
# Added DrawLimitBar to draw a tic mark at the current limit value
# Deleted drawing the 1st and last tags to reduce crowding of the display
# Added a "Selected" flag
# Driver program implements mouse events for limit modification
#   and timer event to drive the animation.
#
# To-Do: Rework meter so that a wider border can be drawn. PANEL(BORDER(METER))
#        Currently the border draws on top of the meter space.

package LinearMeter;
use strict;
use warnings;
use Wx qw(:everything);
use Data::Dumper;
#
# Define the meter object hash ------------------------------------------------
#
sub new {
    my $self                  = {};
    $self-&gt;{METERHEIGHT}      = 300;					# Swap these for horizontal display
    $self-&gt;{METERWIDTH}       = 100;
    $self-&gt;{ACTIVEBAR}        = wxGREEN;
    $self-&gt;{PASSIVEBAR}       = wxWHITE;
    $self-&gt;{VALUECOLOUR}      = wxBLACK;
    $self-&gt;{BORDERCOLOUR}     = wxBLUE;
    $self-&gt;{LIMITCOLOUR}      = wxBLACK;
    $self-&gt;{ALARMLIMITCOLOUR} = wxRED;
    $self-&gt;{TAGSCOLOUR}       = wxBLACK;
    $self-&gt;{SCALEDVALUE}      = 0;
    $self-&gt;{REALVAL}          = 0;
    $self-&gt;{LIMIT}            = 75;					# High-Limit setpoint
    $self-&gt;{TAGSVAL}          = [];
    $self-&gt;{TAGSNUM}          = 0;
    $self-&gt;{STARTTAG}         = 0;
    $self-&gt;{NUMTAGS}          = 10;
    $self-&gt;{INCTAG}           = 10;
    $self-&gt;{MAX}              = 100;					# Span
    $self-&gt;{MIN}              = 0;
    $self-&gt;{INITIALVALUE}     = 0;					# Initial value displayed
    $self-&gt;{LASTPOS}          = 0;					# Last mouse position
    $self-&gt;{DIRHORIZFLAG}     = 0;					# 0-Verticle, 1-Horizontal
    $self-&gt;{SHOWCURRENT}      = 1;
    $self-&gt;{SHOWLIMITS}       = 1;
    $self-&gt;{SHOWLABEL}        = 1;
    $self-&gt;{FONT}             = Wx::Font-&gt;new(8, wxFONTFAMILY_SWISS, wxNORMAL, wxNORMAL);
    $self-&gt;{LABEL}            = "";
    $self-&gt;{SELECTED}	      = 0;
    bless($self);
    return $self;
}
#
# Draw the Linear Meter ----------------------------------------------------------
#
sub Draw {
    my($class, $panel, $Meter) = @_;
    my $dc = Wx::PaintDC-&gt;new($panel);
    my $memdc = Wx::MemoryDC-&gt;new();
    $memdc-&gt;SelectObject(Wx::Bitmap-&gt;new($Meter-&gt;MeterWidth(), $Meter-&gt;MeterHeight()));
    my($w, $h) = $memdc-&gt;GetSizeWH();
    my $brush = Wx::Brush-&gt;new($Meter-&gt;PassiveBar(), wxSOLID);
    $memdc-&gt;SetBackground($brush);
    $memdc-&gt;Clear();
    SetUp($memdc, $Meter);						# Set the initial value and tic marks
    my $pen = Wx::Pen-&gt;new($Meter-&gt;BorderColour(), 3, wxSOLID);
    $memdc-&gt;SetPen($pen);
    $memdc-&gt;DrawRectangle(0, 0, $w, $h);
    $pen = Wx::Pen-&gt;new($Meter-&gt;BorderColour(), 3, wxSOLID);
    $memdc-&gt;SetPen($pen);
    $brush = Wx::Brush-&gt;new($Meter-&gt;ActiveBar(), wxSOLID);
    if($Meter-&gt;RealVal() &gt; $Meter-&gt;Limit()) {$brush = Wx::Brush-&gt;new($Meter-&gt;AlarmLimitColour(), wxSOLID)}
    $memdc-&gt;SetBrush($brush);
    my $yPoint;
    my $rectHeight;
    if($Meter-&gt;DirHorizFlag()) {					# Horizontal Orientation
        $memdc-&gt;DrawRectangle(1, 1, $Meter-&gt;ScaledValue(), $h-2);
    }
    else {								# Verticle Orientation
        $yPoint = $h - $Meter-&gt;ScaledValue();
        if($Meter-&gt;ScaledValue() == 0) {
            $rectHeight = $Meter-&gt;ScaledValue();
        }
        else {
            if($Meter-&gt;RealVal() == $Meter-&gt;Max()) {
               $rectHeight = $Meter-&gt;ScaledValue();
               $yPoint -= 1;
            }
            else {
                $rectHeight = $Meter-&gt;ScaledValue() - 1;
            }
        $memdc-&gt;DrawRectangle(1, $yPoint, $w-2, $rectHeight);
       }
    }
    if($Meter-&gt;ShowCurrent()) {DrawCurrent($memdc, $Meter)}
    if($Meter-&gt;ShowLimits()) {DrawLimits($memdc, $Meter)}
    if($Meter-&gt;TagsNum() &gt; 0) {DrawTags($memdc, $Meter)}
    $dc-&gt;Blit(0, 0, $w, $h, $memdc, 0, 0);			# Keep blit above DrawLabel call
    if($Meter-&gt;ShowLabel()) {DrawLabel($dc, $Meter)}		# &lt;----
}
sub SetUp {							# Set and update the displayed value
    my($dc, $Meter) = @_;
    SetValue($dc, $Meter-&gt;InitialValue(), $Meter);

    if($Meter-&gt;TagsNum() == 0) {				# Build tic marks 1st time through
        for($Meter-&gt;StartTag()..$Meter-&gt;NumTags()) {		# Quick and dirty
            AddTag($_ * $Meter-&gt;IncTag(), $Meter);
        }
    }
} 
sub DrawCurrent {						# Draw the current value as text
    my($dc, $Meter) = @_;
    my($w, $h) = $dc-&gt;GetSizeWH();
    my $valuetext = sprintf("%d", $Meter-&gt;RealVal());
    my ($tw, $th) = $dc-&gt;GetTextExtent($valuetext);
    $dc-&gt;SetTextForeground($Meter-&gt;ValueColour());
    $dc-&gt;SetFont($Meter-&gt;Font());
    $dc-&gt;DrawText($valuetext, $w/2-$tw/2, $h/2-$th/2);    
}
sub DrawLimits {						# Draw Min and Max as text
    my($dc, $Meter) = @_;
    my($w, $h) = $dc-&gt;GetSizeWH();
    $dc-&gt;SetFont($Meter-&gt;Font());
    $dc-&gt;SetTextForeground($Meter-&gt;LimitColour());
    if($Meter-&gt;DirHorizFlag()) {
        my $valuetext = sprintf("%d", $Meter-&gt;Min());
        my ($tw, $th) = $dc-&gt;GetTextExtent($valuetext);
        $dc-&gt;DrawText($valuetext, 5, $h/2-$th/2);
        $valuetext = sprintf("%d", $Meter-&gt;Max());
        ($tw, $th) = $dc-&gt;GetTextExtent($valuetext);
        $dc-&gt;DrawText($valuetext, $w-$tw-5, $h/2-$th/2);
    }
    else {
        my $valuetext = sprintf("%d", $Meter-&gt;Min());
        my ($tw, $th) = $dc-&gt;GetTextExtent($valuetext);
        $dc-&gt;DrawText($valuetext, $w/2-$tw/2, $h-$th-5);
        $valuetext = sprintf("%d", $Meter-&gt;Max());
        ($tw, $th) = $dc-&gt;GetTextExtent($valuetext);
        $dc-&gt;DrawText($valuetext, $w/2-$tw/2, 5);
    }     
}
sub DrawTags {							# Draw tic marks and labels
    my($dc, $Meter) = @_;
    my($w, $h) = $dc-&gt;GetSizeWH();
    my $tcoeff;
    if($Meter-&gt;DirHorizFlag()) {
        $tcoeff = ($w-2)/($Meter-&gt;Max()-$Meter-&gt;Min());
    }
    else {
        $tcoeff = ($h-2)/($Meter-&gt;Max()-$Meter-&gt;Min());
    }
    my $pen = Wx::Pen-&gt;new($Meter-&gt;TagsColour(), 1, wxSOLID);
    $dc-&gt;SetPen($pen);
    my $brush = Wx::Brush-&gt;new($Meter-&gt;TagsColour(), wxSOLID);
    $dc-&gt;SetBrush($brush);
    $dc-&gt;SetTextForeground($Meter-&gt;TagsColour());
    my $tag = 1;
    while($tag &lt; ($Meter-&gt;TagsNum()-1)) {
        my $scalval = (${$Meter-&gt;TagsVal()}[$tag]-$Meter-&gt;Min()) * $tcoeff;
        my $textvalue = sprintf("%d", ${$Meter-&gt;TagsVal()}[$tag]);
        if($Meter-&gt;DirHorizFlag()) {
            $dc-&gt;DrawLine($scalval+1, $h-2, $scalval+1, $h-10);
            my($tw, $th) = $dc-&gt;GetTextExtent($textvalue);
            $dc-&gt;DrawText($textvalue, $scalval+1-($tw/2), $h-10-$th);
        }
        else {
            $dc-&gt;DrawLine($w-2, $h-$scalval-1, $w-10, $h-$scalval-1);
            my($tw, $th) = $dc-&gt;GetTextExtent($textvalue);
            $dc-&gt;DrawText($textvalue, $w-10-$tw, $h-$scalval-($th/2));
        }
    $tag++;
    }
    DrawLimitBar($dc, $Meter);  
}
sub DrawLimitBar {					# Draw small bar at limit setting
    my($dc, $Meter) = @_;
    my($w, $h) = $dc-&gt;GetSizeWH();
    my $tcoeff;
    if($Meter-&gt;DirHorizFlag()) {
        $tcoeff = ($w-2)/($Meter-&gt;Max()-$Meter-&gt;Min());
    }
    else {
        $tcoeff = ($h-2)/($Meter-&gt;Max()-$Meter-&gt;Min());
    }

    my $pen = Wx::Pen-&gt;new(Wx::Colour-&gt;new("orange"), 3, wxSOLID);
    $dc-&gt;SetPen($pen);
    my $brush = Wx::Brush-&gt;new($Meter-&gt;TagsColour(), wxSOLID);
    $dc-&gt;SetBrush($brush);
    $dc-&gt;SetTextForeground($Meter-&gt;TagsColour());

    my $scalval = ($Meter-&gt;Limit()-$Meter-&gt;Min()) * $tcoeff;
    if($Meter-&gt;DirHorizFlag()) {
        $dc-&gt;DrawLine($scalval+1, $h-2, $scalval+1, $h-20);
    }
    else {
        $dc-&gt;DrawLine($w-2, $h-$scalval, $w-20, $h-$scalval);
    }  
}
sub AddTag {						# Add a tic mark to array
    my($val, $Meter) = @_;
    push(@{$Meter-&gt;TagsVal()}, $val);
    $Meter-&gt;TagsNum($#{$Meter-&gt;TagsVal()}+1);    
}
sub SetValue {						# Scale the value for display
    my($dc, $Value, $Meter) = @_;
    my($w, $h) = $dc-&gt;GetSizeWH();
    my $coeff;
    if($Meter-&gt;DirHorizFlag()) {
        $coeff = ($w-2)/($Meter-&gt;Max()-$Meter-&gt;Min());
    }
    else {
        $coeff = ($h-2)/($Meter-&gt;Max()-$Meter-&gt;Min());
    }
    $Meter-&gt;ScaledValue(($Value-$Meter-&gt;Min()) * $coeff);
    $Meter-&gt;RealVal($Value);
}
sub DrawLabel {						# Draw a label at bottom of meter
    my($dc, $Meter) = @_;
    my $memdc = Wx::MemoryDC-&gt;new();
    $memdc-&gt;SelectObject(Wx::Bitmap-&gt;new($Meter-&gt;MeterWidth(), 40));
    my($w, $h) = $memdc-&gt;GetSizeWH();
    my $brush = Wx::Brush-&gt;new(wxLIGHT_GREY, wxSOLID);
    $memdc-&gt;SetBackground($brush);
    my $pen = Wx::Pen-&gt;new($Meter-&gt;TagsColour(), 1, wxSOLID);
    $memdc-&gt;SetPen($pen);
    $memdc-&gt;SetTextForeground($Meter-&gt;TagsColour());
    $memdc-&gt;SetFont($Meter-&gt;Font());
    $memdc-&gt;Clear();
    my @te = $memdc-&gt;GetTextExtent($Meter-&gt;Label());
    my $x = (($w-$te[0])/2)-5;
    $memdc-&gt;DrawText($Meter-&gt;Label(), $x, 5);
    $dc-&gt;Blit(0, $Meter-&gt;MeterHeight(), $w, $Meter-&gt;MeterHeight()+40,  $memdc, 0, 0);
}
#
# Object Accessors - Hand coded method ---------------------------------------------
# Replace with Class::Accessor in the future
#
sub MeterHeight {
    my $self = shift;
    if(@_) { $self-&gt;{METERHEIGHT} = shift }
    return $self-&gt;{METERHEIGHT};
}
sub MeterWidth {
    my $self = shift;
    if(@_) { $self-&gt;{METERWIDTH} = shift }
    return $self-&gt;{METERWIDTH};
}
sub ActiveBar {
    my $self = shift;
    if(@_) { $self-&gt;{ACTIVEBAR} = shift }
    return $self-&gt;{ACTIVEBAR};
}
sub PassiveBar {
    my $self = shift;
    if(@_) { $self-&gt;{PASSIVEBAR} = shift }
    return $self-&gt;{PASSIVEBAR};
}
sub ValueColour {
    my $self = shift;
    if(@_) { $self-&gt;{VALUECOLOUR} = shift }
    return $self-&gt;{VALUECOLOUR};
}
sub BorderColour {
    my $self = shift;
    if(@_) { $self-&gt;{BORDERCOLOUR} = shift }
    return $self-&gt;{BORDERCOLOUR};
}
sub LimitColour {
    my $self = shift;
    if(@_) { $self-&gt;{LIMITCOLOUR} = shift }
    return $self-&gt;{LIMITCOLOUR};
}
sub AlarmLimitColour {
    my $self = shift;
    if(@_) { $self-&gt;{ALARMLIMITCOLOUR} = shift }
    return $self-&gt;{ALARMLIMITCOLOUR};
}
sub TagsColour {
    my $self = shift;
    if(@_) { $self-&gt;{TAGSCOLOUR} = shift }
    return $self-&gt;{TAGSCOLOUR};
}
sub ScaledValue {
    my $self = shift;
    if(@_) { $self-&gt;{SCALEDVALUE} = shift }
    return $self-&gt;{SCALEDVALUE};
}
sub RealVal {
    my $self = shift;
    if(@_) { $self-&gt;{REALVAL} = shift }
    return $self-&gt;{REALVAL};
}
sub Limit {
    my $self = shift;
    if(@_) { $self-&gt;{LIMIT} = shift }
    return $self-&gt;{LIMIT};
}
sub TagsVal {
    my $self = shift;
    if(@_) { $self-&gt;{TAGSVAL} = @_ }
    return $self-&gt;{TAGSVAL};
}
sub TagsNum {
    my $self = shift;
    if(@_) { $self-&gt;{TAGSNUM} = shift }
    return $self-&gt;{TAGSNUM};
}
sub StartTag {
    my $self = shift;
    if(@_) { $self-&gt;{STARTTAG} = shift }
    return $self-&gt;{STARTTAG};
}
sub NumTags {
    my $self = shift;
    if(@_) { $self-&gt;{NUMTAGS} = shift }
    return $self-&gt;{NUMTAGS};
}
sub IncTag {
    my $self = shift;
    if(@_) { $self-&gt;{INCTAG} = shift }
    return $self-&gt;{INCTAG};
}
sub Max {
    my $self = shift;
    if(@_) { $self-&gt;{MAX} = shift }
    return $self-&gt;{MAX};
}
sub Min {
    my $self = shift;
    if(@_) { $self-&gt;{MIN} = shift }
    return $self-&gt;{MIN};
}
sub InitialValue {
    my $self = shift;
    if(@_) { $self-&gt;{INITIALVALUE} = shift }
    return $self-&gt;{INITIALVALUE};
}
sub lastpos {
    my $self = shift;
    if(@_) { $self-&gt;{LASTPOS} = shift }
    return $self-&gt;{LASTPOS};
}
sub DirHorizFlag {
    my $self = shift;
    if(@_) { $self-&gt;{DIRHORIZFLAG} = shift }
    return $self-&gt;{DIRHORIZFLAG};
}
sub ShowCurrent {
    my $self = shift;
    if(@_) { $self-&gt;{SHOWCURRENT} = shift }
    return $self-&gt;{SHOWCURRENT};
}
sub ShowLimits {
    my $self = shift;
    if(@_) { $self-&gt;{SHOWLIMITS} = shift }
    return $self-&gt;{SHOWLIMITS};
}
sub ShowLabel {
    my $self = shift;
    if(@_) { $self-&gt;{SHOWLABEL} = shift }
    return $self-&gt;{SHOWLABEL};
}
sub Font {
    my $self = shift;
    if(@_) { $self-&gt;{FONT} = shift }
    return $self-&gt;{FONT};
}
sub Label {
    my $self = shift;
    if(@_) { $self-&gt;{LABEL} = shift }
    return $self-&gt;{LABEL};
}
sub Selected {
    my $self = shift;
    if(@_) { $self-&gt;{SELECTED} = shift }
    return $self-&gt;{SELECTED};
}
1;&lt;/code&gt;&lt;/readmore&gt;

&lt;p&gt;James&lt;/p&gt;
&lt;div class="pmsig"&gt;&lt;div class="pmsig-982107"&gt;
&lt;p&gt;There's never enough time to do it right, but always enough time to do it over...&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</field>
</data>
</node>
