This quasi-random collection of articles (primarily postings to the
comp.lang.perl.tk newsgroup) are meant to
convey the spirit of trying to come up with either a perl/Tk module in
particular or a Perl-5 module in general. This collection of information does
not mention the use of xs code (h2xs, xsubpp, et al.). For more
illustrative information on xs extensions please see
Howdy-widget.html.
You might want to start by poking around your Tk-b# distribution directory.
Is there something there that already does what you want? Is there something
that is reasonably close - but only requires minor modification?
Next go through the various perl documents - including the
FAQ as well as the various relevant
man pages:
perlmod(1),
perlobj(1),
perlbot(1),
(and please don't forget:
perlpod(1)!)
Post your idea to
comp.lang.perl.tk and discuss it with
others - there might very well be someone working on an approach already.
A clear explanation of all the stuff that gets
put into a module was posted to the mailing list and can be found in the
archive at:
The following by Aaron Sherman was originally posted to
comp.lang.perl.misc and is a good general
discussion of things that typically go into any perl module. Of course
the naming hierarchy for Tk modules is pretty straightforward (e.g.Tk::MyPackage in this case):
Aaron Sherman
Re: is this how you write modules ?
***********************************
Tue, 05 Dec 1995 15:09:45 -0500 I-Kinetics, Inc.
Newsgroups:
comp.lang.perl.misc
References:
The basic idea behind your basic module:
The module should be called something descriptive
(see the module list for more info)
You can make it heirarchical (eg Mail::MIME), but
make sure that it makes sense, and fits with
what's there already (again see the module list
for more info).
The file name itself can be arived at via the following
transforms on the module name:
s/::/\//g;
s/$/\.pm/;
Note that this means that the module name DOES
not contain the .pm extension, only the file name.
Please don't try to put this code anywhere in your
module, I'm just illustrating the process that you
should do mentally to come up with the filename....
Start with a package statement:
package MyPackage;
Now you need to do all of your use/requires (you don't NEED
to do them ALL here, but grouping is probably wise):
require Exporter;
use Carp;
use Socket;
These are some examples. You will almost certainly
need Exporter, since that's the magic that makes
use work. Otherwise, you will have to write your
own import function.
Now you should set up inheiritance:
@ISA=qw(Exporter);
Now you need to export whatever will be exported by default.
This is kind of heavy handed, and you might want to
consider using EXPORT_OK for some of it:
@EXPORT=qw(myfunc);
List the functions, etc that can be exported if the user
wants them:
@EXPORT_OK(myotherfunc myotherotherfunc);
# This allows for "use MyPackage 'myotherfunc'"
This is where you start your actual function defs.
sub myfunc {
...
}
Terminate the module with a true value for require:
1;
__END__
You can always put POD-based documentation after the __END__
which will be processed by perldoc.
-AJS
--
Aaron Sherman
"They're just jealous because they don't have three wise men
or a virgin in their whole organization" - Mayor on ACLU suit
over city nativity scene.
By way of sherzer@dsg59.nad.ford.com (Allen Sherzer)
I-Kinetics, Inc. Pager: (508)545-0584
1 NE Executive Park Fax: (617)270-4979
Burlington, MA 01803-5005 Desk: (617)252-3489
ajs@ajs.com or ajs@openmarket.com WWW: http://ajs.com/~ajs/
The Dial.pm example
Here is an example of a working perl/Tk module, Roy Johnson's
<rjohnson@shell.com> Dial.pm. (Recent distributions of the Tk kit
have this module in the Tk*/Contrib/ directory.) Note that Roy
chose not to require Exporter in this version of Dial.pm, and he
chose to stick the plain old documentation near the top of the file beginning
with the =head1 NAME "statement":
#!/usr/bin/perl
# Dial widget
# ***********
#
# Roy Johnson (rjohnson@shell.com)
# Tue, 22 Aug 95 16:07:59 CDT
#
# Here's a dial widget I wrote as an exercise in learning how to write
# Composite widgets. If noone else has written a good dial widget, I'd
# be happy to let this one be the start. If there is a dial widget
# already out there, I'd like to know about it, but I'd still like my
# code critiqued by any of you who has some time to do so.
#
# I especially want comments regarding:
#
# - features it should have (not so important if it's not going to be
# an "official" widget). See the documentation for features I already
# have in mind.
# - poor use of Tk: working too hard to accomplish something
# - poor use of Perl's object-oriented features
#
# -------- Roy Johnson ---- rjohnson@shell.com ---- Speaking for myself --------
# "When the only tool you have is Perl, the whole | "Hooray for snakes!"
# world begins to look like your oyster." -- Me | -- The Simpsons (29 Apr 93)
# ============== And remember, you heard it first on Usenet News ===============
use Tk;
package Dial;
@ISA = qw(Tk::Frame);
$pi = atan2(1, 1) * 4;
Tk::Widget->Construct('Dial');
=head1 NAME
Dial - an alternative to the scale widget
=head1 SYNOPSIS
use Tk::Dial;
$dial = $top->Dial(-margin => 20,
-radius => 48,
-min => 0,
-max => 100,
-value => 0,
-format => '%d');
margin - blank space to leave around dial
radius - radius of dial
min, max - range of possible values
value - current value
format - printf-style format for displaying format
Values shown above are defaults.
=head1 DESCRIPTION
A dial looks like a speedometer: a 3/4 circle with a needle indicating
the current value. Below the graphical dial is an entry that displays
the current value, and which can be used to enter a value by hand.
The needle is moved by pressing button 1 in the canvas and dragging. The
needle will follow the mouse, even if the mouse leaves the canvas, which
allows for high precision. Alternatively, the user can enter a value in
the entry space and press Return to set the value; the needle will be
set accordingly.
=head1 TO DO
Configure
Tick marks
Step size
=head1 AUTHORS
Roy Johnson, rjohnson@shell.com
Based on a similar widget in XV, a program by John Bradley,
bradley@cis.upenn.edu
=head1 HISTORY
August 1995: Released for critique by pTk mailing list
=cut
@flags = qw(-margin -radius -min -max -value -format);
sub Populate
{
my ($w, $args) = @_;
@$w{@flags} = (20, 48, (0, 100), 0, '%d');
for $key (@flags) {
my $val = delete $args->{$key};
if (defined $val) {
$$w{$key} = $val;
}
}
# Pass other args on to Frame
$w->InheritThis($args);
# Convenience variables, based on flag settings
my ($margin, $radius, $min, $max, $format) = @$w{@flags};
my ($center_x, $center_y) = ($margin + $radius) x 2;
# Create Widgets
my $c = $w->Canvas(-width => 2 * ($radius + $margin),
-height => 1.75 * $radius + $margin);
$c->create('arc',
($center_x - $radius, $center_y - $radius),
($center_x + $radius, $center_y + $radius),
-start => -45, -extent => 270, -style => 'chord',
-width => 2);
$c->pack(-expand => 1, -fill => 'both');
$w->bind($c, '<1>' => \&drawPointer);
$w->bind($c, '' => \&drawPointer);
my $e = $w->Entry(-textvariable => \$w->{-value});
$e->pack();
$w->bind($e, '' => sub { &setvalue($c) });
&setvalue($c);
}
#------------------------------
sub drawPointer
{
my $c = shift;
my $w = $c->parent;
my $e = $c->XEvent;
# Convenience variables, based on flag settings
my ($margin, $radius, $min, $max, $value, $format) = @$w{@flags};
my ($center_x, $center_y) = ($margin + $radius) x 2;
my ($delta_x, $delta_y) = ($e->x - $center_x, $e->y - $center_y);
my $distance = sqrt($delta_x**2 + $delta_y**2);
return if ($distance < 1);
# atan2/pi returns the angle in pi-radians, but out-of-phase;
# here we correct it to be 0 at the start of the arc
my $angle = atan2($delta_y, $delta_x) / $pi + 1.25;
if ($angle > 2) { $angle -= 2 }
if ($angle < 1.5) {
my $factor = $radius/$distance;
my $newx = $center_x + int($factor * $delta_x);
my $newy = $center_y + int($factor * $delta_y);
$c->delete('oldpointer');
$c->create('line', ($newx, $newy, $center_x, $center_y),
-arrow => 'first', -tags => 'oldpointer',
-width => 2);
$w->{-value} = sprintf($format,
$angle / 1.5 * ($max - $min) + $min);
} elsif ($angle < 1.75) {
if ($w->{-value} < $max) {
&setvalue($c);
$w->{-value} = $max;
}
} else {
if ($w->{-value} > $min) {
&setvalue($c);
$w->{-value} = $min;
}
}
}
#------------------------------
sub setvalue {
my $c = shift;
my $w = $c->parent;
my $value = $w->{-value};
# Convenience variables, based on flag settings
my ($margin, $radius, $min, $max, $dummy, $format) = @$w{@flags};
my ($center_x, $center_y) = ($margin + $radius) x 2;
if ($value > $max) {
$value = $max;
} elsif ($value < $min) {
$value = $min;
}
$w->{-value} = sprintf($format, $value);
# value = (angle / 1.5) * (max-min) + min
# Solving backwards...
# value - min = angle / 1.5 * (max-min)
# (value - min) * 1.5 / (max-min) = angle
my $angle = ($value - $min) * 1.5 / ($max - $min);
$angle -= 1.25;
$angle *= $pi;
# Now just figure out X and Y where atan2 == $angle
my($x, $y) = (cos($angle) * $radius, sin($angle) * $radius);
$x += $center_x;
$y += $center_y;
$c->delete('oldpointer');
$c->create('line', ($x, $y, $center_x, $center_y),
-arrow => 'first', -tags => 'oldpointer',
-width => 2);
}
LabeledEntryLabeledRadiobutton
In your Tk-*/demos directory (where you built Tk) there is a sample script
called composite that in turn makes use of (actually it
requires Tk::demos::LabEnLabRad. In the Tk-*/demos/demos directory
there is the LabEnLabRad.pm file that contains the
LabeledEntryLabeledRadiobutton package declaration. The composite
script shows the advantage of object-oriented design of composites: the
reconfiguration is done quickly and with quite simple code.
Composite
The principal ideas behind composite widgets are discussed in the
composite widget class html page that comes
in your doc directory.
One uses a Populate method for Frame based constructs
and an InitObject method for Widget based constructs.
ConfigSpecs
How is the \%args hash handled by one's composite widgets? The answers are
given in the configspec.htm html page that
comes in your doc directory. Calling ->ConfigSpecs(); is useful
for disentangling the configure options that you intend for your subwidgets to
inherit properly.
Composite Widget How-To?
Marc Paquette (Marc.Paquette@softimage.com) Mon, 2 Oct 1995 10:40:50 -0400>>>>> "Mark" == Mark Elston <elston@cave.arc.nasa.gov> writes:
Mark> I have been trying to put together a composite widget (a
Mark> somewhat modified version of the FileSelect widget that came
Mark> with Tk-b8). After looking at the code that exists I began
Mark> to wonder just where to start. I found that the
Mark> documentation that we have so far really does not explain a
Mark> lot of the inner workings of Composites very well. At least
Mark> not if you are just trying to come up to speed on them (like
Mark> me).
Mark> I have read the Composite.pod and ConfigSpec.pod entries and
Mark> looked at Frame.pm and demo/LabEnLabRad.pm and I must admit
Mark> that I am still lost.
Remember what Nick said about b9 when reading the patched
Composite.pod:
Nick>
Nick> There will be a few minor changes in Tk-b9 - I think they are transparent,
Nick> or auto-convertable.
Nick>
Nick> One change is that the ConfigSpecs stuff has been 'pushed down' a level,
Nick> so that you can add them to things which are not Frames.
Nick> I have used this to derive HTMLText class directly from Text.
Mark> For example, what does the 'Component' method do? How about
Mark> 'Advertise'? I still can't make out how 'Delegates' is used
Mark> even after reading the (cryptic) references to it in
Mark> Composite and looking at any examples I can find. Where are
Mark> these methods documented? Are they documented?
For now, looking at the source is the way to go... Hard, but
instructive :-) But here are some answers (bear in mind that
these are the things I understood while looking at the source, they
might not be 100% accurate).
Component
It creates a widget instance of some kind to
be used as a "part" of your composite widget.
You can provide "creation-time" options in the
'Component' call.
Creating a component with this function will
make it part of the published interface to
your composite widget. This is what is called
"Advertizing" your components. The latter is
done when you accept that client code accesses
the component directly.
Advertise
You call this when you decide that it makes
sense to publish the existence of a certain
sub-widget. 'Component' does this for you
(among other things).
Delegates
Used by higher-levels widgets in the
composition hierarchy (a composite widget
is "higher" that its components) to pass down
the responsability to respond to a
method invocation. This is done when
you want to provide part of the interface of
your components to your composite. For
example, the LabEntry composite delegates
everything by default to the Entry component.
Therefore, calling the 'cget' method on a
LabEntry widget instance, will in fact invoke
the 'cget' method of the 'Entry' component.
Mark> ConfigSpecs is better documented but I am still having
Mark> trouble putting it into perspective. Perhaps if I could
Mark> only understand the rest of the stuff that goes on in the
Mark> Populate method ...
Mark> Can anyone help get me up over whatever hump(s) I am facing?
Hope this helps.
Mark> Thanks.
Mark> Mark.
Marc Paquette | Marc.Paquette@Softimage.COM
Administrateur de Systemes / Sysadmin | Softimage Inc
tel: (514) 845-1636 ext. 3426 | 3510 Boulevard St-Laurent
fax: (514) 845-5676 | Montreal (Quebec) H2X 2V2
Here is Marc Paquette's Composite.pod documentation patch:
*** Composite.pod.orig Wed Aug 16 06:08:27 1995
--- Composite.pod Wed Aug 23 10:05:50 1995
***************
*** 1,10 ****
--- 1,12 ----
=head1 NAME
+
Defining a new composite widget class
=head1 SYNOPSIS
+
package Whatever;
@ISA = qw(Tk::Frame); # or Tk::Toplevel
***************
*** 47,69 ****
=head1 DESCRIPTION
! A composite should normaly inherit C from C.
! C; will call C<$cw->InitObject(\%args)> which
! a compoiste will normally inherit from C.
! C will call C, which should
! be defined to create the characteristic subwidgets of the class.
C may call C to direct calls to methods
! to subwidgets. Typically most of not all methods are directed to a single
! subwidget - e.g. ScrolledListbox directs all methods to the core
! Listbox so that $composite->get(...) calls $listbox->get(...).
!
! C should also call C
! once C returns C
! walks through the ConfigSpecs entries and populates
! %$args hash with defaults for options from .Xdefaults etc.
When C returns to C,
! a call to $cw->configure(%$args) and sets *all* the options.
--- 49,147 ----
=head1 DESCRIPTION
!
! The intention behind a composite is to create a higher-level widget,
! sometime called a "super-widget". Most often, a composite will be
! built upon other widgets by B them, as opposed to specializing on them.
! For example, the supplied composite widget C is B an
! C and a C
_end of patch_
I am: Peter Prymmer Wilson Synchrotron Laboratory Cornell University Ithaca,
NY14853