# $Id: Random.pm,v 1.16 2004/10/16 21:08:40 jfontain Exp $

# Perl module sample file with random data

# Corresponding pkgIndex.tcl file content:
# package ifneeded Random 1.9 "source [file join $dir Random.pm]"
# Module name must be capitalized and file extension .pm.
# The version number (1.9 here) must match the module version
# (see below).


package Random; # must be capitalized

# Note: the subroutines after(), yield(), flashMessage(), pushMessage(),
# popMessage() and traceMessage() are defined by the core in the module
# namespace and therefore should be seen as reserved subroutines, not to
# be redefined in the module (this file for the Random module).


use strict;
use warnings; # highly recommended, makes debugging much easier

BEGIN {
    # There is no need to export anything,
    # as moodss core uses fully qualified Perl names

    # The version number is mandatory. It must match with the
    # pkgIndex.tcl file content.
    # Example: our $VERSION = 1.18;
    our $VERSION = qw($Revision: 1.16 $)[1];
    # Note: see version number management requirements in moodss HTML
    # documentation (Module Development section)
}
# Module data is arranged in a hash and an array both named "data",
# It contains configuration values as well as dynamic data
# (see update subroutine).
our %data;
our @data;
our $asynchronous;

# Hash members are:

# - updates: a counter. Must be incremented as soon as the data has been
#   updated. Must be initialized in the module body in all cases as it
#   is used for automatic module detection (may not be changed in the
#   initialize subroutine).
$data{updates} = 0;

# Column numbers must be positive, starting from 0 included. Column members are:
#  - label: the title displayed on the top cell of the data column.
#  - type: valid types are: ascii, clock, dictionary, integer and real.
#    - ascii: simple strings.
#    - clock: accepts most date and time formats (you must really read the Tcl
#      clock command manual page, scan subcommand, for a precise description.
#    - dictionary: strings with eventually embedded numbers, taken into account
#      when sorting the column.
#    - real: floating point values.
#  - message: help text that appears in a widget tip when the mouse
#    cursor is left over the column title cell for a short while.
#  - anchor: optional member. Column data is either centered by default
#    (center), tucked to the left (left) or right side (right) of the
#    column.
$data{columns}[0] = {label => 'name', type => 'ascii', message => 'user name'};
$data{columns}[1] = {label => 'cpu', type => 'real', message => 'cpu usage in percent'};
$data{columns}[2] = {label => 'disk', type => 'integer', message => 'disk usage in megabytes'};
$data{columns}[3] = {label => 'memory', type => 'integer', message => 'memory usage in kilobytes'};
$data{columns}[4] = {label => 'command', type => 'dictionary', message => 'command name', anchor => 'left'};

# - pollTimes: in seconds. A list with the default poll time in first
#   position. The other times can be in any order, as the core will sort
#   them and use the lowest value as the minimum poll time value.
#   The minimum poll seconds must be a reasonable value depending on the
#   processing time of the update subroutine.
$data{pollTimes} = [10, 5, 20, 30, 60, 120, 300];

# - indices: an optional list that specifies the data columns that
#   should be displayed. If not specified, all the data columns are
#   visible.
#   Can be used when there are no views (see below).
#$data{indices} = [0, 1, 2, 3];

# - sort: an optional entry that defines the initial column to be used
#   for sorting the table data and in which direction. The specified
#   column must be visible (see indices member above).
#   Should be a hash with a single entry with the column as index and
#   either the 'increasing' or 'decreasing' keyword as value.
#   Can be used when there are no views (see below).
#$data{sort} = {1 => 'decreasing'};

# - indexColumns: a list that specifies the columns required to uniquely
#   identify a row in the table. It is optional and defaults to the
#   column 0, unless of course you do not have a 0 column index, in
#   which case it is mandatory to specify this list. When specified, all
#   the columns in the list must be visible (see indices member above or
#   in views below).
$data{indexColumns} = [0, 4];

# - views: optional. Arrays of hashes. If specified, it
#   defines one or more views to be used in place of the default
#   view. One table will be displayed per view.
#   For each view, 1 entry must be defined: 'indices' (syntax and usage
#   identical to the 'indices' list above).
#   2 members are optional:
#   'sort' (syntax and usage identical to the 'sort' entry above),
#   and
#   'swap', a boolean, specifies whether the data is displayed in a table
#   with columns and rows swapped (useful when data has only a single or
#   a couple of rows permanently).
#$data{views} = [
#    {indices => [0, 1, 3, 4], sort => {1 => 'decreasing'}, swap => 1},
#    {indices => [0, 2, 4], sort => {2 => 'decreasing'}}
#];
$data{views} = [
    {indices => [0, 1, 3, 4], sort => {1 => 'decreasing'}},
    {indices => [0, 2, 4], sort => {2 => 'decreasing'}}
];

# - persistent: optional (considered false if missing). A boolean
#   value that tells whether row numbers (used by the core, along with
#   the column numbers, to identify data cells) are identically mapped
#   from data row keys across module instances.
$data{persistent} = 1;

# - switches: optional. A hash of boolean values indexed by switches.
#   A switch is a single letter or a string prepended with the - or +
#   sign. The boolean value (0 or 1) specifies whether the switch takes
#   an argument. If the switches list is defined, an appropriate
#   initialize subroutine must be defined in the module (see initialize
#   subroutine example in this module). The core will take care of
#   parsing the command line and reject any invalid switch / value
#   combination for the module. The switches value may not be changed in
#   the initialize subroutine.
$data{switches} =
    {'-a' => 0, '--asynchronous' => 0, '-i' => 0, '--identify' => 0};

# - identifier: optional. A string that uniquely identifies this
#   module. It will be displayed by the core in the initial data tables
#   title area and data cell labels in viewers. This feature can be used
#   for example in modules that gather data from a remote host: in such
#   a case, the identifier could be set to dataType(hostName).
# $data{identifier} = 'random(test)';

# - resizableColumns: optional. A boolean value (0 or 1) which specifies
#   whether displayed data table(s) columns can be manually resized by
#   the user with the mouse. Not available on a per view basis.
# $data{resizableColumns} = 0;

# - helpText: displayed when the module help is launched from the main
#   window help menu. Can be plain text or HTML formatted (<HTML> and
#   <BODY> tags required), in which case it is properly rendered in the
#   module help window (note: tables, frames, and may other tags are not
#   supported: stick to formatted text at the moment).
# Here we load the HTML code from a file in this module directory, which
# the core changes to during the loading stage.
# The HTML code can obviously also be inlined.
$data{helpText} = `cat Random.htm`; # load HTML formatted help

# The initialize subroutine, if it exists, is invoked by the core before
# any update occurs (update subroutine invocation if the module is
# synchronous (always the case in Perl modules)). It can be used for
# module setup that cannot be accomplished during the loading phase.
# The initialize subroutine is optional when the module does not support
# command line arguments, and in such a case takes no arguments.
# The initialize subroutine is mandatory when the module supports command
# line arguments, and in such a case takes an hash as sole argument. The
# hash contains the switched options values, indexed by switch. For
# example, if the command line was:
# $ moodss random --asynchronous --other-option value -x 1234
# the hash will contain:
#   $options{--asynchronous} = 1
#   $options{--other-option} = value
#   $options{-x}             = 1234
# Note that the --asynchronous member value is filled with a boolean
# even though that switch takes no argument.
# For the above example, switches would have been defined as:
#   $data{switches} = [qw(-a 0 --asynchronous 0 --other-option 1 -x 1)];
# In all cases, data members other than updates and switches can be set
# or updated in the initialize subroutine, and still taken into account by
# the core.
sub initialize(%) {
    my %option = @_;
    $asynchronous = ($option{'-a'} || $option{'--asynchronous'});
    if ($asynchronous) {
        $data{pollTimes} = [-10];
        # use the core to call a subroutine after a number of milliseconds
        # without blocking the user interface, as sleep() would do
        after(3000, 'Random::update'); # boot simulation
    }
    if ($option{'-i'} || $option{'--identify'}) {
        # generate a unique module identifier:
        my $identifier = int(rand(100));
        $data{identifier} = "random $identifier";
    }
}

# The terminate subroutine if it exists is invoked by the core when
# the module is unloaded dynamically.
sub terminate() {
    # We could do some cleanup chores right here.
}

# The dynamic data array is 2 dimensional, indexed by row and column.
# The column number must start from 0 up to the total number of
# columns minus 1 (no holes are allowed in the column sequence).
# The row number can take any positive integer value (between 0 and
# 2147483647) and be defined in any order, as long as it is unique
# during the lifetime of the module data. If a new row is created, it
# must take a value that was never used: thus, the number of a row
# that has disappeared is not allowed. Row numbers need not be
# consecutive.
# Voidness for numeric data cells (integer or real type) takes the
# form of the ? character. Reminder: as long as a data row exists, all
# its data cells must exist. Thus voidness cannot be expressed by
# non-existence.
# The update subroutine function is to update module data.
sub update() {
    $data[0][0] = "John\nWo"; $data[0][4] = 'cc';
    $data[0][1] = sprintf('%.1f', rand(30)); $data[0][2] = 100 + int(rand(50)); $data[0][3] = 10 + int(rand(50));
    $data[1][0] = 'Bill'; $data[1][4] = 'xedit';
    $data[1][1] = sprintf('%.1f', rand(3)); $data[1][2] = 300 + int(rand(100)); $data[1][3] = 30 + int(rand(100));
    $data[2][0] = "Anny\nDoe"; $data[2][4] = 'ps';
    $data[2][1] = sprintf('%.1f', rand(5)); $data[2][2] = 200 + int(rand(30)); $data[2][3] = 20 + int(rand(30));
    $data[3][0] = 'Robert'; $data[3][4] = 'top';
    $data[3][1] = sprintf('%.1f', rand(10)); $data[3][2] = 500 + int(rand(150)); $data[3][3] = 50 + int(rand(150));
    $data[4][0] = "Peter\nWard"; $data[4][4] = 'ls';
    $data[4][1] = sprintf('%.1f', rand(8)); $data[4][2] = 50 + int(rand(10)); $data[4][3] = 5 + int(rand(10));
    $data[6][0] = 'Laura'; $data[6][4] = 'emacs';
    $data[6][1] = sprintf('%.1f', rand(20)); $data[6][2] = 90 + int(rand(20)); $data[6][3] = 9 + int(rand(20));
    $data[9][0] = 'Laura'; $data[9][4] = 'cc';
    $data[9][1] = sprintf('%.1f', rand(30)); $data[9][2] = 100 + int(rand(50)); $data[9][3] = 10 + int(rand(50));
    if (int(rand(4)) == 0) {
        # it is possible to post messages to the user:
        flashMessage('the Random module is still at work', 2);
        # also available: pushMessage(), popMessage() and traceMessage()
    }
    $data{updates}++;
    if ($asynchronous) { # simulate asynchronous operation if requested
        # use the core to call a subroutine after a number of milliseconds
        # without blocking the user interface, as sleep() would do
        after(2000 + int(rand(9000)), 'Random::update');
    }
}

END {
    # does not seem to be executed when interpreter is destroyed
    # as module is unloaded: you may use terminate() instead.
}

1; # traditional
