# $Id: randpy.py,v 1.12 2005/06/25 19:30:20 jfontain Exp $


# Python module sample file with random data

# Corresponding pkgIndex.tcl file content:
# package ifneeded randpy 1.5 "source [file join $dir randpy.py]"
# file extension must be .py.
# The version number (1.5 here) must match the module version
# (see below).

from string import split
from random import randint, uniform

# The version number is mandatory. It must match with the pkgIndex.tcl
# file content.

# Example: __version__ = 1.18
__version__ = split('$Revision: 1.12 $')[1]
# Note: see version number management requirements in moodss HTML
# documentation (Module Development section)

# Module data is arranged in a dictionary named "form", where static
# configuration values are kept, and a list of lists (rows of column
# cells) named "data", where dynamic data is placed (see update
# subroutine).
form = {}
data = {} # note: use data = [] if you prefer arrays (see update())
form['64Bits'] = 1 # unless the core is running on a Tcl version before 8.4
asynchronous = 0

# Dictionary 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).
form['updates'] = 0

# Columns data is stored in a list (indexed by column number) of dictionaries.
# 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.
form['columns'] = [
    {'label': 'name', 'type': 'ascii', 'message': 'user name'},
    {'label': 'cpu', 'type': 'real', 'message': 'cpu usage in percent'},
    {'label': 'disk', 'type': 'integer', 'message': 'disk usage in megabytes'},
    {'label': 'memory', 'type': 'integer', 'message': 'memory usage in kilobytes'},
    {'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.
form['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).
#form['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).
#form['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).
form['indexColumns'] = [0, 4]

# - views: optional. List of dictionaries. 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).
#form['views'] = [
#    {'indices': [0, 1, 3, 4], 'sort': {1: 'decreasing'}, 'swap': 1},
#    {'indices': [0, 2, 4], 'sort': {2: 'decreasing'}}
#]
form['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.
form['persistent'] = 1

# - switches: optional. A dictionary 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.
form['switches'] = {'-i': 0, '--identify': 0}
### disable asynchronous option till core can detect updates counter increments:
### form['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).
#form['identifier'] = 'randpy(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.
#form['resizableColumns'] = 1;

# - 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.
file = open('randpy.htm')
form['helpText'] = file.read() # load HTML formatted help
file.close()

# The initialize function, if it exists, is invoked by the core before
# any update occurs (update function 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 function is optional when the module does not support
# command line arguments, and in such a case takes no arguments.
# The initialize function is mandatory when the module supports command
# line arguments, and in such a case takes a dictionary as sole argument.
# The dictionary contains the switched options values, indexed by switch.
# For example, if the command line was:
# $ moodss random --asynchronous --other-option value -x 1234
# the dictionary 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:
#   form['switches'] = {'-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 function, and still taken into account by
# the core.
def initialize(options):
    global form, asynchronous
    try: asynchronous = options['--asynchronous']
    except:
        try: asynchronous = options['-a']
        except: pass
    if asynchronous:
        form['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, 'randpy.update()') # boot simulation
    identify = 0
    try: identify = options['--identify']
    except:
        try: identify = options['-i']
        except: pass
    if identify:
        # generate a unique module identifier:
        form['identifier'] = 'randpy ' + str(randint(0, 100));

# The terminate function if it exists is invoked by the core when
# the module is unloaded dynamically.
def terminate():
    # We could do some cleanup chores right here.
    pass

# 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 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 function purpose is to update module data.
def update():
    global form, data, asynchronous
    data = {
        0:["John\nWo", '%0.1f' % uniform(0, 30), randint(100, 150), randint(10, 60), 'cc'],
        1:['Bill', '%0.1f' % uniform(0, 3), randint(300, 400), randint(30, 130), 'xedit'],
        2:["Anny\nDoe", '%0.1f' % uniform(0, 5), randint(200, 230), randint(20, 50), 'ps'],
        4:["Peter\nWard", '%0.1f' % uniform(0, 5), randint(50, 60), randint(5, 15), 'ls'],
        2147483647:['Laura', '%0.1f' % uniform(0, 5), randint(90, 110), randint(9, 29), 'emacs'],
        2147483648:['Robert', '%0.1f' % uniform(0, 5), randint(500, 650), randint(50, 200), 'top'],
        18446744073709551615:['Laura', '%0.1f' % uniform(0, 5), randint(100, 150), randint(10, 60), 'cc']
    }
    # Note: with a core running on a Tcl version before 8.4, 64 bit integers are not supported
    # and Laura's index can only be 4294967295.
    # Note: instead of a dictionary, arrays are supported as follows, with holes as empty rows,
    # which is obviously much less practical:
    # data = [
    #     ["John\nWo", '%0.1f' % uniform(0, 30), randint(100, 150), randint(10, 60), 'cc'],
    #     ['Bill', '%0.1f' % uniform(0, 3), randint(300, 400), randint(30, 130), 'xedit'],
    #     ["Anny\nDoe", '%0.1f' % uniform(0, 5), randint(200, 230), randint(20, 50), 'ps'],
    #     ['Robert', '%0.1f' % uniform(0, 5), randint(500, 650), randint(50, 200), 'top'],
    #     ["Peter\nWard", '%0.1f' % uniform(0, 5), randint(50, 60), randint(5, 15), 'ls'],
    #     [],
    #     ['Laura', '%0.1f' % uniform(0, 5), randint(90, 110), randint(9, 29), 'emacs'],
    #     [],
    #     [],
    #     ['Laura', '%0.1f' % uniform(0, 5), randint(100, 150), randint(10, 60), 'cc']
    # ]
    form['updates'] = form['updates'] + 1
    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(randint(2000, 9000), 'randpy.update()')
