diff -uNr bkchem-0.12.5/bkchem/oasa/convert.py bkchem-0.12.5-nopiddle/bkchem/oasa/convert.py
--- bkchem-0.12.5/bkchem/oasa/convert.py	2004-10-19 14:36:10.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/convert.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,151 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-try:
-  import oasa
-except ImportError:
-  print "sorry, but python 2.3 or higher is required for oasa"
-
-
-# small helper function
-def bug_message():
-  sys.stderr.write( "Error: the library crashed with the following exception:\n%s\n" % sys.exc_value)
-  sys.stderr.write( "If you are sure your input is OK, please send me a bugreport to beda@zirael.org\n")
-
-
-# the program
-usage_text ="""usage: python convert.py xy [infile] [outfile or stdout]
-
-  where x and y is input and output format, respectively
-  - possible values for x are 's' (smiles), 'i' (INChI), 'm' (molfile), 'c' (CDML)
-  - possible values for y are 's' (smiles), 'i' (INChI), 'm' (molfile)
-
-  if not infile is given the program is started in interactive mode
-
-example: python convert.py is naftalen.ichi naftalen.smiles
-"""
-
-import sys
-if len( sys.argv) < 2:
-  print usage_text
-  sys.exit()
-
-# initialization
-conv = sys.argv[1]
-
-if len( sys.argv) > 2:
-  inname = sys.argv[2]
-else:
-  inname = None
-
-
-if len( sys.argv) > 3:
-  outname = sys.argv[3]
-else:
-  outname = None
-
-# checking of the input values
-try:
-  inmode, outmode = conv
-except:
-  print usage_text
-  print "error: 'xy' input field should be exactly 2 characters long"
-  print
-  sys.exit()
-
-if inmode not in "csim":
-  print usage_text
-  print "error: the input format should be one of s,i,m"
-  print
-  sys.exit()
-
-if outmode not in "sim":
-  print usage_text
-  print "error: the output format should be one of s,m"
-  print
-  sys.exit()
-
-if inname:
-  try:
-    infile = file( inname, 'r')
-  except:
-    print "error: cannot open file %s for reading" % inname
-    print
-    sys.exit()
-else:
-  infile = None
-
-if outname:
-  try:
-    outfile = file( outname, 'w')
-  except:
-    print "error: cannot open file %s for writing" % inname
-    print
-    sys.exit()
-else:
-  outfile = sys.stdout
-
-# the code itself
-
-recoding = {'s':'smiles', 'i':'inchi', 'm':'molfile', 'c':'cdml'}
-
-in_recoder = oasa.__dict__[ recoding[ inmode]]
-out_recoder = oasa.__dict__[ recoding[ outmode]]
-
-import time
-
-if inname:
-  # normal mode
-  t = time.time()
-  try:
-    mol = in_recoder.file_to_mol( infile)
-    out_recoder.mol_to_file( mol, outfile)
-  except:
-    bug_message()
-  infile.close()
-  if outname:
-    outfile.close()
-  else:
-    print
-  sys.stderr.write( "processing time %.2f ms\n" % ((time.time()-t)*1000))
-
-else:
-  # interactive mode
-  import readline
-  try:
-    text = raw_input( recoding[inmode]+": ")
-  except:
-    text = ''
-  while text:
-    t = time.time()
-    try:
-      mol = in_recoder.text_to_mol( text)
-      out_recoder.mol_to_file( mol, outfile)  # outfile is stdout
-    except:
-      bug_message()
-    outfile.write("\n")
-    sys.stderr.write( "processing time %.2f ms\n" % ((time.time()-t)*1000))
-    try:
-      text = raw_input( recoding[inmode]+": ")
-    except:
-      text = ''
-
-    
-# end
-  
diff -uNr bkchem-0.12.5/bkchem/oasa/__init__.py bkchem-0.12.5-nopiddle/bkchem/oasa/__init__.py
--- bkchem-0.12.5/bkchem/oasa/__init__.py	2004-11-23 15:23:23.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/__init__.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,27 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-#import sys
-from oasa import *
-
-#print dir( oasa)
-
-#sys.modules['oasa'] = oasa
-
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/atom.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/atom.py
--- bkchem-0.12.5/bkchem/oasa/oasa/atom.py	2008-04-11 07:26:31.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/atom.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,356 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import sys
-sys.path.append( '../')
-
-import graph
-from chem_vertex import chem_vertex
-import periodic_table as PT
-from common import is_uniquely_sorted
-from oasa_exceptions import oasa_invalid_atom_symbol
-
-import copy
-import itertools
-from warnings import warn
-
-
-
-class atom( chem_vertex):
-  ## ("value","charge","x","y","z","multiplicity","valency","charge","free_sites")
-  attrs_to_copy = chem_vertex.attrs_to_copy + ("symbol", "isotope","explicit_hydrogens")
-
-  def __init__( self, symbol='C', charge=0, coords=None):
-    chem_vertex.__init__( self, coords=coords)
-    self.symbol = symbol
-    self.charge = charge
-    self.isotope = None
-    self.explicit_hydrogens = 0
-
-
-  def matches( self, other):
-    if not isinstance( other, atom):
-      return False
-    if self.symbol == other.symbol and self.valency == other.valency and self.multiplicity == other.multiplicity:
-      # check charge only if other has it set to something non-zero
-      if other.charge and self.charge != other.charge:
-        return False
-      return True
-    return False
-
-
-  ## PROPERTIES
-
-  # symbol
-  def _set_symbol( self, symbol):
-    self._clean_cache()
-    try:
-      self.valency = PT.periodic_table[ symbol]['valency'][0]
-      self.symbol_number = PT.periodic_table[ symbol]['ord']
-    except KeyError:
-      raise oasa_invalid_atom_symbol( "invalid atom symbol", symbol)
-    self._symbol = symbol
-
-  def _get_symbol( self):
-    return self._symbol
-
-  symbol = property( _get_symbol, _set_symbol, None, "atom symbol")
-
-
-
-
-
-  # occupied_valency (overrides chem_vertex occupied_valency)
-  def _get_occupied_valency( self):
-    try:
-      return self._cache['occupied_valency']
-    except KeyError:
-      pass
-
-    bonds_alternating_aromatic = 0
-    bonds_single_aromatic = 0
-    odd_aromatic = False
-    for b in self._neighbors.keys():
-      order = b.order
-      if order == 4:
-        if not odd_aromatic:
-          order = 1
-        else:
-          order = 2
-        bonds_single_aromatic += 1
-        bonds_alternating_aromatic += order
-        odd_aromatic = not odd_aromatic
-      else:
-        bonds_single_aromatic += order
-        bonds_alternating_aromatic += order
-
-    if self.charge:
-      # now we deal with charge
-      if abs( self.charge) > 1:
-        # charges higher than one should always decrease valency
-        charge = abs( self.charge)
-      elif (self.symbol in PT.accept_cation) and (self.charge == 1) and (self.valency <= PT.accept_cation[self.symbol]):
-        # elements that can accept cations to increase their valency (NH4+)
-        charge = -1
-      elif (self.symbol in PT.accept_anion) and (self.charge == -1) and (self.valency <= PT.accept_anion[self.symbol]):
-        # elements that can accept anions to increase their valency (BH4-)
-        charge = -1
-      else:
-        # otherwise charge reduces valency 
-        charge = abs( self.charge)
-    else:
-      charge = 0
-
-    x = bonds_alternating_aromatic+charge+self.multiplicity-1+self.explicit_hydrogens
-    if x > self.valency:
-      # we have computed occupied_valency using alternating single and double
-      # bonds for aromatic bonds, however it led to occupied_valency being higher
-      # than valency - lets replace it using single bonds for aromatic
-      # (this fixed thiophene where occupied_valency of S would be computed to be 3
-      #  and valency raise would be triggered)
-      x = bonds_single_aromatic+charge+self.multiplicity-1+self.explicit_hydrogens
-    #self._cache['occupied_valency'] = x
-    return x
-
-  occupied_valency = property( _get_occupied_valency, None, None, "atoms occupied valency")
-
-
-
-  # multiplicity (overrides chem_vertex multiplicity)
-  def _set_multiplicity( self, multiplicity):
-    chem_vertex._set_multiplicity( self, multiplicity)
-    if self.free_valency < 0:
-      self.raise_valency_to_senseful_value()
-
-  def _get_multiplicity( self):
-    return self._multiplicity
-
-  multiplicity = property( _get_multiplicity, _set_multiplicity, None, "atom multiplicity")
-
-
-
-  # free_sites
-  def _set_free_sites( self, free_sites):
-    self._free_sites = free_sites
-
-  def _get_free_sites( self):
-    if self._free_sites > self.free_valency:
-      return self.free_valency
-    return self._free_sites
-
-  free_sites = property( _get_free_sites, _set_free_sites, None, "atoms free_sites")
-
-
-  # isotope
-  def _set_isotope( self, isotope):
-    if isotope != None and type( isotope) != type( 1):
-      # isotope must be a number or None
-      raise oasa_exceptions.oasa_invalid_value( "isotope", isotope)
-    self._isotope = isotope
-
-  def _get_isotope( self):
-    return self._isotope
-
-  isotope = property( _get_isotope, _set_isotope, None, "isotope")
-
-
-
-  # electronegativity
-  def _get_electronegativity( self):
-    try:
-      return PT.periodic_table[self.symbol]['en']
-    except KeyError:
-      return None
-
-  electronegativity = property( _get_electronegativity, None, None, "atoms electronegativity")
-
-
-
-  # oxidation number
-  def _get_oxidation_number( self):
-    en = self.charge
-    for e, n in self.get_neighbor_edge_pairs():
-      if isinstance( n, atom) and n.symbol != self.symbol:
-        en += e.order * (n.electronegativity > self.electronegativity and 1 or -1)
-    hen = PT.periodic_table['H']['en']
-    en += self.free_valency * (hen > self.electronegativity and 1 or -1) 
-    return en
-
-  oxidation_number = property( _get_oxidation_number, None, None, "atoms oxidation number")
-
-
-  # electron pairs
-  def _get_electron_pairs( self):
-    return (PT.periodic_table[ self.symbol][ 'els'] - sum( [b.order for b in self.neighbor_edges]) -self.charge -self.free_valency -self.multiplicity+1) / 2.0
-
-  electron_pairs = property( _get_electron_pairs, None, None, "get number of atoms electron pairs")
-
-
-
-
-
-      
-  def __str__( self):
-    return "atom '%s'" % str( self.symbol)
-
-
-
-  def get_formula_dict( self):
-    """returns formula as dictionary that can
-    be passed to functions in periodic_table"""
-    ret = PT.formula_dict( self.symbol)
-    if self.free_valency + self.explicit_hydrogens > 0:
-      ret['H'] = self.free_valency + self.explicit_hydrogens
-    return ret
-
-
-
-
-  def raise_valency_to_senseful_value( self):
-    """set atoms valency to the lowest possible, so that free_valency
-    if non-negative (when possible) or highest possible,
-    does not lower valency when set to higher then necessary value"""
-    while self.free_valency < 0:
-      if not self.raise_valency():
-        return
-
-
-
-  def raise_valency( self):
-    """used in case where valency < occupied_valency to try to find a higher one"""
-    for v in PT.periodic_table[ self.symbol]['valency']:
-      if v > self.valency:
-        self.valency = v
-        #self._clean_cache()
-        return True
-    return False
-
-
-  def get_hydrogen_count( self):
-    return self.explicit_hydrogens + self.free_valency
-
-    
-
-  def is_chiral( self):
-    """this code is CIP (Cahn-Ingold-Prelog) based and therefore not necessarily
-    the fastest for this job, however in newer versions it will be able to take care
-    of the chirality of other centres and therefor will be universal,
-    for now it takes only care of the connectivity"""
-    if len( self._neighbors) < 4:
-      # this is not true for N,P and similar !!!
-      return False
-    neighs = self.get_neighbors()
-    cips = []
-    for a in neighs:
-      cips.append( [[], a, a.gen_CIP_sequence( came_from = self)])
-    while not is_uniquely_sorted( cips, cip_sorting_function):
-      for cip in cips:
-        try:
-          cip[0].append( cip[2].next().symbol_number)
-        except StopIteration:
-          cip[0].append( None)
-        except AttributeError:
-          cip[0].append( None)  # None was yielded and it has no symbol_number :)
-      # to test if we have already reached the end
-      for i in range( len( cips)-1):
-        if cip_sorting_function( cips[i], cips[i+1]) == 0 and cips[i][0][-3:-1] == [None, None]:
-          return False
-      cips.sort( cip_sorting_function)
-    return True
-
-
-
-  def get_neighbors_CIP_sorted( self):
-    """return neighbors sorted according to the CIP rules"""
-    neighs = self.get_neighbors()
-    cips = []
-    for a in neighs:
-      cips.append( [[], a, a.gen_CIP_sequence( came_from = self)])
-    while not is_uniquely_sorted( cips, cip_sorting_function):
-      for cip in cips:
-        try:
-          cip[0].append( cip[2].next().symbol_number)
-        except StopIteration:
-          cip[0].append( None)
-        except AttributeError:
-          cip[0].append( None)  # None was yielded and it has no symbol_number :)
-      # to test if we have already reached the end
-      all_finished = len( cip[0]) > 1
-      for cip in cips:
-        if len( cip[0]) > 1:
-          if cip[0][-3:-1] != [None, None]:
-            all_finished = 0
-      if all_finished:
-        break # it can't be uniquely sorted, we are out of atoms
-      cips.sort( cip_sorting_function)
-    return [cip[1] for cip in cips]
-
-
-
-
-
-  def gen_CIP_sequence( self, iter_over=None, came_from=None):
-    """generates the CIP (Cahn-Ingold-Prelog) stream of atoms suitable for
-    comparison in searches for chiral centres, their configuration etc.,
-    the values in different layers (with raising distance from self) are
-    separated by Nones"""
-    yield self
-    yield None
-    neighs = self.get_neighbors()
-    if came_from:
-      assert came_from in neighs
-      neighs.remove( came_from)
-    # end if there are no neighs
-    if not neighs:
-      raise StopIteration
-    # generators of the neighs
-    cips = []
-    for a in neighs:
-      cips.append( [[], [], a.gen_CIP_sequence( came_from = self), 0])
-      #         number_list, atom_list, cip_generator, last yielded index
-    while cips:
-      # this returns the already generated cip of the new first atom in later cycles
-      to_remove = []
-      for cip in cips:
-        try:
-          cs = itertools.takewhile( lambda x: x!=None, cip[2])
-        except StopIteration:
-          to_remove.append( cip)
-          continue
-        cip[1] += cs
-        cip[0] += [c.symbol_number for c in cs]
-      [cips.remove( cip) for cip in to_remove]
-      cips.sort( cip_sorting_function)
-      for cip in cips:
-        for i in range( cip[3], len( cip[1])):
-          yield cip[1][i]
-        cip[3] = len( cip[1])
-      yield None
-
-
-
-def cip_sorting_function( a, b):
-  return -cmp( a[0], b[0])
-
-
-
-##################################################
-# TODO
-
-# chirality for those possesing a free electron pair
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/bond.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/bond.py
--- bkchem-0.12.5/bkchem/oasa/oasa/bond.py	2008-10-14 09:46:12.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/bond.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,117 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-"""the bond resides here"""
-
-##  the order 1,2,3 is self-evident, order=4 means aromatic bond for which no
-##  localized bond order is available
-
-
-import sys
-sys.path.append( '../')
-
-import graph
-from warnings import warn
-import math
-
-
-class bond( graph.edge, object):
-  """is based on edge, however the vertices are not a Set anymore,
-  we need to preserve the order (for instance for wedge bonds).
-  type is 'n'-normal, 'w'-wedge, 'h'-hatch, 'a'-adder, 'b'-bold, 'd'-dash"""
-
-  attrs_to_copy = graph.edge.attrs_to_copy + ("order","aromatic","type")
-
-  def __init__( self, vs=[], order=1, type='n'):
-    graph.edge.__init__( self, vs=vs)
-    self.set_vertices( vs)
-    self.aromatic = None  # None means it was not set
-    self.set_order( order)
-    self.type = type
-    self.properties_ = {}
-    self.stereochemistry = None
-
-
-  def __str__( self):
-    if self._vertices:
-      return "bond between %s %s" % tuple( map( str, self.vertices))
-    else:
-      return "bond, no vertices set"
-
-
-  def matches( self, other):
-    if self.order == other.order:
-      return True
-    return False
-
-
-
-  def set_vertices( self, vs=[]):
-    """sets the vertices this edge connects"""
-    assert len( vs) == 2 or len( vs) == 0
-    if len( vs) == 2 and vs[0] == vs[1]:
-      warn( "creating bond with both ends equal", UserWarning, 2)
-    self._vertices = list( vs)
-
-  def get_vertices( self):
-    return self._vertices
-
-  def set_order( self, order):
-    [a.bond_order_changed() for a in self.vertices]
-    if order == 4:
-      self._order = None
-      self.aromatic = 1
-    else:
-      self._order = order
-      #self.aromatic = None
-
-  def get_order( self):
-    if self._order == None and self.aromatic:
-      return 4
-    else:
-      return self._order
-
-
-  def get_length( self):
-    if len( self.vertices) == 2:
-      v1, v2 = self.vertices
-      return math.sqrt( (v1.x-v2.x)**2 + (v1.y-v2.y)**2)
-    else:
-      return 0
-      
-
-
-  ## PROPERTIES
-
-  vertices = property( get_vertices, set_vertices, None,
-                       "property, the tuple of 2 vertices (start,end)")
-
-  order = property( get_order, set_order, None,
-                    """property, 1-3 for normal bonds, 4 for aromatic bonds for which
-                    localized order is not available, for localized aromatic bonds
-                    check the bond.aromatic boolean attribute""")
-  
-  length = property( get_length, None, None,
-                     """the bond length""")
-
-  ## END OF PROPERTIES
-
-### TODO
-
-# support for the bond.aromatic
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/cairo_out.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/cairo_out.py
--- bkchem-0.12.5/bkchem/oasa/oasa/cairo_out.py	2008-10-14 09:44:51.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/cairo_out.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,713 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-import cairo
-import geometry
-import math
-import misc
-import operator
-import copy
-import sys
-
-
-class cairo_out:
-
-  """
-  This object is used to draw OASA molecules using cairo. Cairo supports different
-  'surfaces' which represent different file formats.
-  This object implements PNG file drawing, but should be general enough to work with other
-  formats, provided modified version of create_surface and write_surface methods are provided
-  when this class is subclassed.
-
-  Usage:
-
-  c = cairo_out( scaling=2.0, margin=10, font_size=20, bond_width=6)
-  c.show_hydrogens_on_hetero = True
-  c.mol_to_cairo( mol, 'outfile.png') # mol is oasa molecule
-
-  # attributes can be set in constructor or afterwards as normal attributes
-  # default options are set and described in the cairo_out.default_options dictionary
-  """
-
-  _temp_margin = 200
-
-  atom_colors_minimal = {'O': (1,0,0),
-                         'N': (0,0,1),
-                         'S': (.5,.5,0),
-                         'Cl': (0,0.8,0),
-                         'Br': (.5,0,0),
-                         }
-
-  ## taken from: http://bodr.svn.sourceforge.net/viewvc/*checkout*/bodr/trunk/bodr/elements/elements.xml?revision=34&content-type=text%2Fplain
-  ## C and H modified to be black
-  ## F,S,Cl modified to a darker color (suggested by Toon Verstraelen)
-  atom_colors_full = {
-    'H': (0.00,0.00,0.00), 'He': (0.85,1.00,1.00),
-    'Li': (0.80,0.50,1.00), 'Be': (0.76,1.00,0.00), 'B': (1.00,0.71,0.71),
-    'C': (0.0,0.0,0.0), 'N': (0.05,0.05,1.00), 'O': (1.00,0.05,0.05),
-    'F': (0.30,0.70,0.70), 'Ne': (0.70,0.89,0.96), 'Na': (0.67,0.36,0.95),
-    'Mg': (0.54,1.00,0.00), 'Al': (0.75,0.65,0.65), 'Si': (0.50,0.60,0.60),
-    'P': (1.00,0.50,0.00), 'S': (0.80,0.80,0.20), 'Cl': (0.00,0.80,0.00),
-    'Ar': (0.50,0.82,0.89), 'K': (0.56,0.25,0.83), 'Ca': (0.24,1.00,0.00),
-    'Sc': (0.90,0.90,0.90), 'Ti': (0.75,0.76,0.78), 'V': (0.65,0.65,0.67),
-    'Cr': (0.54,0.60,0.78), 'Mn': (0.61,0.48,0.78), 'Fe': (0.50,0.48,0.78),
-    'Co': (0.44,0.48,0.78), 'Ni': (0.36,0.48,0.76), 'Cu': (1.00,0.48,0.38),
-    'Zn': (0.49,0.50,0.69), 'Ga': (0.76,0.56,0.56), 'Ge': (0.40,0.56,0.56),
-    'As': (0.74,0.50,0.89), 'Se': (1.00,0.63,0.00), 'Br': (0.65,0.16,0.16),
-    'Kr': (0.36,0.72,0.82), 'Rb': (0.44,0.18,0.69), 'Sr': (0.00,1.00,0.00),
-    'Y': (0.58,1.00,1.00), 'Zr': (0.58,0.88,0.88), 'Nb': (0.45,0.76,0.79),
-    'Mo': (0.33,0.71,0.71), 'Tc': (0.23,0.62,0.62), 'Ru': (0.14,0.56,0.56),
-    'Rh': (0.04,0.49,0.55), 'Pd': (0.00,0.41,0.52), 'Ag': (0.88,0.88,1.00),
-    'Cd': (1.00,0.85,0.56), 'In': (0.65,0.46,0.45), 'Sn': (0.40,0.50,0.50),
-    'Sb': (0.62,0.39,0.71), 'Te': (0.83,0.48,0.00), 'I': (0.58,0.00,0.58),
-    'Xe': (0.26,0.62,0.69), 'Cs': (0.34,0.09,0.56), 'Ba': (0.00,0.79,0.00),
-    'La': (0.44,0.83,1.00), 'Ce': (1.00,1.00,0.78), 'Pr': (0.85,1.00,0.78),
-    'Nd': (0.78,1.00,0.78), 'Pm': (0.64,1.00,0.78), 'Sm': (0.56,1.00,0.78),
-    'Eu': (0.38,1.00,0.78), 'Gd': (0.27,1.00,0.78), 'Tb': (0.19,1.00,0.78),
-    'Dy': (0.12,1.00,0.78), 'Ho': (0.00,1.00,0.61), 'Er': (0.00,0.90,0.46),
-    'Tm': (0.00,0.83,0.32), 'Yb': (0.00,0.75,0.22), 'Lu': (0.00,0.67,0.14),
-    'Hf': (0.30,0.76,1.00), 'Ta': (0.30,0.65,1.00), 'W': (0.13,0.58,0.84),
-    'Re': (0.15,0.49,0.67), 'Os': (0.15,0.40,0.59), 'Ir': (0.09,0.33,0.53),
-    'Pt': (0.96,0.93,0.82), 'Au': (0.80,0.82,0.12), 'Hg': (0.71,0.71,0.76),
-    'Tl': (0.65,0.33,0.30), 'Pb': (0.34,0.35,0.38), 'Bi': (0.62,0.31,0.71),
-    'Po': (0.67,0.36,0.00), 'At': (0.46,0.31,0.27), 'Rn': (0.26,0.51,0.59),
-    'Fr': (0.26,0.00,0.40), 'Ra': (0.00,0.49,0.00), 'Ac': (0.44,0.67,0.98),
-    'Th': (0.00,0.73,1.00), 'Pa': (0.00,0.63,1.00), 'U': (0.00,0.56,1.00),
-    'Np': (0.00,0.50,1.00), 'Pu': (0.00,0.42,1.00), 'Am': (0.33,0.36,0.95),
-    'Cm': (0.47,0.36,0.89), 'Bk': (0.54,0.31,0.89), 'Cf': (0.63,0.21,0.83),
-    'Es': (0.70,0.12,0.83), 'Fm': (0.70,0.12,0.73), 'Md': (0.70,0.05,0.65),
-    'No': (0.74,0.05,0.53), 'Lr': (0.78,0.00,0.40), 'Rf': (0.80,0.00,0.35),
-    'Db': (0.82,0.00,0.31), 'Sg': (0.85,0.00,0.27), 'Bh': (0.88,0.00,0.22),
-    'Hs': (0.90,0.00,0.18), 'Mt': (0.91,0.00,0.15), 'Ds': (0.92,0.00,0.14),
-    'Rg': (0.93,0.00,0.13),
-    }
-
-  # all the following values are settable using the constructor, e.g.
-  # cairo_out( margin=20, bond_width=3.0)
-  # all metrics is scaled properly, the values corespond to pixels only
-  # when scaling is 1.0
-  default_options = {
-    'scaling': 1.0,
-    # should atom coordinates be rounded to whole pixels before rendering?
-    # This improves image sharpness but might slightly change the geometry
-    'align_coords': True,
-    # the following also draws hydrogens on shown carbons
-    'show_hydrogens_on_hetero': False,
-    'show_carbon_symbol': False, 
-    'margin': 15,
-    'line_width': 1.0,
-    # how far second bond is drawn
-    'bond_width': 6.0,
-    'wedge_width': 6.0,
-    'font_name': "Arial",
-    'font_size': 16,
-    'background_color': (1,1,1),
-    'color_atoms': True,
-    'color_bonds': True,
-    'space_around_atom': 2,
-    # you can choose between atom_colors_full, atom_colors_minimal
-    # or provide a custom dictionary
-    'atom_colors': atom_colors_full,
-    # proportion between subscript and normal letters size
-    'subscript_size_ratio': 0.8,
-    # how much to shorten second line of double and triple bonds (0-0.5)
-    'bond_second_line_shortening': 0.15,
-    # the following two are just for playing
-    # - without antialiasing the output is ugly
-    'antialias_text': True,
-    'antialias_drawing': True,
-    # this will only change appearance of overlapping text
-    'add_background_to_text': False,
-    }
-
-
-  def __init__( self, **kw):
-    for k,v in self.__class__.default_options.iteritems():
-      setattr( self, k, v)
-    # list of paths that contribute to the bounding box (probably no edges)
-    self._vertex_to_bbox = {} # vertex-to-bbox mapping
-    self._bboxes = [] # for overall bbox calcualtion
-    for k,v in kw.items():
-      if k in self.__class__.default_options:
-        setattr( self, k, v)
-      else:
-        raise Exception( "unknown attribute '%s' passed to constructor" % k)
-
-
-  def draw_mol( self, mol):
-    if not self.surface:
-      raise Exception( "You must initialize cairo surface before drawing, use 'create_surface' to do it.")
-    self.molecule = mol
-    for v in mol.vertices:
-      self._draw_vertex( v)
-    for e in copy.copy( mol.edges):
-      self._draw_edge( e)
-
-  def create_surface( self, w, h):
-    """currently implements PNG writting, but might be overriden to write other types;
-    w and h are minimal estimated width and height"""
-    # trick - we use bigger surface and then copy from it to a new surface and crop
-    self.surface = cairo.ImageSurface( cairo.FORMAT_ARGB32, w, h)
-
-
-  def write_surface( self):
-    """currently implements PNG writting, but might be overriden to write other types"""
-    # Because it is not possible to calculate the bounding box of a drawing before its drawn (mainly
-    # because we don't know the size of text items), this object internally draws to a surface with
-    # large margins and the saves a cropped version into a file (this is done by drawing to a new
-    # surface and using the old one as source.
-    #self.surface.write_to_png( self.filename)
-    #return 
-    # real width and height
-    x1, y1, x2, y2 = self._get_bbox()
-    x1, y1 = self.context.user_to_device( x1, y1)
-    x2, y2 = self.context.user_to_device( x2, y2)
-    width = int( x2 - x1 + 2*self.margin*self.scaling)
-    height = int( y2 - y1 + 2*self.margin*self.scaling)
-    surface = cairo.ImageSurface( cairo.FORMAT_ARGB32, width, height)
-    context = cairo.Context( surface)
-    context.set_source_rgb( *self.background_color)
-    context.rectangle( 0, 0, width, height)
-    context.fill()
-    context.set_source_surface( self.surface, round( -x1+self.margin*self.scaling), round( -y1+self.margin*self.scaling))
-    context.rectangle( 0, 0, width, height)
-    context.fill()
-    context.show_page()
-    surface.write_to_png( self.filename)
-    surface.finish()
-
-
-  def mol_to_cairo( self, mol, filename):
-    x1, y1, x2, y2 = None, None, None, None
-    for v in mol.vertices:
-      v.y = -v.y # flip coords - molfiles have them the other way around
-      if self.align_coords:
-        v.x = self._round( v.x)
-        v.y = self._round( v.y)
-      if x1 == None or x1 > v.x:
-        x1 = v.x
-      if x2 == None or x2 < v.x:
-        x2 = v.x
-      if y1 == None or y1 > v.y:
-        y1 = v.y
-      if y2 == None or y2 < v.y:
-        y2 = v.y
-    w = int( x2 - x1)
-    h = int( y2 - y1)
-    self._bboxes.append( (x1,y1,x2,y2))
-
-    self.filename = filename
-
-    # create cairo surface for drawing
-    _w = int( w+2*self.scaling*self._temp_margin)
-    _h = int( h+2*self.scaling*self._temp_margin)
-    self.create_surface( _w, _h)
-
-    self.context = cairo.Context( self.surface)
-    if not self.antialias_drawing:
-      self.context.set_antialias( cairo.ANTIALIAS_NONE)
-    if not self.antialias_text:
-      options = self.context.get_font_options()
-      options.set_antialias( cairo.ANTIALIAS_NONE)
-      self.context.set_font_options( options)
-    self.context.translate( round( -x1*self.scaling+self.scaling*self._temp_margin), round( -y1*self.scaling+self.scaling*self._temp_margin))
-    self.context.scale( self.scaling, self.scaling)
-    self.context.rectangle( x1, y1, w, h)
-    self.context.new_path()
-    self.context.set_source_rgb( 0, 0, 0)
-    self.draw_mol( mol)
-    self.context.show_page()
-    # write the content to the file
-    self.write_surface()
-    self.surface.finish()
-    # flip y coordinates back
-    for v in mol.vertices:
-      v.y = -v.y
-
-  def _round( self, x):
-    if self.line_width % 2:
-      return round( x) + 0.5
-    return round( x)
-
-  def _draw_edge( self, e):
-    def draw_plain_or_colored_line( _start, _end, second=False):
-      """second means if this is not the main line, drawing might be different"""
-      if not has_shown_vertex or not self.color_bonds:
-        if not second:
-          self._draw_line( _start, _end, line_width=self.line_width, capstyle=cairo.LINE_CAP_ROUND)
-        else:
-          self._draw_line( _start, _end, line_width=self.line_width, capstyle=cairo.LINE_CAP_BUTT)
-      else:
-        self._draw_colored_line( _start, _end, line_width=self.line_width, start_color=color1, end_color=color2)
-
-    def draw_plain_or_colored_wedge( _start, _end):
-      x1, y1 = _start
-      x2, y2 = _end
-      x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, self.wedge_width/2.0)
-      xa, ya, xb, yb = geometry.find_parallel( x1, y1, x2, y2, self.line_width/2.0) 
-      # no coloring now
-      if not has_shown_vertex or not self.color_bonds:
-        self._create_cairo_path( [(xa, ya), (x0, y0), (2*x2-x0, 2*y2-y0), (2*x1-xa, 2*y1-ya)], closed=True)
-        self.context.set_source_rgb( 0,0,0)
-        self.context.fill()
-      else:
-        # ratio 0.4 looks better than 0.5 because the area difference
-        # is percieved more than length difference
-        ratio = 0.4
-        xm1 = ratio*xa + (1-ratio)*x0 
-        ym1 = ratio*ya + (1-ratio)*y0
-        xm2 = (1-ratio)*(2*x2-x0) + ratio*(2*x1-xa)
-        ym2 = (1-ratio)*(2*y2-y0) + ratio*(2*y1-ya)
-        self.context.set_source_rgb( *color1)
-        self._create_cairo_path( [(xa,ya), (xm1,ym1), (xm2,ym2), (2*x1-xa, 2*y1-ya)], closed=True)
-        self.context.fill()
-        self.context.set_source_rgb( *color2)
-        self._create_cairo_path( [(xm1,ym1), (x0, y0), (2*x2-x0, 2*y2-y0), (xm2,ym2)], closed=True)
-        self.context.fill()
-
-    def draw_plain_or_colored_hatch( _start, _end):
-      x1, y1 = _start
-      x2, y2 = _end
-      # no coloring now
-      x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, self.wedge_width/2.0)
-      xa, ya, xb, yb = geometry.find_parallel( x1, y1, x2, y2, self.line_width/2.0) 
-      d = math.sqrt( (x1-x2)**2 + (y1-y2)**2) # length of the bond
-      if d == 0:  
-        return  # to prevent division by zero
-      dx1 = (x0 - xa)/d 
-      dy1 = (y0 - ya)/d 
-      dx2 = (2*x2 -x0 -2*x1 +xa)/d 
-      dy2 = (2*y2 -y0 -2*y1 +ya)/d 
-      # we have to decide if the first line should be at the position of the first atom
-      draw_start = 1  # is index not boolean
-      if not v1 in self._vertex_to_bbox and v1.occupied_valency > 1:
-        draw_start = 1
-      draw_end = 1    # is added to index not boolean
-      if not v2 in self._vertex_to_bbox and v2.occupied_valency > 1:
-        draw_end = 0
-      # adjust the step length
-      step_size = 2*(self.line_width)
-      ns = round( d / step_size) or 1
-      step_size = d / ns
-      # now we finally draw
-      self.context.set_line_cap( cairo.LINE_CAP_BUTT)
-      self.context.set_source_rgb( *color1)
-      middle = 0.5 * (draw_start + int( round( d/ step_size)) + draw_end - 2)
-      for i in range( draw_start, int( round( d/ step_size)) +draw_end):
-        coords = [xa+dx1*i*step_size, ya+dy1*i*step_size, 2*x1-xa+dx2*i*step_size, 2*y1-ya+dy2*i*step_size] 
-        if coords[0] == coords[2] and coords[1] == coords[3]:
-          if (dx1+dx2) > (dy1+dy2): 
-            coords[0] += 1
-          else:
-            coords[1] += 1
-        self._create_cairo_path( [coords[:2],coords[2:]])
-        if i >= middle:
-          self.context.stroke()
-          self.context.set_source_rgb( *color2)
-      self.context.stroke()
-
-    # code itself
-
-    coords = self._where_to_draw_from_and_to( e)
-    if not coords:
-      return 
-    start = coords[:2]
-    end = coords[2:]
-    v1, v2 = e.vertices
-    if self.color_bonds:
-      color1 = self.atom_colors.get( v1.symbol, (0,0,0))
-      color2 = self.atom_colors.get( v2.symbol, (0,0,0))
-    else:
-      color1 = color2 = (0,0,0)
-    has_shown_vertex = bool( [1 for _v in e.vertices if _v in self._vertex_to_bbox])
-
-    if e.order == 1:
-      if e.type == 'w':
-        draw_plain_or_colored_wedge( start, end)
-      elif e.type == 'h':
-        draw_plain_or_colored_hatch( start, end)
-      else:
-        draw_plain_or_colored_line( start, end)
-      
-    if e.order == 2:
-      side = 0
-      # find how to center the bonds
-      # rings have higher priority in setting the positioning
-      in_ring = False
-      for ring in self.molecule.get_smallest_independent_cycles():
-        double_bonds = len( [b for b in self.molecule.vertex_subgraph_to_edge_subgraph(ring) if b.order == 2])
-        if v1 in ring and v2 in ring:
-          in_ring = True
-          side += double_bonds * reduce( operator.add, [geometry.on_which_side_is_point( start+end, (a.x,a.y)) for a in ring if a!=v1 and a!=v2])
-      # if rings did not decide, use the other neigbors
-      if not side:
-        for v in v1.neighbors + v2.neighbors:
-          if v != v1 and v!= v2:
-            side += geometry.on_which_side_is_point( start+end, (v.x, v.y))
-      # if neighbors did not decide either
-      if not side and (in_ring or not has_shown_vertex):
-        if in_ring:
-          # we don't want centered bonds inside rings
-          side = 1 # select arbitrary value
-        else:
-          # bond between two unshown atoms - we want to center them only in some cases
-          if len( v1.neighbors) == 1 and len( v2.neighbors) == 1:
-            # both atoms have only one neighbor 
-            side = 0
-          elif len( v1.neighbors) < 3 and len( v2.neighbors) < 3:
-            # try to figure out which side is more towards the center of the molecule
-            side = reduce( operator.add, [geometry.on_which_side_is_point( start+end, (a.x,a.y))
-                                          for a in self.molecule.vertices if a!=v1 and a!=v2], 0)
-            if not side:
-              side = 1 # we choose arbitrary value, we don't want centering
-      if side:
-        draw_plain_or_colored_line( start, end)
-        x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], self.bond_width*misc.signum( side))
-        # shorten the second line
-        length = geometry.point_distance( x1,y1,x2,y2)
-        if v2 not in self._vertex_to_bbox:
-          x2, y2 = geometry.elongate_line( x1, y1, x2, y2, -self.bond_second_line_shortening*length)
-        if v1 not in self._vertex_to_bbox:
-          x1, y1 = geometry.elongate_line( x2, y2, x1, y1, -self.bond_second_line_shortening*length)
-        draw_plain_or_colored_line( (x1, y1), (x2, y2), second=True)
-      else:
-        for i in (1,-1):
-          x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], i*self.bond_width*0.5)
-          draw_plain_or_colored_line( (x1, y1), (x2, y2))
-
-    elif e.order == 3:
-      self._draw_line( start, end, line_width=self.line_width)
-      for i in (1,-1):
-        x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], i*self.bond_width*0.7)
-        draw_plain_or_colored_line( (x1, y1), (x2, y2), second=True)
-    
-
-  def _where_to_draw_from_and_to( self, b):
-    # at first check if the bboxes are not overlapping
-    atom1, atom2 = b.vertices
-    x1, y1 = atom1.x, atom1.y
-    x2, y2 = atom2.x, atom2.y
-    bbox1 = self._vertex_to_bbox.get( atom1, None)
-    bbox2 = self._vertex_to_bbox.get( atom2, None)
-    if bbox1 and bbox2 and geometry.do_rectangles_intersect( bbox1, bbox2):
-      return None
-    # then we continue with computation
-    if bbox1:
-      x1, y1 = geometry.intersection_of_line_and_rect( (x1,y1,x2,y2), bbox1, round_edges=0)
-    if bbox2:
-      x2, y2 = geometry.intersection_of_line_and_rect( (x1,y1,x2,y2), bbox2, round_edges=0)
-    if geometry.point_distance( x1, y1, x2, y2) <= 1.0:
-      return None
-    else:
-      return (x1, y1, x2, y2)
-
-  def _is_there_place( self, atom, x, y):
-    x1, y1 = atom.x, atom.y
-    angle1 = geometry.clockwise_angle_from_east( x-x1, y-y1)
-    for n in atom.neighbors:
-      angle = geometry.clockwise_angle_from_east( n.x-x1, n.y-y1)
-      if abs( angle - angle1) < 0.3:
-        return False
-    return True
-
-
-  def _find_place_around_atom( self, atom):
-    x, y = atom.x, atom.y
-    coords = [(a.x,a.y) for a in atom.neighbors]
-    # now we can compare the angles
-    angles = [geometry.clockwise_angle_from_east( x1-x, y1-y) for x1,y1 in coords]
-    angles.append( 2*math.pi + min( angles))
-    angles.sort()
-    angles.reverse()
-    diffs = misc.list_difference( angles)
-    i = diffs.index( max( diffs))
-    angle = (angles[i] +angles[i+1]) / 2
-    return angle
-
-  def _draw_vertex( self, v):
-    pos = sum( [(a.x < v.x) and -1 or 1 for a in v.neighbors if abs(a.x-v.x)>0.2])
-    if 'show_symbol' in v.properties_:
-      show_symbol = v.properties_['show_symbol']
-    else:
-      show_symbol = (v.symbol != "C" or v.degree == 0 or self.show_carbon_symbol)
-    if show_symbol:
-      x = v.x
-      y = v.y
-      text = v.symbol
-      hs = ""
-      if self.show_hydrogens_on_hetero or v.properties_.get( 'show_hydrogens', False):
-        if v.free_valency == 1:
-          hs = "H"
-        elif v.free_valency > 1:
-          hs = "H<sub>%d</sub>" % v.free_valency
-      if not hs:
-        pos = -1
-      if pos <= 0:
-        text += hs
-      else:
-        text = hs + text
-      # charge
-      charge = ""
-      if v.charge == 1:
-        charge = "<sup>+</sup>"
-      elif v.charge == -1:
-        charge = "<sup>&#x2212;</sup>"
-      elif v.charge > 1:
-        charge = "<sup>%d+</sup>" % v.charge
-      elif v.charge < -1:
-        charge = "<sup>%d&#x2212;</sup>" % abs( v.charge)
-      if charge:
-        if self._is_there_place( v, v.x+3, v.y-2) or v.charge < 0:
-          # we place negative charge regardless of available place
-          # otherwise minus might be mistaken for a bond
-          text += charge
-          charge = ""
-      
-      # coloring
-      if self.color_atoms:
-        color = self.atom_colors.get( v.symbol, (0,0,0))
-      else:
-        color = (0,0,0)
-      center_letter = pos <= 0 and 'first' or 'last'
-      bbox = self._draw_text( (x,y), text, center_letter=center_letter, color=color)
-      self._vertex_to_bbox[v] = geometry.expand_rectangle( bbox, self.space_around_atom)
-
-      # sometimes charge is done here, if it wasn't done before
-      if charge:
-        assert v.charge > 0
-        # if charge was not dealt with we change its appearance from 2+ to ++
-        charge = v.charge * "+"
-        if self._is_there_place( v, v.x, v.y-10):
-          angle = 1.5*math.pi
-        elif self._is_there_place( v, v.x, v.y+10):
-          angle = 0.5*math.pi
-        else:
-          angle = self._find_place_around_atom( v)
-        self.context.set_font_size( self.subscript_size_ratio * self.font_size)
-        xbearing, ybearing, width, height, x_advance, y_advance = self.context.text_extents( charge)
-        x0 = v.x + 40*math.cos( angle)
-        y0 = v.y + 40*math.sin( angle)
-        line = (v.x,v.y,x0,y0)
-        charge_bbox = [x0-0.5*width,y0-0.5*height,x0+0.5*width,y0+0.5*height]
-        x1, y1 = geometry.intersection_of_line_and_rect( line, bbox, round_edges=0)
-        x2, y2 = geometry.intersection_of_line_and_rect( line, charge_bbox, round_edges=0)
-        x2, y2 = geometry.elongate_line( x1,y1,x2,y2, -self.space_around_atom)
-        x = x0 + x1 - x2
-        y = y0 + y1 - y2
-        # draw
-        self.context.set_source_rgb( *color)
-        self.context.move_to( round( x-xbearing-0.5*width), round( y+0.5*height))
-        self.context.show_text( charge)
-
-
-
-  ## ------------------------------ lowlevel drawing methods ------------------------------
-
-  def _draw_colored_line( self, start, end, line_width=1, capstyle=cairo.LINE_CAP_BUTT,
-                          start_color=(0,0,0), end_color=(0,0,0)):
-    x1,y1 = start
-    x2,y2 = end
-    length = geometry.point_distance( x1,y1,x2,y2)
-    xn2,yn2 = geometry.elongate_line( x1,y1,x2,y2, -0.5*length)
-    line1 = [(x1,y1),(xn2,yn2)]
-    xn1,yn1 = geometry.elongate_line( x2,y2,x1,y1, -0.5*length)
-    line2 = [(xn1,yn1),(x2,y2)]
-    self.context.set_line_cap( cairo.LINE_CAP_BUTT) # this is forced here
-    self.context.set_line_width( line_width)
-    for line,color in zip( [line1,line2], [start_color,end_color]):
-      self.context.set_source_rgb( *color)
-      self._create_cairo_path( line, closed=False)
-      self.context.stroke()
-    
-
-  def _draw_line( self, start, end, line_width=1, capstyle=cairo.LINE_CAP_BUTT, color=(0,0,0)):
-    self.context.set_source_rgb( *color)
-    # cap style
-    self.context.set_line_cap( capstyle)
-    # line width
-    self.context.set_line_width( line_width)
-    # the path itself 
-    cs = [start, end]
-    self._create_cairo_path( cs, closed=False)
-    # stroke it
-    self.context.stroke()
-
-
-  def _draw_text( self, xy, text, font_name=None, font_size=None, center_letter=None,
-                  color=(0,0,0)):
-    import xml.sax
-    class text_chunk:
-      def __init__( self, text, attrs=None):
-        self.text = text
-        self.attrs = attrs or set()
-
-    class FtextHandler ( xml.sax.ContentHandler):
-      def __init__( self):
-        xml.sax.ContentHandler.__init__( self)
-        self._above = []
-        self.chunks = []
-        self._text = ""
-      def startElement( self, name, attrs):
-        self._closeCurrentText()
-        self._above.append( name)
-      def endElement( self, name):
-        self._closeCurrentText()
-        self._above.pop( -1)
-      def _closeCurrentText( self):
-        if self._text:
-          self.chunks.append( text_chunk( self._text, attrs = set( self._above)))
-          self._text = ""
-      def characters( self, data):
-        self._text += data
-
-    # parse the text for markup
-    handler = FtextHandler()
-    try:
-      xml.sax.parseString( "<x>%s</x>" % text, handler)
-    except:
-      chunks = [text_chunk( text)]
-    else:
-      chunks = handler.chunks
-
-    if not font_name:
-      font_name = self.font_name
-    if not font_size:
-      font_size = self.font_size
-
-    # font properties
-    self.context.select_font_face( font_name)
-    self.context.set_font_size( font_size)
-    asc, desc, letter_height, _a, _b = self.context.font_extents()
-    x, y = xy
-    if center_letter == 'first':
-      if "sup" in chunks[0].attrs or 'sub' in chunks[0].attrs:
-        self.context.set_font_size( int( font_size * self.subscript_size_ratio))
-      else:
-        self.context.set_font_size( font_size)
-      xbearing, ybearing, width, height, x_advance, y_advance = self.context.text_extents( chunks[0].text[0])
-      x -= 0.5*x_advance
-      y += 0.5*height
-    elif center_letter == 'last':
-      # this is more complicated - we must do a dry run of the text layout
-      _dx = 0
-      for i,chunk in enumerate( chunks):
-        if "sup" in chunk.attrs or 'sub' in chunk.attrs:
-          self.context.set_font_size( int( font_size * self.subscript_size_ratio))
-        else:
-          self.context.set_font_size( font_size)
-        xbearing, ybearing, width, height, x_advance, y_advance = self.context.text_extents( chunk.text)
-        _dx += x_advance
-      # last letter
-      xbearing, ybearing, width, height, x_advance, y_advance = self.context.text_extents( chunk.text[-1])      
-      x -= _dx - 0.5*x_advance
-      y += 0.5*height
-      
-    self.context.new_path()
-    x1 = round( x)
-    bbox = None
-    for chunk in chunks:
-      y1 = round( y)
-      if "sup" in chunk.attrs:
-        y1 -= asc / 2
-        self.context.set_font_size( int( font_size * self.subscript_size_ratio))
-      elif "sub" in chunk.attrs:
-        y1 += asc / 2
-        self.context.set_font_size( int( font_size * self.subscript_size_ratio))
-      else:
-        self.context.set_font_size( font_size)
-      xbearing, ybearing, width, height, x_advance, y_advance = self.context.text_extents( chunk.text)
-      # background
-      if self.add_background_to_text:
-        self.context.rectangle( x1+xbearing, y1+ybearing, width, height)
-        self.context.set_source_rgb( *self.background_color)
-        self.context.fill()
-        #self.context.set_line_width( 3)
-        #self.context.stroke()
-      # text itself
-      _bbox = [x1+xbearing, y1+ybearing, x1+xbearing+width, y1+ybearing+height]
-      self._bboxes.append( _bbox)
-      # store bbox for the first chunk only
-      if not bbox or center_letter=='last':
-        bbox = _bbox
-      self.context.set_source_rgb( *color)
-      self.context.move_to( x1, y1)
-      self.context.show_text( chunk.text)
-      #self.context.fill()
-      x1 += x_advance
-    return bbox
-
-
-
-  def _draw_rectangle( self, coords, fill_color=(1,1,1)):
-    #outline = self.paper.itemcget( item, 'outline')
-    x1, y1, x2, y2 = coords
-    self.context.set_line_join( cairo.LINE_JOIN_MITER)
-    self.context.rectangle( x1, y1, x2-x1, y2-y1)
-    self.context.set_source_rgb( *fill_color)
-    self.context.fill_preserve()
-    #self.context.set_line_width( width)
-    #self.set_cairo_color( outline)
-    self.context.stroke()
-
-    
-    
-  def _create_cairo_path( self, points, closed=False):
-    x, y = points[0]
-    self.context.move_to( x, y)
-    for (x,y) in points[1:]:
-      self.context.line_to( x, y)
-    if closed:
-      self.context.close_path()
-
-  def _get_bbox( self):
-    bbox = list( self._bboxes[0])
-    for _bbox in self._bboxes[1:]:
-      x1,y1,x2,y2 = _bbox
-      if x1 < bbox[0]:
-        bbox[0] = x1
-      if y1 < bbox[1]:
-        bbox[1] = y1
-      if x2 > bbox[2]:
-        bbox[2] = x2
-      if y2 > bbox[3]:
-        bbox[3] = y2
-    return bbox
-
-
-def mol_to_png( mol, filename, **kw):
-  c = cairo_out( **kw)
-  c.mol_to_cairo( mol, filename)
-
-
-if __name__ == "__main__":
-
-  import smiles
-
-  mol = smiles.text_to_mol( "FCCSCl", calc_coords=30)
-  #mol.vertices[0].properties_['show_hydrogens'] = False
-  #mol.vertices[1].properties_['show_symbol'] = False
-  #mol.vertices[2].properties_['show_symbol'] = True
-  mol_to_png( mol, "output.png", show_hydrogens_on_hetero=True)
-
-##   import inchi
-##   mol = inchi.text_to_mol( "1/C7H6O2/c8-7(9)6-4-2-1-3-5-6/h1-5H,(H,8,9)", include_hydrogens=False, calc_coords=30)
-##   mol_to_png( mol, "output.png")
-
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/cdml.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/cdml.py
--- bkchem-0.12.5/bkchem/oasa/oasa/cdml.py	2008-02-19 04:11:40.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/cdml.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,169 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-from plugin import plugin
-from molecule import molecule
-from atom import atom
-from bond import bond
-import dom_extensions as dom_ext
-import xml.dom.minidom as dom
-from known_groups import cdml_to_smiles
-from periodic_table import periodic_table as PT
-from coords_generator import calculate_coords
-import smiles
-
-
-
-def read_cdml( text):
-  """returns the last molecule for now"""
-  doc = dom.parseString( text)
-  #if doc.childNodes()[0].nodeName == 'svg':
-  #  path = "/svg/cdml/molecule"
-  #else:
-  #  path = "/cdml/molecule"
-  path = "//molecule"
-  do_not_continue_this_mol = 0
-  for mol_el in dom_ext.simpleXPathSearch( doc, path):
-    atom_id_remap = {}
-    mol = molecule()
-    groups = []
-    for atom_el in dom_ext.simpleXPathSearch( mol_el, "atom"):
-      name = atom_el.getAttribute( 'name')
-      if not name:
-        #print "this molecule has an invalid symbol"
-        do_not_continue_this_mol = 1
-        break
-      pos = dom_ext.simpleXPathSearch( atom_el, 'point')[0]
-      x = cm_to_float_coord( pos.getAttribute('x'))
-      y = cm_to_float_coord( pos.getAttribute('y'))
-      z = cm_to_float_coord( pos.getAttribute('z'))
-      if name in PT:
-        # its really an atom 
-        a = atom( symbol=name,
-                  charge=atom_el.getAttribute( 'charge') and int( atom_el.getAttribute( 'charge')) or 0,
-                  coords=( x, y, z))
-        mol.add_vertex( v=a)
-      elif name in cdml_to_smiles:
-        # its a known group
-        group = smiles.text_to_mol( cdml_to_smiles[ name], calc_coords=0)
-        a = group.vertices[0]
-        a.x = x
-        a.y = y
-        a.z = z
-        mol.insert_a_graph( group) 
-      atom_id_remap[ atom_el.getAttribute( 'id')] = a
-    if do_not_continue_this_mol:
-      break
-
-    for bond_el in dom_ext.simpleXPathSearch( mol_el, "bond"):
-      type = bond_el.getAttribute( 'type')
-      if type[1] == u'0':
-        # we ignore bonds with order 0
-        continue
-      v1 = atom_id_remap[ bond_el.getAttribute( 'start')]
-      v2 = atom_id_remap[ bond_el.getAttribute( 'end')]
-      e = bond( order=int( type[1]), type=type[0])
-      mol.add_edge( v1, v2, e=e)
-
-    if mol.is_connected():
-      # this is here to handle diborane and similar weird things
-      yield mol
-    else:
-      for comp in mol.get_disconnected_subgraphs():
-        yield comp
-      
-
-def cm_to_float_coord( x):
-  if not x:
-    return 0
-  if x[-2:] == 'cm':
-    return float( x[:-2])*72/2.54
-  else:
-    return float( x)
-    
-
-##################################################
-# MODULE INTERFACE
-
-from StringIO import StringIO
-
-reads_text = 1
-reads_files = 1
-writes_text = 0
-writes_files = 0
-
-def file_to_mol( f):
-  return text_to_mol( f.read())
-
-def text_to_mol( text):
-  gen = read_cdml( text)
-  try:
-    mol = gen.next()
-  except StopIteration:
-    return None
-  calculate_coords( mol, bond_length=-1)
-  return mol
-
-#
-##################################################
-  
-
-##################################################
-# DEMO
-
-if __name__ == '__main__':
-
-  import sys
-
-  if len( sys.argv) < 1:
-    print "you must supply a filename"
-    sys.exit()
-
-  # parsing of the file
-
-  file_name = sys.argv[1]
-  f = file( file_name, 'r')
-  mol = file_to_mol( f)
-  f.close()
-
-  import time
-
-  t = time.time()
-  lens = map( len, mol.get_all_cycles())
-  lens.sort()
-  print lens
-  print time.time() -t 
-  print "total %d rings" % len( lens)
-
-##     mring = mol.get_new_induced_subgraph( ring, mol.vertex_subgraph_to_edge_subgraph( ring))
-##     if not mring.is_connected():
-##       print map( len, [a for a in mring.get_connected_components()])
-##       for vs in mring.get_connected_components():
-##         print [a.symbol for a in vs]
-      #import molfile
-      #print molfile.mol_to_text( mring)
-
-
-  #calculate_coords( mol, bond_length=-1)
-
-  #for a in mol.vertices:
-  #  print a.x, a.y
-
-  print mol
-  #print smiles.mol_to_text( mol)
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/chem_vertex.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/chem_vertex.py
--- bkchem-0.12.5/bkchem/oasa/oasa/chem_vertex.py	2008-04-15 03:31:26.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/chem_vertex.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,183 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import sys
-sys.path.append( '../')
-
-import graph
-import periodic_table as PT
-from common import is_uniquely_sorted
-
-import copy
-from warnings import warn
-
-
-
-class chem_vertex( graph.vertex):
-
-  """this class is a parent class of atoms, groups etc., it defines common properties
-  for vertices used in chemical context. It should not be instantiated directly, but
-  rather inherited from."""
-
-  attrs_to_copy = graph.vertex.attrs_to_copy + ("charge","x","y","z","multiplicity","valency","charge","free_sites")
-
-  def __init__( self, coords=None):
-    graph.vertex.__init__( self)
-    self.charge = 0
-    self.free_sites = 0
-    # None means not set (used)
-    if coords:
-      self.x, self.y, self.z = coords
-    else:
-      self.x = None
-      self.y = None 
-      self.z = None
-    self._multiplicity = 1
-
-
-  def matches( self, other):
-    if other is self:
-      return False
-    return True
-      
-
-  ## PROPERTIES
-
-  # coords
-  def _set_coords( self, coords):
-    if len( coords) == 2:
-      self.x, self.y = coords
-      self.z = 0
-    elif len( coords) == 3:
-      self.x, self.y, self.z = coords
-    else:
-      raise "wrong number of coordinates"
-
-  def _get_coords( self):
-    return self.x, self.y, self.z
-
-  coords = property( _get_coords, _set_coords, None, "atom coords")
-
-
-
-
-  # charge
-  def _set_charge( self, charge):
-    self._clean_cache()
-    self._charge = charge
-
-  def _get_charge( self):
-    return self._charge
-
-  charge = property( _get_charge, _set_charge, None, "atom charge")
-
-
-
-  # multiplicity
-  def _set_multiplicity( self, multiplicity):
-    self._clean_cache()
-    self._multiplicity = multiplicity
-
-  def _get_multiplicity( self):
-    return self._multiplicity
-
-  multiplicity = property( _get_multiplicity, _set_multiplicity, None, "atom multiplicity")
-
-
-
-  # valency
-  def _set_valency( self, valency):
-    self._clean_cache()
-    self._valency = valency
-
-  def _get_valency( self):
-    return self._valency
-
-  valency = property( _get_valency, _set_valency, None, "atoms valency")
-
-
-  # occupied_valency
-  def _get_occupied_valency( self):
-    i = 0
-    for b in self._neighbors.keys():
-      ord = b.order
-      if ord == 4:
-        ord = 1
-      i += ord
-    return i
-
-  occupied_valency = property( _get_occupied_valency, None, None, "atoms occupied valency")
-
-
-  # free_valency
-  def _get_free_valency( self):
-    try:
-      return self._cache[ 'free_valency']
-    except KeyError:
-      x = self.valency - self.occupied_valency
-      self._cache[ 'free_valency'] = x
-      return x
-
-  free_valency = property( _get_free_valency, None, None, "atoms free valency")
-
-
-  # weight
-  def _get_weight( self):
-    try:
-      return PT.periodic_table[self.symbol]['weight']
-    except:
-      return 0
-
-  weight = property( _get_weight, None, None, "atom weight")
-
-  # free_sites
-  def _set_free_sites( self, free_sites):
-    self._free_sites = free_sites
-
-  def _get_free_sites( self):
-    really_free = self._free_sites - self.occupied_valency
-    if really_free < 0:
-      return 0
-    else:
-      return really_free
-
-  free_sites = property( _get_free_sites, _set_free_sites, None, "atoms free_sites")
-
-
-  def get_x( self):
-    return self.x or 0
-
-  def get_y( self):
-    return self.y or 0
-
-  def get_z( self):
-    return self.z or 0
-
-  def has_aromatic_bonds( self):
-    for b in self._neighbors.keys():
-      if b.aromatic:
-        return 1
-    return 0
-
-  def bond_order_changed( self):
-    """called by a bond when its order was changed"""
-    self._clean_cache()
-
-  def get_hydrogen_count( self):
-    return 0
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/common.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/common.py
--- bkchem-0.12.5/bkchem/oasa/oasa/common.py	2008-10-14 09:43:48.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/common.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,57 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-
-
-def gen_combinations_of_series( series):
-  """series is a list of lists (tuples), the generator yields
-  lists by combining each element of each list with each other"""
-  counter = len( series) * [0]
-  end = [i-1 for i in map( len, series)]
-  counter[0] = -1 # dirty trick
-  while counter != end:
-    for i, e in enumerate( end):
-      if counter[i] < e:
-        counter[i] += 1
-        for j in range( i):
-          counter[j] = 0
-        break
-    yield [s[ counter[ j]] for j,s in enumerate( series)]
-
-
-def is_uniquely_sorted( series, sorting_function=None):
-  """you put a *sorted* series inside and get the information whether all the items
-  are unique (there are no two same items)"""
-  sf = sorting_function or cmp
-  for i in range( len( series)-1):
-    if sf( series[i], series[i+1]) == 0:
-      return False
-  return True
-
-
-
-def least_common_item( series):
-  d = {}
-  for i in series:
-    d[i] = d.get( i, 0) + 1
-  return d.keys()[ d.values().index( min( d.values()))]
-
-
-    
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/config.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/config.py
--- bkchem-0.12.5/bkchem/oasa/oasa/config.py	2008-02-19 04:11:40.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/config.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,34 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-import molecule
-
-
-class Config (object):
-  """this is a singleton class for library wide configuration"""
-  
-  molecule_class = molecule.molecule
-
-  def create_molecule( self):
-    return self.molecule_class()
-  create_molecule = classmethod( create_molecule)
-
-
-  inchi_binary_path = "/home/beda/bin/cInChI-1-102b"
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/converter_base.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/converter_base.py
--- bkchem-0.12.5/bkchem/oasa/oasa/converter_base.py	2008-02-19 05:08:54.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/converter_base.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,68 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-"""Here resides the abstract parent class for all converters"""
-
-
-class converter_base( object):
-
-  STATUS_OK = 1
-  STATUS_CRITICAL_ERROR = 2
-
-  # standard converter attrs
-  reads_text = False
-  writes_text = False
-  reads_files = False
-  writes_files = False
-
-  default_configuration = {}
-
-  def __init__( self):
-    self.configuration = {}
-    for k,v in self.default_configuration.iteritems():
-      self.configuration[k] = v
-    self.cleanup()
-
-  def clean_logs( self):
-    # here all warnings for the last run should be stored
-    self.warnings = []
-    # here error messages for the last run will be stored
-    self.errors = []
-
-  def mols_to_text( self, structures):
-    self.clean_logs()
-    pass
-
-  def read_text( self, text):
-    self.clean_logs()
-    pass
-
-  def mols_to_file( self, structures, f):
-    self.clean_logs()
-    pass
-
-  def read_file( self, f):
-    self.clean_logs()
-    pass
-
-  def cleanup( self):
-    self.clean_logs()
-    self.result = []
-    self.last_status = None
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/coords_generator.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/coords_generator.py
--- bkchem-0.12.5/bkchem/oasa/oasa/coords_generator.py	2008-10-14 09:44:51.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/coords_generator.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,542 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-from __future__ import division
-from math import pi, sqrt, sin, cos
-import geometry
-import misc
-
-import warnings
-
-class coords_generator:
-
-  def __init__( self, bond_length=1):
-    self.bond_length = bond_length
-
-  def calculate_coords( self, mol, bond_length=0, force=0):
-    """the bond_length (when given) sets the self.bond_length,
-    if bond_length == -1 we suppose that there is already part of the molecule containing
-    coords and we calculate the bond_length from it;
-    force says if we should recalc all coords"""
-    processed = []
-    self.mol = mol
-    # stereochemistry info
-    self.stereo = {}
-    for st in self.mol.stereochemistry:
-      if st.__class__.__name__ == "cis_trans_stereochemistry":
-        for a in (st.references[0],st.references[-1]):
-          self.stereo[a] = self.stereo.get( a, []) + [st]
-    # at first we have a look if there is already something with coords
-    atms = set( [a for a in mol.vertices if a.x != None and a.y != None])
-    # then we check if they are in a continuos block but not the whole molecule
-    # (in this case we regenerate all the coords if force, otherwise exit)
-    if len( atms) == len( mol.vertices) and not force:
-      return 
-    elif not force and atms and not len( atms) == len( mol.vertices):
-      # this is here just to setup the molecule well
-      self.rings = mol.get_smallest_independent_cycles()
-      # it is - we can use it as backbone
-      sub = mol.get_new_induced_subgraph( atms, mol.vertex_subgraph_to_edge_subgraph( atms))
-      subs = [comp for comp in sub.get_connected_components()]
-      if len( subs) == 1:
-        backbone = atms
-      else:
-        # we should not be here, but when yes than we have to solve it
-        maxlength = max( map( len, subs))
-        backbone = [su for su in subs if len( su) == maxlength][0]
-        # we have to set the coords to None (for sure)
-        for sub in subs:
-          if sub != backbone:
-            for a in sub:
-              a.x = None
-              a.y = None
-      # now we check if we have to calculate bond_length from the backbone
-      if bond_length < 0:
-        bls = []
-        for b in mol.vertex_subgraph_to_edge_subgraph( backbone):
-          a1, a2 = b.vertices
-          bls.append( sqrt( (a1.x-a2.x)**2 + (a1.y-a2.y)**2))
-        self.bond_length = sum( bls) / len( bls)
-      elif bond_length == 0:
-        pass
-      else:
-        self.bond_length = bond_length
-      # at last we have to remove rings that have coords from the self.rings
-      for ring in self.rings[:]:
-        if backbone >= ring:
-          self.rings.remove( ring)
-    else:
-      if force:
-        for a in self.mol.vertices:
-          a.x = None
-          a.y = None
-      if bond_length > 0:
-        # here we must have bond_length > 0
-        self.bond_length = bond_length
-      # we create a backbone, it is either one of the rings or
-      # the longest chain in case of acyclic molecule
-      if mol.contains_cycle():
-        # ring
-        self.rings = mol.get_smallest_independent_cycles()
-        # find the most crowded ring
-        jmax = 0
-        imax = 0
-        for i, ring in enumerate( self.rings):
-          j = 0
-          for r in self.rings:
-            if r != ring and r & ring:
-              j += 1
-          if j > jmax:
-            jmax = j
-            imax = i
-        #
-        backbone = self.rings.pop(imax)
-        gcoords = gen_ring_coords( len( backbone), side_length=self.bond_length)
-        for v in mol.sort_vertices_in_path( backbone):
-          v.x, v.y = gcoords.next()
-        processed += backbone
-        processed += self.process_all_anelated_rings( backbone)
-      elif len( mol.vertices) == 1:
-        a = mol.vertices[0]
-        a.x, a.y, a.z = 0, 0, 0
-        backbone = [a]
-      else:
-        # chain - we process 2 atoms as backbone and leave the rest for the code
-        a1 = self.mol.vertices[0]
-        a2 = a1.neighbors[0]
-        a1.x, a1.y = 0, 0
-        a2.x, a2.y = self.bond_length, 0
-        backbone = [a1,a2]
-        self.rings = []
-    processed += backbone
-    self._continue_with_the_coords( mol, processed=processed)
-    for v in mol.vertices:
-      if v.z == None:
-        v.z = 0
-
-  def _continue_with_the_coords( self, mol, processed=[]):
-    """processes the atoms in circles around the backbone (processed) until all is done"""
-    while processed:
-      new_processed = []
-      for v in processed:
-        if len( [o for o in self.mol.vertices if o.x == None]) == 0:
-          # its all done
-          return
-        # look if v is part of a ring
-        ring = None
-        for r in self.rings:
-          if v in r:
-            ring = r
-            break
-        if not ring:
-          # v is not in a ring - we can continue
-          new_processed += self.process_atom_neigbors( v)
-        else:
-          # v is in ring so we process the ring
-          if len( processed) > 1 and mol.defines_connected_subgraph_v( processed) and set( processed) <= ring:
-            new_processed += self.process_all_anelated_rings( processed)
-          else:
-            self.rings.remove( ring)
-            ring = mol.sort_vertices_in_path( ring, start_from=v)
-            ring.remove( v)
-            d = [a for a in v.get_neighbors() if a.x != None and a.y != None][0] # should always work
-            ca = geometry.clockwise_angle_from_east( v.x-d.x, v.y-d.y)
-            size = len( ring)+1
-            da = deg_to_rad( 180 -180*(size-2)/size)
-            gcoords = gen_angle_stream( da, start_from=ca-pi/2+da/2)
-            # here we generate the coords
-            self.apply_gen_to_atoms( gcoords, ring, v)
-            ring.append( v)
-            new_processed += ring
-            new_processed += self.process_all_anelated_rings( ring)
-      processed = new_processed
-
-  def process_all_anelated_rings( self, base):
-    out = []
-    to_go = []
-    b = set( base)
-    for r in self.rings:
-      if r & b:
-        to_go.append( r)
-    for r in to_go:
-      out += self.process_one_anelated_ring( base)
-    if out:
-      for b in to_go:
-        out += self.process_all_anelated_rings( b)
-    return out
-      
-
-  def process_one_anelated_ring( self, base):
-    mol = self.mol
-    out = []
-    ring = None
-    b = set( base)
-    for r in self.rings:
-      if r & b:
-        ring = r
-        break
-    if ring:
-      self.rings.remove( ring)
-      inter = b & ring
-      to_go = [a for a in ring if a.x == None or a.y == None]
-      if len( ring) - len( to_go) == len( inter):
-        out += self._process_simply_anelated_ring( ring, base)
-      else:
-        out += self._process_multi_anelated_ring( ring)
-    return out
-
-
-  def process_atom_neigbors( self, v):
-    def get_angle_at_side( v, d, d2, relation, attach_angle):
-      if attach_angle == 180:
-        # shortcut
-        return attach_angle
-      side = geometry.on_which_side_is_point( (d.x,d.y,v.x,v.y), (d2.x,d2.y))
-      an = angle + deg_to_rad( attach_angle)
-      x = v.x + self.bond_length*cos( an)
-      y = v.y + self.bond_length*sin( an)
-      if relation*side == geometry.on_which_side_is_point( (d.x,d.y,v.x,v.y), (x,y)):
-        return attach_angle
-      else:
-        return -attach_angle
-      
-    to_go = [a for a in v.get_neighbors() if a.x == None or a.y == None]
-    done = [a for a in v.get_neighbors() if a not in to_go]
-    if len( done) == 1 and (len( to_go) == 1 or len( to_go) == 2 and [1 for _t in to_go if _t in self.stereo]):
-      # only simple non-branched chain or branched with stereo
-      d = done[0]
-      if len( to_go) == 1:
-        t = to_go[0]
-      else:
-        t = [_t for _t in to_go if _t in self.stereo][0]
-      # decide angle
-      angle_to_add = 120
-      bond = v.get_edge_leading_to( t)
-      # triple bonds
-      if 3 in [_e.order for _e in v.get_neighbor_edges()]:
-        angle_to_add = 180
-      # cumulated double bonds
-      if bond.order == 2:
-        _b = v.get_edge_leading_to( d)
-        if _b.order == 2:
-          angle_to_add = 180
-      angle = geometry.clockwise_angle_from_east( d.x-v.x, d.y-v.y)
-      dns = d.get_neighbors()
-      placed = False
-      # stereochemistry (E/Z)
-      if t in self.stereo:
-        ss = [st for st in self.stereo[t] if not None in st.get_other_end( t).coords[:2]]
-        if ss:
-          st = ss[0] # we choose the first one if more are present
-          d2 = st.get_other_end( t)
-          # other is processed, we need to adapt
-          relation = st.value == st.OPPOSITE_SIDE and -1 or 1
-          angle_to_add = get_angle_at_side( v, d, d2, relation, angle_to_add)
-          placed = True
-      if not placed and len( dns) == 2:
-        # to support the all trans of simple chains without stereochemistry
-        d2 = (dns[0] == v) and dns[1] or dns[0]
-        if d2.x != None and d2.y != None:
-          angle_to_add = get_angle_at_side( v, d, d2, -1, angle_to_add)
-      an = angle + deg_to_rad( angle_to_add)
-      t.x = v.x + self.bond_length*cos( an)
-      t.y = v.y + self.bond_length*sin( an)
-      if len( to_go) > 1:
-        self.process_atom_neigbors( v)
-    else:
-      # branched chain
-      angles = [geometry.clockwise_angle_from_east( at.x-v.x, at.y-v.y) for at in done]
-      angles.append( 2*pi + min( angles))
-      angles.sort()
-      angles.reverse()
-      diffs = misc.list_difference( angles)
-      i = diffs.index( max( diffs))
-      angle = (angles[i] -angles[i+1]) / (len( to_go)+1)
-      gcoords = gen_coords_from_stream( gen_angle_stream( angle, start_from=angles[i+1]+angle),
-                                        length = self.bond_length)
-      for a in to_go:
-        dx, dy = gcoords.next()
-        a.x = v.x + dx
-        a.y = v.y + dy
-    return to_go
-
-
-  def apply_gen_to_atoms( self, gen, atoms, start, bond_length=None):
-    bl = bond_length or self.bond_length
-    x, y = start.x, start.y
-    for v in atoms:
-      a = gen.next()
-      x += bl*cos( a)
-      y += bl*sin( a)
-      v.x, v.y = x, y
-
-
-  def _process_simply_anelated_ring( self, ring, base):
-    out = []
-    inter = [v for v in ring if v.x != None and v.y != None]
-    if len( inter) == 1:
-      # rings are connected via one atom
-      v = inter.pop() # the atom of concatenation
-      ring = self.mol.sort_vertices_in_path( ring, start_from=v)
-      base_neighs = [a for a in v.get_neighbors() if a in base]
-      if len( base_neighs) < 2:
-        raise "this should not happen"
-      d1 = base_neighs[0] 
-      d2 = base_neighs[1]
-      ca1 = geometry.clockwise_angle_from_east( v.x-d1.x, v.y-d1.y)
-      ca2 = geometry.clockwise_angle_from_east( v.x-d2.x, v.y-d2.y)
-      ca = (ca1+ca2)/2
-      if abs( ca1-ca2) < pi:
-        ca += -pi/2
-      else:
-        ca += pi/2
-      size = len( ring)
-      da = deg_to_rad(180 -180.0*(size-2)/size)
-      gcoords = gen_angle_stream( da, start_from=ca + da/2)
-      ring.remove( v)
-      # here we generate the coords
-      self.apply_gen_to_atoms( gcoords, ring, v)
-      ring.append( v)
-      out += ring
-    elif len( inter) == 2:
-      # there are two atoms common to the rings
-      v1, v2 = inter # the atoms of concatenation
-      ring = self.mol.sort_vertices_in_path( ring, start_from=v1)
-      ring.remove( v1)
-      ring.remove( v2)
-      if not v1 in ring[0].get_neighbors():
-        v1, v2 = v2, v1
-      side = sum( [geometry.on_which_side_is_point((v1.x,v1.y,v2.x,v2.y),(v.x,v.y)) for v in base])
-      if not side:
-        warnings.warn( "this should not happen")
-      ca = geometry.clockwise_angle_from_east( v1.x-v2.x, v1.y-v2.y)
-      size = len( ring)+2
-      da = deg_to_rad(180 -180.0*(size-2)/size)
-      if side > 0:
-        da = -da
-      gcoords = gen_angle_stream( da, start_from=ca+da)
-      self.apply_gen_to_atoms( gcoords, ring, v1)
-      ring.append( v1)
-      ring.append( v2)
-      out += ring
-    else:
-      # there are more than 2 atoms common
-      if len( ring) == len( base):
-        out += self._process_multi_anelated_ring( ring, angle_shift=15)
-        #raise( "i don't how to handle this yet")
-      else:
-        out += self._process_multi_anelated_ring( ring)
-    return out
-
-  def _process_multi_anelated_ring( self, ring, angle_shift=0):
-    out = []
-    to_go = [v for v in ring if v.x == None or v.y == None]
-    if not to_go:
-      # it was all already done
-      return []
-    back = [v for v in ring if v.x != None and v.y != None]
-    sorted_back = self.mol.sort_vertices_in_path( back)
-    if not sorted_back:
-      # the already set atoms are not in one path - we have to process it "per partes"
-      # it should not happen with the construction method we use
-      raise( "i am not able to handle this, it should normaly not happen. please send me the input.")
-    else:
-      v1 = sorted_back[0]
-      v2 = sorted_back[-1]
-      v3 = sorted_back[1]
-      to_go = self.mol.sort_vertices_in_path( to_go)
-      if v1 not in to_go[0].get_neighbors():
-        v1, v2 = v2, v1
-      blocked_angle = sum_of_ring_internal_angles( len( back))
-      overall_angle = sum_of_ring_internal_angles( len( ring))
-      da = optimal_ring_iternal_angle( len( ring))  # internal angle
-      # if there are 2 rings of same size inside each other, we need to use the angle_shift
-      if angle_shift:
-        da += 2*angle_shift/(len( to_go))
-      ca = deg_to_rad( 180-(overall_angle - blocked_angle - len( to_go) * da + angle_shift)/2)  # connection angle
-      side = sum( [geometry.on_which_side_is_point( (v1.x,v1.y,v2.x,v2.y),(v.x,v.y)) for v in back if v != v1 and v != v2])
-      # we need to make sure that the ring is drawn on the right side
-      if side > 0:
-        ca = -ca
-      ca += geometry.clockwise_angle_from_east( v1.x-v2.x, v1.y-v2.y)
-      da = 180-da  # for drawing we use external angle
-      # we must ensure that the ring will progress towards the second end
-      if geometry.on_which_side_is_point( (v1.x,v1.y,v3.x,v3.y),(v2.x,v2.y)) < 0:
-        da = -da
-      # dry run to see where we get
-      gcoords = gen_angle_stream( deg_to_rad( da), start_from= ca)
-      x, y = v1.x, v1.y
-      for i in range( len( to_go) +1):
-        a = gcoords.next()
-        x += self.bond_length*cos( a)
-        y += self.bond_length*sin( a)
-      # end of dry run, we can scale the bond_length now
-      length = geometry.line_length( (v1.x,v1.y,v2.x,v2.y))
-      real_length = geometry.line_length( (v1.x,v1.y,x,y))
-      bl = self.bond_length * length / real_length
-      gcoords = gen_angle_stream( deg_to_rad( da), start_from= ca)
-      # and here we go
-      self.apply_gen_to_atoms( gcoords, to_go, v1, bond_length=bl)
-      out += to_go
-    return out
-
-def sum_of_ring_internal_angles( size):
-  return (size-2)*180
-
-def optimal_ring_iternal_angle( size, angle_shift=0):
-  return sum_of_ring_internal_angles( size)/size
-
-def gen_ring_coords( size, side_length=1):
-  coords = gen_coords_from_deg_stream( gen_angle_stream( 180 -180.0*(size-2)/size),
-                                       length=side_length)
-  x , y = 0, 0
-  for i in range( size):
-    dx, dy = coords.next()
-    x += dx
-    y += dy
-    yield x,y
-
-
-def gen_angle_stream( angle, start_from=0, alternate=0):
-  yield start_from
-  if alternate:
-    while 1:
-      yield angle
-      yield -angle
-  else:
-    a = start_from
-    while 1:
-      a += angle
-      yield a
-
-def gen_coords_from_deg_stream( stream, length=1):
-  for a in stream:
-    ang = deg_to_rad( a)
-    yield ( length*cos( ang), length*sin( ang))
-
-def gen_coords_from_stream( stream, length=1):
-  for a in stream:
-    yield ( length*cos( a), length*sin( a))
-    
-def deg_to_rad( deg):
-  return pi*deg/180
-
-def rad_to_deg( rad):
-  return 180*rad/pi
-
-
-
-
-def show_mol( mol):
-  from Tkinter import Tk, Canvas, Frame
-
-  app = Tk()
-  app.title( "oasa")
-
-  mainFrame = Frame( app)
-  mainFrame.pack( fill='both', expand=1)
-
-  paper = Canvas( mainFrame, width=640, height=480, background="white", closeenough=5)
-  paper.pack()
-
-  xmin, xmax, ymin, ymax = None,None,None,None
-  for a in mol.vertices:
-    if xmin == None or a.x < xmin:
-      xmin = a.x
-    if xmax == None or a.x > xmax:
-      xmax = a.x
-    if ymin == None or a.y < ymin:
-      ymin = a.y
-    if ymax == None or a.y > ymax:
-      ymax = a.y
-
-  dx = xmax-xmin
-  dy = ymax-ymin
-  #print "dx", dy, ymax, ymin
-  range = min( (600.0/dx, 450.0/dy))/2
-  xp = 640-range*dx
-  yp = 480-range*dy
-  xtrans = lambda xx: range*(xx - xmin)+xp/2
-  ytrans = lambda xx: range*(xx - ymin)+yp/2
-
-  for b in mol.edges:
-    a1, a2 = b.vertices
-    x1 = xtrans( a1.x)
-    x2 = xtrans( a2.x)
-    y1 = ytrans( a1.y)
-    y2 = ytrans( a2.y)
-    paper.create_line( x1, y1, x2, y2, fill='black')
-    paper.create_text( (x1+x2)/2, (y1+y2)/2, text=str( b.order), fill="#F00")
-  for v in mol.vertices: 
-    x = xtrans( v.x)
-    y = ytrans( v.y)
-    #paper.create_oval( x-5, y-5, x+5, y+5, fill="#0F0")
-    paper.create_text( x, y, text=v.symbol, fill="#0F0")
-  app.mainloop()
-
-
-def calculate_coords( mol, bond_length=0, force=0):
-  g = coords_generator()
-  g.calculate_coords( mol, bond_length=bond_length, force=force)
-
-
-
-##################################################
-# DEMO
-
-if __name__ == '__main__':
-
-  import smiles
-  from molecule import molecule
-
-  #sm = "CP(c1ccccc1)(c2ccccc2)c3ccccc3"
-  #sm = 'C1CC2C1CCCC3C2CC(CCC4)C4C3'
-  #sm = "C\C=C/CC#CCCCC\C=C=C=C/CC"
-  sm = "C/C(Cl)=C(\O)C"
-  #sm = "C1CCC1C/C=C\CCCCC"
-  #sm = "C25C1C3C5C4C2C1C34"
-  #sm = 'C1CC2CCC1CC2'
-  #sm = 'C1CC5(CC(C)CC5)CC(C)C12CC(CC(C(C)(C)CCCC)CCC)CC23C(CC)CC3'
-  #sm = 'CCCC(C)(CCC)CCC(Cl)C(CCCCC)CCC(C)CCC'
-
-  print "oasa::coords_generator DEMO"
-  print "generating coords for following smiles"
-  print "  %s" % sm
- 
-  mol = smiles.text_to_mol( sm, calc_coords=False)
-
-  import time
-  #cg = coords_generator()
-  t = time.time()
-  calculate_coords( mol, force=1)
-  print "generation time: %.3f ms" % ((time.time()-t)*1000)
-
-  show_mol( mol)
-
-
-
-##################################################
-# TODO
-
-# when an atom with degree 4 is created not as part of the backbone the neighbors are
-#   organized in a square instead of the usual 120 deg between 2 and the rest on the other side
-
-# triple bond should be linear
-
-##################################################
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/coords_optimizer.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/coords_optimizer.py
--- bkchem-0.12.5/bkchem/oasa/oasa/coords_optimizer.py	2008-10-14 09:43:48.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/coords_optimizer.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,240 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-from __future__ import division
-from math import pi, sqrt, sin, cos, atan2
-import geometry
-import misc
-
-import warnings
-#import graph
-
-
-def sum_of_ring_internal_angles( size):
-  return (size-2)*180
-
-def optimal_ring_iternal_angle( size, angle_shift=0):
-  return sum_of_ring_internal_angles( size)/size
-
-
-def gen_variations(items, n):
-    if n==0:
-      yield []
-    else:
-      for i in xrange( len(items)-n+1):
-        for v in gen_variations(items[i+1:],n-1):
-          yield [items[i]]+v
-
-
-def deg_to_rad( deg):
-  return pi*deg/180
-
-def rad_to_deg( rad):
-  return 180*rad/pi
-
-
-
-
-class coords_optimizer:
-
-  def __init__( self):
-    self.cutoff_rms_grad = 1e-5
-    self.cutoff_max_grad = 3e-5
-    self.max_iter_number = 1000
-
-
-  def optimize_coords( self, mol, bond_length=1, callback=None):
-    """callback may be used to obtain information about the running optimization,
-    it is called after each step with three parameters - step number, RMS grad and maxgrad"""
-    self.mol = mol
-
-    if bond_length < 0:
-      bls = []
-      for b in mol.edges:
-        a1, a2 = b.vertices
-        bls.append( sqrt( (a1.x-a2.x)**2 + (a1.y-a2.y)**2))
-      self.bond_length = sum( bls) / len( bls)
-    else:
-      self.bond_length = bond_length
-
-    sumdd, max_grad = self.step()
-    rms_grad = sumdd / len( self.mol.vertices)
-    i = 0
-    ok = True
-    self.end_reason = "converged"
-    
-    while rms_grad > (self.cutoff_rms_grad / self.bond_length) or max_grad > (self.cutoff_max_grad / self.bond_length):
-      i += 1
-      if callback:
-        callback( i, rms_grad, max_grad)
-
-      sumdd, max_grad = self.step()
-      rms_grad = sumdd / len( self.mol.vertices)
-
-      if rms_grad > 1000:
-        self.end_reason = "gradien too large"
-        ok = False
-        break
-      if i > self.max_iter_number:
-        self.end_reason = "max_iter_number iterations without converged result"
-        ok = False
-        break
-
-    self.i = i
-    self.rms_grad = rms_grad
-    self.max_grad = max_grad
-    return ok
-
-
-  def step( self, callback=None):
-
-    for v in self.mol.vertices:
-      v.properties_['dx'] = 0
-      v.properties_['dy'] = 0
-
-    for v in self.mol.vertices:
-      self.set_gradient( v)
-
-    dd = 0
-    mdd = 0
-    for v in self.mol.vertices:
-      dx = v.properties_['dx']
-      dy = v.properties_['dy']
-      v.x += dx
-      v.y += dy
-      d = dx**2 + dy**2
-      dd += d
-      if d > mdd:
-        mdd = d
-
-    return sqrt( dd), sqrt( mdd)
-
-
-
-  def set_gradient( self, vertex):
-    dx, dy = 0, 0
-    for v in vertex.neighbors:
-      gx, gy = self.get_length_gradient( vertex, v)
-      dx -= gx/2.0
-      dy -= gy/2.0
-    vertex.properties_['dx'] += dx
-    vertex.properties_['dy'] += dy
-    
-    dx, dy = 0, 0
-    vs = list( vertex.neighbors)
-    variations = list( gen_variations( vs, 2))
-    if variations:
-      for var in variations:
-        v1, v2 = var
-        gx1, gy1, gx2, gy2 = self.get_angle_gradient( deg_to_rad( 120), vertex, v1, v2)
-        v1.properties_['dx'] -= gx1/4.0
-        v1.properties_['dy'] -= gy1/4.0
-        v2.properties_['dx'] -= gx2/4.0
-        v2.properties_['dy'] -= gy2/4.0
-
-
-  def get_length_gradient( self, v1, v2, opt_length=0):
-    if not opt_length:
-      opt_length = self.bond_length
-    dx = v1.x - v2.x
-    dy = v1.y - v2.y
-    dist = sqrt( dx**2 + dy**2)
-    dd = dist - opt_length
-    gx = (dx / dist) * dd
-    gy = (dy / dist) * dd
-    return gx, gy
-
-
-  def get_angle_gradient( self, opt_angle, refv, v1, v2):
-    expl = sqrt( 3) * self.bond_length
-    gx1, gy1 = self.get_length_gradient( v1, v2, opt_length=expl)
-    gx2 = -gx1
-    gy2 = -gy1
-    return gx1, gy1, gx2, gy2
-
-
-  def get_angle_gradient2( self, opt_angle, refv, v1, v2):
-    ang1 = geometry.clockwise_angle_from_east( v1.x-refv.x, v1.y-refv.y)
-    ang2 = geometry.clockwise_angle_from_east( v2.x-refv.x, v2.y-refv.y)
-    ang = ang1 - ang2
-    sign = -geometry.on_which_side_is_point( (refv.x, refv.y, v1.x, v1.y), (v2.x, v2.y))
-    
-    while ang < 0:
-      ang += 2*pi
-    if ang > pi:
-      ang = 2*pi - ang
-      sign *= -1
-
-    dang = (ang - opt_angle) / 2
-
-    gx1 = (cos( ang1 - sign*dang) - cos( ang1)) * sqrt( (refv.x-v1.x)**2 + (refv.y-v1.y)**2) #self.bond_length #(v1.x-refv.x)
-    gy1 = (sin( ang1 - sign*dang) - sin( ang1)) * sqrt( (refv.x-v1.x)**2 + (refv.y-v1.y)**2) #self.bond_length #(v1.y-refv.y)
-
-    gx2 = (cos( ang2 + sign*dang) - cos( ang2)) * sqrt( (refv.x-v2.x)**2 + (refv.y-v2.y)**2) #self.bond_length #(v2.x-refv.x)
-    gy2 = (sin( ang2 + sign*dang) - sin( ang2)) * sqrt( (refv.x-v2.x)**2 + (refv.y-v2.y)**2) #self.bond_length #(v2.y-refv.y)
-
-
-    # control
-    ang1 = geometry.clockwise_angle_from_east( v1.x-refv.x+gx1, v1.y-refv.y+gy1)
-    ang2 = geometry.clockwise_angle_from_east( v2.x-refv.x+gx2, v2.y-refv.y+gy2)
-    ang = ang1 - ang2
-    sign = geometry.on_which_side_is_point( (refv.x, refv.y, v1.x, v1.y), (v2.x, v2.y))
-    
-    while ang < 0:
-      ang += 2*pi
-      #sign *= -1
-    if ang > pi:
-      ang = 2*pi - ang
-      #sign *= -1
-
-    dang2 = (ang - opt_angle) / 2
-
-    if abs( dang2) > abs( dang):
-      print "fuck", rad_to_deg( ang1), rad_to_deg( ang2), rad_to_deg( dang)
-    else:
-      #rint "good", rad_to_deg( ang1), rad_to_deg( ang2), rad_to_deg( dang)
-      pass
-
-    return gx1, gy1, gx2, gy2
-
-
-
-
-if __name__ == "__main__":
-
-  import smiles
-  import time
-
-  mol = smiles.text_to_mol( 'CC(C)C(C)CCCC')
-
-  print [(v.x,v.y) for v in mol.vertices]
-
-  opt = coords_optimizer()
-  t = time.time()
-  ok = opt.optimize_coords( mol, bond_length=0.9)
-  print "converged:", ok
-  print "%d iterations, RMS grad %f, max grad %f" % (opt.i, opt.rms_grad, opt.max_grad)
-  print "time %.1f ms" % ((time.time() - t) * 1000)
-
-
-  print [(v.x,v.y) for v in mol.vertices]
-
-  import coords_generator
-
-  coords_generator.show_mol( mol)
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/dom_extensions.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/dom_extensions.py
--- bkchem-0.12.5/bkchem/oasa/oasa/dom_extensions.py	2008-03-28 08:28:47.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/dom_extensions.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,152 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of BKchem - a chemical drawing program
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-#
-#
-#
-#--------------------------------------------------------------------------
-
-"""some extensions to DOM for more convenient work"""
-
-import re
-import xml.dom.minidom as dom
-import operator
-#from __future__ import division
-
-
-def safe_indent( element, level=0, step=2):
-  """indents DOM tree. Does not add any extra whitespaces to text elements."""
-  if not element.childNodes:
-    pass
-  elif reduce( operator.add, [isinstance( child, dom.Text) for child in element.childNodes], 0):
-    pass
-  elif element.nodeName == 'ftext' or element.nodeName == 'text':
-    pass
-  else:
-    for child in element.childNodes[:]:
-      element.insertBefore( element.ownerDocument.createTextNode("\n"+(level+step)*" "), child)
-      safe_indent( child, level=level+step, step=step)
-    element.appendChild(  element.ownerDocument.createTextNode("\n"+level*" "))
-
-
-def elementUnder( parent, name, attributes=()):
-  """creates element inside of parent and returns it,
-  attributes are as sequence of (name,value) sequences"""
-  if parent.DOCUMENT_NODE == parent.nodeType:
-    a = parent.createElement( name)
-  else:
-    a = parent.ownerDocument.createElement( name)
-  parent.appendChild( a)
-  if attributes:
-    for i in attributes:
-      a.setAttribute( i[0], i[1])
-  return a
-
-def textOnlyElementUnder( parent, name, text, attributes=()):
-  a = elementUnder( parent, name, attributes=attributes)
-  if attributes:
-    for i in attributes:
-      a.setAttribute( i[0], i[1])
-  a.appendChild( parent.ownerDocument.createTextNode( text))
-  return a
-  
-def getTextFromElement( element):
-  text = ''
-  for child in element.childNodes:
-    if child.nodeValue:
-      text += child.nodeValue
-  return text
-
-def childNodesWithoutEmptySpaces( node):
-  return filter( isNotEmptyText, node.childNodes)
-
-def isNotEmptyText( element):
-  empty = re.compile('^\s*$')
-  if element.nodeValue and empty.match( element.nodeValue): #(element.nodeValue == '\n') or (element.nodeValue == '\t'):
-    return 0
-  else:
-    return 1
-
-def getAllTextFromElement( element):
-  """like getTextFromElement but mines text also from child nodes"""
-  text = ''
-  for child in element.childNodes:
-    if child.nodeValue:
-      text += child.nodeValue
-    else:
-      text += getAllTextFromElement( child)
-  return text
-
-def setAttributes( element, attributes):
-  for a in attributes:
-    element.setAttribute( a[0], a[1])
-  
-def getAttributes( element, names):
-  """returns a list of attribute values from a list of attr names"""
-  return [element.getAttribute( n) for n in names]
-
-def getParentNameList( element):
-  """returns a list of parent names (from father to grandfather...)"""
-  par = element.parentNode
-  if par != None:
-    return [par.nodeName] + getParentNameList( par)
-  else:
-    return []
-
-def getFirstChildNamed( element, name):
-  l = [o for o in element.childNodes if (not o.nodeValue) and (o.localName == name)]
-  if l:
-    return l[0]
-  else:
-    return None
-
-
-
-def isOnlyTags( text):
-  """this function takes a !string! as an argument and returns true if text is only tags"""
-  try:
-    doc = dom.parseString( '<a>%s</a>' % text)
-  except:
-    return 0
-  if getAllTextFromElement( doc):
-    return 0
-  return 1
-  
-
-def simpleXPathSearch( element, path):
-  atomic_paths = path.split( "/")
-  out = [element]
-  for atomic_path in atomic_paths:
-    search_with_path = lambda x: _atomicXPathSearch( x, atomic_path)
-    out =  reduce( operator.add, map( search_with_path, out), [])
-  return out
-
-
-
-def _atomicXPathSearch( element, path):
-  if path == "":
-    if element.DOCUMENT_NODE == element.nodeType:
-      return [element]
-    else:
-      return [element.ownerDocument]
-  if path == "*":
-    return element.childNodes
-  else:
-    return element.getElementsByTagName( path)
-
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/elements.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/elements.py
--- bkchem-0.12.5/bkchem/oasa/oasa/elements.py	2008-11-25 06:38:51.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/elements.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,24 +0,0 @@
-import periodic_table
-
-elements = [(v['ord'],(k,v)) for (k,v) in periodic_table.periodic_table.iteritems()]
-elements.sort()
-
-sym_to_emass = {}
-f = file( "exact_masses.txt", "r")
-for line in f:
-  vs = line.split()
-  if len( vs) == 2:
-    k,v = vs
-    sym_to_emass[k] = float(v)
-f.close()
-
-import pprint
-for ord,(k,v) in elements:
-  v['key'] = '"%s"' % k
-  if k in sym_to_emass:
-    v['exact_mass'] = sym_to_emass[k]
-    print '%(key)-5s: {"ord": %(ord)3d, "weight": %(weight)8.4f, "exact_mass": %(exact_mass)12.8f, "en": %(en)4.2f, "els": %(els)2d, "valency": %(valency)s},' % (v) 
-  elif "query" in v:
-    print '"%s":'%k, "%s,"%v
-  else:
-    print "ssssssshhhhhiiiiiiitttttt"
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/geometry.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/geometry.py
--- bkchem-0.12.5/bkchem/oasa/oasa/geometry.py	2008-09-22 04:37:05.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/geometry.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,523 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-"""support module for some geometric mesurements ( geometric tramforms are in transform.py)"""
-
-from __future__ import division
-from math import sqrt, atan2, pi, cos, sin
-from misc import signum, normalize_coords
-import operator
-
-
-
-def find_parallel( x1, y1, x2, y2, d):
-  "returns tuple of coordinates for parallel abscissa in distance d"
-  # following is here to ensure that signum of "d" clearly determines
-  # the side of line on whitch the parallel is drawn  
-  if round( y2, 3) -round( y1, 3) != 0:
-    if y2 < y1:
-      d = -d 
-    k = -(x2-x1)/(y2-y1)
-    x = ( d + sqrt( k**2 +1)*x1)/ sqrt( k**2 +1)
-    y = y1 + k*( x -x1)
-    x0 = ( d + sqrt( k**2 +1)*x2)/ sqrt( k**2 +1)
-    y0 = y2 + k*( x0 -x2)
-  else:
-    if x1 > x2:
-      d = -d
-    x, x0 = x1, x2
-    y = y1 - d
-    y0 = y2 - d
-  return (x, y, x0, y0)
-
-def get_parallel_distance( l1, l2):
-  "returns distance of two parallels - does not check whether the two are really parallels"
-  x1a, y1a, x2a, y2a = l1
-  x1b, y1b, x2b, y2b = l2
-  if x1a == x2a:
-    return y1a-y2a
-  elif y1a == y2a:
-    return x1a-x2a
-  else:
-    ka = (y2a - y1a)/(x2a - x1a)
-    qa = y1a - ka*x1a
-    kb = ka
-    qb = y1b - kb*x1b
-    k_rev = -1/ka
-    q_rev = y1a - k_rev*x1a
-    x0 = (qb - q_rev)/(k_rev - kb)
-    y0 = k_rev*x0 + q_rev
-    d = sqrt( (x1a-x0)**2 + (y1a-y0)**2)
-    if qa < qb:
-      d = -d
-    return d
-  
-
-def get_parallel_signum( l1, l2):
-  x1a, y1a, x2a, y2a = l1
-  x1b, y1b, x2b, y2b = l2
-  if x1a == x2a:
-    return signum( -x1a + x1b)
-  elif y1a == y2a:
-    return signum( -y1a+y1b)
-  else:
-    return signum( -x2a+x2b)
-
-def on_which_side_is_point( line, point, threshold=0):
-  """tells whether a point is on one side of a line or on the other (1,0,-1) - 0 is for point on line.
-  line is given as sequence of four coordinates, point as sequence of two coords,
-  threshold means what smallest angle is considered to still be on the line"""
-  x1, y1, x2, y2 = line
-  x, y = point
-  a = atan2( y-y1, x-x1)
-  b = atan2( y2-y1, x2-x1)
-  if a*b < 0 and abs(a-b) > pi:
-    if a < 0:
-      a += 2*pi
-    else:
-      b += 2*pi
-  if abs( a-b) <= threshold or abs( abs( a-b) -pi) <= threshold:
-    return 0    
-  elif a-b < 0:
-    return 1
-  else:
-    return -1
-
-def point_on_circle( center_x, center_y, radius, direction = (), resolution = 15):
-  """finds point on circle in direction of (dx, dy), optionaly rounds the angle
-  according to resolution"""
-  dx, dy = direction
-  if resolution:
-    angle = round( atan2( dy, dx)/(pi*resolution/180.0))*(pi*resolution/180.0)
-  else:
-    angle = atan2( dy, dx)
-  x = center_x + round( cos( angle) *radius, 2)
-  y = center_y + round( sin( angle) *radius, 2)
-  return x,y
-
-
-def angle_between_lines( l1, l2):
-  """returns angle between two lines"""
-  a = clockwise_angle_from_east( l1[2]-l1[0], l1[3]-l1[1]) - clockwise_angle_from_east( l2[2]-l2[0], l2[3]-l2[1])
-  if a > pi:
-    return a - 2*pi
-  elif a < -pi:
-    return a + 2*pi
-  else:
-    return a
-
-
-def clockwise_angle_from_east( dx, dy):
-  """returns the angle in clockwise direction between the center-east line and direction"""
-  angle = atan2( dy, dx)
-  if angle < 0:
-    angle = 2*pi + angle
-  return angle
-
-
-
-def intersection_of_line_and_rect( line, rect, round_edges=0):
-  """finds a point where a line and a rectangle intersect,
-  both are given as lists of len == 4"""
-  lx0, ly0, lx1, ly1 = map( float, line)
-  rx0, ry0, rx1, ry1 = map( float, normalize_coords( rect))
-
-  # find which end of line is in the rect and reverse the line if needed
-  if (lx0 > rx0) and (lx0 < rx1) and (ly0 > ry0) and (ly0 < ry1):
-    lx0, lx1 = lx1, lx0
-    ly0, ly1 = ly1, ly0
-
-  # the computation itself
-  ldx = lx1 - lx0
-  ldy = ly1 - ly0
-
-  if abs( ldx) > 0.0001:
-    # we calculate using y = f(x)
-    k = ldy/ldx
-    q = ly0 - k*lx0
-    if ldx < 0:
-      xx = rx1
-    else:
-      xx = rx0
-    xy = k*xx + q
-    # the result must be in the rectangle boundaries
-    # but sometimes is not because rounding problems
-    if not ry0 <= xy <= ry1:
-      xx = lx0
-      xy = ly0
-  else:
-    xx = lx0
-    xy = ly0
-    
-  if abs( ldy) > 0.0001:
-    # we calculate using x = f(y)
-    k = ldx/ldy
-    q = lx0 - k*ly0
-    if ldy < 0:
-      yy = ry1
-    else:
-      yy = ry0
-    yx = k*yy + q
-    # the result must be in the rectangle boundaries
-    # but sometimes is not because rounding problems
-    if not rx0 <= yx <= rx1:
-      yy = ly0
-      yx = lx0
-  else:
-    yy = ly0
-    yx = lx0
-    
-  if point_distance( lx0, ly0, xx, xy) < point_distance( lx0, ly0, yx, yy):
-    return (yx, yy)
-  else:
-    return (xx, xy)
-
-
-
-def point_distance( x1, y1, x2, y2):
-  return sqrt( (x2-x1)**2 + (y2-y1)**2)
-
-
-def line_length( (x1,y1,x2,y2)):
-  return point_distance( x1, y1, x2, y2)
-
-
-def rectangle_intersection( rect1, rect2):
-  pass
-
-
-def do_rectangles_intersect( rect1, rect2):
-  xs1 = [rect1[0], rect1[2]]
-  xs2 = [rect2[0], rect2[2]]  
-  xs = xs1 + xs2
-  ys1 = [rect1[1], rect1[3]]
-  ys2 = [rect2[1], rect2[3]]
-  ys = ys1 + ys2
-  
-  dx1 = abs( xs1[0] - xs1[1])
-  dx2 = abs( xs2[0] - xs2[1])
-  dx = max( xs) - min( xs)
-
-  dy1 = abs( ys1[0] - ys1[1])
-  dy2 = abs( ys2[0] - ys2[1])
-  dy = max( ys) - min( ys)
-
-  if dx1+dx2 > dx and dy1+dy2 > dy:
-    return True
-  else:
-    return False
-
-
-def is_point_inside_polygon( point, polygon):
-  """point is a tuple or list of length == 2,
-  polygon is a tuple or list of points"""
-  a = 0
-  for i in range( len( polygon)-1):
-    a += angle_between_lines( point+polygon[i+1], point+polygon[i])
-  a += angle_between_lines( point+polygon[0], point+polygon[ len( polygon)-1])
-  a = abs( a)
-  if a < 0.1:
-    return False
-  else:
-    return True
-
-def tkspline_to_quadratic_bezier( points):   #points = ((x1,y1),(x2,y2),...)
-    if len(points) > 2:
-      qbeziers = []
-      startx,starty = points[0]             #start point
-      curvex,curvey = points[1]             #point that defines curvature
-      for nextx,nexty in points[2:-1]:
-        endx = (curvex+nextx)/2             #calculate midway point
-        endy = (curvey+nexty)/2             #to find end point
-        qbeziers.append((startx,starty,curvex,curvey,endx,endy))
-        
-        startx,starty = endx,endy           #start where the last ended
-        curvex,curvey = nextx,nexty
-      
-      endx,endy = points[-1]                #no midway calculation for last point
-      qbeziers.append((startx,starty,curvex,curvey,endx,endy)) 
-        
-    else:
-      startx,starty = points[0]             #If only two points are given
-      endx,endy = points[1]                 #make a straight line
-      curvex = (startx+endx)/2
-      curvey = (starty+endy)/2
-      qbeziers=((startx,starty,curvex,curvey,endx,endy),)
-    
-    return qbeziers
-
-def tkspline_to_cubic_bezier( points):   #points = ((x1,y1),(x2,y2),...)
-    qbeziers = tkspline_to_quadratic_bezier( points)
-    cbeziers = []
-    for qbez in qbeziers:
-      startx, starty = qbez[0], qbez[1]
-      endx, endy = qbez[4], qbez[5]
-      qcurvex, qcurvey = qbez[2], qbez[3]
-      
-      ccurvexa, ccurveya = startx + 2/3*(qcurvex-startx), starty + 2/3*(qcurvey-starty)
-      ccurvexb, ccurveyb = ccurvexa + 1/3*(endx-startx), ccurveya + 1/3*(endy-starty)
-      cbeziers.append( (startx, starty, ccurvexa, ccurveya, ccurvexb, ccurveyb, endx, endy))
-    # http://fontforge.sourceforge.net/bezier.html
-    return cbeziers
-    
-
-
-def point_at_distance_from_line( x1, y1, x2, y2, d):
-  "returns tuple of coordinates for a point in distance d from line orthogonal to point (x2,y2)"
-  if round( y2, 3) -round( y1, 3) != 0:
-    if y2 < y1:
-      d = -d 
-    k = -(x2-x1)/(y2-y1)
-    x0 = ( d + sqrt( k**2 +1)*x2)/ sqrt( k**2 +1)
-    y0 = y2 + k*( x0 -x2)
-  else:
-    if x1 > x2:
-      d = -d
-    x0 = x2
-    y0 = y2 - d
-  return (x0, y0)
-
-
-def intersection_of_two_lines (x1,y1,x2,y2,x3,y3,x4,y4,parallel_detection_threshold=3): 
-  """lines 1-2 and 3-4
-  returns x,y, 0 if succesful or 1 if parallel
-  y=mx+c is used
-  parallel_detection_threshold is a negative decadic logarithm of minimal displacement of m
-  that is considered parallel"""
-  if x1-x2 == 0:                          
-    if x3-x4 == 0:                      
-      return 0,0,1,0        #lines paralell
-    m2 = (y3-y4)/(x3-x4)
-    c2 = y3 - m2 * x3
-    rex,rey = x1,m2*x1+c2        
-  elif x3-x4 == 0:
-    m1 = (y1-y2)/(x1-x2)
-    c1 = y1 - m1 * x1
-    rex,rey = x3,m1*x3+c1
-  else:
-    m1 = (y1-y2)/(x1-x2)
-    m2 = (y3-y4)/(x3-x4)
-    if round(m1-m2,parallel_detection_threshold) == 0:
-      return 0,0,1,0            #lines paralell
-    c2 = y3 - m2 * x3
-    c1 = y1 - m1 * x1
-    rex = -(c2-c1)/(m2-m1)
-    rey = (c1*m2-c2*m1)/(m2-m1)
-  #check if point is on the lines
-  online1 = is_point_beween_points_of_line( (x1,y1,x2,y2), (rex,rey))
-  online2 = is_point_beween_points_of_line( (x3,y3,x4,y4), (rex,rey))
-  if online1 and online2:
-    online = 3
-  elif online1:
-    online = 1
-  elif online2:
-    online = 2
-  else:
-    online = 0
-  # x-coord, y-coord , paralell(0 or 1), on line (0=no line, 1=on line 1-2, 2=on line 3-4, 3=on both) 
-  return rex,rey,0,online
-
-
-def mirror_point_on_line (xa,ya,x1,y1,x2,y2):
-  """mirroring point a on line 1-2"""
-  if x1-x2 == 0:                           #y=mc+c is used                                           
-    return 2*x1-xa,ya
-  elif y1-y2 == 0:
-    return xa,2*y1-ya
-  else:
-    m=(y1-y2)/(x1-x2)
-    cl=y1-m*x1
-    ca=ya+(1/m)*xa
-    xbl = (ca-cl)/(m+1/m)   #point bl is intersection with line
-    ybl = -(1/m)*xbl+ca
-    xb = xbl*2-xa
-    yb = ybl*2-ya
-    return xb,yb
-
-def elongate_line (x1,y1,x2,y2,d):
-  """line 1-2 will be elongatet at point 2 negative d will make it shorter"""
-  if x1-x2 == 0:
-    rex = x2
-    if y2 > y1: 
-      rey = y2 + d
-    else:
-      rey = y2 - d
-  else:
-    m = (y1-y2)/(x1-x2)
-    dx = sqrt( d**2 / (1 + m**2))
-    dy = m * dx
-    if dy < 0:
-      dy=-dy
-    if d<0: 
-      dx,dy = -dx, -dy
-    if x2 > x1:
-      rex = x2 + dx
-    else:
-      rex = x2 - dx
-    if y2 > y1:
-      rey = y2 + dy
-    else:
-      rey = y2 - dy
-  return rex, rey
-
-
-def coordinate_flat_list_to_xy_tuples( coords):
-  ret = []
-  for i in range( 0, len( coords), 2):
-    ret.append( (coords[i],coords[i+1]))
-  return ret
-    
-
-def find_parallel_polyline( coords, d):
-  """take and returns list of tuples of coordinates for parallel abscissa in distance d"""
-  res = []
-  i = 0
-  for x,y in coords:
-    if i == 0:
-      x1, y1 = coords[1]
-      res.append( point_at_distance_from_line( x1,y1,x,y,-d))
-    elif i == len( coords)-1:
-      x1, y1 = coords[-2]
-      res.append( point_at_distance_from_line( x1,y1,x,y,d))
-    else:
-      #internal point
-      x0, y0 = coords[i-1] #previous point
-      x1, y1 = coords[i+1] #next point
-      xa1,ya1,xa2,ya2 = find_parallel( x0, y0, x, y, d)
-      xb1,yb1,xb2,yb2 = find_parallel( x1, y1, x, y, -d)
-      xx, yy, parallel, online = intersection_of_two_lines( xa1,ya1,xa2,ya2,xb1,yb1,xb2,yb2,parallel_detection_threshold=1)
-      if not parallel:
-        if online:
-          # it the cross-point is on one of the lines, this could not be the case
-          # where the point runs too far from the original line
-          res.append( (xx, yy))
-        else:
-          # we should check for very near parallels going back -
-          # the cross-point is very far from the original line, we need to fix it
-          xa, ya = elongate_line(xa1,ya1,xa2,ya2,abs(d))
-          if point_distance(xa,ya,xa2,ya2) < point_distance(xa,ya,xx,yy):
-            xb, yb = elongate_line(xb1,yb1,xb2,yb2,abs(d))
-            res.append( (xa, ya))          
-            res.append( (xb, yb))
-          else:
-            res.append( (xx, yy))
-      else:
-        # is 1-2-3 straight or does 2-3 go back from 1-2
-        if point_distance(x,y,x0,y0) > point_distance(x0,y0,x1,y1):
-          d *= -1
-          res.append((xa2,ya2)) # going back
-        else:
-          res.append((xa2,ya2)) # going forward
-    i += 1
-  return res
-
-
-
-def point_on_quadratic_bezier( coords, t):
-  """based on info from http://en.wikipedia.org/wiki/B%C3%A9zier_curve"""
-  (x0,y0,x1,y1,x2,y2) = coords
-  x = (1-t)**2*x0 + 2*t*(1-t)*x1 + t**2*x2
-  y = (1-t)**2*y0 + 2*t*(1-t)*y1 + t**2*y2
-  return x,y
-
-
-
-def quadratic_beziere_to_polyline( point, n=10):
-  t = 0
-  points = []
-  for i in range( n+1):
-    points.append( point_on_quadratic_bezier( point, t))
-    t += 1.0/n
-  return points
-
-
-def is_point_beween_points_of_line( line, point):
-  """this function asserts that the point is on a line and
-  computes if it is between the points defining the line"""
-  x1, y1, x2, y2 = line
-  x, y = point
-  if point_distance( x1,y1,x,y) + point_distance( x2,y2,x,y) > 1.02 * point_distance( x1,y1,x2,y2):
-    return False
-  return True
-
-
-def plane_normal_from_3_points( point1, point2, point3):
-  for point in (point1,point2,point3):
-    if None in point:
-      return None  # some coords are missing
-  import transform
-  x1,y1,z1 = point1
-  x2,y2,z2 = point2
-  x3,y3,z3 = point3
-  m0 = transform.matrix( [[x1,y1,z1],[x2,y2,z2],[x3,y3,z3]])
-  m1 = transform.matrix( [[1,y1,z1],[1,y2,z2],[1,y3,z3]])
-  m2 = transform.matrix( [[x1,1,z1],[x2,1,z2],[x3,1,z3]])
-  m3 = transform.matrix( [[x1,y1,1],[x2,y2,1],[x3,y3,1]])
-  #d0 = 1.0*m0.get_determinant()
-  d1 = 1.0*m1.get_determinant()
-  d2 = 1.0*m2.get_determinant()
-  d3 = 1.0*m3.get_determinant()
-  a = d1 #/d0
-  b = d2 #/d0
-  c = d3 #/d0
-  return a,b,c
-
-def angle_between_planes( plane1, plane2):
-  a1,b1,c1 = plane1
-  a2,b2,c2 = plane2
-  cos = (a1*a2 + b1*b2 + c1*c2) / sqrt( a1**2+b1**2+c1**2) / sqrt( a2**2+b2**2+c2**2)
-  return cos
-
-def same_or_oposite_side( plane1,plane2):
-  a1,b1,c1 = plane1
-  a2,b2,c2 = plane2
-  a = a1+a2
-  b = b1+b2
-  c = c1+c2
-  if (a**2 + b**2 + c**2) > (a1**2 + b1**2 + c1**2):
-    return 1
-  else:
-    return -1
-
-
-def expand_rectangle( coords, d):
-  x1,y1,x2,y2 = coords
-  return x1-d,y1-d,x2+d,y2+d
-
-
-def create_transformation_to_coincide_point_with_z_axis( mov, point):
-  """takes 3d coordinates 'point' (vector (0,0,0)->point) and returns 3d transform object
-  (transform3d.transform3d) that performs rotation to get 'point' onto z axis (x,y)=(0,0)
-  with positive 'z'.
-  NOTE: this is probably far from efficient, but it works
-  """
-  from transform3d import transform3d
-  t = transform3d()
-  a,b,c = mov
-  t.set_move( -a, -b, -c)
-  t.set_rotation_y( atan2( point[0], point[2]))
-  x,y,z = t.transform_xyz( *point)
-  t.set_rotation_x( -atan2( y, sqrt(x**2+z**2)))
-  x,y,z = t.transform_xyz( *point)
-  if z < 0:
-      t.set_rotation_x( pi)
-  t.set_move( *mov)
-  return t
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/graph/basic.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/basic.py
--- bkchem-0.12.5/bkchem/oasa/oasa/graph/basic.py	2008-02-19 04:11:36.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/basic.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,42 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-#
-# Last edited: $Date: 2003/12/23 18:06:15 $
-#
-#--------------------------------------------------------------------------
-
-
-class attribute_flexible_class:
-  """this class provides mechanism for addition and removing of attributes on fly"""
-
-  def __init__( self):
-    pass
-
-  def add_attribute( self, name, value=None):
-    if name not in self.__dict__:
-      self.__dict__[ name] = value
-      return 1
-    return 0
-
-  def del_attribute( self, name):
-    if name not in self.__dict__:
-      del self.__dict__[ name]
-      return 1
-    return 0
-    
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/graph/diedge.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/diedge.py
--- bkchem-0.12.5/bkchem/oasa/oasa/graph/diedge.py	2008-02-19 04:11:36.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/diedge.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,51 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-
-class diedge:
-
-  def __init__( self, vs=[]):
-    self.vertices = []
-    self.set_vertices( vs)
-    self.properties_ = {}
-
-  def __str__( self):
-    return "directed edge between %s %s" % tuple( map( str, self.vertices))
-
-  def set_vertices( self, vs=[]):
-    if vs and len( vs) == 2:
-      self.vertices = vs
-
-  def get_vertices( self):
-    return self.vertices
-
-  def get_neighbor_edges( self):
-    v1, v2 = self.vertices
-    out1 = [e for e in v1.get_neighbor_edges() if e!=self]
-    out2 = [e for e in v2.get_neighbor_edges() if e!=self]
-    return out1 + out2
-
-  def get_neighbor_edges2( self):
-    """returns 2 lists of neighbor edges (one for one side, one for the other)"""
-    v1, v2 = self.vertices
-    out1 = [e for e in v1.get_neighbor_edges() if e!=self]
-    out2 = [e for e in v2.get_neighbor_edges() if e!=self]
-    return out1, out2
-    
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/graph/digraph.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/digraph.py
--- bkchem-0.12.5/bkchem/oasa/oasa/graph/digraph.py	2008-10-14 09:44:51.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/digraph.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,148 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-"""this module contains a graph class that provides a minimalistic
-graph implementation suitable for analysis of chemical problems"""
-
-
-from diedge import diedge
-from vertex import vertex
-import warnings
-import copy
-import operator
-import graph
-
-
-
-class digraph( graph.graph):
-  """provides a minimalistic graph implementation suitable for analysis of chemical problems,
-  even if some care was taken to make the graph work with nonsimple graphs, there are cases where it won't!"""
-
-  vertex_class = vertex
-  edge_class = diedge
-
-
-  def __str__( self):
-    str = "digraph G(V,E), |V|=%d, |E|=%d" % ( len( self.vertices), len( self.edges))
-    return str
-
-  ## MODIFICATION METHODS
-
-
-  def add_edge( self, v1, v2, e=None):
-    """adds an edge to a graph connecting vertices v1 and v2, if e argument is not given creates a new one.
-    returns None if operation fails or the edge instance if successful"""
-    i1 = self._get_vertex_index( v1)
-    i2 = self._get_vertex_index( v2)
-    if i1 == None or i2 == None:
-      warnings.warn( "Adding edge to a vertex not present in graph failed (of course)", UserWarning, 3)
-      return None
-    # to get the vertices if v1 and v2 were indexes
-    v1 = self.vertices[ i1]
-    v2 = self.vertices[ i2]
-    if not e:
-      e = self.create_edge()
-    e.set_vertices( (v1,v2))
-    self.edges.add( e)
-    v1.add_neighbor( v2, e)
-    return e
-
-
-
-  def get_diameter( self):
-    diameter = 0
-    best = None
-    best_path = None
-    for v in self.vertices:
-      dist = self.mark_vertices_with_distance_from( v)
-      if dist > diameter:
-        diameter = dist
-        best = v
-        end = [x for x in self.vertices if 'd' in x.properties_ and x.properties_['d'] == dist][0]
-        best_path = self.get_random_longest_path_numbered( v, end)
-
-    print "path"
-    best_path.reverse()
-    for v in best_path:
-      print v
-    return diameter
-
-
-  def get_connected_components( self):
-    """returns the connected components of graph in a form o list of lists of vertices"""
-    comp = set() # just processed component 
-    comps = []
-    not_processed = set( self.vertices)
-    if not_processed:
-      recent = set() # [not_processed.pop()])
-    processed = set()
-      
-    while not_processed:
-      recent = set( reduce( operator.add, [a.get_neighbors() for a in recent], [])) & not_processed
-      processed = recent | comp
-      for e in self.edges:
-        v1, v2 = e.get_vertices()
-        if (v1 in processed and v2 in not_processed):
-          recent.add( v2)
-        elif (v2 in processed and v1 in not_processed):
-          recent.add( v1)
-      if not recent:
-        if comp:
-          yield comp
-        recent = set( [not_processed.pop()])
-        comp = recent
-      else:
-        comp |= recent
-        not_processed -= recent
-    # when there is only one atom in the last piece it is not yielded in the loop
-    yield comp
-
-  
-  def get_random_longest_path_numbered( self, start, end):
-    """vertices have to be freshly marked with distance"""
-    now = end
-    path = []
-    d = end.properties_['d']
-    while now:
-      d -= 1
-      path.append( now)
-      ns = [v for v in self.vertices if 'd' in v.properties_ and v.properties_['d'] == d and now in v.neighbors]
-      if ns:
-        now = ns[0]
-      else:
-        now = None
-    return path
-
-
-  def get_graphviz_text_dump( self):
-    ret = '''digraph "dump" {
-    fontpath="/usr/share/fonts/corefonts";
-    ratio=compress
-    nodesep=0.1;
-    ranksep=0.3;
-    rankdir=LR;
-    node [shape=box,fontsize=10,fontname=Arial,height=0.3];
-    '''
-    for v in self.vertices:
-      for n in v.neighbors:
-        ret += '"%s" -> "%s";\n' % (v,n);
-      ret += '"%s" [label="%s"];\n' % (v,v.value)
-    ret += "}"
-    return ret
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/graph/edge.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/edge.py
--- bkchem-0.12.5/bkchem/oasa/oasa/graph/edge.py	2008-10-14 09:44:51.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/edge.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,73 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-
-from warnings import warn
-import copy
-
-class edge:
-
-  attrs_to_copy = ("disconnected",)
-
-  def __init__( self, vs=[]):
-    self.vertices = set([])
-    self.set_vertices( vs)
-    self.properties_ = {}
-    self.disconnected = False
-
-  def __str__( self):
-    return "edge between %s %s" % tuple( map( str, self.vertices))
-
-  def copy( self):
-    other = self.__class__()
-    for attr in self.attrs_to_copy:
-      setattr( other, attr, copy.copy( getattr( self, attr)))
-    return other
-
-  def set_vertices( self, vs=[]):
-    if vs and len( vs) == 2:
-      self.vertices = set( vs)
-
-  def get_vertices( self):
-    return self.vertices
-
-  def get_neighbor_edges( self):
-    v1, v2 = self.vertices
-    out1 = [e for e in v1.get_neighbor_edges() if e!=self]
-    out2 = [e for e in v2.get_neighbor_edges() if e!=self]
-    return out1 + out2
-
-  def get_neighbor_edges2( self):
-    """returns 2 lists of neighbor edges (one for one side, one for the other)"""
-    v1, v2 = self.vertices
-    out1 = [e for e in v1.get_neighbor_edges() if e!=self]
-    out2 = [e for e in v2.get_neighbor_edges() if e!=self]
-    return out1, out2
-    
-  def _get_disconnected( self):
-    return self._disconnected
-
-  def _set_disconnected( self, d):
-    self._disconnected = d
-   # if d:
-   #   warn(a "aaaaaa", UserWarning, 4)
-
-
-  disconnected = property( _get_disconnected, _set_disconnected)
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/graph/graph.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/graph.py
--- bkchem-0.12.5/bkchem/oasa/oasa/graph/graph.py	2008-10-14 10:04:46.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/graph.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,1252 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-"""this module contains a graph class that provides a minimalistic
-graph implementation suitable for analysis of chemical problems"""
-
-from edge import edge
-from vertex import vertex
-import warnings
-import copy
-from types import *
-import operator
-import time
-
-
-class graph( object):
-  """provides a minimalistic graph implementation suitable for analysis of chemical problems,
-  even if some care was taken to make the graph work with nonsimple graphs, there are cases where it won't!"""
-
-  uses_cache = True
-
-
-  def __init__( self, vertices = []):
-    if vertices:
-      self.vertices = vertices
-    else:
-      self.vertices = []
-    self.edges = set()
-    self.disconnected_edges = set()
-    self._cache = {}
-
-
-
-  def __str__( self):
-    str = "graph G(V,E), |V|=%d, |E|=%d" % ( len( self.vertices), len( self.edges))
-    return str
-
-
-
-  def copy( self):
-    """provides a really shallow copy, the vertex and edge objects will remain the same,
-    only the graph itself is different"""
-    c = self.create_graph()
-    c.vertices = copy.copy( self.vertices)
-    for e in self.edges:
-      i, j = e.get_vertices()
-      c.add_edge( i, j, e)
-    return c
-
-  def deep_copy( self):
-    """provides a deep copy of the graph. The result is an isomorphic graph,
-    all the used objects are different"""
-    c = self.create_graph()
-    for v in self.vertices:
-      c.add_vertex()
-    for e in self.edges:
-      i, j = e.get_vertices()
-      c.add_edge( i, j)
-    return c
-    
-  def create_vertex( self):
-    return vertex()
-
-  def create_edge( self):
-    return edge()
-
-  def create_graph( self):
-    return self.__class__()
-  
-
-
-  ## MODIFICATION METHODS
-
-
-  def delete_vertex( self, v):
-    self.vertices.remove( v)
-    self._flush_cache()
-
-
-  def add_vertex( self, v=None):
-    """adds a vertex to a graph, if v argument is not given creates a new one.
-    returns None if vertex is already present or the vertex instance if successful"""
-    if not v:
-      v = self.create_vertex()
-    if v not in self.vertices:
-      self.vertices.append( v)
-    else:
-      warnings.warn( "Added vertex is already present in graph %s" % str( v), UserWarning, 2)
-      return None
-    self._flush_cache()
-    return v
-
-
-
-  def add_edge( self, v1, v2, e=None):
-    """adds an edge to a graph connecting vertices v1 and v2, if e argument is not given creates a new one.
-    returns None if operation fails or the edge instance if successful"""
-    i1 = self._get_vertex_index( v1)
-    i2 = self._get_vertex_index( v2)
-    if i1 == None or i2 == None:
-      warnings.warn( "Adding edge to a vertex not present in graph failed (of course)", UserWarning, 3)
-      return None
-    # to get the vertices if v1 and v2 were indexes
-    v1 = self.vertices[ i1]
-    v2 = self.vertices[ i2]
-    if not e:
-      e = self.create_edge()
-    e.set_vertices( (v1,v2))
-    self.edges.add( e)
-    v1.add_neighbor( v2, e)
-    v2.add_neighbor( v1, e)
-    self._flush_cache()
-    return e
-
-
-
-  def insert_a_graph( self, gr):
-    """inserts all edges and vertices to the graph"""
-    self.vertices.extend( gr.vertices)
-    self.edges.update( gr.edges)
-    self._flush_cache()
-    
-
-  # do we need it?
-  def connect_a_graph( self, gr, v1, v2, e=None):
-    """gr is a graph, v1 is vertex in self, v2 is vertex in gr, bond is what to use for connection"""
-    self.insert_a_graph( gr)
-    self.add_edge( v1, v2, e=e)
-    
-
-
-  def disconnect( self, v1, v2):
-    """disconnects vertices v1 and v2, on success returns the edge"""
-    if v1 != None and v2 != None:
-      e = self.get_edge_between( v1, v2)
-      if e:
-        self.edges.remove( e)
-        v1.remove_neighbor( v2)
-        v2.remove_neighbor( v1)
-      self._flush_cache()
-      return e
-    else:
-      return None
-
-
-
-  def disconnect_edge( self, e):
-    self.edges.remove( e)
-    v1, v2 = e.get_vertices()
-    v1.remove_edge_and_neighbor( e)
-    v2.remove_edge_and_neighbor( e)
-    self._flush_cache()
-    
-
-
-
-  def remove_vertex( self, v):
-    for neigh in v.get_neighbors():
-      self.disconnect( v, neigh)
-    self.delete_vertex( v)
-      
-
-
-  def get_edge_between( self, v1, v2):
-    """takes two vertices"""
-    for e in v1.get_neighbor_edges():
-      if e in v2.get_neighbor_edges():
-        return e
-    return None
-
-
-  def temporarily_disconnect_edge( self, e):
-    self.edges.remove( e)
-    self.disconnected_edges.add( e)
-    e.disconnected = True
-    self._flush_cache()
-    return e
-
-
-
-  def reconnect_temporarily_disconnected_edge( self, e):
-    assert e in self.disconnected_edges
-    self.disconnected_edges.remove( e)
-    self.edges.add( e)
-    e.disconnected = False
-    self._flush_cache()
-    
-
-  def reconnect_temporarily_disconnected_edges( self):
-    while self.disconnected_edges:
-      e = self.disconnected_edges.pop()
-      e.disconnected = False
-      self.edges.add( e)
-    self._flush_cache()
-
-
-  ## PROPERTIES METHODS
-  ## BOOLEAN
-    
-  def is_connected( self):
-    if len( self.edges) < len( self.vertices) -1:
-      # in this case it cannot be connected
-      return False
-    i = 0
-    for x in self.get_connected_components():
-      i += 1
-      if i > 1:
-        return False
-    return True
-  #return len( self.get_connected_components()) == 1
-
-  def is_tree( self):
-    return self.is_connected() and len( self.vertices)-1 == len( self.edges)
-
-  def is_cycle( self):
-    for i in self.get_degrees():
-      if i != 2:
-        return False
-    return True
-
-  def is_euler( self):
-    for i in self.get_degrees():
-      if i % 2:
-        return False
-    return True
-
-  def contains_cycle( self):
-    """this assumes that the graph is connected"""
-    assert self.is_connected()
-    return not self.is_tree()
-
-
-  def is_edge_a_bridge( self, e):
-    """tells whether an edge is bridge"""
-    start = list( e.vertices)[0]
-    # find number of vertices accessible from one of the edge endpoints
-    self.mark_vertices_with_distance_from( start)
-    c1 = len( [v for v in self.vertices if 'd' in v.properties_])
-    # disconnect the eddge
-    self.temporarily_disconnect_edge( e)
-    # find the number of vertices accessible now
-    self.mark_vertices_with_distance_from( start)
-    c2 = len( [v for v in self.vertices if 'd' in v.properties_])
-    # if they differ, we've got a bridge
-    if c1 > c2:
-      x = 1
-    else:
-      x = 0
-    self.reconnect_temporarily_disconnected_edge( e)
-    return x
-
-
-
-  def is_edge_a_bridge_fast_and_dangerous( self, e):
-    """should be used only in case of repetitive questions for the same edge in cases
-    where no edges are added to the graph between the questions (if brigde==1 the value
-    is stored and returned, which is safe only in case no edges are added)"""
-    try:
-      return e.properties_['bridge']
-    except:
-      if self.is_edge_a_bridge( e):
-        e.properties_['bridge'] = 1
-        return 1
-      else:
-        return 0
-
-
-  def get_pieces_after_edge_removal( self, e):
-    self.temporarily_disconnect_edge( e)
-    ps = [i for i in self.get_connected_components()]
-    self.reconnect_temporarily_disconnected_edge( e)
-    return ps
-
-
-  def get_size_of_pieces_after_edge_removal( self, e):
-    v1, v2 = e.vertices
-    self.disconnect( v1, v2)
-    ps = [i for i in self.get_connected_components()]
-    self.add_edge( v1, v2, e=e)
-    return map( len, ps)
-    
-
-
-  ## ANALYSIS
-
-  def get_connected_components( self):
-    """returns the connected components of graph in a form o list of lists of vertices"""
-    comp = set() # just processed component 
-    comps = []
-    not_processed = set( self.vertices)
-    if not_processed:
-      recent = set() # [not_processed.pop()])
-    while not_processed:
-      recent = set( reduce( operator.add, [a.get_neighbors() for a in recent], [])) & not_processed
-      if not recent:
-        if comp:
-          yield comp
-        recent = set( [not_processed.pop()])
-        comp = recent
-      else:
-        comp.update( recent)
-        not_processed -= recent
-    # when there is only one atom in the last piece it is not yielded in the loop
-    yield comp
-
-
-
-  def get_disconnected_subgraphs( self):
-    """returns the subgraphs of self, it is dangerous as it reuses the original vertices and
-    edges, therefore it should be used only when the old self is no longer needed."""
-    vss = self.get_connected_components()
-    out = []
-    for vs in vss:
-      out.append( self.get_induced_subgraph_from_vertices( vs))
-    return out
-
-
-  def get_induced_subgraph_from_vertices( self, vs):
-    """it creates a new graph, however uses the old vertices and edges!"""
-    g = self.create_graph()
-    for v in vs:
-      g.add_vertex( v)
-    for e in self.vertex_subgraph_to_edge_subgraph( vs):
-      v1, v2 = e.get_vertices()
-      if v1 in vs and v2 in vs:
-        g.add_edge( v1, v2, e)  # BUG - it should copy the edge?
-    return g
-    
-      
-  def get_degrees( self):
-    """returns a generator of degrees, this is useful because for many properties
-    the whole list is not important"""
-    for v in self.vertices:
-      yield v.get_degree()
-  
-
-  def get_neighbors( self, v):
-    """Info - available also trough the vertex.get_neighbors()"""
-    for i in self.get_neighbors_indexes( v):
-      yield self.vertices[ i]
-
-  def get_neighbors_indexes( self, v):
-    i1 = self._get_vertex_index( v)
-    for i, x in enumerate( self.vertices[ i1]):
-      if x and i!=i1:
-        yield i
-
-
-  def get_smallest_independent_cycles( self):
-    """returns a set of smallest possible independent cycles,
-    other cycles in graph are guaranteed to be combinations of them"""
-    return map( self.edge_subgraph_to_vertex_subgraph, self.get_smallest_independent_cycles_e())
-                
-
-  def get_smallest_independent_cycles_dangerous_and_cached( self):
-    try:
-      return self._cache['cycles']
-    except KeyError:
-      self._cache['cycles'] = self.get_smallest_independent_cycles()
-      return self._cache['cycles']
-    
-
-
-
-  def get_almost_all_cycles_e( self):
-    """returns almost all cycles found in the graph as sets of edges
-    this version is not perfect as it sometimes forgets a few rings"""
-    all_cycles = []
-    self.mark_vertices_with_distance_from( self.vertices[0])
-    to_go = set()
-    for ps in self._get_all_ring_end_points():
-      to_go.update( ps)
-    for ps in self._get_all_ring_start_points():
-      to_go.update( ps)
-    while to_go:
-      v = to_go.pop()
-      cycles = self._get_cycles_for_vertex( v, to_reach=v)
-      all_cycles += cycles
-    all_cycles = set( map( frozenset, all_cycles))
-    self.clean_distance_from_vertices()
-    return all_cycles
-
-
-
-  def get_all_cycles_e( self):
-    """returns all cycles found in the graph as sets of edges;
-    this version of the algorithm strips all non-cyclic (bridge) edges
-    and then searches for cycles in the rest"""
-    if not self.contains_cycle():
-      return set()
-    self.temporarily_strip_bridge_edges()
-
-    to_go = set( [v for v in self.vertices if v.degree])
-    all_cycles = []
-    while to_go:
-      v = to_go.pop()
-      cycles = self._get_cycles_for_vertex( v, to_reach=v)
-      all_cycles += cycles
-      to_go -= set( [ver for ver in reduce( operator.or_, map( self.edge_subgraph_to_vertex_subgraph, cycles), set()) if ver.degree == 2])
-    all_cycles = set( map( frozenset, all_cycles))
-
-    self.reconnect_temporarily_disconnected_edges()
-    
-    return all_cycles
-
-
-
-  def get_all_cycles_e_old( self):
-    """returns all cycles found in the graph as sets of edges"""
-    to_go = set( self.vertices)
-    for v in self.vertices:
-      if v.degree == 1:
-        to_go.remove( v)
-    all_cycles = []
-    while to_go:
-      v = to_go.pop()
-      cycles = self._get_cycles_for_vertex( v, to_reach=v)
-      all_cycles += cycles
-      to_go -= set( [ver for ver in reduce( operator.or_, map( self.edge_subgraph_to_vertex_subgraph, cycles), set()) if ver.degree == 2])
-    all_cycles = set( map( frozenset, all_cycles))
-    return all_cycles
-
-
-
-
-  def _get_cycles_for_vertex( self, v, to_reach=None, processed=None):
-    if not processed:
-      processed = set()
-    for e, neigh in v.get_neighbor_edge_pairs():
-      if neigh == to_reach and len( processed) > 1:
-        return [set( [e])]
-    all_cycles = []
-    for e, neigh in v.get_neighbor_edge_pairs():
-      if neigh not in processed:
-        p = set([v]) | processed
-        cycles = self._get_cycles_for_vertex( neigh, to_reach=to_reach, processed=p)
-        if cycles:
-          for cycle in cycles:
-            cycle.add( e)
-        all_cycles += cycles
-    return all_cycles
-
-
-
-
-
-  def get_smallest_independent_cycles_e( self):
-    """returns a set of smallest possible independent cycles as list of Sets of edges,
-    other cycles in graph are guaranteed to be combinations of them.
-    Gasteiger J. (Editor), Engel T. (Editor), Chemoinformatics : A Textbook, John Wiley & Sons 2001,
-    ISBN 3527306811, 174."""
-    assert self.is_connected()
-    ncycles = len( self.edges) - len( self.vertices) + 2 - len( list( self.get_connected_components()))
-
-    # check if the graph is connected, don't know if we should do it...
-    if ncycles < 0:
-      warnings.warn( "The number of edges is smaller than number of vertices-1, the molecule must be disconnected, which means there is something wrong with it.", UserWarning, 3)
-      ncycles = 0
-
-    # trivial case of linear molecule
-    if ncycles == 0:
-      return set()
-
-    # the code itself
-    self.temporarily_strip_bridge_edges()
-    cycles = set()
-
-    vs = [v for v in self.vertices if v.degree]
-    while vs and len( cycles) < ncycles:
-      new_cycles = set()
-      vs2 = [v for v in vs if v.degree == 2]
-      # disconnect something if there are no edges of degree 2
-      removed_e = None
-      if not vs2:
-        for v in vs:
-          if v.degree == 3:
-            removed_e = list( v.neighbor_edges)[0]
-            self.temporarily_disconnect_edge( removed_e)
-            break
-      vs2 = [v for v in vs if v.degree == 2]
-      assert len( vs2) > 0
-      # get rings for all degree==2 vertices
-      for v in vs2:
-        gen = self._get_smallest_cycles_for_vertex( v, to_reach=v)
-        for x in gen:
-          if x:
-            new_cycles.update( set( x))
-            break
-      cycles.update( new_cycles)
-      # strip the cycles
-      to_disconnect = set()
-      for cycle in new_cycles:
-        # find the longest degree==2 chain in each cycle
-        paths = set()
-        to_go = set( [v for v in self.edge_subgraph_to_vertex_subgraph( cycle) if v.degree == 2])
-        while to_go:
-          now = set( [to_go.pop()])            
-          path = set( now)
-          while now:
-            now = reduce( operator.or_, [set( [n for n in v.neighbors if n.degree == 2]) for v in now])
-            now &= to_go
-            to_go -= now
-            path.update( now)
-          if path:
-            paths.add( frozenset( path))
-        #if not paths:
-        #  paths.add( now)
-        l = max( map( len, paths))
-        path = [p for p in paths if len( p) == l][0]
-        # now mark them for disconnection
-        v1 = set( path).pop()
-        to_disconnect.add( list( v1.neighbor_edges)[0])
-
-      # disconnect the new edges
-      [self.temporarily_disconnect_edge( e) for e in to_disconnect]
-      # reconnect the edge removed on the top
-      if removed_e:
-        self.reconnect_temporarily_disconnected_edge( removed_e)
-
-      # strip the degree==1 vertices
-      vs1 = [v for v in self.vertices if v.degree == 1]
-      while vs1:
-        for v in vs1:
-          # we have to ask the degree, because the bond might have been stripped in this run
-          if v.degree:
-            e = v.neighbor_edges[0]
-            self.temporarily_disconnect_edge( e)
-        vs1 = [v for v in self.vertices if v.degree == 1]
-
-      vs = [v for v in self.vertices if v.degree]          
-
-    # remove extra cycles in some cases like adamantane
-    if len( cycles) - ncycles > 0:
-      # sort cycles according to length
-      cs = [(len( c), c) for c in cycles]
-      cs.sort()
-      cs = [c[1] for c in cs]
-      # now try to remove the biggest ones
-      while len( cs) - ncycles > 0:
-        c = set( cs.pop( -1))
-        print c, reduce( operator.or_, map( set, cs))
-        if not c <= reduce( operator.or_, map( set, cs)):
-          print "AAA"
-          cs.insert( 0, c)
-      cycles = set( cs)
-
-    # count the cycles and report warnings if their number is wrong
-    if len( cycles) < ncycles:
-      warnings.warn( "The number of cycles found (%d) is smaller than the theoretical value %d (|E|-|V|+1)" % (len( cycles), ncycles), UserWarning, 2)
-    elif len( cycles) > ncycles: 
-      warnings.warn( "The number of independent cycles found (%d) is larger than the theoretical value %d (|E|-|V|+1), but I cannot improve it." % (len( cycles), ncycles), UserWarning, 2)
-
-    self.reconnect_temporarily_disconnected_edges()
-      
-    return cycles
-
-
-
-  def _get_smallest_cycle_for_vertex( self, v, to_reach=None, came_from=None, went_through=None):
-    """ingenious generator-based breadth-first search (BFS) to find one (random) smallest
-    cycle for given vertex. It yields None or cycle for each depth level"""
-    for e, neigh in v.get_neighbor_edge_pairs():
-      if neigh == to_reach and e != came_from:
-        yield set( [came_from, e])
-    gens = []
-    yield None
-    w = went_through and went_through+[v] or [v]
-    for e, neigh in v.get_neighbor_edge_pairs():
-      if (not went_through or neigh not in went_through) and not e == came_from:
-        gens.append( self._get_smallest_cycle_for_vertex( neigh, to_reach=to_reach, came_from=e, went_through=w))
-    while 1:
-      for i, gen in enumerate( gens):
-        ret = gen.next()
-        if ret:
-          if came_from:
-            yield set( [came_from]) | ret
-          else:
-            yield ret
-      yield None
-
-
-  def _get_smallest_cycles_for_vertex( self, v, to_reach=None, came_from=None, went_through=None):
-    """ingenious generator-based breadth-first search (BFS) to find smallest
-    cycles for given vertex. It yields None or cycles for each depth level"""
-    ret = []
-    for e, neigh in v.get_neighbor_edge_pairs():
-      if neigh == to_reach and e != came_from:
-        ret.append( frozenset( [came_from, e]))
-    yield ret
-
-    gens = []
-    w = went_through and went_through+[v] or [v]
-    for e, neigh in v.get_neighbor_edge_pairs():
-      # we dont want to go back, therefore we use went_through
-      if (not went_through or neigh not in went_through) and not e == came_from:
-        gens.append( self._get_smallest_cycles_for_vertex( neigh, to_reach=to_reach, came_from=e, went_through=w))
-    while 1:
-      all_rets = []
-      for i, gen in enumerate( gens):
-        rets = gen.next()
-        new_rets = []
-        if rets:
-          for ret in rets:
-            if came_from:
-              ret = set( ret)
-              ret.update( set( [came_from]))
-              ret = frozenset( ret)
-            new_rets.append( ret)
-          all_rets.extend( frozenset( new_rets))
-      yield all_rets
-
-
-
-  def get_all_cycles( self):
-    """returns all cycles found in the graph as sets of vertices,
-    use get_all_cycles_e to get the edge variant, which is better for building new
-    graphs as the mapping edges => vertices is unambiguous, while edges=>vertices=>edges might
-    include some more edges"""
-    return map( self.edge_subgraph_to_vertex_subgraph, self.get_all_cycles_e())
-
-
-      
-
-
-  def mark_vertices_with_distance_from( self, v):
-    """returns the maximum d"""
-    self.clean_distance_from_vertices()
-    return self._mark_vertices_with_distance_from( v)
-
-  def clean_distance_from_vertices( self):
-    for i in self.vertices:
-      try:
-        del i.properties_['d']
-      except KeyError:
-        pass
-
-  def mark_edges_with_distance_from( self, e1):
-    for e in self.edges:
-      try:
-        del e.properties_['d']
-      except KeyError:
-        pass
-    marked = set( [e1])
-    new = set( [e1])
-    dist = 0
-    e1.properties_['dist'] = dist
-    while new:
-      new_new = set()
-      dist += 1
-      for e in new:
-        for ne in e.get_neighbor_edges():
-          if not ne in marked:
-            ne.properties_['dist'] = dist
-            new_new.add( ne)
-      new = new_new
-      marked.update( new)
-
-  def get_path_between_edges( self, e1, e2):
-    self.mark_edges_with_distance_from( e1)
-    if not "dist" in e2.properties_:
-      return None
-    else:
-      path = [e2]
-      _e = e2
-      for i in range( e2.properties_['dist']-1, -1, -1):
-        _e = [ee for ee in _e.get_neighbor_edges() if ee.properties_['dist'] == i][0]
-        path.append( _e)
-      return path
-  
-
-  def _gen_diameter_progress( self):
-    """this generator iteratively generates graph diameter during its computation,
-    the result is the last value, it is only interesting for monitoring of the computation
-    as it can be pretty time consuming"""
-    diameter = 0
-    best = None
-    best_path = None
-    for v in self.vertices:
-      dist = self.mark_vertices_with_distance_from( v)
-      if dist > diameter:
-        diameter = dist
-        best = v
-        #end = [x for x in self.vertices if x.properties_['d'] == dist][0]
-        #best_path = get_path_down_to( end, v)
-        yield diameter
-    if diameter == 0:
-      yield 0
-    #best_path.reverse()
-
-
-  def _get_width_from_vertex( self, v):
-    """returns width of the graph as calculated from vertex v"""
-    d = 0
-    to_mark = set([v])
-    marked = set()
-    while to_mark:
-      marked_before = marked
-      marked = to_mark
-      to_mark_next = set( reduce( operator.add, [i.get_neighbors() for i in to_mark], []))
-      to_mark = to_mark_next - marked - marked_before
-      d += 1
-    return d-1
-
-
-  def get_diameter( self):
-    d = self._get_cache( "diameter")
-    if not d:
-      g = self._gen_diameter_progress()
-      for i in g:
-        pass
-      self._set_cache( "diameter", i)
-      return i
-    else:
-      return d
-
-
-# does not work because the mark_vertices_with_distance_from is not thread safe,
-# it writes to v.properties_['d']. It would have to be switched to v.properties_['dX']
-# where X is the number of the thread
-##   def get_diameter_multi_thread( self, processes=2, group_by=100):
-##     threads = []
-##     attrs = list( self.vertices)
-##     diameter = 0
-    
-##     while attrs or threads:
-##       if len( threads) < processes and attrs:
-##         if len( attrs) > group_by:
-##           _as = attrs[0:group_by]
-##           del attrs[0:group_by]
-##         else:
-##           _as = attrs
-##           attrs = []
-
-##         t = MyThread( self.mark_vertices_with_distance_from, _as)
-##         threads.append( t)
-##         t.start()
-##       else:
-##         time.sleep( 0.05)
-
-##       dead = [t for t in threads if not t.isAlive()]
-##       for d in dead:
-##         dist = max( d.ret)
-##         if dist > diameter:
-##           diameter = dist
-
-##       threads = [t for t in threads if t.isAlive()]
-
-##     return diameter
-
-
-    
-
-
-  def vertex_subgraph_to_edge_subgraph( self, cycle):
-    ret = set()
-    for v1 in cycle:
-      for (e,n) in v1.get_neighbor_edge_pairs():
-        if n in cycle:
-          ret.add( e)
-    return ret
-
-  def edge_subgraph_to_vertex_subgraph( self, cycle):
-    ret = set()
-    for e in cycle:
-      v1, v2 = e.get_vertices()
-      ret.add( v1)
-      ret.add( v2)
-    return ret
-
-  def get_new_induced_subgraph( self, vertices, edges):
-    """returns a induced subgraph that is newly created and can be therefore freely
-    changed without worry about the original."""
-    sub = self.create_graph()
-    vertex_map = {}
-    i = 0
-    for v in vertices:
-      new_v = v.copy()
-      sub.add_vertex( new_v)
-      vertex_map[v] = i
-      i += 1
-    for e in edges:
-      new_e = e.copy()
-      v1, v2 = e.get_vertices()
-      sub.add_edge( vertex_map[v1], vertex_map[v2], new_e)
-    return sub
-
-  def defines_connected_subgraph_e( self, edges):
-    sub = self.get_new_induced_subgraph( self.edge_subgraph_to_vertex_subgraph( edges), edges)
-    return sub.is_connected()
-
-  def defines_connected_subgraph_v( self, vertices):
-    sub = self.get_new_induced_subgraph( vertices, self.vertex_subgraph_to_edge_subgraph( vertices))
-    return sub.is_connected()
-  
-
-  def find_path_between( self, start, end, dont_go_through=[]):
-    """finds path between two vertices, if dont_go_through is given (as a list of vertices and edges),
-    only paths not containing these vertices will be given (or None is returned if such a path
-    does not exist"""
-    ### DOES NOT WORK WELL WITH RINGS, FOR THIS RECURSIVE DESIGN WILL BE NEEDED
-    self.mark_vertices_with_distance_from( start)
-    d = end.properties_['d']
-    out = [end]
-    rend = end
-    for i in range( d, 0, -1):
-      vs = [v for e,v in rend.get_neighbor_edge_pairs() if v.properties_['d'] == i-1 and (v not in dont_go_through or e not in dont_go_through)]
-      if not vs:
-        return None
-      v = vs[0]
-      out.append( v)
-      rend = v
-    return out
-
-  def sort_vertices_in_path( self, path, start_from=None):
-    """returns None if there is no path"""
-    rng = copy.copy( path)
-    if start_from:
-      a = start_from
-      rng.remove( a)
-    else:
-      a = None
-      # for acyclic path we need to find one end
-      for at in path:
-        if len( [v for v in at.get_neighbors() if v in path]) == 1:
-          a = at
-          rng.remove( at)
-          break
-      if not a:
-        a = rng.pop() # for rings
-    out = [a]
-    while rng:
-      try:
-        a = [i for i in a.get_neighbors() if i in rng][0]
-      except IndexError:
-        return None
-      out.append( a)
-      rng.remove( a)
-    return out
-
-
-  def temporarily_strip_bridge_edges( self):
-    """strip all edges that ar ea bridge, thus leaving only the cycles connected"""
-    bridge_found = True
-    while bridge_found:
-      vs = [v for v in self.vertices if v.degree == 1]
-      while vs:
-        for v in vs:
-          # we have to ask the degree, because the bond might have been stripped in this run
-          if v.degree:
-            e = v.neighbor_edges[0]
-            self.temporarily_disconnect_edge( e)
-        vs = [v for v in self.vertices if v.degree == 1]
-      bridge_found = False
-      for e in self.edges:
-        if self.is_edge_a_bridge( e):
-          bridge_found = True
-          break
-      if bridge_found:
-        self.temporarily_disconnect_edge( e)
-
-
-  def dump_simple_text_file( self, f):
-    """dumps itself into a simple text file, one edge per line described as
-    indexes of vertices separated with a space, the first line has the vertex
-    count.
-    f is an file-like object opened for writing"""
-    f.write( "%d\n" % len( self.vertices))
-    for v in self.vertices:
-      i1 = self.vertices.index( v)
-      for n in v.neighbors:
-        i2 = self.vertices.index( n)
-        f.write( "%d %d\n" % (i1, i2))
-
-
-  def read_simple_text_file( self, f):
-    """fills itself from a simple text file, one edge per line described as
-    indexes of vertices separated with a space, the first line has the vertex
-    count.
-    f is an file-like object opened for reading"""
-    num = int( f.readline().strip())
-    for i in range( num):
-      self.add_vertex( vertex())
-    for line in f:
-      i1, i2 = map( int, line.split())
-      self.add_edge( self.vertices[i1], self.vertices[i2])
-
-  def path_exists( self, a1, a2):
-    self.mark_vertices_with_distance_from( a1)
-    if 'd' in a2.properties_:
-      return True
-    else:
-      return False
-
-  ## STATIC METHODS
-
-##   def get_random_longest_path_numbered( self, start):
-##     """vertices have to be freshly marked with distance"""
-##     now = start
-##     path = []
-##     d = 0
-##     while now:
-##       d += 1
-##       path.append( now)
-##       ns = [v for v in now.neighbors if 'd' in v.properties_ and v.properties_['d'] == d]
-##       if ns:
-##         now = ns[0]
-##       else:
-##         now = None
-##     return path
-
-##   get_random_longest_path_numbered = classmethod( get_random_longest_path_numbered)
-
-
-##   def get_random_longest_path( self, start):
-##     self.mark_vertices_with_distance_from( start)
-##     return self.get_random_longest_path_numbered( start)
-
-##   get_random_longest_path = classmethod( get_random_longest_path)
-
-
-
-
-  # PRIVATE METHODS
-
-  def _get_vertex_index( self, v):
-    """if v is already an index, return v, otherwise return index of v on None"""
-    if type( v) == IntType and v < len( self.vertices):
-      return v
-    try:
-      return self.vertices.index( v)
-    except ValueError:
-      return None
-
-
-  def _read_file( self, name="/home/beda/oasa/oasa/mol.graph"):
-    self.vertices = []
-    self.edges = set()
-    f = file( name, 'r')
-    vs = f.readline()
-    [self.add_vertex() for i in vs.split(" ")]
-    for l in f.readlines():
-      c, a, b = l.split(' ')
-      self.add_edge( self.vertices[int(a)], self.vertices[int(b)])
-    f.close()
-
-
-  def _mark_vertices_with_distance_from( self, v):
-    """returns the maximum d"""
-    d = 0
-    to_mark = set([v])
-    while to_mark:
-      to_mark_next = set()
-      for i in to_mark:
-        i.properties_['d'] = d
-
-      for i in to_mark:
-        for j in i.get_neighbors():
-          if 'd' not in j.properties_:
-            to_mark_next.add( j)
-
-      to_mark = to_mark_next
-      d += 1
-
-    return d-1
-
-
-  def _get_some_cycles( self):
-    if len( self.vertices) <= 2:
-      raise StopIteration
-    self.mark_vertices_with_distance_from( self.vertices[0])
-    for end in self._get_all_ring_end_points():
-      for start in self._get_all_ring_start_points():
-        ring = is_there_a_ring_between( start, end)
-        if ring:
-          yield ring
-
-
-  def _get_all_ring_end_points( self):
-    already_there = []
-    for v in self.vertices:
-      if v in already_there:
-        continue
-      vs_ed, vs_ver = is_ring_end_vertex( v)
-      if vs_ed:
-        yield vs_ed
-      if vs_ver:
-        already_there.extend( vs_ver)
-        yield vs_ver
-
-  def _get_all_ring_start_points( self):
-    already_there = []
-    for v in self.vertices:
-      if v in already_there:
-        continue
-      vs_ed, vs_ver = is_ring_start_vertex( v)
-      if vs_ed:
-        yield vs_ed
-      if vs_ver:
-        already_there.extend( vs_ver)
-        yield vs_ver
-
-
-
-  def _flush_cache( self):
-    self._cache = {}
-
-
-  def _set_cache( self, name, value):
-    if self.uses_cache:
-      self._cache[ name] = value
-
-
-  def _get_cache( self, name):
-    return self._cache.get( name, None)
-    
-
-
-
-
-
-def is_ring_end_vertex( v):
-# NEEDS NEW COMMENT
-#  """tells if a vertex has two neighbors with 'd' one smaller and equal or
-#  one neighbor with equal 'd'. These are the conditions for a cycle end.
-#  Returns the set([v]), in second case 'v' and the other with same 'd'"""
-  ed, ver = None, None
-  for x in v.get_neighbors():
-    if v.properties_['d'] == x.properties_['d']:
-      if len( v.get_neighbors_with_distance( v.properties_['d']-1)) and len( x.get_neighbors_with_distance( v.properties_['d']-1)):
-        ed = set([ x, v])
-    for y in v.get_neighbors():
-      if x != y:
-        if (x.properties_['d'] == y.properties_['d']) and (x.properties_['d'] == v.properties_['d']-1):
-          ver = set([v])
-  return ed, ver
-
-def is_ring_start_vertex( v):
-# NEEDS NEW COMMENT
-#  """tells if a vertex has two neighbors with 'd' one higher and equal or one neighbor with
-#  equal 'd' (then both have neighbors with one higher 'd'). These are the conditions for a cycle start.
-#  Returns boolean"""
-  ed, ver = None, None
-  for x in v.get_neighbors():
-    if v.properties_['d'] == x.properties_['d']:
-      if len( v.get_neighbors_with_distance( v.properties_['d']+1)) and len( x.get_neighbors_with_distance( v.properties_['d']+1)):
-        ed = set([ x, v])
-    for y in v.get_neighbors():
-      if x != y:
-        if (x.properties_['d'] == y.properties_['d']) and (x.properties_['d'] == v.properties_['d']+1):
-          ver = set([v])
-          break
-  return ed, ver
-
-def get_first_closer_by_one( v):
-  d = v.properties_['d']
-  for x in v.get_neighbors():
-    if x.properties_['d'] == d-1:
-      return x
-  return None
-
-def is_there_a_ring_between( start, end):
-  pths = []
-  for e in end:
-    for s in start:
-      ps = get_paths_down_to( e, s)
-      if ps:
-        ## for end-edge there is only one path from each point important
-        if len( end) == 2:
-          ps = [ps[0]]
-        ## for end-vertex the end vertex must not be part of the set in order to filter it
-        if len( end) == 1:
-          for p in ps:
-            p.remove( e)
-        pths.extend( ps)
-  ## we use sets for filtering off the path that have common vertices 
-  paths = [set( p) for p in pths]
-  final = []
-  while len( paths) > 0:
-    p1 = paths.pop(0)
-    to_del= []
-    for p2 in paths:
-      if p1 & p2:
-        to_del.append( p2)
-    for p2 in to_del:
-      paths.remove( p2)
-    final.append( p1)
-  if len( final) >= 2:
-    final.append( end | start)
-    ret = set( reduce( operator.or_, final))
-    return ret
-  return False
-                
-def get_paths_down_to( end, start):
-  paths = []
-  if end == start:
-    return None
-  for x in end.get_neighbors():
-    if x.properties_['d'] == end.properties_['d']-1:
-      ps = get_path_down_to( x, start)
-      if ps != None:
-        ps.append( end)
-        paths.append( ps)
-  return paths
-
-
-def get_path_down_to( end, start):
-  if end == start:
-    return []
-  for x in end.get_neighbors():
-    if x.properties_['d'] == end.properties_['d']-1:
-      ps = get_path_down_to( x, start)
-      if ps != None:
-        ps.append( end)
-        return ps
-
-  return None
-
-
-
-
-
-def filter_off_dependent_cycles( cycles):
-  """this filtres off all cycles that are combinations of smaller cycles."""
-  cs = list( cycles)
-  level = 2
-  cs.sort( lambda a,b: -len( a) + len(b))
-  while level < len( cs):
-    to_del = set()
-    for c in cs:
-      combs = gen_variations( [x for x in cs if x != c and x not in to_del and x & c], level)
-      for combl in combs:
-        comb = set( combl)
-        if not comb & to_del and (c <= reduce( operator.or_, comb)) and reduce( operator.and_, comb):
-          to_del.add( c)
-          break
-    [cs.remove( x) for x in to_del]
-    level += 1
-  return cs
-
-
-
-## def filter_off_dependent_cycles( cycles):
-##   cs = list( cycles)
-##   level = 2
-##   cs.sort( lambda a,b: -len( a) + len(b))
-##   print len( cs), map( len, cs)
-##   while level < len( cs):
-##     to_del = set()
-##     for c in cs:
-##       combs = gen_variations( [x for x in cs if x != c and x & c], level)
-##       for combl in combs:
-##         comb = set( combl)
-##         if not comb & to_del and (c <= reduce( operator.or_, comb)):
-##           to_del.add( c)
-##           print "remove", len( c), map( len, comb)
-##           break
-##     [cs.remove( x) for x in to_del]
-##     level += 1
-##   return cs
-
-
-
-
-def gen_variations(items, n):
-    if n==0:
-      yield []
-    else:
-      for i in xrange( len(items)-n+1):
-        for v in gen_variations(items[i+1:],n-1):
-          yield [items[i]]+v
-
-
-
-
-from threading import Thread
-import time
-
-
-class MyThread( Thread):
-
-  def __init__( self, function, attrs):
-    """function is the function to run, attrs a list of attrs for which to run the
-    consecutively the function, additional_attrs and kw_attrs are attrs that are added to
-    each call of function"""
-    Thread.__init__( self)
-    self.function = function
-    self.attrs = attrs
-    self.ret = []
-
-
-  def run( self):
-    for a in self.attrs:
-      self.ret.append( self.function( a))
-
-
-
-
-
-## import profile
-
-## g = graph()
-## g._read_file()
-## print g
-## print [c for c in g.get_connected_components()]
-
-## for e in g.edges:
-##   print g.is_edge_a_bridge( e),
-## print
-
-## print len( [i for i in g.get_connected_components()])
-## for e in g.edges:
-##   d1, d2 = [x.get_degree() for x in e.get_vertices()]
-##   if d1 != 2 or d2 != 2:
-##     v1, v2 = e.vertices
-##     break
-## g.disconnect( v1, v2)
-## print len( [i for i in g.get_connected_components()])
-
-
-## g = g.deep_copy()
-## print g
-## #profile.run( 'g.get_all_cycles()')
-## c = g.get_smallest_independent_cycles()
-## print 'cycles %d, lengths %s' % (len( c), str( map( len, c)))
-## c = g.get_all_cycles()
-## print 'cycles %d, lengths %s' % (len( c), str( map( len, c)))
-
-
-
-
-##################################################
-# TODO
-
-
-##################################################
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/graph/__init__.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/__init__.py
--- bkchem-0.12.5/bkchem/oasa/oasa/graph/__init__.py	2008-02-19 04:11:36.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/__init__.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,29 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import graph
-import vertex
-import edge
-import digraph
-
-graph = graph.graph
-vertex = vertex.vertex
-edge = edge.edge
-digraph = digraph.digraph
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/graph/multi_thread.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/multi_thread.py
--- bkchem-0.12.5/bkchem/oasa/oasa/graph/multi_thread.py	2008-10-14 09:23:36.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/multi_thread.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,87 +0,0 @@
-
-
-import types
-from threading import Thread
-import time
-
-
-class CustomThread( Thread):
-
-  def __init__( self, function, attrs, additional_attrs=None, kw_attrs=None):
-    """function is the function to run, attrs a list of attrs for which to run the
-    consecutively the function, additional_attrs and kw_attrs are attrs that are added to
-    each call of function"""
-    Thread.__init__( self)
-    self.function = function
-    self.attrs = attrs
-    self.additional_attrs = additional_attrs
-    self.kw_attrs = kw_attrs
-
-
-  def run( self):
-    for a in self.attrs:
-      aa = self.additional_attrs or []
-      ka = self.kw_attrs or {}
-      self.function( a, *aa, **ka)
-
-
-
-def run_in_threads( function, attrs, processes=1, group_by=1, additional_attrs=None, kw_attrs=None):
-    """function is the function to run, attrs a list of attrs for which to run the
-    consecutively the function, additional_attrs and kw_attrs are attrs that are added to
-    each call of function,
-
-    *group_by* means how many attributes should be processed in one thread, this is useful when a
-    single run of the function is too fast and the overhead of starting a thread is too high,
-    it is good to set this value so that a runtime of one group is higher than at least 100ms,
-
-    *processes* is a number of parallel threads that should run."""
-
-    #if processes == 1:
-    #  # in case of one process it is not economic to spawn any threads
-    #  for num in nums:
-    #    process_one_rfc( num, hit_dictionary, id_remap, logging=logger( verbose=options.verbose, quiet=options.quiet))
-
-    assert type( group_by) == types.IntType
-    assert type( processes) == types.IntType
-    
-    threads = []
-    while attrs or threads:
-      if len( threads) < processes and attrs:
-        if len( attrs) > group_by:
-          _as = attrs[0:group_by]
-          del attrs[0:group_by]
-        else:
-          _as = attrs
-          attrs = []
-
-        t = CustomThread( function, _as, additional_attrs=additional_attrs, kw_attrs=kw_attrs)
-        threads.append( t)
-        t.start()
-      else:
-        time.sleep( 0.05)
-
-      threads = [t for t in threads if t.isAlive()]
-
-
-
-if __name__ == "__main__":
-
-  import operator
-
-  def fact( n):
-    if n == 0:
-      ret = 1
-    elif n == 1:
-      ret = 1
-    else:
-      ret = reduce( operator.mul, range( 2, n+1))
-
-    print n
-    time.sleep( 0.02)
-    return ret
-      
-
-  ns = range( 1, 500)
-
-  run_in_threads( fact, ns, processes=1, group_by=10)
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/graph/vertex.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/vertex.py
--- bkchem-0.12.5/bkchem/oasa/oasa/graph/vertex.py	2008-10-14 09:43:48.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/graph/vertex.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,120 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-"""implements the vertex class"""
-
-#import basic
-
-import copy
-
-
-class vertex( object):
-  """simple vertex class, normaly would not be needed but it can speed up many analytical tasks
-  to store data directly in vertex and not get them from the graph connectivity matrix.
-  vertex has a value attribute used to store arbitrary object"""
-
-  attrs_to_copy = ("value",)
-
-  def __init__( self):
-    self.properties_ = {} # used to store intermediate properties such as distances etc.
-    self.value = None  # used to store any object associated with the vertex
-    self._neighbors = {} # set of all neighbors
-    self._clean_cache()
-    
-    
-  def __str__( self):
-    return ("vertex, value=%s, degree=%d, " % (str( self.value), self.get_degree()) )+str(self.properties_)
-
-  def _clean_cache( self):
-    self._cache = {}
-
-  def copy( self):
-    other = self.__class__()
-    for attr in self.attrs_to_copy:
-      setattr( other, attr, copy.copy( getattr( self, attr)))
-    return other
-  
-  def add_neighbor( self, v, e):
-    """adds a neighbor connected via e"""
-    self._clean_cache()
-    self._neighbors[ e] = v
-
-  def remove_neighbor( self, v):
-    self._clean_cache()
-    to_del = None
-    for k, vv in self._neighbors.iteritems():
-      if v == vv:
-        to_del = k
-        break
-    if to_del:
-      del self._neighbors[ to_del]
-    else:
-      raise "cannot remove non-existing neighbor"
-
-  def remove_edge_and_neighbor( self, e):
-    self._clean_cache()
-    if e in self._neighbors.keys():
-      del self._neighbors[ e]
-    else:
-      raise "cannot remove non-existing edge", e
-
-
-  def get_neighbors( self):
-    return [v for (e,v) in self._neighbors.iteritems() if not e.disconnected]
-    #for i in self._neighbors.itervalues():
-    #  yield i
-
-
-  def get_neighbor_connected_via( self, e):
-    return self._neighbors[ e]
-
-  def get_edge_leading_to( self, a):
-    for b, at in self._neighbors.iteritems():
-      if a == at:
-        return b
-    return None
-
-  def get_degree( self):
-    return len( self.neighbors)
-
-
-  def get_neighbors_with_distance( self, d):
-    ret = []
-    for v in self.neighbors:
-      if 'd' in v.properties_ and v.properties_['d'] == d:
-        ret.append( v)
-    return ret
-
-
-  def get_neighbor_edge_pairs( self):
-    for e,v in self._neighbors.iteritems():
-      if not e.disconnected:
-        yield e,v
-
-  def get_neighbor_edges( self):
-    return [e for e in self._neighbors.keys() if not e.disconnected]
-
-
-
-  # PROPERTIES
-
-  neighbors = property( get_neighbors, None, None, "the neighboring vertices")
-  neighbor_edges = property( get_neighbor_edges, None, None, "the neighboring edges")
-  degree = property( get_degree, None, None, "the degree of the vertex")
-  
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/inchi_key.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/inchi_key.py
--- bkchem-0.12.5/bkchem/oasa/oasa/inchi_key.py	2008-10-14 09:44:51.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/inchi_key.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,1278 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import sys
-
-ver = sys.version_info
-ver2 = ver[0] * 10  + ver[1]
-if ver2 < 25:
-  try:
-    import mhash
-  except:
-    raise Exception( "python-mhash library was not found, make sure it is installed")
-else:
-  import hashlib
-
-import operator
-import re
-
-
-triplets = [ 
-"AAA","AAB","AAC","AAD","AAE","AAF","AAG","AAH","AAI","AAJ","AAK","AAL","AAM","AAN","AAO","AAP",
-"AAQ","AAR","AAS","AAT","AAU","AAV","AAW","AAX","AAY","AAZ","ABA","ABB","ABC","ABD","ABE","ABF",
-"ABG","ABH","ABI","ABJ","ABK","ABL","ABM","ABN","ABO","ABP","ABQ","ABR","ABS","ABT","ABU","ABV",
-"ABW","ABX","ABY","ABZ","ACA","ACB","ACC","ACD","ACE","ACF","ACG","ACH","ACI","ACJ","ACK","ACL",
-"ACM","ACN","ACO","ACP","ACQ","ACR","ACS","ACT","ACU","ACV","ACW","ACX","ACY","ACZ","ADA","ADB",
-"ADC","ADD","ADE","ADF","ADG","ADH","ADI","ADJ","ADK","ADL","ADM","ADN","ADO","ADP","ADQ","ADR",
-"ADS","ADT","ADU","ADV","ADW","ADX","ADY","ADZ","AEA","AEB","AEC","AED","AEE","AEF","AEG","AEH",
-"AEI","AEJ","AEK","AEL","AEM","AEN","AEO","AEP","AEQ","AER","AES","AET","AEU","AEV","AEW","AEX",
-"AEY","AEZ","AFA","AFB","AFC","AFD","AFE","AFF","AFG","AFH","AFI","AFJ","AFK","AFL","AFM","AFN",
-"AFO","AFP","AFQ","AFR","AFS","AFT","AFU","AFV","AFW","AFX","AFY","AFZ","AGA","AGB","AGC","AGD",
-"AGE","AGF","AGG","AGH","AGI","AGJ","AGK","AGL","AGM","AGN","AGO","AGP","AGQ","AGR","AGS","AGT",
-"AGU","AGV","AGW","AGX","AGY","AGZ","AHA","AHB","AHC","AHD","AHE","AHF","AHG","AHH","AHI","AHJ",
-"AHK","AHL","AHM","AHN","AHO","AHP","AHQ","AHR","AHS","AHT","AHU","AHV","AHW","AHX","AHY","AHZ",
-"AIA","AIB","AIC","AID","AIE","AIF","AIG","AIH","AII","AIJ","AIK","AIL","AIM","AIN","AIO","AIP",
-"AIQ","AIR","AIS","AIT","AIU","AIV","AIW","AIX","AIY","AIZ","AJA","AJB","AJC","AJD","AJE","AJF",
-"AJG","AJH","AJI","AJJ","AJK","AJL","AJM","AJN","AJO","AJP","AJQ","AJR","AJS","AJT","AJU","AJV",
-"AJW","AJX","AJY","AJZ","AKA","AKB","AKC","AKD","AKE","AKF","AKG","AKH","AKI","AKJ","AKK","AKL",
-"AKM","AKN","AKO","AKP","AKQ","AKR","AKS","AKT","AKU","AKV","AKW","AKX","AKY","AKZ","ALA","ALB",
-"ALC","ALD","ALE","ALF","ALG","ALH","ALI","ALJ","ALK","ALL","ALM","ALN","ALO","ALP","ALQ","ALR",
-"ALS","ALT","ALU","ALV","ALW","ALX","ALY","ALZ","AMA","AMB","AMC","AMD","AME","AMF","AMG","AMH",
-"AMI","AMJ","AMK","AML","AMM","AMN","AMO","AMP","AMQ","AMR","AMS","AMT","AMU","AMV","AMW","AMX",
-"AMY","AMZ","ANA","ANB","ANC","AND","ANE","ANF","ANG","ANH","ANI","ANJ","ANK","ANL","ANM","ANN",
-"ANO","ANP","ANQ","ANR","ANS","ANT","ANU","ANV","ANW","ANX","ANY","ANZ","AOA","AOB","AOC","AOD",
-"AOE","AOF","AOG","AOH","AOI","AOJ","AOK","AOL","AOM","AON","AOO","AOP","AOQ","AOR","AOS","AOT",
-"AOU","AOV","AOW","AOX","AOY","AOZ","APA","APB","APC","APD","APE","APF","APG","APH","API","APJ",
-"APK","APL","APM","APN","APO","APP","APQ","APR","APS","APT","APU","APV","APW","APX","APY","APZ",
-"AQA","AQB","AQC","AQD","AQE","AQF","AQG","AQH","AQI","AQJ","AQK","AQL","AQM","AQN","AQO","AQP",
-"AQQ","AQR","AQS","AQT","AQU","AQV","AQW","AQX","AQY","AQZ","ARA","ARB","ARC","ARD","ARE","ARF",
-"ARG","ARH","ARI","ARJ","ARK","ARL","ARM","ARN","ARO","ARP","ARQ","ARR","ARS","ART","ARU","ARV",
-"ARW","ARX","ARY","ARZ","ASA","ASB","ASC","ASD","ASE","ASF","ASG","ASH","ASI","ASJ","ASK","ASL",
-"ASM","ASN","ASO","ASP","ASQ","ASR","ASS","AST","ASU","ASV","ASW","ASX","ASY","ASZ","ATA","ATB",
-"ATC","ATD","ATE","ATF","ATG","ATH","ATI","ATJ","ATK","ATL","ATM","ATN","ATO","ATP","ATQ","ATR",
-"ATS","ATT","ATU","ATV","ATW","ATX","ATY","ATZ","AUA","AUB","AUC","AUD","AUE","AUF","AUG","AUH",
-"AUI","AUJ","AUK","AUL","AUM","AUN","AUO","AUP","AUQ","AUR","AUS","AUT","AUU","AUV","AUW","AUX",
-"AUY","AUZ","AVA","AVB","AVC","AVD","AVE","AVF","AVG","AVH","AVI","AVJ","AVK","AVL","AVM","AVN",
-"AVO","AVP","AVQ","AVR","AVS","AVT","AVU","AVV","AVW","AVX","AVY","AVZ","AWA","AWB","AWC","AWD",
-"AWE","AWF","AWG","AWH","AWI","AWJ","AWK","AWL","AWM","AWN","AWO","AWP","AWQ","AWR","AWS","AWT",
-"AWU","AWV","AWW","AWX","AWY","AWZ","AXA","AXB","AXC","AXD","AXE","AXF","AXG","AXH","AXI","AXJ",
-"AXK","AXL","AXM","AXN","AXO","AXP","AXQ","AXR","AXS","AXT","AXU","AXV","AXW","AXX","AXY","AXZ",
-"AYA","AYB","AYC","AYD","AYE","AYF","AYG","AYH","AYI","AYJ","AYK","AYL","AYM","AYN","AYO","AYP",
-"AYQ","AYR","AYS","AYT","AYU","AYV","AYW","AYX","AYY","AYZ","AZA","AZB","AZC","AZD","AZE","AZF",
-"AZG","AZH","AZI","AZJ","AZK","AZL","AZM","AZN","AZO","AZP","AZQ","AZR","AZS","AZT","AZU","AZV",
-"AZW","AZX","AZY","AZZ","BAA","BAB","BAC","BAD","BAE","BAF","BAG","BAH","BAI","BAJ","BAK","BAL",
-"BAM","BAN","BAO","BAP","BAQ","BAR","BAS","BAT","BAU","BAV","BAW","BAX","BAY","BAZ","BBA","BBB",
-"BBC","BBD","BBE","BBF","BBG","BBH","BBI","BBJ","BBK","BBL","BBM","BBN","BBO","BBP","BBQ","BBR",
-"BBS","BBT","BBU","BBV","BBW","BBX","BBY","BBZ","BCA","BCB","BCC","BCD","BCE","BCF","BCG","BCH",
-"BCI","BCJ","BCK","BCL","BCM","BCN","BCO","BCP","BCQ","BCR","BCS","BCT","BCU","BCV","BCW","BCX",
-"BCY","BCZ","BDA","BDB","BDC","BDD","BDE","BDF","BDG","BDH","BDI","BDJ","BDK","BDL","BDM","BDN",
-"BDO","BDP","BDQ","BDR","BDS","BDT","BDU","BDV","BDW","BDX","BDY","BDZ","BEA","BEB","BEC","BED",
-"BEE","BEF","BEG","BEH","BEI","BEJ","BEK","BEL","BEM","BEN","BEO","BEP","BEQ","BER","BES","BET",
-"BEU","BEV","BEW","BEX","BEY","BEZ","BFA","BFB","BFC","BFD","BFE","BFF","BFG","BFH","BFI","BFJ",
-"BFK","BFL","BFM","BFN","BFO","BFP","BFQ","BFR","BFS","BFT","BFU","BFV","BFW","BFX","BFY","BFZ",
-"BGA","BGB","BGC","BGD","BGE","BGF","BGG","BGH","BGI","BGJ","BGK","BGL","BGM","BGN","BGO","BGP",
-"BGQ","BGR","BGS","BGT","BGU","BGV","BGW","BGX","BGY","BGZ","BHA","BHB","BHC","BHD","BHE","BHF",
-"BHG","BHH","BHI","BHJ","BHK","BHL","BHM","BHN","BHO","BHP","BHQ","BHR","BHS","BHT","BHU","BHV",
-"BHW","BHX","BHY","BHZ","BIA","BIB","BIC","BID","BIE","BIF","BIG","BIH","BII","BIJ","BIK","BIL",
-"BIM","BIN","BIO","BIP","BIQ","BIR","BIS","BIT","BIU","BIV","BIW","BIX","BIY","BIZ","BJA","BJB",
-"BJC","BJD","BJE","BJF","BJG","BJH","BJI","BJJ","BJK","BJL","BJM","BJN","BJO","BJP","BJQ","BJR",
-"BJS","BJT","BJU","BJV","BJW","BJX","BJY","BJZ","BKA","BKB","BKC","BKD","BKE","BKF","BKG","BKH",
-"BKI","BKJ","BKK","BKL","BKM","BKN","BKO","BKP","BKQ","BKR","BKS","BKT","BKU","BKV","BKW","BKX",
-"BKY","BKZ","BLA","BLB","BLC","BLD","BLE","BLF","BLG","BLH","BLI","BLJ","BLK","BLL","BLM","BLN",
-"BLO","BLP","BLQ","BLR","BLS","BLT","BLU","BLV","BLW","BLX","BLY","BLZ","BMA","BMB","BMC","BMD",
-"BME","BMF","BMG","BMH","BMI","BMJ","BMK","BML","BMM","BMN","BMO","BMP","BMQ","BMR","BMS","BMT",
-"BMU","BMV","BMW","BMX","BMY","BMZ","BNA","BNB","BNC","BND","BNE","BNF","BNG","BNH","BNI","BNJ",
-"BNK","BNL","BNM","BNN","BNO","BNP","BNQ","BNR","BNS","BNT","BNU","BNV","BNW","BNX","BNY","BNZ",
-"BOA","BOB","BOC","BOD","BOE","BOF","BOG","BOH","BOI","BOJ","BOK","BOL","BOM","BON","BOO","BOP",
-"BOQ","BOR","BOS","BOT","BOU","BOV","BOW","BOX","BOY","BOZ","BPA","BPB","BPC","BPD","BPE","BPF",
-"BPG","BPH","BPI","BPJ","BPK","BPL","BPM","BPN","BPO","BPP","BPQ","BPR","BPS","BPT","BPU","BPV",
-"BPW","BPX","BPY","BPZ","BQA","BQB","BQC","BQD","BQE","BQF","BQG","BQH","BQI","BQJ","BQK","BQL",
-"BQM","BQN","BQO","BQP","BQQ","BQR","BQS","BQT","BQU","BQV","BQW","BQX","BQY","BQZ","BRA","BRB",
-"BRC","BRD","BRE","BRF","BRG","BRH","BRI","BRJ","BRK","BRL","BRM","BRN","BRO","BRP","BRQ","BRR",
-"BRS","BRT","BRU","BRV","BRW","BRX","BRY","BRZ","BSA","BSB","BSC","BSD","BSE","BSF","BSG","BSH",
-"BSI","BSJ","BSK","BSL","BSM","BSN","BSO","BSP","BSQ","BSR","BSS","BST","BSU","BSV","BSW","BSX",
-"BSY","BSZ","BTA","BTB","BTC","BTD","BTE","BTF","BTG","BTH","BTI","BTJ","BTK","BTL","BTM","BTN",
-"BTO","BTP","BTQ","BTR","BTS","BTT","BTU","BTV","BTW","BTX","BTY","BTZ","BUA","BUB","BUC","BUD",
-"BUE","BUF","BUG","BUH","BUI","BUJ","BUK","BUL","BUM","BUN","BUO","BUP","BUQ","BUR","BUS","BUT",
-"BUU","BUV","BUW","BUX","BUY","BUZ","BVA","BVB","BVC","BVD","BVE","BVF","BVG","BVH","BVI","BVJ",
-"BVK","BVL","BVM","BVN","BVO","BVP","BVQ","BVR","BVS","BVT","BVU","BVV","BVW","BVX","BVY","BVZ",
-"BWA","BWB","BWC","BWD","BWE","BWF","BWG","BWH","BWI","BWJ","BWK","BWL","BWM","BWN","BWO","BWP",
-"BWQ","BWR","BWS","BWT","BWU","BWV","BWW","BWX","BWY","BWZ","BXA","BXB","BXC","BXD","BXE","BXF",
-"BXG","BXH","BXI","BXJ","BXK","BXL","BXM","BXN","BXO","BXP","BXQ","BXR","BXS","BXT","BXU","BXV",
-"BXW","BXX","BXY","BXZ","BYA","BYB","BYC","BYD","BYE","BYF","BYG","BYH","BYI","BYJ","BYK","BYL",
-"BYM","BYN","BYO","BYP","BYQ","BYR","BYS","BYT","BYU","BYV","BYW","BYX","BYY","BYZ","BZA","BZB",
-"BZC","BZD","BZE","BZF","BZG","BZH","BZI","BZJ","BZK","BZL","BZM","BZN","BZO","BZP","BZQ","BZR",
-"BZS","BZT","BZU","BZV","BZW","BZX","BZY","BZZ","CAA","CAB","CAC","CAD","CAE","CAF","CAG","CAH",
-"CAI","CAJ","CAK","CAL","CAM","CAN","CAO","CAP","CAQ","CAR","CAS","CAT","CAU","CAV","CAW","CAX",
-"CAY","CAZ","CBA","CBB","CBC","CBD","CBE","CBF","CBG","CBH","CBI","CBJ","CBK","CBL","CBM","CBN",
-"CBO","CBP","CBQ","CBR","CBS","CBT","CBU","CBV","CBW","CBX","CBY","CBZ","CCA","CCB","CCC","CCD",
-"CCE","CCF","CCG","CCH","CCI","CCJ","CCK","CCL","CCM","CCN","CCO","CCP","CCQ","CCR","CCS","CCT",
-"CCU","CCV","CCW","CCX","CCY","CCZ","CDA","CDB","CDC","CDD","CDE","CDF","CDG","CDH","CDI","CDJ",
-"CDK","CDL","CDM","CDN","CDO","CDP","CDQ","CDR","CDS","CDT","CDU","CDV","CDW","CDX","CDY","CDZ",
-"CEA","CEB","CEC","CED","CEE","CEF","CEG","CEH","CEI","CEJ","CEK","CEL","CEM","CEN","CEO","CEP",
-"CEQ","CER","CES","CET","CEU","CEV","CEW","CEX","CEY","CEZ","CFA","CFB","CFC","CFD","CFE","CFF",
-"CFG","CFH","CFI","CFJ","CFK","CFL","CFM","CFN","CFO","CFP","CFQ","CFR","CFS","CFT","CFU","CFV",
-"CFW","CFX","CFY","CFZ","CGA","CGB","CGC","CGD","CGE","CGF","CGG","CGH","CGI","CGJ","CGK","CGL",
-"CGM","CGN","CGO","CGP","CGQ","CGR","CGS","CGT","CGU","CGV","CGW","CGX","CGY","CGZ","CHA","CHB",
-"CHC","CHD","CHE","CHF","CHG","CHH","CHI","CHJ","CHK","CHL","CHM","CHN","CHO","CHP","CHQ","CHR",
-"CHS","CHT","CHU","CHV","CHW","CHX","CHY","CHZ","CIA","CIB","CIC","CID","CIE","CIF","CIG","CIH",
-"CII","CIJ","CIK","CIL","CIM","CIN","CIO","CIP","CIQ","CIR","CIS","CIT","CIU","CIV","CIW","CIX",
-"CIY","CIZ","CJA","CJB","CJC","CJD","CJE","CJF","CJG","CJH","CJI","CJJ","CJK","CJL","CJM","CJN",
-"CJO","CJP","CJQ","CJR","CJS","CJT","CJU","CJV","CJW","CJX","CJY","CJZ","CKA","CKB","CKC","CKD",
-"CKE","CKF","CKG","CKH","CKI","CKJ","CKK","CKL","CKM","CKN","CKO","CKP","CKQ","CKR","CKS","CKT",
-"CKU","CKV","CKW","CKX","CKY","CKZ","CLA","CLB","CLC","CLD","CLE","CLF","CLG","CLH","CLI","CLJ",
-"CLK","CLL","CLM","CLN","CLO","CLP","CLQ","CLR","CLS","CLT","CLU","CLV","CLW","CLX","CLY","CLZ",
-"CMA","CMB","CMC","CMD","CME","CMF","CMG","CMH","CMI","CMJ","CMK","CML","CMM","CMN","CMO","CMP",
-"CMQ","CMR","CMS","CMT","CMU","CMV","CMW","CMX","CMY","CMZ","CNA","CNB","CNC","CND","CNE","CNF",
-"CNG","CNH","CNI","CNJ","CNK","CNL","CNM","CNN","CNO","CNP","CNQ","CNR","CNS","CNT","CNU","CNV",
-"CNW","CNX","CNY","CNZ","COA","COB","COC","COD","COE","COF","COG","COH","COI","COJ","COK","COL",
-"COM","CON","COO","COP","COQ","COR","COS","COT","COU","COV","COW","COX","COY","COZ","CPA","CPB",
-"CPC","CPD","CPE","CPF","CPG","CPH","CPI","CPJ","CPK","CPL","CPM","CPN","CPO","CPP","CPQ","CPR",
-"CPS","CPT","CPU","CPV","CPW","CPX","CPY","CPZ","CQA","CQB","CQC","CQD","CQE","CQF","CQG","CQH",
-"CQI","CQJ","CQK","CQL","CQM","CQN","CQO","CQP","CQQ","CQR","CQS","CQT","CQU","CQV","CQW","CQX",
-"CQY","CQZ","CRA","CRB","CRC","CRD","CRE","CRF","CRG","CRH","CRI","CRJ","CRK","CRL","CRM","CRN",
-"CRO","CRP","CRQ","CRR","CRS","CRT","CRU","CRV","CRW","CRX","CRY","CRZ","CSA","CSB","CSC","CSD",
-"CSE","CSF","CSG","CSH","CSI","CSJ","CSK","CSL","CSM","CSN","CSO","CSP","CSQ","CSR","CSS","CST",
-"CSU","CSV","CSW","CSX","CSY","CSZ","CTA","CTB","CTC","CTD","CTE","CTF","CTG","CTH","CTI","CTJ",
-"CTK","CTL","CTM","CTN","CTO","CTP","CTQ","CTR","CTS","CTT","CTU","CTV","CTW","CTX","CTY","CTZ",
-"CUA","CUB","CUC","CUD","CUE","CUF","CUG","CUH","CUI","CUJ","CUK","CUL","CUM","CUN","CUO","CUP",
-"CUQ","CUR","CUS","CUT","CUU","CUV","CUW","CUX","CUY","CUZ","CVA","CVB","CVC","CVD","CVE","CVF",
-"CVG","CVH","CVI","CVJ","CVK","CVL","CVM","CVN","CVO","CVP","CVQ","CVR","CVS","CVT","CVU","CVV",
-"CVW","CVX","CVY","CVZ","CWA","CWB","CWC","CWD","CWE","CWF","CWG","CWH","CWI","CWJ","CWK","CWL",
-"CWM","CWN","CWO","CWP","CWQ","CWR","CWS","CWT","CWU","CWV","CWW","CWX","CWY","CWZ","CXA","CXB",
-"CXC","CXD","CXE","CXF","CXG","CXH","CXI","CXJ","CXK","CXL","CXM","CXN","CXO","CXP","CXQ","CXR",
-"CXS","CXT","CXU","CXV","CXW","CXX","CXY","CXZ","CYA","CYB","CYC","CYD","CYE","CYF","CYG","CYH",
-"CYI","CYJ","CYK","CYL","CYM","CYN","CYO","CYP","CYQ","CYR","CYS","CYT","CYU","CYV","CYW","CYX",
-"CYY","CYZ","CZA","CZB","CZC","CZD","CZE","CZF","CZG","CZH","CZI","CZJ","CZK","CZL","CZM","CZN",
-"CZO","CZP","CZQ","CZR","CZS","CZT","CZU","CZV","CZW","CZX","CZY","CZZ","DAA","DAB","DAC","DAD",
-"DAE","DAF","DAG","DAH","DAI","DAJ","DAK","DAL","DAM","DAN","DAO","DAP","DAQ","DAR","DAS","DAT",
-"DAU","DAV","DAW","DAX","DAY","DAZ","DBA","DBB","DBC","DBD","DBE","DBF","DBG","DBH","DBI","DBJ",
-"DBK","DBL","DBM","DBN","DBO","DBP","DBQ","DBR","DBS","DBT","DBU","DBV","DBW","DBX","DBY","DBZ",
-"DCA","DCB","DCC","DCD","DCE","DCF","DCG","DCH","DCI","DCJ","DCK","DCL","DCM","DCN","DCO","DCP",
-"DCQ","DCR","DCS","DCT","DCU","DCV","DCW","DCX","DCY","DCZ","DDA","DDB","DDC","DDD","DDE","DDF",
-"DDG","DDH","DDI","DDJ","DDK","DDL","DDM","DDN","DDO","DDP","DDQ","DDR","DDS","DDT","DDU","DDV",
-"DDW","DDX","DDY","DDZ","DEA","DEB","DEC","DED","DEE","DEF","DEG","DEH","DEI","DEJ","DEK","DEL",
-"DEM","DEN","DEO","DEP","DEQ","DER","DES","DET","DEU","DEV","DEW","DEX","DEY","DEZ","DFA","DFB",
-"DFC","DFD","DFE","DFF","DFG","DFH","DFI","DFJ","DFK","DFL","DFM","DFN","DFO","DFP","DFQ","DFR",
-"DFS","DFT","DFU","DFV","DFW","DFX","DFY","DFZ","DGA","DGB","DGC","DGD","DGE","DGF","DGG","DGH",
-"DGI","DGJ","DGK","DGL","DGM","DGN","DGO","DGP","DGQ","DGR","DGS","DGT","DGU","DGV","DGW","DGX",
-"DGY","DGZ","DHA","DHB","DHC","DHD","DHE","DHF","DHG","DHH","DHI","DHJ","DHK","DHL","DHM","DHN",
-"DHO","DHP","DHQ","DHR","DHS","DHT","DHU","DHV","DHW","DHX","DHY","DHZ","DIA","DIB","DIC","DID",
-"DIE","DIF","DIG","DIH","DII","DIJ","DIK","DIL","DIM","DIN","DIO","DIP","DIQ","DIR","DIS","DIT",
-"DIU","DIV","DIW","DIX","DIY","DIZ","DJA","DJB","DJC","DJD","DJE","DJF","DJG","DJH","DJI","DJJ",
-"DJK","DJL","DJM","DJN","DJO","DJP","DJQ","DJR","DJS","DJT","DJU","DJV","DJW","DJX","DJY","DJZ",
-"DKA","DKB","DKC","DKD","DKE","DKF","DKG","DKH","DKI","DKJ","DKK","DKL","DKM","DKN","DKO","DKP",
-"DKQ","DKR","DKS","DKT","DKU","DKV","DKW","DKX","DKY","DKZ","DLA","DLB","DLC","DLD","DLE","DLF",
-"DLG","DLH","DLI","DLJ","DLK","DLL","DLM","DLN","DLO","DLP","DLQ","DLR","DLS","DLT","DLU","DLV",
-"DLW","DLX","DLY","DLZ","DMA","DMB","DMC","DMD","DME","DMF","DMG","DMH","DMI","DMJ","DMK","DML",
-"DMM","DMN","DMO","DMP","DMQ","DMR","DMS","DMT","DMU","DMV","DMW","DMX","DMY","DMZ","DNA","DNB",
-"DNC","DND","DNE","DNF","DNG","DNH","DNI","DNJ","DNK","DNL","DNM","DNN","DNO","DNP","DNQ","DNR",
-"DNS","DNT","DNU","DNV","DNW","DNX","DNY","DNZ","DOA","DOB","DOC","DOD","DOE","DOF","DOG","DOH",
-"DOI","DOJ","DOK","DOL","DOM","DON","DOO","DOP","DOQ","DOR","DOS","DOT","DOU","DOV","DOW","DOX",
-"DOY","DOZ","DPA","DPB","DPC","DPD","DPE","DPF","DPG","DPH","DPI","DPJ","DPK","DPL","DPM","DPN",
-"DPO","DPP","DPQ","DPR","DPS","DPT","DPU","DPV","DPW","DPX","DPY","DPZ","DQA","DQB","DQC","DQD",
-"DQE","DQF","DQG","DQH","DQI","DQJ","DQK","DQL","DQM","DQN","DQO","DQP","DQQ","DQR","DQS","DQT",
-"DQU","DQV","DQW","DQX","DQY","DQZ","DRA","DRB","DRC","DRD","DRE","DRF","DRG","DRH","DRI","DRJ",
-"DRK","DRL","DRM","DRN","DRO","DRP","DRQ","DRR","DRS","DRT","DRU","DRV","DRW","DRX","DRY","DRZ",
-"DSA","DSB","DSC","DSD","DSE","DSF","DSG","DSH","DSI","DSJ","DSK","DSL","DSM","DSN","DSO","DSP",
-"DSQ","DSR","DSS","DST","DSU","DSV","DSW","DSX","DSY","DSZ","DTA","DTB","DTC","DTD","DTE","DTF",
-"DTG","DTH","DTI","DTJ","DTK","DTL","DTM","DTN","DTO","DTP","DTQ","DTR","DTS","DTT","DTU","DTV",
-"DTW","DTX","DTY","DTZ","DUA","DUB","DUC","DUD","DUE","DUF","DUG","DUH","DUI","DUJ","DUK","DUL",
-"DUM","DUN","DUO","DUP","DUQ","DUR","DUS","DUT","DUU","DUV","DUW","DUX","DUY","DUZ","DVA","DVB",
-"DVC","DVD","DVE","DVF","DVG","DVH","DVI","DVJ","DVK","DVL","DVM","DVN","DVO","DVP","DVQ","DVR",
-"DVS","DVT","DVU","DVV","DVW","DVX","DVY","DVZ","DWA","DWB","DWC","DWD","DWE","DWF","DWG","DWH",
-"DWI","DWJ","DWK","DWL","DWM","DWN","DWO","DWP","DWQ","DWR","DWS","DWT","DWU","DWV","DWW","DWX",
-"DWY","DWZ","DXA","DXB","DXC","DXD","DXE","DXF","DXG","DXH","DXI","DXJ","DXK","DXL","DXM","DXN",
-"DXO","DXP","DXQ","DXR","DXS","DXT","DXU","DXV","DXW","DXX","DXY","DXZ","DYA","DYB","DYC","DYD",
-"DYE","DYF","DYG","DYH","DYI","DYJ","DYK","DYL","DYM","DYN","DYO","DYP","DYQ","DYR","DYS","DYT",
-"DYU","DYV","DYW","DYX","DYY","DYZ","DZA","DZB","DZC","DZD","DZE","DZF","DZG","DZH","DZI","DZJ",
-"DZK","DZL","DZM","DZN","DZO","DZP","DZQ","DZR","DZS","DZT","DZU","DZV","DZW","DZX","DZY","DZZ",
-
-#/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-# E-starteds are intentionally omitted 
-#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
-"FAA","FAB","FAC","FAD","FAE","FAF","FAG","FAH","FAI","FAJ","FAK","FAL",
-"FAM","FAN","FAO","FAP","FAQ","FAR","FAS","FAT","FAU","FAV","FAW","FAX","FAY","FAZ","FBA","FBB",
-"FBC","FBD","FBE","FBF","FBG","FBH","FBI","FBJ","FBK","FBL","FBM","FBN","FBO","FBP","FBQ","FBR",
-"FBS","FBT","FBU","FBV","FBW","FBX","FBY","FBZ","FCA","FCB","FCC","FCD","FCE","FCF","FCG","FCH",
-"FCI","FCJ","FCK","FCL","FCM","FCN","FCO","FCP","FCQ","FCR","FCS","FCT","FCU","FCV","FCW","FCX",
-"FCY","FCZ","FDA","FDB","FDC","FDD","FDE","FDF","FDG","FDH","FDI","FDJ","FDK","FDL","FDM","FDN",
-"FDO","FDP","FDQ","FDR","FDS","FDT","FDU","FDV","FDW","FDX","FDY","FDZ","FEA","FEB","FEC","FED",
-"FEE","FEF","FEG","FEH","FEI","FEJ","FEK","FEL","FEM","FEN","FEO","FEP","FEQ","FER","FES","FET",
-"FEU","FEV","FEW","FEX","FEY","FEZ","FFA","FFB","FFC","FFD","FFE","FFF","FFG","FFH","FFI","FFJ",
-"FFK","FFL","FFM","FFN","FFO","FFP","FFQ","FFR","FFS","FFT","FFU","FFV","FFW","FFX","FFY","FFZ",
-"FGA","FGB","FGC","FGD","FGE","FGF","FGG","FGH","FGI","FGJ","FGK","FGL","FGM","FGN","FGO","FGP",
-"FGQ","FGR","FGS","FGT","FGU","FGV","FGW","FGX","FGY","FGZ","FHA","FHB","FHC","FHD","FHE","FHF",
-"FHG","FHH","FHI","FHJ","FHK","FHL","FHM","FHN","FHO","FHP","FHQ","FHR","FHS","FHT","FHU","FHV",
-"FHW","FHX","FHY","FHZ","FIA","FIB","FIC","FID","FIE","FIF","FIG","FIH","FII","FIJ","FIK","FIL",
-"FIM","FIN","FIO","FIP","FIQ","FIR","FIS","FIT","FIU","FIV","FIW","FIX","FIY","FIZ","FJA","FJB",
-"FJC","FJD","FJE","FJF","FJG","FJH","FJI","FJJ","FJK","FJL","FJM","FJN","FJO","FJP","FJQ","FJR",
-"FJS","FJT","FJU","FJV","FJW","FJX","FJY","FJZ","FKA","FKB","FKC","FKD","FKE","FKF","FKG","FKH",
-"FKI","FKJ","FKK","FKL","FKM","FKN","FKO","FKP","FKQ","FKR","FKS","FKT","FKU","FKV","FKW","FKX",
-"FKY","FKZ","FLA","FLB","FLC","FLD","FLE","FLF","FLG","FLH","FLI","FLJ","FLK","FLL","FLM","FLN",
-"FLO","FLP","FLQ","FLR","FLS","FLT","FLU","FLV","FLW","FLX","FLY","FLZ","FMA","FMB","FMC","FMD",
-"FME","FMF","FMG","FMH","FMI","FMJ","FMK","FML","FMM","FMN","FMO","FMP","FMQ","FMR","FMS","FMT",
-"FMU","FMV","FMW","FMX","FMY","FMZ","FNA","FNB","FNC","FND","FNE","FNF","FNG","FNH","FNI","FNJ",
-"FNK","FNL","FNM","FNN","FNO","FNP","FNQ","FNR","FNS","FNT","FNU","FNV","FNW","FNX","FNY","FNZ",
-"FOA","FOB","FOC","FOD","FOE","FOF","FOG","FOH","FOI","FOJ","FOK","FOL","FOM","FON","FOO","FOP",
-"FOQ","FOR","FOS","FOT","FOU","FOV","FOW","FOX","FOY","FOZ","FPA","FPB","FPC","FPD","FPE","FPF",
-"FPG","FPH","FPI","FPJ","FPK","FPL","FPM","FPN","FPO","FPP","FPQ","FPR","FPS","FPT","FPU","FPV",
-"FPW","FPX","FPY","FPZ","FQA","FQB","FQC","FQD","FQE","FQF","FQG","FQH","FQI","FQJ","FQK","FQL",
-"FQM","FQN","FQO","FQP","FQQ","FQR","FQS","FQT","FQU","FQV","FQW","FQX","FQY","FQZ","FRA","FRB",
-"FRC","FRD","FRE","FRF","FRG","FRH","FRI","FRJ","FRK","FRL","FRM","FRN","FRO","FRP","FRQ","FRR",
-"FRS","FRT","FRU","FRV","FRW","FRX","FRY","FRZ","FSA","FSB","FSC","FSD","FSE","FSF","FSG","FSH",
-"FSI","FSJ","FSK","FSL","FSM","FSN","FSO","FSP","FSQ","FSR","FSS","FST","FSU","FSV","FSW","FSX",
-"FSY","FSZ","FTA","FTB","FTC","FTD","FTE","FTF","FTG","FTH","FTI","FTJ","FTK","FTL","FTM","FTN",
-"FTO","FTP","FTQ","FTR","FTS","FTT","FTU","FTV","FTW","FTX","FTY","FTZ","FUA","FUB","FUC","FUD",
-"FUE","FUF","FUG","FUH","FUI","FUJ","FUK","FUL","FUM","FUN","FUO","FUP","FUQ","FUR","FUS","FUT",
-"FUU","FUV","FUW","FUX","FUY","FUZ","FVA","FVB","FVC","FVD","FVE","FVF","FVG","FVH","FVI","FVJ",
-"FVK","FVL","FVM","FVN","FVO","FVP","FVQ","FVR","FVS","FVT","FVU","FVV","FVW","FVX","FVY","FVZ",
-"FWA","FWB","FWC","FWD","FWE","FWF","FWG","FWH","FWI","FWJ","FWK","FWL","FWM","FWN","FWO","FWP",
-"FWQ","FWR","FWS","FWT","FWU","FWV","FWW","FWX","FWY","FWZ","FXA","FXB","FXC","FXD","FXE","FXF",
-"FXG","FXH","FXI","FXJ","FXK","FXL","FXM","FXN","FXO","FXP","FXQ","FXR","FXS","FXT","FXU","FXV",
-"FXW","FXX","FXY","FXZ","FYA","FYB","FYC","FYD","FYE","FYF","FYG","FYH","FYI","FYJ","FYK","FYL",
-"FYM","FYN","FYO","FYP","FYQ","FYR","FYS","FYT","FYU","FYV","FYW","FYX","FYY","FYZ","FZA","FZB",
-"FZC","FZD","FZE","FZF","FZG","FZH","FZI","FZJ","FZK","FZL","FZM","FZN","FZO","FZP","FZQ","FZR",
-"FZS","FZT","FZU","FZV","FZW","FZX","FZY","FZZ","GAA","GAB","GAC","GAD","GAE","GAF","GAG","GAH",
-"GAI","GAJ","GAK","GAL","GAM","GAN","GAO","GAP","GAQ","GAR","GAS","GAT","GAU","GAV","GAW","GAX",
-"GAY","GAZ","GBA","GBB","GBC","GBD","GBE","GBF","GBG","GBH","GBI","GBJ","GBK","GBL","GBM","GBN",
-"GBO","GBP","GBQ","GBR","GBS","GBT","GBU","GBV","GBW","GBX","GBY","GBZ","GCA","GCB","GCC","GCD",
-"GCE","GCF","GCG","GCH","GCI","GCJ","GCK","GCL","GCM","GCN","GCO","GCP","GCQ","GCR","GCS","GCT",
-"GCU","GCV","GCW","GCX","GCY","GCZ","GDA","GDB","GDC","GDD","GDE","GDF","GDG","GDH","GDI","GDJ",
-"GDK","GDL","GDM","GDN","GDO","GDP","GDQ","GDR","GDS","GDT","GDU","GDV","GDW","GDX","GDY","GDZ",
-"GEA","GEB","GEC","GED","GEE","GEF","GEG","GEH","GEI","GEJ","GEK","GEL","GEM","GEN","GEO","GEP",
-"GEQ","GER","GES","GET","GEU","GEV","GEW","GEX","GEY","GEZ","GFA","GFB","GFC","GFD","GFE","GFF",
-"GFG","GFH","GFI","GFJ","GFK","GFL","GFM","GFN","GFO","GFP","GFQ","GFR","GFS","GFT","GFU","GFV",
-"GFW","GFX","GFY","GFZ","GGA","GGB","GGC","GGD","GGE","GGF","GGG","GGH","GGI","GGJ","GGK","GGL",
-"GGM","GGN","GGO","GGP","GGQ","GGR","GGS","GGT","GGU","GGV","GGW","GGX","GGY","GGZ","GHA","GHB",
-"GHC","GHD","GHE","GHF","GHG","GHH","GHI","GHJ","GHK","GHL","GHM","GHN","GHO","GHP","GHQ","GHR",
-"GHS","GHT","GHU","GHV","GHW","GHX","GHY","GHZ","GIA","GIB","GIC","GID","GIE","GIF","GIG","GIH",
-"GII","GIJ","GIK","GIL","GIM","GIN","GIO","GIP","GIQ","GIR","GIS","GIT","GIU","GIV","GIW","GIX",
-"GIY","GIZ","GJA","GJB","GJC","GJD","GJE","GJF","GJG","GJH","GJI","GJJ","GJK","GJL","GJM","GJN",
-"GJO","GJP","GJQ","GJR","GJS","GJT","GJU","GJV","GJW","GJX","GJY","GJZ","GKA","GKB","GKC","GKD",
-"GKE","GKF","GKG","GKH","GKI","GKJ","GKK","GKL","GKM","GKN","GKO","GKP","GKQ","GKR","GKS","GKT",
-"GKU","GKV","GKW","GKX","GKY","GKZ","GLA","GLB","GLC","GLD","GLE","GLF","GLG","GLH","GLI","GLJ",
-"GLK","GLL","GLM","GLN","GLO","GLP","GLQ","GLR","GLS","GLT","GLU","GLV","GLW","GLX","GLY","GLZ",
-"GMA","GMB","GMC","GMD","GME","GMF","GMG","GMH","GMI","GMJ","GMK","GML","GMM","GMN","GMO","GMP",
-"GMQ","GMR","GMS","GMT","GMU","GMV","GMW","GMX","GMY","GMZ","GNA","GNB","GNC","GND","GNE","GNF",
-"GNG","GNH","GNI","GNJ","GNK","GNL","GNM","GNN","GNO","GNP","GNQ","GNR","GNS","GNT","GNU","GNV",
-"GNW","GNX","GNY","GNZ","GOA","GOB","GOC","GOD","GOE","GOF","GOG","GOH","GOI","GOJ","GOK","GOL",
-"GOM","GON","GOO","GOP","GOQ","GOR","GOS","GOT","GOU","GOV","GOW","GOX","GOY","GOZ","GPA","GPB",
-"GPC","GPD","GPE","GPF","GPG","GPH","GPI","GPJ","GPK","GPL","GPM","GPN","GPO","GPP","GPQ","GPR",
-"GPS","GPT","GPU","GPV","GPW","GPX","GPY","GPZ","GQA","GQB","GQC","GQD","GQE","GQF","GQG","GQH",
-"GQI","GQJ","GQK","GQL","GQM","GQN","GQO","GQP","GQQ","GQR","GQS","GQT","GQU","GQV","GQW","GQX",
-"GQY","GQZ","GRA","GRB","GRC","GRD","GRE","GRF","GRG","GRH","GRI","GRJ","GRK","GRL","GRM","GRN",
-"GRO","GRP","GRQ","GRR","GRS","GRT","GRU","GRV","GRW","GRX","GRY","GRZ","GSA","GSB","GSC","GSD",
-"GSE","GSF","GSG","GSH","GSI","GSJ","GSK","GSL","GSM","GSN","GSO","GSP","GSQ","GSR","GSS","GST",
-"GSU","GSV","GSW","GSX","GSY","GSZ","GTA","GTB","GTC","GTD","GTE","GTF","GTG","GTH","GTI","GTJ",
-"GTK","GTL","GTM","GTN","GTO","GTP","GTQ","GTR","GTS","GTT","GTU","GTV","GTW","GTX","GTY","GTZ",
-"GUA","GUB","GUC","GUD","GUE","GUF","GUG","GUH","GUI","GUJ","GUK","GUL","GUM","GUN","GUO","GUP",
-"GUQ","GUR","GUS","GUT","GUU","GUV","GUW","GUX","GUY","GUZ","GVA","GVB","GVC","GVD","GVE","GVF",
-"GVG","GVH","GVI","GVJ","GVK","GVL","GVM","GVN","GVO","GVP","GVQ","GVR","GVS","GVT","GVU","GVV",
-"GVW","GVX","GVY","GVZ","GWA","GWB","GWC","GWD","GWE","GWF","GWG","GWH","GWI","GWJ","GWK","GWL",
-"GWM","GWN","GWO","GWP","GWQ","GWR","GWS","GWT","GWU","GWV","GWW","GWX","GWY","GWZ","GXA","GXB",
-"GXC","GXD","GXE","GXF","GXG","GXH","GXI","GXJ","GXK","GXL","GXM","GXN","GXO","GXP","GXQ","GXR",
-"GXS","GXT","GXU","GXV","GXW","GXX","GXY","GXZ","GYA","GYB","GYC","GYD","GYE","GYF","GYG","GYH",
-"GYI","GYJ","GYK","GYL","GYM","GYN","GYO","GYP","GYQ","GYR","GYS","GYT","GYU","GYV","GYW","GYX",
-"GYY","GYZ","GZA","GZB","GZC","GZD","GZE","GZF","GZG","GZH","GZI","GZJ","GZK","GZL","GZM","GZN",
-"GZO","GZP","GZQ","GZR","GZS","GZT","GZU","GZV","GZW","GZX","GZY","GZZ","HAA","HAB","HAC","HAD",
-"HAE","HAF","HAG","HAH","HAI","HAJ","HAK","HAL","HAM","HAN","HAO","HAP","HAQ","HAR","HAS","HAT",
-"HAU","HAV","HAW","HAX","HAY","HAZ","HBA","HBB","HBC","HBD","HBE","HBF","HBG","HBH","HBI","HBJ",
-"HBK","HBL","HBM","HBN","HBO","HBP","HBQ","HBR","HBS","HBT","HBU","HBV","HBW","HBX","HBY","HBZ",
-"HCA","HCB","HCC","HCD","HCE","HCF","HCG","HCH","HCI","HCJ","HCK","HCL","HCM","HCN","HCO","HCP",
-"HCQ","HCR","HCS","HCT","HCU","HCV","HCW","HCX","HCY","HCZ","HDA","HDB","HDC","HDD","HDE","HDF",
-"HDG","HDH","HDI","HDJ","HDK","HDL","HDM","HDN","HDO","HDP","HDQ","HDR","HDS","HDT","HDU","HDV",
-"HDW","HDX","HDY","HDZ","HEA","HEB","HEC","HED","HEE","HEF","HEG","HEH","HEI","HEJ","HEK","HEL",
-"HEM","HEN","HEO","HEP","HEQ","HER","HES","HET","HEU","HEV","HEW","HEX","HEY","HEZ","HFA","HFB",
-"HFC","HFD","HFE","HFF","HFG","HFH","HFI","HFJ","HFK","HFL","HFM","HFN","HFO","HFP","HFQ","HFR",
-"HFS","HFT","HFU","HFV","HFW","HFX","HFY","HFZ","HGA","HGB","HGC","HGD","HGE","HGF","HGG","HGH",
-"HGI","HGJ","HGK","HGL","HGM","HGN","HGO","HGP","HGQ","HGR","HGS","HGT","HGU","HGV","HGW","HGX",
-"HGY","HGZ","HHA","HHB","HHC","HHD","HHE","HHF","HHG","HHH","HHI","HHJ","HHK","HHL","HHM","HHN",
-"HHO","HHP","HHQ","HHR","HHS","HHT","HHU","HHV","HHW","HHX","HHY","HHZ","HIA","HIB","HIC","HID",
-"HIE","HIF","HIG","HIH","HII","HIJ","HIK","HIL","HIM","HIN","HIO","HIP","HIQ","HIR","HIS","HIT",
-"HIU","HIV","HIW","HIX","HIY","HIZ","HJA","HJB","HJC","HJD","HJE","HJF","HJG","HJH","HJI","HJJ",
-"HJK","HJL","HJM","HJN","HJO","HJP","HJQ","HJR","HJS","HJT","HJU","HJV","HJW","HJX","HJY","HJZ",
-"HKA","HKB","HKC","HKD","HKE","HKF","HKG","HKH","HKI","HKJ","HKK","HKL","HKM","HKN","HKO","HKP",
-"HKQ","HKR","HKS","HKT","HKU","HKV","HKW","HKX","HKY","HKZ","HLA","HLB","HLC","HLD","HLE","HLF",
-"HLG","HLH","HLI","HLJ","HLK","HLL","HLM","HLN","HLO","HLP","HLQ","HLR","HLS","HLT","HLU","HLV",
-"HLW","HLX","HLY","HLZ","HMA","HMB","HMC","HMD","HME","HMF","HMG","HMH","HMI","HMJ","HMK","HML",
-"HMM","HMN","HMO","HMP","HMQ","HMR","HMS","HMT","HMU","HMV","HMW","HMX","HMY","HMZ","HNA","HNB",
-"HNC","HND","HNE","HNF","HNG","HNH","HNI","HNJ","HNK","HNL","HNM","HNN","HNO","HNP","HNQ","HNR",
-"HNS","HNT","HNU","HNV","HNW","HNX","HNY","HNZ","HOA","HOB","HOC","HOD","HOE","HOF","HOG","HOH",
-"HOI","HOJ","HOK","HOL","HOM","HON","HOO","HOP","HOQ","HOR","HOS","HOT","HOU","HOV","HOW","HOX",
-"HOY","HOZ","HPA","HPB","HPC","HPD","HPE","HPF","HPG","HPH","HPI","HPJ","HPK","HPL","HPM","HPN",
-"HPO","HPP","HPQ","HPR","HPS","HPT","HPU","HPV","HPW","HPX","HPY","HPZ","HQA","HQB","HQC","HQD",
-"HQE","HQF","HQG","HQH","HQI","HQJ","HQK","HQL","HQM","HQN","HQO","HQP","HQQ","HQR","HQS","HQT",
-"HQU","HQV","HQW","HQX","HQY","HQZ","HRA","HRB","HRC","HRD","HRE","HRF","HRG","HRH","HRI","HRJ",
-"HRK","HRL","HRM","HRN","HRO","HRP","HRQ","HRR","HRS","HRT","HRU","HRV","HRW","HRX","HRY","HRZ",
-"HSA","HSB","HSC","HSD","HSE","HSF","HSG","HSH","HSI","HSJ","HSK","HSL","HSM","HSN","HSO","HSP",
-"HSQ","HSR","HSS","HST","HSU","HSV","HSW","HSX","HSY","HSZ","HTA","HTB","HTC","HTD","HTE","HTF",
-"HTG","HTH","HTI","HTJ","HTK","HTL","HTM","HTN","HTO","HTP","HTQ","HTR","HTS","HTT","HTU","HTV",
-"HTW","HTX","HTY","HTZ","HUA","HUB","HUC","HUD","HUE","HUF","HUG","HUH","HUI","HUJ","HUK","HUL",
-"HUM","HUN","HUO","HUP","HUQ","HUR","HUS","HUT","HUU","HUV","HUW","HUX","HUY","HUZ","HVA","HVB",
-"HVC","HVD","HVE","HVF","HVG","HVH","HVI","HVJ","HVK","HVL","HVM","HVN","HVO","HVP","HVQ","HVR",
-"HVS","HVT","HVU","HVV","HVW","HVX","HVY","HVZ","HWA","HWB","HWC","HWD","HWE","HWF","HWG","HWH",
-"HWI","HWJ","HWK","HWL","HWM","HWN","HWO","HWP","HWQ","HWR","HWS","HWT","HWU","HWV","HWW","HWX",
-"HWY","HWZ","HXA","HXB","HXC","HXD","HXE","HXF","HXG","HXH","HXI","HXJ","HXK","HXL","HXM","HXN",
-"HXO","HXP","HXQ","HXR","HXS","HXT","HXU","HXV","HXW","HXX","HXY","HXZ","HYA","HYB","HYC","HYD",
-"HYE","HYF","HYG","HYH","HYI","HYJ","HYK","HYL","HYM","HYN","HYO","HYP","HYQ","HYR","HYS","HYT",
-"HYU","HYV","HYW","HYX","HYY","HYZ","HZA","HZB","HZC","HZD","HZE","HZF","HZG","HZH","HZI","HZJ",
-"HZK","HZL","HZM","HZN","HZO","HZP","HZQ","HZR","HZS","HZT","HZU","HZV","HZW","HZX","HZY","HZZ",
-"IAA","IAB","IAC","IAD","IAE","IAF","IAG","IAH","IAI","IAJ","IAK","IAL","IAM","IAN","IAO","IAP",
-"IAQ","IAR","IAS","IAT","IAU","IAV","IAW","IAX","IAY","IAZ","IBA","IBB","IBC","IBD","IBE","IBF",
-"IBG","IBH","IBI","IBJ","IBK","IBL","IBM","IBN","IBO","IBP","IBQ","IBR","IBS","IBT","IBU","IBV",
-"IBW","IBX","IBY","IBZ","ICA","ICB","ICC","ICD","ICE","ICF","ICG","ICH","ICI","ICJ","ICK","ICL",
-"ICM","ICN","ICO","ICP","ICQ","ICR","ICS","ICT","ICU","ICV","ICW","ICX","ICY","ICZ","IDA","IDB",
-"IDC","IDD","IDE","IDF","IDG","IDH","IDI","IDJ","IDK","IDL","IDM","IDN","IDO","IDP","IDQ","IDR",
-"IDS","IDT","IDU","IDV","IDW","IDX","IDY","IDZ","IEA","IEB","IEC","IED","IEE","IEF","IEG","IEH",
-"IEI","IEJ","IEK","IEL","IEM","IEN","IEO","IEP","IEQ","IER","IES","IET","IEU","IEV","IEW","IEX",
-"IEY","IEZ","IFA","IFB","IFC","IFD","IFE","IFF","IFG","IFH","IFI","IFJ","IFK","IFL","IFM","IFN",
-"IFO","IFP","IFQ","IFR","IFS","IFT","IFU","IFV","IFW","IFX","IFY","IFZ","IGA","IGB","IGC","IGD",
-"IGE","IGF","IGG","IGH","IGI","IGJ","IGK","IGL","IGM","IGN","IGO","IGP","IGQ","IGR","IGS","IGT",
-"IGU","IGV","IGW","IGX","IGY","IGZ","IHA","IHB","IHC","IHD","IHE","IHF","IHG","IHH","IHI","IHJ",
-"IHK","IHL","IHM","IHN","IHO","IHP","IHQ","IHR","IHS","IHT","IHU","IHV","IHW","IHX","IHY","IHZ",
-"IIA","IIB","IIC","IID","IIE","IIF","IIG","IIH","III","IIJ","IIK","IIL","IIM","IIN","IIO","IIP",
-"IIQ","IIR","IIS","IIT","IIU","IIV","IIW","IIX","IIY","IIZ","IJA","IJB","IJC","IJD","IJE","IJF",
-"IJG","IJH","IJI","IJJ","IJK","IJL","IJM","IJN","IJO","IJP","IJQ","IJR","IJS","IJT","IJU","IJV",
-"IJW","IJX","IJY","IJZ","IKA","IKB","IKC","IKD","IKE","IKF","IKG","IKH","IKI","IKJ","IKK","IKL",
-"IKM","IKN","IKO","IKP","IKQ","IKR","IKS","IKT","IKU","IKV","IKW","IKX","IKY","IKZ","ILA","ILB",
-"ILC","ILD","ILE","ILF","ILG","ILH","ILI","ILJ","ILK","ILL","ILM","ILN","ILO","ILP","ILQ","ILR",
-"ILS","ILT","ILU","ILV","ILW","ILX","ILY","ILZ","IMA","IMB","IMC","IMD","IME","IMF","IMG","IMH",
-"IMI","IMJ","IMK","IML","IMM","IMN","IMO","IMP","IMQ","IMR","IMS","IMT","IMU","IMV","IMW","IMX",
-"IMY","IMZ","INA","INB","INC","IND","INE","INF","ING","INH","INI","INJ","INK","INL","INM","INN",
-"INO","INP","INQ","INR","INS","INT","INU","INV","INW","INX","INY","INZ","IOA","IOB","IOC","IOD",
-"IOE","IOF","IOG","IOH","IOI","IOJ","IOK","IOL","IOM","ION","IOO","IOP","IOQ","IOR","IOS","IOT",
-"IOU","IOV","IOW","IOX","IOY","IOZ","IPA","IPB","IPC","IPD","IPE","IPF","IPG","IPH","IPI","IPJ",
-"IPK","IPL","IPM","IPN","IPO","IPP","IPQ","IPR","IPS","IPT","IPU","IPV","IPW","IPX","IPY","IPZ",
-"IQA","IQB","IQC","IQD","IQE","IQF","IQG","IQH","IQI","IQJ","IQK","IQL","IQM","IQN","IQO","IQP",
-"IQQ","IQR","IQS","IQT","IQU","IQV","IQW","IQX","IQY","IQZ","IRA","IRB","IRC","IRD","IRE","IRF",
-"IRG","IRH","IRI","IRJ","IRK","IRL","IRM","IRN","IRO","IRP","IRQ","IRR","IRS","IRT","IRU","IRV",
-"IRW","IRX","IRY","IRZ","ISA","ISB","ISC","ISD","ISE","ISF","ISG","ISH","ISI","ISJ","ISK","ISL",
-"ISM","ISN","ISO","ISP","ISQ","ISR","ISS","IST","ISU","ISV","ISW","ISX","ISY","ISZ","ITA","ITB",
-"ITC","ITD","ITE","ITF","ITG","ITH","ITI","ITJ","ITK","ITL","ITM","ITN","ITO","ITP","ITQ","ITR",
-"ITS","ITT","ITU","ITV","ITW","ITX","ITY","ITZ","IUA","IUB","IUC","IUD","IUE","IUF","IUG","IUH",
-"IUI","IUJ","IUK","IUL","IUM","IUN","IUO","IUP","IUQ","IUR","IUS","IUT","IUU","IUV","IUW","IUX",
-"IUY","IUZ","IVA","IVB","IVC","IVD","IVE","IVF","IVG","IVH","IVI","IVJ","IVK","IVL","IVM","IVN",
-"IVO","IVP","IVQ","IVR","IVS","IVT","IVU","IVV","IVW","IVX","IVY","IVZ","IWA","IWB","IWC","IWD",
-"IWE","IWF","IWG","IWH","IWI","IWJ","IWK","IWL","IWM","IWN","IWO","IWP","IWQ","IWR","IWS","IWT",
-"IWU","IWV","IWW","IWX","IWY","IWZ","IXA","IXB","IXC","IXD","IXE","IXF","IXG","IXH","IXI","IXJ",
-"IXK","IXL","IXM","IXN","IXO","IXP","IXQ","IXR","IXS","IXT","IXU","IXV","IXW","IXX","IXY","IXZ",
-"IYA","IYB","IYC","IYD","IYE","IYF","IYG","IYH","IYI","IYJ","IYK","IYL","IYM","IYN","IYO","IYP",
-"IYQ","IYR","IYS","IYT","IYU","IYV","IYW","IYX","IYY","IYZ","IZA","IZB","IZC","IZD","IZE","IZF",
-"IZG","IZH","IZI","IZJ","IZK","IZL","IZM","IZN","IZO","IZP","IZQ","IZR","IZS","IZT","IZU","IZV",
-"IZW","IZX","IZY","IZZ","JAA","JAB","JAC","JAD","JAE","JAF","JAG","JAH","JAI","JAJ","JAK","JAL",
-"JAM","JAN","JAO","JAP","JAQ","JAR","JAS","JAT","JAU","JAV","JAW","JAX","JAY","JAZ","JBA","JBB",
-"JBC","JBD","JBE","JBF","JBG","JBH","JBI","JBJ","JBK","JBL","JBM","JBN","JBO","JBP","JBQ","JBR",
-"JBS","JBT","JBU","JBV","JBW","JBX","JBY","JBZ","JCA","JCB","JCC","JCD","JCE","JCF","JCG","JCH",
-"JCI","JCJ","JCK","JCL","JCM","JCN","JCO","JCP","JCQ","JCR","JCS","JCT","JCU","JCV","JCW","JCX",
-"JCY","JCZ","JDA","JDB","JDC","JDD","JDE","JDF","JDG","JDH","JDI","JDJ","JDK","JDL","JDM","JDN",
-"JDO","JDP","JDQ","JDR","JDS","JDT","JDU","JDV","JDW","JDX","JDY","JDZ","JEA","JEB","JEC","JED",
-"JEE","JEF","JEG","JEH","JEI","JEJ","JEK","JEL","JEM","JEN","JEO","JEP","JEQ","JER","JES","JET",
-"JEU","JEV","JEW","JEX","JEY","JEZ","JFA","JFB","JFC","JFD","JFE","JFF","JFG","JFH","JFI","JFJ",
-"JFK","JFL","JFM","JFN","JFO","JFP","JFQ","JFR","JFS","JFT","JFU","JFV","JFW","JFX","JFY","JFZ",
-"JGA","JGB","JGC","JGD","JGE","JGF","JGG","JGH","JGI","JGJ","JGK","JGL","JGM","JGN","JGO","JGP",
-"JGQ","JGR","JGS","JGT","JGU","JGV","JGW","JGX","JGY","JGZ","JHA","JHB","JHC","JHD","JHE","JHF",
-"JHG","JHH","JHI","JHJ","JHK","JHL","JHM","JHN","JHO","JHP","JHQ","JHR","JHS","JHT","JHU","JHV",
-"JHW","JHX","JHY","JHZ","JIA","JIB","JIC","JID","JIE","JIF","JIG","JIH","JII","JIJ","JIK","JIL",
-"JIM","JIN","JIO","JIP","JIQ","JIR","JIS","JIT","JIU","JIV","JIW","JIX","JIY","JIZ","JJA","JJB",
-"JJC","JJD","JJE","JJF","JJG","JJH","JJI","JJJ","JJK","JJL","JJM","JJN","JJO","JJP","JJQ","JJR",
-"JJS","JJT","JJU","JJV","JJW","JJX","JJY","JJZ","JKA","JKB","JKC","JKD","JKE","JKF","JKG","JKH",
-"JKI","JKJ","JKK","JKL","JKM","JKN","JKO","JKP","JKQ","JKR","JKS","JKT","JKU","JKV","JKW","JKX",
-"JKY","JKZ","JLA","JLB","JLC","JLD","JLE","JLF","JLG","JLH","JLI","JLJ","JLK","JLL","JLM","JLN",
-"JLO","JLP","JLQ","JLR","JLS","JLT","JLU","JLV","JLW","JLX","JLY","JLZ","JMA","JMB","JMC","JMD",
-"JME","JMF","JMG","JMH","JMI","JMJ","JMK","JML","JMM","JMN","JMO","JMP","JMQ","JMR","JMS","JMT",
-"JMU","JMV","JMW","JMX","JMY","JMZ","JNA","JNB","JNC","JND","JNE","JNF","JNG","JNH","JNI","JNJ",
-"JNK","JNL","JNM","JNN","JNO","JNP","JNQ","JNR","JNS","JNT","JNU","JNV","JNW","JNX","JNY","JNZ",
-"JOA","JOB","JOC","JOD","JOE","JOF","JOG","JOH","JOI","JOJ","JOK","JOL","JOM","JON","JOO","JOP",
-"JOQ","JOR","JOS","JOT","JOU","JOV","JOW","JOX","JOY","JOZ","JPA","JPB","JPC","JPD","JPE","JPF",
-"JPG","JPH","JPI","JPJ","JPK","JPL","JPM","JPN","JPO","JPP","JPQ","JPR","JPS","JPT","JPU","JPV",
-"JPW","JPX","JPY","JPZ","JQA","JQB","JQC","JQD","JQE","JQF","JQG","JQH","JQI","JQJ","JQK","JQL",
-"JQM","JQN","JQO","JQP","JQQ","JQR","JQS","JQT","JQU","JQV","JQW","JQX","JQY","JQZ","JRA","JRB",
-"JRC","JRD","JRE","JRF","JRG","JRH","JRI","JRJ","JRK","JRL","JRM","JRN","JRO","JRP","JRQ","JRR",
-"JRS","JRT","JRU","JRV","JRW","JRX","JRY","JRZ","JSA","JSB","JSC","JSD","JSE","JSF","JSG","JSH",
-"JSI","JSJ","JSK","JSL","JSM","JSN","JSO","JSP","JSQ","JSR","JSS","JST","JSU","JSV","JSW","JSX",
-"JSY","JSZ","JTA","JTB","JTC","JTD","JTE","JTF","JTG","JTH","JTI","JTJ","JTK","JTL","JTM","JTN",
-"JTO","JTP","JTQ","JTR","JTS","JTT","JTU","JTV","JTW","JTX","JTY","JTZ","JUA","JUB","JUC","JUD",
-"JUE","JUF","JUG","JUH","JUI","JUJ","JUK","JUL","JUM","JUN","JUO","JUP","JUQ","JUR","JUS","JUT",
-"JUU","JUV","JUW","JUX","JUY","JUZ","JVA","JVB","JVC","JVD","JVE","JVF","JVG","JVH","JVI","JVJ",
-"JVK","JVL","JVM","JVN","JVO","JVP","JVQ","JVR","JVS","JVT","JVU","JVV","JVW","JVX","JVY","JVZ",
-"JWA","JWB","JWC","JWD","JWE","JWF","JWG","JWH","JWI","JWJ","JWK","JWL","JWM","JWN","JWO","JWP",
-"JWQ","JWR","JWS","JWT","JWU","JWV","JWW","JWX","JWY","JWZ","JXA","JXB","JXC","JXD","JXE","JXF",
-"JXG","JXH","JXI","JXJ","JXK","JXL","JXM","JXN","JXO","JXP","JXQ","JXR","JXS","JXT","JXU","JXV",
-"JXW","JXX","JXY","JXZ","JYA","JYB","JYC","JYD","JYE","JYF","JYG","JYH","JYI","JYJ","JYK","JYL",
-"JYM","JYN","JYO","JYP","JYQ","JYR","JYS","JYT","JYU","JYV","JYW","JYX","JYY","JYZ","JZA","JZB",
-"JZC","JZD","JZE","JZF","JZG","JZH","JZI","JZJ","JZK","JZL","JZM","JZN","JZO","JZP","JZQ","JZR",
-"JZS","JZT","JZU","JZV","JZW","JZX","JZY","JZZ","KAA","KAB","KAC","KAD","KAE","KAF","KAG","KAH",
-"KAI","KAJ","KAK","KAL","KAM","KAN","KAO","KAP","KAQ","KAR","KAS","KAT","KAU","KAV","KAW","KAX",
-"KAY","KAZ","KBA","KBB","KBC","KBD","KBE","KBF","KBG","KBH","KBI","KBJ","KBK","KBL","KBM","KBN",
-"KBO","KBP","KBQ","KBR","KBS","KBT","KBU","KBV","KBW","KBX","KBY","KBZ","KCA","KCB","KCC","KCD",
-"KCE","KCF","KCG","KCH","KCI","KCJ","KCK","KCL","KCM","KCN","KCO","KCP","KCQ","KCR","KCS","KCT",
-"KCU","KCV","KCW","KCX","KCY","KCZ","KDA","KDB","KDC","KDD","KDE","KDF","KDG","KDH","KDI","KDJ",
-"KDK","KDL","KDM","KDN","KDO","KDP","KDQ","KDR","KDS","KDT","KDU","KDV","KDW","KDX","KDY","KDZ",
-"KEA","KEB","KEC","KED","KEE","KEF","KEG","KEH","KEI","KEJ","KEK","KEL","KEM","KEN","KEO","KEP",
-"KEQ","KER","KES","KET","KEU","KEV","KEW","KEX","KEY","KEZ","KFA","KFB","KFC","KFD","KFE","KFF",
-"KFG","KFH","KFI","KFJ","KFK","KFL","KFM","KFN","KFO","KFP","KFQ","KFR","KFS","KFT","KFU","KFV",
-"KFW","KFX","KFY","KFZ","KGA","KGB","KGC","KGD","KGE","KGF","KGG","KGH","KGI","KGJ","KGK","KGL",
-"KGM","KGN","KGO","KGP","KGQ","KGR","KGS","KGT","KGU","KGV","KGW","KGX","KGY","KGZ","KHA","KHB",
-"KHC","KHD","KHE","KHF","KHG","KHH","KHI","KHJ","KHK","KHL","KHM","KHN","KHO","KHP","KHQ","KHR",
-"KHS","KHT","KHU","KHV","KHW","KHX","KHY","KHZ","KIA","KIB","KIC","KID","KIE","KIF","KIG","KIH",
-"KII","KIJ","KIK","KIL","KIM","KIN","KIO","KIP","KIQ","KIR","KIS","KIT","KIU","KIV","KIW","KIX",
-"KIY","KIZ","KJA","KJB","KJC","KJD","KJE","KJF","KJG","KJH","KJI","KJJ","KJK","KJL","KJM","KJN",
-"KJO","KJP","KJQ","KJR","KJS","KJT","KJU","KJV","KJW","KJX","KJY","KJZ","KKA","KKB","KKC","KKD",
-"KKE","KKF","KKG","KKH","KKI","KKJ","KKK","KKL","KKM","KKN","KKO","KKP","KKQ","KKR","KKS","KKT",
-"KKU","KKV","KKW","KKX","KKY","KKZ","KLA","KLB","KLC","KLD","KLE","KLF","KLG","KLH","KLI","KLJ",
-"KLK","KLL","KLM","KLN","KLO","KLP","KLQ","KLR","KLS","KLT","KLU","KLV","KLW","KLX","KLY","KLZ",
-"KMA","KMB","KMC","KMD","KME","KMF","KMG","KMH","KMI","KMJ","KMK","KML","KMM","KMN","KMO","KMP",
-"KMQ","KMR","KMS","KMT","KMU","KMV","KMW","KMX","KMY","KMZ","KNA","KNB","KNC","KND","KNE","KNF",
-"KNG","KNH","KNI","KNJ","KNK","KNL","KNM","KNN","KNO","KNP","KNQ","KNR","KNS","KNT","KNU","KNV",
-"KNW","KNX","KNY","KNZ","KOA","KOB","KOC","KOD","KOE","KOF","KOG","KOH","KOI","KOJ","KOK","KOL",
-"KOM","KON","KOO","KOP","KOQ","KOR","KOS","KOT","KOU","KOV","KOW","KOX","KOY","KOZ","KPA","KPB",
-"KPC","KPD","KPE","KPF","KPG","KPH","KPI","KPJ","KPK","KPL","KPM","KPN","KPO","KPP","KPQ","KPR",
-"KPS","KPT","KPU","KPV","KPW","KPX","KPY","KPZ","KQA","KQB","KQC","KQD","KQE","KQF","KQG","KQH",
-"KQI","KQJ","KQK","KQL","KQM","KQN","KQO","KQP","KQQ","KQR","KQS","KQT","KQU","KQV","KQW","KQX",
-"KQY","KQZ","KRA","KRB","KRC","KRD","KRE","KRF","KRG","KRH","KRI","KRJ","KRK","KRL","KRM","KRN",
-"KRO","KRP","KRQ","KRR","KRS","KRT","KRU","KRV","KRW","KRX","KRY","KRZ","KSA","KSB","KSC","KSD",
-"KSE","KSF","KSG","KSH","KSI","KSJ","KSK","KSL","KSM","KSN","KSO","KSP","KSQ","KSR","KSS","KST",
-"KSU","KSV","KSW","KSX","KSY","KSZ","KTA","KTB","KTC","KTD","KTE","KTF","KTG","KTH","KTI","KTJ",
-"KTK","KTL","KTM","KTN","KTO","KTP","KTQ","KTR","KTS","KTT","KTU","KTV","KTW","KTX","KTY","KTZ",
-"KUA","KUB","KUC","KUD","KUE","KUF","KUG","KUH","KUI","KUJ","KUK","KUL","KUM","KUN","KUO","KUP",
-"KUQ","KUR","KUS","KUT","KUU","KUV","KUW","KUX","KUY","KUZ","KVA","KVB","KVC","KVD","KVE","KVF",
-"KVG","KVH","KVI","KVJ","KVK","KVL","KVM","KVN","KVO","KVP","KVQ","KVR","KVS","KVT","KVU","KVV",
-"KVW","KVX","KVY","KVZ","KWA","KWB","KWC","KWD","KWE","KWF","KWG","KWH","KWI","KWJ","KWK","KWL",
-"KWM","KWN","KWO","KWP","KWQ","KWR","KWS","KWT","KWU","KWV","KWW","KWX","KWY","KWZ","KXA","KXB",
-"KXC","KXD","KXE","KXF","KXG","KXH","KXI","KXJ","KXK","KXL","KXM","KXN","KXO","KXP","KXQ","KXR",
-"KXS","KXT","KXU","KXV","KXW","KXX","KXY","KXZ","KYA","KYB","KYC","KYD","KYE","KYF","KYG","KYH",
-"KYI","KYJ","KYK","KYL","KYM","KYN","KYO","KYP","KYQ","KYR","KYS","KYT","KYU","KYV","KYW","KYX",
-"KYY","KYZ","KZA","KZB","KZC","KZD","KZE","KZF","KZG","KZH","KZI","KZJ","KZK","KZL","KZM","KZN",
-"KZO","KZP","KZQ","KZR","KZS","KZT","KZU","KZV","KZW","KZX","KZY","KZZ","LAA","LAB","LAC","LAD",
-"LAE","LAF","LAG","LAH","LAI","LAJ","LAK","LAL","LAM","LAN","LAO","LAP","LAQ","LAR","LAS","LAT",
-"LAU","LAV","LAW","LAX","LAY","LAZ","LBA","LBB","LBC","LBD","LBE","LBF","LBG","LBH","LBI","LBJ",
-"LBK","LBL","LBM","LBN","LBO","LBP","LBQ","LBR","LBS","LBT","LBU","LBV","LBW","LBX","LBY","LBZ",
-"LCA","LCB","LCC","LCD","LCE","LCF","LCG","LCH","LCI","LCJ","LCK","LCL","LCM","LCN","LCO","LCP",
-"LCQ","LCR","LCS","LCT","LCU","LCV","LCW","LCX","LCY","LCZ","LDA","LDB","LDC","LDD","LDE","LDF",
-"LDG","LDH","LDI","LDJ","LDK","LDL","LDM","LDN","LDO","LDP","LDQ","LDR","LDS","LDT","LDU","LDV",
-"LDW","LDX","LDY","LDZ","LEA","LEB","LEC","LED","LEE","LEF","LEG","LEH","LEI","LEJ","LEK","LEL",
-"LEM","LEN","LEO","LEP","LEQ","LER","LES","LET","LEU","LEV","LEW","LEX","LEY","LEZ","LFA","LFB",
-"LFC","LFD","LFE","LFF","LFG","LFH","LFI","LFJ","LFK","LFL","LFM","LFN","LFO","LFP","LFQ","LFR",
-"LFS","LFT","LFU","LFV","LFW","LFX","LFY","LFZ","LGA","LGB","LGC","LGD","LGE","LGF","LGG","LGH",
-"LGI","LGJ","LGK","LGL","LGM","LGN","LGO","LGP","LGQ","LGR","LGS","LGT","LGU","LGV","LGW","LGX",
-"LGY","LGZ","LHA","LHB","LHC","LHD","LHE","LHF","LHG","LHH","LHI","LHJ","LHK","LHL","LHM","LHN",
-"LHO","LHP","LHQ","LHR","LHS","LHT","LHU","LHV","LHW","LHX","LHY","LHZ","LIA","LIB","LIC","LID",
-"LIE","LIF","LIG","LIH","LII","LIJ","LIK","LIL","LIM","LIN","LIO","LIP","LIQ","LIR","LIS","LIT",
-"LIU","LIV","LIW","LIX","LIY","LIZ","LJA","LJB","LJC","LJD","LJE","LJF","LJG","LJH","LJI","LJJ",
-"LJK","LJL","LJM","LJN","LJO","LJP","LJQ","LJR","LJS","LJT","LJU","LJV","LJW","LJX","LJY","LJZ",
-"LKA","LKB","LKC","LKD","LKE","LKF","LKG","LKH","LKI","LKJ","LKK","LKL","LKM","LKN","LKO","LKP",
-"LKQ","LKR","LKS","LKT","LKU","LKV","LKW","LKX","LKY","LKZ","LLA","LLB","LLC","LLD","LLE","LLF",
-"LLG","LLH","LLI","LLJ","LLK","LLL","LLM","LLN","LLO","LLP","LLQ","LLR","LLS","LLT","LLU","LLV",
-"LLW","LLX","LLY","LLZ","LMA","LMB","LMC","LMD","LME","LMF","LMG","LMH","LMI","LMJ","LMK","LML",
-"LMM","LMN","LMO","LMP","LMQ","LMR","LMS","LMT","LMU","LMV","LMW","LMX","LMY","LMZ","LNA","LNB",
-"LNC","LND","LNE","LNF","LNG","LNH","LNI","LNJ","LNK","LNL","LNM","LNN","LNO","LNP","LNQ","LNR",
-"LNS","LNT","LNU","LNV","LNW","LNX","LNY","LNZ","LOA","LOB","LOC","LOD","LOE","LOF","LOG","LOH",
-"LOI","LOJ","LOK","LOL","LOM","LON","LOO","LOP","LOQ","LOR","LOS","LOT","LOU","LOV","LOW","LOX",
-"LOY","LOZ","LPA","LPB","LPC","LPD","LPE","LPF","LPG","LPH","LPI","LPJ","LPK","LPL","LPM","LPN",
-"LPO","LPP","LPQ","LPR","LPS","LPT","LPU","LPV","LPW","LPX","LPY","LPZ","LQA","LQB","LQC","LQD",
-"LQE","LQF","LQG","LQH","LQI","LQJ","LQK","LQL","LQM","LQN","LQO","LQP","LQQ","LQR","LQS","LQT",
-"LQU","LQV","LQW","LQX","LQY","LQZ","LRA","LRB","LRC","LRD","LRE","LRF","LRG","LRH","LRI","LRJ",
-"LRK","LRL","LRM","LRN","LRO","LRP","LRQ","LRR","LRS","LRT","LRU","LRV","LRW","LRX","LRY","LRZ",
-"LSA","LSB","LSC","LSD","LSE","LSF","LSG","LSH","LSI","LSJ","LSK","LSL","LSM","LSN","LSO","LSP",
-"LSQ","LSR","LSS","LST","LSU","LSV","LSW","LSX","LSY","LSZ","LTA","LTB","LTC","LTD","LTE","LTF",
-"LTG","LTH","LTI","LTJ","LTK","LTL","LTM","LTN","LTO","LTP","LTQ","LTR","LTS","LTT","LTU","LTV",
-"LTW","LTX","LTY","LTZ","LUA","LUB","LUC","LUD","LUE","LUF","LUG","LUH","LUI","LUJ","LUK","LUL",
-"LUM","LUN","LUO","LUP","LUQ","LUR","LUS","LUT","LUU","LUV","LUW","LUX","LUY","LUZ","LVA","LVB",
-"LVC","LVD","LVE","LVF","LVG","LVH","LVI","LVJ","LVK","LVL","LVM","LVN","LVO","LVP","LVQ","LVR",
-"LVS","LVT","LVU","LVV","LVW","LVX","LVY","LVZ","LWA","LWB","LWC","LWD","LWE","LWF","LWG","LWH",
-"LWI","LWJ","LWK","LWL","LWM","LWN","LWO","LWP","LWQ","LWR","LWS","LWT","LWU","LWV","LWW","LWX",
-"LWY","LWZ","LXA","LXB","LXC","LXD","LXE","LXF","LXG","LXH","LXI","LXJ","LXK","LXL","LXM","LXN",
-"LXO","LXP","LXQ","LXR","LXS","LXT","LXU","LXV","LXW","LXX","LXY","LXZ","LYA","LYB","LYC","LYD",
-"LYE","LYF","LYG","LYH","LYI","LYJ","LYK","LYL","LYM","LYN","LYO","LYP","LYQ","LYR","LYS","LYT",
-"LYU","LYV","LYW","LYX","LYY","LYZ","LZA","LZB","LZC","LZD","LZE","LZF","LZG","LZH","LZI","LZJ",
-"LZK","LZL","LZM","LZN","LZO","LZP","LZQ","LZR","LZS","LZT","LZU","LZV","LZW","LZX","LZY","LZZ",
-"MAA","MAB","MAC","MAD","MAE","MAF","MAG","MAH","MAI","MAJ","MAK","MAL","MAM","MAN","MAO","MAP",
-"MAQ","MAR","MAS","MAT","MAU","MAV","MAW","MAX","MAY","MAZ","MBA","MBB","MBC","MBD","MBE","MBF",
-"MBG","MBH","MBI","MBJ","MBK","MBL","MBM","MBN","MBO","MBP","MBQ","MBR","MBS","MBT","MBU","MBV",
-"MBW","MBX","MBY","MBZ","MCA","MCB","MCC","MCD","MCE","MCF","MCG","MCH","MCI","MCJ","MCK","MCL",
-"MCM","MCN","MCO","MCP","MCQ","MCR","MCS","MCT","MCU","MCV","MCW","MCX","MCY","MCZ","MDA","MDB",
-"MDC","MDD","MDE","MDF","MDG","MDH","MDI","MDJ","MDK","MDL","MDM","MDN","MDO","MDP","MDQ","MDR",
-"MDS","MDT","MDU","MDV","MDW","MDX","MDY","MDZ","MEA","MEB","MEC","MED","MEE","MEF","MEG","MEH",
-"MEI","MEJ","MEK","MEL","MEM","MEN","MEO","MEP","MEQ","MER","MES","MET","MEU","MEV","MEW","MEX",
-"MEY","MEZ","MFA","MFB","MFC","MFD","MFE","MFF","MFG","MFH","MFI","MFJ","MFK","MFL","MFM","MFN",
-"MFO","MFP","MFQ","MFR","MFS","MFT","MFU","MFV","MFW","MFX","MFY","MFZ","MGA","MGB","MGC","MGD",
-"MGE","MGF","MGG","MGH","MGI","MGJ","MGK","MGL","MGM","MGN","MGO","MGP","MGQ","MGR","MGS","MGT",
-"MGU","MGV","MGW","MGX","MGY","MGZ","MHA","MHB","MHC","MHD","MHE","MHF","MHG","MHH","MHI","MHJ",
-"MHK","MHL","MHM","MHN","MHO","MHP","MHQ","MHR","MHS","MHT","MHU","MHV","MHW","MHX","MHY","MHZ",
-"MIA","MIB","MIC","MID","MIE","MIF","MIG","MIH","MII","MIJ","MIK","MIL","MIM","MIN","MIO","MIP",
-"MIQ","MIR","MIS","MIT","MIU","MIV","MIW","MIX","MIY","MIZ","MJA","MJB","MJC","MJD","MJE","MJF",
-"MJG","MJH","MJI","MJJ","MJK","MJL","MJM","MJN","MJO","MJP","MJQ","MJR","MJS","MJT","MJU","MJV",
-"MJW","MJX","MJY","MJZ","MKA","MKB","MKC","MKD","MKE","MKF","MKG","MKH","MKI","MKJ","MKK","MKL",
-"MKM","MKN","MKO","MKP","MKQ","MKR","MKS","MKT","MKU","MKV","MKW","MKX","MKY","MKZ","MLA","MLB",
-"MLC","MLD","MLE","MLF","MLG","MLH","MLI","MLJ","MLK","MLL","MLM","MLN","MLO","MLP","MLQ","MLR",
-"MLS","MLT","MLU","MLV","MLW","MLX","MLY","MLZ","MMA","MMB","MMC","MMD","MME","MMF","MMG","MMH",
-"MMI","MMJ","MMK","MML","MMM","MMN","MMO","MMP","MMQ","MMR","MMS","MMT","MMU","MMV","MMW","MMX",
-"MMY","MMZ","MNA","MNB","MNC","MND","MNE","MNF","MNG","MNH","MNI","MNJ","MNK","MNL","MNM","MNN",
-"MNO","MNP","MNQ","MNR","MNS","MNT","MNU","MNV","MNW","MNX","MNY","MNZ","MOA","MOB","MOC","MOD",
-"MOE","MOF","MOG","MOH","MOI","MOJ","MOK","MOL","MOM","MON","MOO","MOP","MOQ","MOR","MOS","MOT",
-"MOU","MOV","MOW","MOX","MOY","MOZ","MPA","MPB","MPC","MPD","MPE","MPF","MPG","MPH","MPI","MPJ",
-"MPK","MPL","MPM","MPN","MPO","MPP","MPQ","MPR","MPS","MPT","MPU","MPV","MPW","MPX","MPY","MPZ",
-"MQA","MQB","MQC","MQD","MQE","MQF","MQG","MQH","MQI","MQJ","MQK","MQL","MQM","MQN","MQO","MQP",
-"MQQ","MQR","MQS","MQT","MQU","MQV","MQW","MQX","MQY","MQZ","MRA","MRB","MRC","MRD","MRE","MRF",
-"MRG","MRH","MRI","MRJ","MRK","MRL","MRM","MRN","MRO","MRP","MRQ","MRR","MRS","MRT","MRU","MRV",
-"MRW","MRX","MRY","MRZ","MSA","MSB","MSC","MSD","MSE","MSF","MSG","MSH","MSI","MSJ","MSK","MSL",
-"MSM","MSN","MSO","MSP","MSQ","MSR","MSS","MST","MSU","MSV","MSW","MSX","MSY","MSZ","MTA","MTB",
-"MTC","MTD","MTE","MTF","MTG","MTH","MTI","MTJ","MTK","MTL","MTM","MTN","MTO","MTP","MTQ","MTR",
-"MTS","MTT","MTU","MTV","MTW","MTX","MTY","MTZ","MUA","MUB","MUC","MUD","MUE","MUF","MUG","MUH",
-"MUI","MUJ","MUK","MUL","MUM","MUN","MUO","MUP","MUQ","MUR","MUS","MUT","MUU","MUV","MUW","MUX",
-"MUY","MUZ","MVA","MVB","MVC","MVD","MVE","MVF","MVG","MVH","MVI","MVJ","MVK","MVL","MVM","MVN",
-"MVO","MVP","MVQ","MVR","MVS","MVT","MVU","MVV","MVW","MVX","MVY","MVZ","MWA","MWB","MWC","MWD",
-"MWE","MWF","MWG","MWH","MWI","MWJ","MWK","MWL","MWM","MWN","MWO","MWP","MWQ","MWR","MWS","MWT",
-"MWU","MWV","MWW","MWX","MWY","MWZ","MXA","MXB","MXC","MXD","MXE","MXF","MXG","MXH","MXI","MXJ",
-"MXK","MXL","MXM","MXN","MXO","MXP","MXQ","MXR","MXS","MXT","MXU","MXV","MXW","MXX","MXY","MXZ",
-"MYA","MYB","MYC","MYD","MYE","MYF","MYG","MYH","MYI","MYJ","MYK","MYL","MYM","MYN","MYO","MYP",
-"MYQ","MYR","MYS","MYT","MYU","MYV","MYW","MYX","MYY","MYZ","MZA","MZB","MZC","MZD","MZE","MZF",
-"MZG","MZH","MZI","MZJ","MZK","MZL","MZM","MZN","MZO","MZP","MZQ","MZR","MZS","MZT","MZU","MZV",
-"MZW","MZX","MZY","MZZ","NAA","NAB","NAC","NAD","NAE","NAF","NAG","NAH","NAI","NAJ","NAK","NAL",
-"NAM","NAN","NAO","NAP","NAQ","NAR","NAS","NAT","NAU","NAV","NAW","NAX","NAY","NAZ","NBA","NBB",
-"NBC","NBD","NBE","NBF","NBG","NBH","NBI","NBJ","NBK","NBL","NBM","NBN","NBO","NBP","NBQ","NBR",
-"NBS","NBT","NBU","NBV","NBW","NBX","NBY","NBZ","NCA","NCB","NCC","NCD","NCE","NCF","NCG","NCH",
-"NCI","NCJ","NCK","NCL","NCM","NCN","NCO","NCP","NCQ","NCR","NCS","NCT","NCU","NCV","NCW","NCX",
-"NCY","NCZ","NDA","NDB","NDC","NDD","NDE","NDF","NDG","NDH","NDI","NDJ","NDK","NDL","NDM","NDN",
-"NDO","NDP","NDQ","NDR","NDS","NDT","NDU","NDV","NDW","NDX","NDY","NDZ","NEA","NEB","NEC","NED",
-"NEE","NEF","NEG","NEH","NEI","NEJ","NEK","NEL","NEM","NEN","NEO","NEP","NEQ","NER","NES","NET",
-"NEU","NEV","NEW","NEX","NEY","NEZ","NFA","NFB","NFC","NFD","NFE","NFF","NFG","NFH","NFI","NFJ",
-"NFK","NFL","NFM","NFN","NFO","NFP","NFQ","NFR","NFS","NFT","NFU","NFV","NFW","NFX","NFY","NFZ",
-"NGA","NGB","NGC","NGD","NGE","NGF","NGG","NGH","NGI","NGJ","NGK","NGL","NGM","NGN","NGO","NGP",
-"NGQ","NGR","NGS","NGT","NGU","NGV","NGW","NGX","NGY","NGZ","NHA","NHB","NHC","NHD","NHE","NHF",
-"NHG","NHH","NHI","NHJ","NHK","NHL","NHM","NHN","NHO","NHP","NHQ","NHR","NHS","NHT","NHU","NHV",
-"NHW","NHX","NHY","NHZ","NIA","NIB","NIC","NID","NIE","NIF","NIG","NIH","NII","NIJ","NIK","NIL",
-"NIM","NIN","NIO","NIP","NIQ","NIR","NIS","NIT","NIU","NIV","NIW","NIX","NIY","NIZ","NJA","NJB",
-"NJC","NJD","NJE","NJF","NJG","NJH","NJI","NJJ","NJK","NJL","NJM","NJN","NJO","NJP","NJQ","NJR",
-"NJS","NJT","NJU","NJV","NJW","NJX","NJY","NJZ","NKA","NKB","NKC","NKD","NKE","NKF","NKG","NKH",
-"NKI","NKJ","NKK","NKL","NKM","NKN","NKO","NKP","NKQ","NKR","NKS","NKT","NKU","NKV","NKW","NKX",
-"NKY","NKZ","NLA","NLB","NLC","NLD","NLE","NLF","NLG","NLH","NLI","NLJ","NLK","NLL","NLM","NLN",
-"NLO","NLP","NLQ","NLR","NLS","NLT","NLU","NLV","NLW","NLX","NLY","NLZ","NMA","NMB","NMC","NMD",
-"NME","NMF","NMG","NMH","NMI","NMJ","NMK","NML","NMM","NMN","NMO","NMP","NMQ","NMR","NMS","NMT",
-"NMU","NMV","NMW","NMX","NMY","NMZ","NNA","NNB","NNC","NND","NNE","NNF","NNG","NNH","NNI","NNJ",
-"NNK","NNL","NNM","NNN","NNO","NNP","NNQ","NNR","NNS","NNT","NNU","NNV","NNW","NNX","NNY","NNZ",
-"NOA","NOB","NOC","NOD","NOE","NOF","NOG","NOH","NOI","NOJ","NOK","NOL","NOM","NON","NOO","NOP",
-"NOQ","NOR","NOS","NOT","NOU","NOV","NOW","NOX","NOY","NOZ","NPA","NPB","NPC","NPD","NPE","NPF",
-"NPG","NPH","NPI","NPJ","NPK","NPL","NPM","NPN","NPO","NPP","NPQ","NPR","NPS","NPT","NPU","NPV",
-"NPW","NPX","NPY","NPZ","NQA","NQB","NQC","NQD","NQE","NQF","NQG","NQH","NQI","NQJ","NQK","NQL",
-"NQM","NQN","NQO","NQP","NQQ","NQR","NQS","NQT","NQU","NQV","NQW","NQX","NQY","NQZ","NRA","NRB",
-"NRC","NRD","NRE","NRF","NRG","NRH","NRI","NRJ","NRK","NRL","NRM","NRN","NRO","NRP","NRQ","NRR",
-"NRS","NRT","NRU","NRV","NRW","NRX","NRY","NRZ","NSA","NSB","NSC","NSD","NSE","NSF","NSG","NSH",
-"NSI","NSJ","NSK","NSL","NSM","NSN","NSO","NSP","NSQ","NSR","NSS","NST","NSU","NSV","NSW","NSX",
-"NSY","NSZ","NTA","NTB","NTC","NTD","NTE","NTF","NTG","NTH","NTI","NTJ","NTK","NTL","NTM","NTN",
-"NTO","NTP","NTQ","NTR","NTS","NTT","NTU","NTV","NTW","NTX","NTY","NTZ","NUA","NUB","NUC","NUD",
-"NUE","NUF","NUG","NUH","NUI","NUJ","NUK","NUL","NUM","NUN","NUO","NUP","NUQ","NUR","NUS","NUT",
-"NUU","NUV","NUW","NUX","NUY","NUZ","NVA","NVB","NVC","NVD","NVE","NVF","NVG","NVH","NVI","NVJ",
-"NVK","NVL","NVM","NVN","NVO","NVP","NVQ","NVR","NVS","NVT","NVU","NVV","NVW","NVX","NVY","NVZ",
-"NWA","NWB","NWC","NWD","NWE","NWF","NWG","NWH","NWI","NWJ","NWK","NWL","NWM","NWN","NWO","NWP",
-"NWQ","NWR","NWS","NWT","NWU","NWV","NWW","NWX","NWY","NWZ","NXA","NXB","NXC","NXD","NXE","NXF",
-"NXG","NXH","NXI","NXJ","NXK","NXL","NXM","NXN","NXO","NXP","NXQ","NXR","NXS","NXT","NXU","NXV",
-"NXW","NXX","NXY","NXZ","NYA","NYB","NYC","NYD","NYE","NYF","NYG","NYH","NYI","NYJ","NYK","NYL",
-"NYM","NYN","NYO","NYP","NYQ","NYR","NYS","NYT","NYU","NYV","NYW","NYX","NYY","NYZ","NZA","NZB",
-"NZC","NZD","NZE","NZF","NZG","NZH","NZI","NZJ","NZK","NZL","NZM","NZN","NZO","NZP","NZQ","NZR",
-"NZS","NZT","NZU","NZV","NZW","NZX","NZY","NZZ","OAA","OAB","OAC","OAD","OAE","OAF","OAG","OAH",
-"OAI","OAJ","OAK","OAL","OAM","OAN","OAO","OAP","OAQ","OAR","OAS","OAT","OAU","OAV","OAW","OAX",
-"OAY","OAZ","OBA","OBB","OBC","OBD","OBE","OBF","OBG","OBH","OBI","OBJ","OBK","OBL","OBM","OBN",
-"OBO","OBP","OBQ","OBR","OBS","OBT","OBU","OBV","OBW","OBX","OBY","OBZ","OCA","OCB","OCC","OCD",
-"OCE","OCF","OCG","OCH","OCI","OCJ","OCK","OCL","OCM","OCN","OCO","OCP","OCQ","OCR","OCS","OCT",
-"OCU","OCV","OCW","OCX","OCY","OCZ","ODA","ODB","ODC","ODD","ODE","ODF","ODG","ODH","ODI","ODJ",
-"ODK","ODL","ODM","ODN","ODO","ODP","ODQ","ODR","ODS","ODT","ODU","ODV","ODW","ODX","ODY","ODZ",
-"OEA","OEB","OEC","OED","OEE","OEF","OEG","OEH","OEI","OEJ","OEK","OEL","OEM","OEN","OEO","OEP",
-"OEQ","OER","OES","OET","OEU","OEV","OEW","OEX","OEY","OEZ","OFA","OFB","OFC","OFD","OFE","OFF",
-"OFG","OFH","OFI","OFJ","OFK","OFL","OFM","OFN","OFO","OFP","OFQ","OFR","OFS","OFT","OFU","OFV",
-"OFW","OFX","OFY","OFZ","OGA","OGB","OGC","OGD","OGE","OGF","OGG","OGH","OGI","OGJ","OGK","OGL",
-"OGM","OGN","OGO","OGP","OGQ","OGR","OGS","OGT","OGU","OGV","OGW","OGX","OGY","OGZ","OHA","OHB",
-"OHC","OHD","OHE","OHF","OHG","OHH","OHI","OHJ","OHK","OHL","OHM","OHN","OHO","OHP","OHQ","OHR",
-"OHS","OHT","OHU","OHV","OHW","OHX","OHY","OHZ","OIA","OIB","OIC","OID","OIE","OIF","OIG","OIH",
-"OII","OIJ","OIK","OIL","OIM","OIN","OIO","OIP","OIQ","OIR","OIS","OIT","OIU","OIV","OIW","OIX",
-"OIY","OIZ","OJA","OJB","OJC","OJD","OJE","OJF","OJG","OJH","OJI","OJJ","OJK","OJL","OJM","OJN",
-"OJO","OJP","OJQ","OJR","OJS","OJT","OJU","OJV","OJW","OJX","OJY","OJZ","OKA","OKB","OKC","OKD",
-"OKE","OKF","OKG","OKH","OKI","OKJ","OKK","OKL","OKM","OKN","OKO","OKP","OKQ","OKR","OKS","OKT",
-"OKU","OKV","OKW","OKX","OKY","OKZ","OLA","OLB","OLC","OLD","OLE","OLF","OLG","OLH","OLI","OLJ",
-"OLK","OLL","OLM","OLN","OLO","OLP","OLQ","OLR","OLS","OLT","OLU","OLV","OLW","OLX","OLY","OLZ",
-"OMA","OMB","OMC","OMD","OME","OMF","OMG","OMH","OMI","OMJ","OMK","OML","OMM","OMN","OMO","OMP",
-"OMQ","OMR","OMS","OMT","OMU","OMV","OMW","OMX","OMY","OMZ","ONA","ONB","ONC","OND","ONE","ONF",
-"ONG","ONH","ONI","ONJ","ONK","ONL","ONM","ONN","ONO","ONP","ONQ","ONR","ONS","ONT","ONU","ONV",
-"ONW","ONX","ONY","ONZ","OOA","OOB","OOC","OOD","OOE","OOF","OOG","OOH","OOI","OOJ","OOK","OOL",
-"OOM","OON","OOO","OOP","OOQ","OOR","OOS","OOT","OOU","OOV","OOW","OOX","OOY","OOZ","OPA","OPB",
-"OPC","OPD","OPE","OPF","OPG","OPH","OPI","OPJ","OPK","OPL","OPM","OPN","OPO","OPP","OPQ","OPR",
-"OPS","OPT","OPU","OPV","OPW","OPX","OPY","OPZ","OQA","OQB","OQC","OQD","OQE","OQF","OQG","OQH",
-"OQI","OQJ","OQK","OQL","OQM","OQN","OQO","OQP","OQQ","OQR","OQS","OQT","OQU","OQV","OQW","OQX",
-"OQY","OQZ","ORA","ORB","ORC","ORD","ORE","ORF","ORG","ORH","ORI","ORJ","ORK","ORL","ORM","ORN",
-"ORO","ORP","ORQ","ORR","ORS","ORT","ORU","ORV","ORW","ORX","ORY","ORZ","OSA","OSB","OSC","OSD",
-"OSE","OSF","OSG","OSH","OSI","OSJ","OSK","OSL","OSM","OSN","OSO","OSP","OSQ","OSR","OSS","OST",
-"OSU","OSV","OSW","OSX","OSY","OSZ","OTA","OTB","OTC","OTD","OTE","OTF","OTG","OTH","OTI","OTJ",
-"OTK","OTL","OTM","OTN","OTO","OTP","OTQ","OTR","OTS","OTT","OTU","OTV","OTW","OTX","OTY","OTZ",
-"OUA","OUB","OUC","OUD","OUE","OUF","OUG","OUH","OUI","OUJ","OUK","OUL","OUM","OUN","OUO","OUP",
-"OUQ","OUR","OUS","OUT","OUU","OUV","OUW","OUX","OUY","OUZ","OVA","OVB","OVC","OVD","OVE","OVF",
-"OVG","OVH","OVI","OVJ","OVK","OVL","OVM","OVN","OVO","OVP","OVQ","OVR","OVS","OVT","OVU","OVV",
-"OVW","OVX","OVY","OVZ","OWA","OWB","OWC","OWD","OWE","OWF","OWG","OWH","OWI","OWJ","OWK","OWL",
-"OWM","OWN","OWO","OWP","OWQ","OWR","OWS","OWT","OWU","OWV","OWW","OWX","OWY","OWZ","OXA","OXB",
-"OXC","OXD","OXE","OXF","OXG","OXH","OXI","OXJ","OXK","OXL","OXM","OXN","OXO","OXP","OXQ","OXR",
-"OXS","OXT","OXU","OXV","OXW","OXX","OXY","OXZ","OYA","OYB","OYC","OYD","OYE","OYF","OYG","OYH",
-"OYI","OYJ","OYK","OYL","OYM","OYN","OYO","OYP","OYQ","OYR","OYS","OYT","OYU","OYV","OYW","OYX",
-"OYY","OYZ","OZA","OZB","OZC","OZD","OZE","OZF","OZG","OZH","OZI","OZJ","OZK","OZL","OZM","OZN",
-"OZO","OZP","OZQ","OZR","OZS","OZT","OZU","OZV","OZW","OZX","OZY","OZZ","PAA","PAB","PAC","PAD",
-"PAE","PAF","PAG","PAH","PAI","PAJ","PAK","PAL","PAM","PAN","PAO","PAP","PAQ","PAR","PAS","PAT",
-"PAU","PAV","PAW","PAX","PAY","PAZ","PBA","PBB","PBC","PBD","PBE","PBF","PBG","PBH","PBI","PBJ",
-"PBK","PBL","PBM","PBN","PBO","PBP","PBQ","PBR","PBS","PBT","PBU","PBV","PBW","PBX","PBY","PBZ",
-"PCA","PCB","PCC","PCD","PCE","PCF","PCG","PCH","PCI","PCJ","PCK","PCL","PCM","PCN","PCO","PCP",
-"PCQ","PCR","PCS","PCT","PCU","PCV","PCW","PCX","PCY","PCZ","PDA","PDB","PDC","PDD","PDE","PDF",
-"PDG","PDH","PDI","PDJ","PDK","PDL","PDM","PDN","PDO","PDP","PDQ","PDR","PDS","PDT","PDU","PDV",
-"PDW","PDX","PDY","PDZ","PEA","PEB","PEC","PED","PEE","PEF","PEG","PEH","PEI","PEJ","PEK","PEL",
-"PEM","PEN","PEO","PEP","PEQ","PER","PES","PET","PEU","PEV","PEW","PEX","PEY","PEZ","PFA","PFB",
-"PFC","PFD","PFE","PFF","PFG","PFH","PFI","PFJ","PFK","PFL","PFM","PFN","PFO","PFP","PFQ","PFR",
-"PFS","PFT","PFU","PFV","PFW","PFX","PFY","PFZ","PGA","PGB","PGC","PGD","PGE","PGF","PGG","PGH",
-"PGI","PGJ","PGK","PGL","PGM","PGN","PGO","PGP","PGQ","PGR","PGS","PGT","PGU","PGV","PGW","PGX",
-"PGY","PGZ","PHA","PHB","PHC","PHD","PHE","PHF","PHG","PHH","PHI","PHJ","PHK","PHL","PHM","PHN",
-"PHO","PHP","PHQ","PHR","PHS","PHT","PHU","PHV","PHW","PHX","PHY","PHZ","PIA","PIB","PIC","PID",
-"PIE","PIF","PIG","PIH","PII","PIJ","PIK","PIL","PIM","PIN","PIO","PIP","PIQ","PIR","PIS","PIT",
-"PIU","PIV","PIW","PIX","PIY","PIZ","PJA","PJB","PJC","PJD","PJE","PJF","PJG","PJH","PJI","PJJ",
-"PJK","PJL","PJM","PJN","PJO","PJP","PJQ","PJR","PJS","PJT","PJU","PJV","PJW","PJX","PJY","PJZ",
-"PKA","PKB","PKC","PKD","PKE","PKF","PKG","PKH","PKI","PKJ","PKK","PKL","PKM","PKN","PKO","PKP",
-"PKQ","PKR","PKS","PKT","PKU","PKV","PKW","PKX","PKY","PKZ","PLA","PLB","PLC","PLD","PLE","PLF",
-"PLG","PLH","PLI","PLJ","PLK","PLL","PLM","PLN","PLO","PLP","PLQ","PLR","PLS","PLT","PLU","PLV",
-"PLW","PLX","PLY","PLZ","PMA","PMB","PMC","PMD","PME","PMF","PMG","PMH","PMI","PMJ","PMK","PML",
-"PMM","PMN","PMO","PMP","PMQ","PMR","PMS","PMT","PMU","PMV","PMW","PMX","PMY","PMZ","PNA","PNB",
-"PNC","PND","PNE","PNF","PNG","PNH","PNI","PNJ","PNK","PNL","PNM","PNN","PNO","PNP","PNQ","PNR",
-"PNS","PNT","PNU","PNV","PNW","PNX","PNY","PNZ","POA","POB","POC","POD","POE","POF","POG","POH",
-"POI","POJ","POK","POL","POM","PON","POO","POP","POQ","POR","POS","POT","POU","POV","POW","POX",
-"POY","POZ","PPA","PPB","PPC","PPD","PPE","PPF","PPG","PPH","PPI","PPJ","PPK","PPL","PPM","PPN",
-"PPO","PPP","PPQ","PPR","PPS","PPT","PPU","PPV","PPW","PPX","PPY","PPZ","PQA","PQB","PQC","PQD",
-"PQE","PQF","PQG","PQH","PQI","PQJ","PQK","PQL","PQM","PQN","PQO","PQP","PQQ","PQR","PQS","PQT",
-"PQU","PQV","PQW","PQX","PQY","PQZ","PRA","PRB","PRC","PRD","PRE","PRF","PRG","PRH","PRI","PRJ",
-"PRK","PRL","PRM","PRN","PRO","PRP","PRQ","PRR","PRS","PRT","PRU","PRV","PRW","PRX","PRY","PRZ",
-"PSA","PSB","PSC","PSD","PSE","PSF","PSG","PSH","PSI","PSJ","PSK","PSL","PSM","PSN","PSO","PSP",
-"PSQ","PSR","PSS","PST","PSU","PSV","PSW","PSX","PSY","PSZ","PTA","PTB","PTC","PTD","PTE","PTF",
-"PTG","PTH","PTI","PTJ","PTK","PTL","PTM","PTN","PTO","PTP","PTQ","PTR","PTS","PTT","PTU","PTV",
-"PTW","PTX","PTY","PTZ","PUA","PUB","PUC","PUD","PUE","PUF","PUG","PUH","PUI","PUJ","PUK","PUL",
-"PUM","PUN","PUO","PUP","PUQ","PUR","PUS","PUT","PUU","PUV","PUW","PUX","PUY","PUZ","PVA","PVB",
-"PVC","PVD","PVE","PVF","PVG","PVH","PVI","PVJ","PVK","PVL","PVM","PVN","PVO","PVP","PVQ","PVR",
-"PVS","PVT","PVU","PVV","PVW","PVX","PVY","PVZ","PWA","PWB","PWC","PWD","PWE","PWF","PWG","PWH",
-"PWI","PWJ","PWK","PWL","PWM","PWN","PWO","PWP","PWQ","PWR","PWS","PWT","PWU","PWV","PWW","PWX",
-"PWY","PWZ","PXA","PXB","PXC","PXD","PXE","PXF","PXG","PXH","PXI","PXJ","PXK","PXL","PXM","PXN",
-"PXO","PXP","PXQ","PXR","PXS","PXT","PXU","PXV","PXW","PXX","PXY","PXZ","PYA","PYB","PYC","PYD",
-"PYE","PYF","PYG","PYH","PYI","PYJ","PYK","PYL","PYM","PYN","PYO","PYP","PYQ","PYR","PYS","PYT",
-"PYU","PYV","PYW","PYX","PYY","PYZ","PZA","PZB","PZC","PZD","PZE","PZF","PZG","PZH","PZI","PZJ",
-"PZK","PZL","PZM","PZN","PZO","PZP","PZQ","PZR","PZS","PZT","PZU","PZV","PZW","PZX","PZY","PZZ",
-"QAA","QAB","QAC","QAD","QAE","QAF","QAG","QAH","QAI","QAJ","QAK","QAL","QAM","QAN","QAO","QAP",
-"QAQ","QAR","QAS","QAT","QAU","QAV","QAW","QAX","QAY","QAZ","QBA","QBB","QBC","QBD","QBE","QBF",
-"QBG","QBH","QBI","QBJ","QBK","QBL","QBM","QBN","QBO","QBP","QBQ","QBR","QBS","QBT","QBU","QBV",
-"QBW","QBX","QBY","QBZ","QCA","QCB","QCC","QCD","QCE","QCF","QCG","QCH","QCI","QCJ","QCK","QCL",
-"QCM","QCN","QCO","QCP","QCQ","QCR","QCS","QCT","QCU","QCV","QCW","QCX","QCY","QCZ","QDA","QDB",
-"QDC","QDD","QDE","QDF","QDG","QDH","QDI","QDJ","QDK","QDL","QDM","QDN","QDO","QDP","QDQ","QDR",
-"QDS","QDT","QDU","QDV","QDW","QDX","QDY","QDZ","QEA","QEB","QEC","QED","QEE","QEF","QEG","QEH",
-"QEI","QEJ","QEK","QEL","QEM","QEN","QEO","QEP","QEQ","QER","QES","QET","QEU","QEV","QEW","QEX",
-"QEY","QEZ","QFA","QFB","QFC","QFD","QFE","QFF","QFG","QFH","QFI","QFJ","QFK","QFL","QFM","QFN",
-"QFO","QFP","QFQ","QFR","QFS","QFT","QFU","QFV","QFW","QFX","QFY","QFZ","QGA","QGB","QGC","QGD",
-"QGE","QGF","QGG","QGH","QGI","QGJ","QGK","QGL","QGM","QGN","QGO","QGP","QGQ","QGR","QGS","QGT",
-"QGU","QGV","QGW","QGX","QGY","QGZ","QHA","QHB","QHC","QHD","QHE","QHF","QHG","QHH","QHI","QHJ",
-"QHK","QHL","QHM","QHN","QHO","QHP","QHQ","QHR","QHS","QHT","QHU","QHV","QHW","QHX","QHY","QHZ",
-"QIA","QIB","QIC","QID","QIE","QIF","QIG","QIH","QII","QIJ","QIK","QIL","QIM","QIN","QIO","QIP",
-"QIQ","QIR","QIS","QIT","QIU","QIV","QIW","QIX","QIY","QIZ","QJA","QJB","QJC","QJD","QJE","QJF",
-"QJG","QJH","QJI","QJJ","QJK","QJL","QJM","QJN","QJO","QJP","QJQ","QJR","QJS","QJT","QJU","QJV",
-"QJW","QJX","QJY","QJZ","QKA","QKB","QKC","QKD","QKE","QKF","QKG","QKH","QKI","QKJ","QKK","QKL",
-"QKM","QKN","QKO","QKP","QKQ","QKR","QKS","QKT","QKU","QKV","QKW","QKX","QKY","QKZ","QLA","QLB",
-"QLC","QLD","QLE","QLF","QLG","QLH","QLI","QLJ","QLK","QLL","QLM","QLN","QLO","QLP","QLQ","QLR",
-"QLS","QLT","QLU","QLV","QLW","QLX","QLY","QLZ","QMA","QMB","QMC","QMD","QME","QMF","QMG","QMH",
-"QMI","QMJ","QMK","QML","QMM","QMN","QMO","QMP","QMQ","QMR","QMS","QMT","QMU","QMV","QMW","QMX",
-"QMY","QMZ","QNA","QNB","QNC","QND","QNE","QNF","QNG","QNH","QNI","QNJ","QNK","QNL","QNM","QNN",
-"QNO","QNP","QNQ","QNR","QNS","QNT","QNU","QNV","QNW","QNX","QNY","QNZ","QOA","QOB","QOC","QOD",
-"QOE","QOF","QOG","QOH","QOI","QOJ","QOK","QOL","QOM","QON","QOO","QOP","QOQ","QOR","QOS","QOT",
-"QOU","QOV","QOW","QOX","QOY","QOZ","QPA","QPB","QPC","QPD","QPE","QPF","QPG","QPH","QPI","QPJ",
-"QPK","QPL","QPM","QPN","QPO","QPP","QPQ","QPR","QPS","QPT","QPU","QPV","QPW","QPX","QPY","QPZ",
-"QQA","QQB","QQC","QQD","QQE","QQF","QQG","QQH","QQI","QQJ","QQK","QQL","QQM","QQN","QQO","QQP",
-"QQQ","QQR","QQS","QQT","QQU","QQV","QQW","QQX","QQY","QQZ","QRA","QRB","QRC","QRD","QRE","QRF",
-"QRG","QRH","QRI","QRJ","QRK","QRL","QRM","QRN","QRO","QRP","QRQ","QRR","QRS","QRT","QRU","QRV",
-"QRW","QRX","QRY","QRZ","QSA","QSB","QSC","QSD","QSE","QSF","QSG","QSH","QSI","QSJ","QSK","QSL",
-"QSM","QSN","QSO","QSP","QSQ","QSR","QSS","QST","QSU","QSV","QSW","QSX","QSY","QSZ","QTA","QTB",
-"QTC","QTD","QTE","QTF","QTG","QTH","QTI","QTJ","QTK","QTL","QTM","QTN","QTO","QTP","QTQ","QTR",
-"QTS","QTT","QTU","QTV","QTW","QTX","QTY","QTZ","QUA","QUB","QUC","QUD","QUE","QUF","QUG","QUH",
-"QUI","QUJ","QUK","QUL","QUM","QUN","QUO","QUP","QUQ","QUR","QUS","QUT","QUU","QUV","QUW","QUX",
-"QUY","QUZ","QVA","QVB","QVC","QVD","QVE","QVF","QVG","QVH","QVI","QVJ","QVK","QVL","QVM","QVN",
-"QVO","QVP","QVQ","QVR","QVS","QVT","QVU","QVV","QVW","QVX","QVY","QVZ","QWA","QWB","QWC","QWD",
-"QWE","QWF","QWG","QWH","QWI","QWJ","QWK","QWL","QWM","QWN","QWO","QWP","QWQ","QWR","QWS","QWT",
-"QWU","QWV","QWW","QWX","QWY","QWZ","QXA","QXB","QXC","QXD","QXE","QXF","QXG","QXH","QXI","QXJ",
-"QXK","QXL","QXM","QXN","QXO","QXP","QXQ","QXR","QXS","QXT","QXU","QXV","QXW","QXX","QXY","QXZ",
-"QYA","QYB","QYC","QYD","QYE","QYF","QYG","QYH","QYI","QYJ","QYK","QYL","QYM","QYN","QYO","QYP",
-"QYQ","QYR","QYS","QYT","QYU","QYV","QYW","QYX","QYY","QYZ","QZA","QZB","QZC","QZD","QZE","QZF",
-"QZG","QZH","QZI","QZJ","QZK","QZL","QZM","QZN","QZO","QZP","QZQ","QZR","QZS","QZT","QZU","QZV",
-"QZW","QZX","QZY","QZZ","RAA","RAB","RAC","RAD","RAE","RAF","RAG","RAH","RAI","RAJ","RAK","RAL",
-"RAM","RAN","RAO","RAP","RAQ","RAR","RAS","RAT","RAU","RAV","RAW","RAX","RAY","RAZ","RBA","RBB",
-"RBC","RBD","RBE","RBF","RBG","RBH","RBI","RBJ","RBK","RBL","RBM","RBN","RBO","RBP","RBQ","RBR",
-"RBS","RBT","RBU","RBV","RBW","RBX","RBY","RBZ","RCA","RCB","RCC","RCD","RCE","RCF","RCG","RCH",
-"RCI","RCJ","RCK","RCL","RCM","RCN","RCO","RCP","RCQ","RCR","RCS","RCT","RCU","RCV","RCW","RCX",
-"RCY","RCZ","RDA","RDB","RDC","RDD","RDE","RDF","RDG","RDH","RDI","RDJ","RDK","RDL","RDM","RDN",
-"RDO","RDP","RDQ","RDR","RDS","RDT","RDU","RDV","RDW","RDX","RDY","RDZ","REA","REB","REC","RED",
-"REE","REF","REG","REH","REI","REJ","REK","REL","REM","REN","REO","REP","REQ","RER","RES","RET",
-"REU","REV","REW","REX","REY","REZ","RFA","RFB","RFC","RFD","RFE","RFF","RFG","RFH","RFI","RFJ",
-"RFK","RFL","RFM","RFN","RFO","RFP","RFQ","RFR","RFS","RFT","RFU","RFV","RFW","RFX","RFY","RFZ",
-"RGA","RGB","RGC","RGD","RGE","RGF","RGG","RGH","RGI","RGJ","RGK","RGL","RGM","RGN","RGO","RGP",
-"RGQ","RGR","RGS","RGT","RGU","RGV","RGW","RGX","RGY","RGZ","RHA","RHB","RHC","RHD","RHE","RHF",
-"RHG","RHH","RHI","RHJ","RHK","RHL","RHM","RHN","RHO","RHP","RHQ","RHR","RHS","RHT","RHU","RHV",
-"RHW","RHX","RHY","RHZ","RIA","RIB","RIC","RID","RIE","RIF","RIG","RIH","RII","RIJ","RIK","RIL",
-"RIM","RIN","RIO","RIP","RIQ","RIR","RIS","RIT","RIU","RIV","RIW","RIX","RIY","RIZ","RJA","RJB",
-"RJC","RJD","RJE","RJF","RJG","RJH","RJI","RJJ","RJK","RJL","RJM","RJN","RJO","RJP","RJQ","RJR",
-"RJS","RJT","RJU","RJV","RJW","RJX","RJY","RJZ","RKA","RKB","RKC","RKD","RKE","RKF","RKG","RKH",
-"RKI","RKJ","RKK","RKL","RKM","RKN","RKO","RKP","RKQ","RKR","RKS","RKT","RKU","RKV","RKW","RKX",
-"RKY","RKZ","RLA","RLB","RLC","RLD","RLE","RLF","RLG","RLH","RLI","RLJ","RLK","RLL","RLM","RLN",
-"RLO","RLP","RLQ","RLR","RLS","RLT","RLU","RLV","RLW","RLX","RLY","RLZ","RMA","RMB","RMC","RMD",
-"RME","RMF","RMG","RMH","RMI","RMJ","RMK","RML","RMM","RMN","RMO","RMP","RMQ","RMR","RMS","RMT",
-"RMU","RMV","RMW","RMX","RMY","RMZ","RNA","RNB","RNC","RND","RNE","RNF","RNG","RNH","RNI","RNJ",
-"RNK","RNL","RNM","RNN","RNO","RNP","RNQ","RNR","RNS","RNT","RNU","RNV","RNW","RNX","RNY","RNZ",
-"ROA","ROB","ROC","ROD","ROE","ROF","ROG","ROH","ROI","ROJ","ROK","ROL","ROM","RON","ROO","ROP",
-"ROQ","ROR","ROS","ROT","ROU","ROV","ROW","ROX","ROY","ROZ","RPA","RPB","RPC","RPD","RPE","RPF",
-"RPG","RPH","RPI","RPJ","RPK","RPL","RPM","RPN","RPO","RPP","RPQ","RPR","RPS","RPT","RPU","RPV",
-"RPW","RPX","RPY","RPZ","RQA","RQB","RQC","RQD","RQE","RQF","RQG","RQH","RQI","RQJ","RQK","RQL",
-"RQM","RQN","RQO","RQP","RQQ","RQR","RQS","RQT","RQU","RQV","RQW","RQX","RQY","RQZ","RRA","RRB",
-"RRC","RRD","RRE","RRF","RRG","RRH","RRI","RRJ","RRK","RRL","RRM","RRN","RRO","RRP","RRQ","RRR",
-"RRS","RRT","RRU","RRV","RRW","RRX","RRY","RRZ","RSA","RSB","RSC","RSD","RSE","RSF","RSG","RSH",
-"RSI","RSJ","RSK","RSL","RSM","RSN","RSO","RSP","RSQ","RSR","RSS","RST","RSU","RSV","RSW","RSX",
-"RSY","RSZ","RTA","RTB","RTC","RTD","RTE","RTF","RTG","RTH","RTI","RTJ","RTK","RTL","RTM","RTN",
-"RTO","RTP","RTQ","RTR","RTS","RTT","RTU","RTV","RTW","RTX","RTY","RTZ","RUA","RUB","RUC","RUD",
-"RUE","RUF","RUG","RUH","RUI","RUJ","RUK","RUL","RUM","RUN","RUO","RUP","RUQ","RUR","RUS","RUT",
-"RUU","RUV","RUW","RUX","RUY","RUZ","RVA","RVB","RVC","RVD","RVE","RVF","RVG","RVH","RVI","RVJ",
-"RVK","RVL","RVM","RVN","RVO","RVP","RVQ","RVR","RVS","RVT","RVU","RVV","RVW","RVX","RVY","RVZ",
-"RWA","RWB","RWC","RWD","RWE","RWF","RWG","RWH","RWI","RWJ","RWK","RWL","RWM","RWN","RWO","RWP",
-"RWQ","RWR","RWS","RWT","RWU","RWV","RWW","RWX","RWY","RWZ","RXA","RXB","RXC","RXD","RXE","RXF",
-"RXG","RXH","RXI","RXJ","RXK","RXL","RXM","RXN","RXO","RXP","RXQ","RXR","RXS","RXT","RXU","RXV",
-"RXW","RXX","RXY","RXZ","RYA","RYB","RYC","RYD","RYE","RYF","RYG","RYH","RYI","RYJ","RYK","RYL",
-"RYM","RYN","RYO","RYP","RYQ","RYR","RYS","RYT","RYU","RYV","RYW","RYX","RYY","RYZ","RZA","RZB",
-"RZC","RZD","RZE","RZF","RZG","RZH","RZI","RZJ","RZK","RZL","RZM","RZN","RZO","RZP","RZQ","RZR",
-"RZS","RZT","RZU","RZV","RZW","RZX","RZY","RZZ","SAA","SAB","SAC","SAD","SAE","SAF","SAG","SAH",
-"SAI","SAJ","SAK","SAL","SAM","SAN","SAO","SAP","SAQ","SAR","SAS","SAT","SAU","SAV","SAW","SAX",
-"SAY","SAZ","SBA","SBB","SBC","SBD","SBE","SBF","SBG","SBH","SBI","SBJ","SBK","SBL","SBM","SBN",
-"SBO","SBP","SBQ","SBR","SBS","SBT","SBU","SBV","SBW","SBX","SBY","SBZ","SCA","SCB","SCC","SCD",
-"SCE","SCF","SCG","SCH","SCI","SCJ","SCK","SCL","SCM","SCN","SCO","SCP","SCQ","SCR","SCS","SCT",
-"SCU","SCV","SCW","SCX","SCY","SCZ","SDA","SDB","SDC","SDD","SDE","SDF","SDG","SDH","SDI","SDJ",
-"SDK","SDL","SDM","SDN","SDO","SDP","SDQ","SDR","SDS","SDT","SDU","SDV","SDW","SDX","SDY","SDZ",
-"SEA","SEB","SEC","SED","SEE","SEF","SEG","SEH","SEI","SEJ","SEK","SEL","SEM","SEN","SEO","SEP",
-"SEQ","SER","SES","SET","SEU","SEV","SEW","SEX","SEY","SEZ","SFA","SFB","SFC","SFD","SFE","SFF",
-"SFG","SFH","SFI","SFJ","SFK","SFL","SFM","SFN","SFO","SFP","SFQ","SFR","SFS","SFT","SFU","SFV",
-"SFW","SFX","SFY","SFZ","SGA","SGB","SGC","SGD","SGE","SGF","SGG","SGH","SGI","SGJ","SGK","SGL",
-"SGM","SGN","SGO","SGP","SGQ","SGR","SGS","SGT","SGU","SGV","SGW","SGX","SGY","SGZ","SHA","SHB",
-"SHC","SHD","SHE","SHF","SHG","SHH","SHI","SHJ","SHK","SHL","SHM","SHN","SHO","SHP","SHQ","SHR",
-"SHS","SHT","SHU","SHV","SHW","SHX","SHY","SHZ","SIA","SIB","SIC","SID","SIE","SIF","SIG","SIH",
-"SII","SIJ","SIK","SIL","SIM","SIN","SIO","SIP","SIQ","SIR","SIS","SIT","SIU","SIV","SIW","SIX",
-"SIY","SIZ","SJA","SJB","SJC","SJD","SJE","SJF","SJG","SJH","SJI","SJJ","SJK","SJL","SJM","SJN",
-"SJO","SJP","SJQ","SJR","SJS","SJT","SJU","SJV","SJW","SJX","SJY","SJZ","SKA","SKB","SKC","SKD",
-"SKE","SKF","SKG","SKH","SKI","SKJ","SKK","SKL","SKM","SKN","SKO","SKP","SKQ","SKR","SKS","SKT",
-"SKU","SKV","SKW","SKX","SKY","SKZ","SLA","SLB","SLC","SLD","SLE","SLF","SLG","SLH","SLI","SLJ",
-"SLK","SLL","SLM","SLN","SLO","SLP","SLQ","SLR","SLS","SLT","SLU","SLV","SLW","SLX","SLY","SLZ",
-"SMA","SMB","SMC","SMD","SME","SMF","SMG","SMH","SMI","SMJ","SMK","SML","SMM","SMN","SMO","SMP",
-"SMQ","SMR","SMS","SMT","SMU","SMV","SMW","SMX","SMY","SMZ","SNA","SNB","SNC","SND","SNE","SNF",
-"SNG","SNH","SNI","SNJ","SNK","SNL","SNM","SNN","SNO","SNP","SNQ","SNR","SNS","SNT","SNU","SNV",
-"SNW","SNX","SNY","SNZ","SOA","SOB","SOC","SOD","SOE","SOF","SOG","SOH","SOI","SOJ","SOK","SOL",
-"SOM","SON","SOO","SOP","SOQ","SOR","SOS","SOT","SOU","SOV","SOW","SOX","SOY","SOZ","SPA","SPB",
-"SPC","SPD","SPE","SPF","SPG","SPH","SPI","SPJ","SPK","SPL","SPM","SPN","SPO","SPP","SPQ","SPR",
-"SPS","SPT","SPU","SPV","SPW","SPX","SPY","SPZ","SQA","SQB","SQC","SQD","SQE","SQF","SQG","SQH",
-"SQI","SQJ","SQK","SQL","SQM","SQN","SQO","SQP","SQQ","SQR","SQS","SQT","SQU","SQV","SQW","SQX",
-"SQY","SQZ","SRA","SRB","SRC","SRD","SRE","SRF","SRG","SRH","SRI","SRJ","SRK","SRL","SRM","SRN",
-"SRO","SRP","SRQ","SRR","SRS","SRT","SRU","SRV","SRW","SRX","SRY","SRZ","SSA","SSB","SSC","SSD",
-"SSE","SSF","SSG","SSH","SSI","SSJ","SSK","SSL","SSM","SSN","SSO","SSP","SSQ","SSR","SSS","SST",
-"SSU","SSV","SSW","SSX","SSY","SSZ","STA","STB","STC","STD","STE","STF","STG","STH","STI","STJ",
-"STK","STL","STM","STN","STO","STP","STQ","STR","STS","STT","STU","STV","STW","STX","STY","STZ",
-"SUA","SUB","SUC","SUD","SUE","SUF","SUG","SUH","SUI","SUJ","SUK","SUL","SUM","SUN","SUO","SUP",
-"SUQ","SUR","SUS","SUT","SUU","SUV","SUW","SUX","SUY","SUZ","SVA","SVB","SVC","SVD","SVE","SVF",
-"SVG","SVH","SVI","SVJ","SVK","SVL","SVM","SVN","SVO","SVP","SVQ","SVR","SVS","SVT","SVU","SVV",
-"SVW","SVX","SVY","SVZ","SWA","SWB","SWC","SWD","SWE","SWF","SWG","SWH","SWI","SWJ","SWK","SWL",
-"SWM","SWN","SWO","SWP","SWQ","SWR","SWS","SWT","SWU","SWV","SWW","SWX","SWY","SWZ","SXA","SXB",
-"SXC","SXD","SXE","SXF","SXG","SXH","SXI","SXJ","SXK","SXL","SXM","SXN","SXO","SXP","SXQ","SXR",
-"SXS","SXT","SXU","SXV","SXW","SXX","SXY","SXZ","SYA","SYB","SYC","SYD","SYE","SYF","SYG","SYH",
-"SYI","SYJ","SYK","SYL","SYM","SYN","SYO","SYP","SYQ","SYR","SYS","SYT","SYU","SYV","SYW","SYX",
-"SYY","SYZ","SZA","SZB","SZC","SZD","SZE","SZF","SZG","SZH","SZI","SZJ","SZK","SZL","SZM","SZN",
-"SZO","SZP","SZQ","SZR","SZS","SZT","SZU","SZV","SZW","SZX","SZY","SZZ",
-#/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-# TAA to TTV - 516 triplets - intentionally omitted 
-# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
-"TTW","TTX","TTY","TTZ","TUA","TUB","TUC","TUD","TUE","TUF","TUG","TUH","TUI","TUJ","TUK","TUL",
-"TUM","TUN","TUO","TUP","TUQ","TUR","TUS","TUT","TUU","TUV","TUW","TUX","TUY","TUZ","TVA","TVB",
-"TVC","TVD","TVE","TVF","TVG","TVH","TVI","TVJ","TVK","TVL","TVM","TVN","TVO","TVP","TVQ","TVR",
-"TVS","TVT","TVU","TVV","TVW","TVX","TVY","TVZ","TWA","TWB","TWC","TWD","TWE","TWF","TWG","TWH",
-"TWI","TWJ","TWK","TWL","TWM","TWN","TWO","TWP","TWQ","TWR","TWS","TWT","TWU","TWV","TWW","TWX",
-"TWY","TWZ","TXA","TXB","TXC","TXD","TXE","TXF","TXG","TXH","TXI","TXJ","TXK","TXL","TXM","TXN",
-"TXO","TXP","TXQ","TXR","TXS","TXT","TXU","TXV","TXW","TXX","TXY","TXZ","TYA","TYB","TYC","TYD",
-"TYE","TYF","TYG","TYH","TYI","TYJ","TYK","TYL","TYM","TYN","TYO","TYP","TYQ","TYR","TYS","TYT",
-"TYU","TYV","TYW","TYX","TYY","TYZ","TZA","TZB","TZC","TZD","TZE","TZF","TZG","TZH","TZI","TZJ",
-"TZK","TZL","TZM","TZN","TZO","TZP","TZQ","TZR","TZS","TZT","TZU","TZV","TZW","TZX","TZY","TZZ",
-"UAA","UAB","UAC","UAD","UAE","UAF","UAG","UAH","UAI","UAJ","UAK","UAL","UAM","UAN","UAO","UAP",
-"UAQ","UAR","UAS","UAT","UAU","UAV","UAW","UAX","UAY","UAZ","UBA","UBB","UBC","UBD","UBE","UBF",
-"UBG","UBH","UBI","UBJ","UBK","UBL","UBM","UBN","UBO","UBP","UBQ","UBR","UBS","UBT","UBU","UBV",
-"UBW","UBX","UBY","UBZ","UCA","UCB","UCC","UCD","UCE","UCF","UCG","UCH","UCI","UCJ","UCK","UCL",
-"UCM","UCN","UCO","UCP","UCQ","UCR","UCS","UCT","UCU","UCV","UCW","UCX","UCY","UCZ","UDA","UDB",
-"UDC","UDD","UDE","UDF","UDG","UDH","UDI","UDJ","UDK","UDL","UDM","UDN","UDO","UDP","UDQ","UDR",
-"UDS","UDT","UDU","UDV","UDW","UDX","UDY","UDZ","UEA","UEB","UEC","UED","UEE","UEF","UEG","UEH",
-"UEI","UEJ","UEK","UEL","UEM","UEN","UEO","UEP","UEQ","UER","UES","UET","UEU","UEV","UEW","UEX",
-"UEY","UEZ","UFA","UFB","UFC","UFD","UFE","UFF","UFG","UFH","UFI","UFJ","UFK","UFL","UFM","UFN",
-"UFO","UFP","UFQ","UFR","UFS","UFT","UFU","UFV","UFW","UFX","UFY","UFZ","UGA","UGB","UGC","UGD",
-"UGE","UGF","UGG","UGH","UGI","UGJ","UGK","UGL","UGM","UGN","UGO","UGP","UGQ","UGR","UGS","UGT",
-"UGU","UGV","UGW","UGX","UGY","UGZ","UHA","UHB","UHC","UHD","UHE","UHF","UHG","UHH","UHI","UHJ",
-"UHK","UHL","UHM","UHN","UHO","UHP","UHQ","UHR","UHS","UHT","UHU","UHV","UHW","UHX","UHY","UHZ",
-"UIA","UIB","UIC","UID","UIE","UIF","UIG","UIH","UII","UIJ","UIK","UIL","UIM","UIN","UIO","UIP",
-"UIQ","UIR","UIS","UIT","UIU","UIV","UIW","UIX","UIY","UIZ","UJA","UJB","UJC","UJD","UJE","UJF",
-"UJG","UJH","UJI","UJJ","UJK","UJL","UJM","UJN","UJO","UJP","UJQ","UJR","UJS","UJT","UJU","UJV",
-"UJW","UJX","UJY","UJZ","UKA","UKB","UKC","UKD","UKE","UKF","UKG","UKH","UKI","UKJ","UKK","UKL",
-"UKM","UKN","UKO","UKP","UKQ","UKR","UKS","UKT","UKU","UKV","UKW","UKX","UKY","UKZ","ULA","ULB",
-"ULC","ULD","ULE","ULF","ULG","ULH","ULI","ULJ","ULK","ULL","ULM","ULN","ULO","ULP","ULQ","ULR",
-"ULS","ULT","ULU","ULV","ULW","ULX","ULY","ULZ","UMA","UMB","UMC","UMD","UME","UMF","UMG","UMH",
-"UMI","UMJ","UMK","UML","UMM","UMN","UMO","UMP","UMQ","UMR","UMS","UMT","UMU","UMV","UMW","UMX",
-"UMY","UMZ","UNA","UNB","UNC","UND","UNE","UNF","UNG","UNH","UNI","UNJ","UNK","UNL","UNM","UNN",
-"UNO","UNP","UNQ","UNR","UNS","UNT","UNU","UNV","UNW","UNX","UNY","UNZ","UOA","UOB","UOC","UOD",
-"UOE","UOF","UOG","UOH","UOI","UOJ","UOK","UOL","UOM","UON","UOO","UOP","UOQ","UOR","UOS","UOT",
-"UOU","UOV","UOW","UOX","UOY","UOZ","UPA","UPB","UPC","UPD","UPE","UPF","UPG","UPH","UPI","UPJ",
-"UPK","UPL","UPM","UPN","UPO","UPP","UPQ","UPR","UPS","UPT","UPU","UPV","UPW","UPX","UPY","UPZ",
-"UQA","UQB","UQC","UQD","UQE","UQF","UQG","UQH","UQI","UQJ","UQK","UQL","UQM","UQN","UQO","UQP",
-"UQQ","UQR","UQS","UQT","UQU","UQV","UQW","UQX","UQY","UQZ","URA","URB","URC","URD","URE","URF",
-"URG","URH","URI","URJ","URK","URL","URM","URN","URO","URP","URQ","URR","URS","URT","URU","URV",
-"URW","URX","URY","URZ","USA","USB","USC","USD","USE","USF","USG","USH","USI","USJ","USK","USL",
-"USM","USN","USO","USP","USQ","USR","USS","UST","USU","USV","USW","USX","USY","USZ","UTA","UTB",
-"UTC","UTD","UTE","UTF","UTG","UTH","UTI","UTJ","UTK","UTL","UTM","UTN","UTO","UTP","UTQ","UTR",
-"UTS","UTT","UTU","UTV","UTW","UTX","UTY","UTZ","UUA","UUB","UUC","UUD","UUE","UUF","UUG","UUH",
-"UUI","UUJ","UUK","UUL","UUM","UUN","UUO","UUP","UUQ","UUR","UUS","UUT","UUU","UUV","UUW","UUX",
-"UUY","UUZ","UVA","UVB","UVC","UVD","UVE","UVF","UVG","UVH","UVI","UVJ","UVK","UVL","UVM","UVN",
-"UVO","UVP","UVQ","UVR","UVS","UVT","UVU","UVV","UVW","UVX","UVY","UVZ","UWA","UWB","UWC","UWD",
-"UWE","UWF","UWG","UWH","UWI","UWJ","UWK","UWL","UWM","UWN","UWO","UWP","UWQ","UWR","UWS","UWT",
-"UWU","UWV","UWW","UWX","UWY","UWZ","UXA","UXB","UXC","UXD","UXE","UXF","UXG","UXH","UXI","UXJ",
-"UXK","UXL","UXM","UXN","UXO","UXP","UXQ","UXR","UXS","UXT","UXU","UXV","UXW","UXX","UXY","UXZ",
-"UYA","UYB","UYC","UYD","UYE","UYF","UYG","UYH","UYI","UYJ","UYK","UYL","UYM","UYN","UYO","UYP",
-"UYQ","UYR","UYS","UYT","UYU","UYV","UYW","UYX","UYY","UYZ","UZA","UZB","UZC","UZD","UZE","UZF",
-"UZG","UZH","UZI","UZJ","UZK","UZL","UZM","UZN","UZO","UZP","UZQ","UZR","UZS","UZT","UZU","UZV",
-"UZW","UZX","UZY","UZZ","VAA","VAB","VAC","VAD","VAE","VAF","VAG","VAH","VAI","VAJ","VAK","VAL",
-"VAM","VAN","VAO","VAP","VAQ","VAR","VAS","VAT","VAU","VAV","VAW","VAX","VAY","VAZ","VBA","VBB",
-"VBC","VBD","VBE","VBF","VBG","VBH","VBI","VBJ","VBK","VBL","VBM","VBN","VBO","VBP","VBQ","VBR",
-"VBS","VBT","VBU","VBV","VBW","VBX","VBY","VBZ","VCA","VCB","VCC","VCD","VCE","VCF","VCG","VCH",
-"VCI","VCJ","VCK","VCL","VCM","VCN","VCO","VCP","VCQ","VCR","VCS","VCT","VCU","VCV","VCW","VCX",
-"VCY","VCZ","VDA","VDB","VDC","VDD","VDE","VDF","VDG","VDH","VDI","VDJ","VDK","VDL","VDM","VDN",
-"VDO","VDP","VDQ","VDR","VDS","VDT","VDU","VDV","VDW","VDX","VDY","VDZ","VEA","VEB","VEC","VED",
-"VEE","VEF","VEG","VEH","VEI","VEJ","VEK","VEL","VEM","VEN","VEO","VEP","VEQ","VER","VES","VET",
-"VEU","VEV","VEW","VEX","VEY","VEZ","VFA","VFB","VFC","VFD","VFE","VFF","VFG","VFH","VFI","VFJ",
-"VFK","VFL","VFM","VFN","VFO","VFP","VFQ","VFR","VFS","VFT","VFU","VFV","VFW","VFX","VFY","VFZ",
-"VGA","VGB","VGC","VGD","VGE","VGF","VGG","VGH","VGI","VGJ","VGK","VGL","VGM","VGN","VGO","VGP",
-"VGQ","VGR","VGS","VGT","VGU","VGV","VGW","VGX","VGY","VGZ","VHA","VHB","VHC","VHD","VHE","VHF",
-"VHG","VHH","VHI","VHJ","VHK","VHL","VHM","VHN","VHO","VHP","VHQ","VHR","VHS","VHT","VHU","VHV",
-"VHW","VHX","VHY","VHZ","VIA","VIB","VIC","VID","VIE","VIF","VIG","VIH","VII","VIJ","VIK","VIL",
-"VIM","VIN","VIO","VIP","VIQ","VIR","VIS","VIT","VIU","VIV","VIW","VIX","VIY","VIZ","VJA","VJB",
-"VJC","VJD","VJE","VJF","VJG","VJH","VJI","VJJ","VJK","VJL","VJM","VJN","VJO","VJP","VJQ","VJR",
-"VJS","VJT","VJU","VJV","VJW","VJX","VJY","VJZ","VKA","VKB","VKC","VKD","VKE","VKF","VKG","VKH",
-"VKI","VKJ","VKK","VKL","VKM","VKN","VKO","VKP","VKQ","VKR","VKS","VKT","VKU","VKV","VKW","VKX",
-"VKY","VKZ","VLA","VLB","VLC","VLD","VLE","VLF","VLG","VLH","VLI","VLJ","VLK","VLL","VLM","VLN",
-"VLO","VLP","VLQ","VLR","VLS","VLT","VLU","VLV","VLW","VLX","VLY","VLZ","VMA","VMB","VMC","VMD",
-"VME","VMF","VMG","VMH","VMI","VMJ","VMK","VML","VMM","VMN","VMO","VMP","VMQ","VMR","VMS","VMT",
-"VMU","VMV","VMW","VMX","VMY","VMZ","VNA","VNB","VNC","VND","VNE","VNF","VNG","VNH","VNI","VNJ",
-"VNK","VNL","VNM","VNN","VNO","VNP","VNQ","VNR","VNS","VNT","VNU","VNV","VNW","VNX","VNY","VNZ",
-"VOA","VOB","VOC","VOD","VOE","VOF","VOG","VOH","VOI","VOJ","VOK","VOL","VOM","VON","VOO","VOP",
-"VOQ","VOR","VOS","VOT","VOU","VOV","VOW","VOX","VOY","VOZ","VPA","VPB","VPC","VPD","VPE","VPF",
-"VPG","VPH","VPI","VPJ","VPK","VPL","VPM","VPN","VPO","VPP","VPQ","VPR","VPS","VPT","VPU","VPV",
-"VPW","VPX","VPY","VPZ","VQA","VQB","VQC","VQD","VQE","VQF","VQG","VQH","VQI","VQJ","VQK","VQL",
-"VQM","VQN","VQO","VQP","VQQ","VQR","VQS","VQT","VQU","VQV","VQW","VQX","VQY","VQZ","VRA","VRB",
-"VRC","VRD","VRE","VRF","VRG","VRH","VRI","VRJ","VRK","VRL","VRM","VRN","VRO","VRP","VRQ","VRR",
-"VRS","VRT","VRU","VRV","VRW","VRX","VRY","VRZ","VSA","VSB","VSC","VSD","VSE","VSF","VSG","VSH",
-"VSI","VSJ","VSK","VSL","VSM","VSN","VSO","VSP","VSQ","VSR","VSS","VST","VSU","VSV","VSW","VSX",
-"VSY","VSZ","VTA","VTB","VTC","VTD","VTE","VTF","VTG","VTH","VTI","VTJ","VTK","VTL","VTM","VTN",
-"VTO","VTP","VTQ","VTR","VTS","VTT","VTU","VTV","VTW","VTX","VTY","VTZ","VUA","VUB","VUC","VUD",
-"VUE","VUF","VUG","VUH","VUI","VUJ","VUK","VUL","VUM","VUN","VUO","VUP","VUQ","VUR","VUS","VUT",
-"VUU","VUV","VUW","VUX","VUY","VUZ","VVA","VVB","VVC","VVD","VVE","VVF","VVG","VVH","VVI","VVJ",
-"VVK","VVL","VVM","VVN","VVO","VVP","VVQ","VVR","VVS","VVT","VVU","VVV","VVW","VVX","VVY","VVZ",
-"VWA","VWB","VWC","VWD","VWE","VWF","VWG","VWH","VWI","VWJ","VWK","VWL","VWM","VWN","VWO","VWP",
-"VWQ","VWR","VWS","VWT","VWU","VWV","VWW","VWX","VWY","VWZ","VXA","VXB","VXC","VXD","VXE","VXF",
-"VXG","VXH","VXI","VXJ","VXK","VXL","VXM","VXN","VXO","VXP","VXQ","VXR","VXS","VXT","VXU","VXV",
-"VXW","VXX","VXY","VXZ","VYA","VYB","VYC","VYD","VYE","VYF","VYG","VYH","VYI","VYJ","VYK","VYL",
-"VYM","VYN","VYO","VYP","VYQ","VYR","VYS","VYT","VYU","VYV","VYW","VYX","VYY","VYZ","VZA","VZB",
-"VZC","VZD","VZE","VZF","VZG","VZH","VZI","VZJ","VZK","VZL","VZM","VZN","VZO","VZP","VZQ","VZR",
-"VZS","VZT","VZU","VZV","VZW","VZX","VZY","VZZ","WAA","WAB","WAC","WAD","WAE","WAF","WAG","WAH",
-"WAI","WAJ","WAK","WAL","WAM","WAN","WAO","WAP","WAQ","WAR","WAS","WAT","WAU","WAV","WAW","WAX",
-"WAY","WAZ","WBA","WBB","WBC","WBD","WBE","WBF","WBG","WBH","WBI","WBJ","WBK","WBL","WBM","WBN",
-"WBO","WBP","WBQ","WBR","WBS","WBT","WBU","WBV","WBW","WBX","WBY","WBZ","WCA","WCB","WCC","WCD",
-"WCE","WCF","WCG","WCH","WCI","WCJ","WCK","WCL","WCM","WCN","WCO","WCP","WCQ","WCR","WCS","WCT",
-"WCU","WCV","WCW","WCX","WCY","WCZ","WDA","WDB","WDC","WDD","WDE","WDF","WDG","WDH","WDI","WDJ",
-"WDK","WDL","WDM","WDN","WDO","WDP","WDQ","WDR","WDS","WDT","WDU","WDV","WDW","WDX","WDY","WDZ",
-"WEA","WEB","WEC","WED","WEE","WEF","WEG","WEH","WEI","WEJ","WEK","WEL","WEM","WEN","WEO","WEP",
-"WEQ","WER","WES","WET","WEU","WEV","WEW","WEX","WEY","WEZ","WFA","WFB","WFC","WFD","WFE","WFF",
-"WFG","WFH","WFI","WFJ","WFK","WFL","WFM","WFN","WFO","WFP","WFQ","WFR","WFS","WFT","WFU","WFV",
-"WFW","WFX","WFY","WFZ","WGA","WGB","WGC","WGD","WGE","WGF","WGG","WGH","WGI","WGJ","WGK","WGL",
-"WGM","WGN","WGO","WGP","WGQ","WGR","WGS","WGT","WGU","WGV","WGW","WGX","WGY","WGZ","WHA","WHB",
-"WHC","WHD","WHE","WHF","WHG","WHH","WHI","WHJ","WHK","WHL","WHM","WHN","WHO","WHP","WHQ","WHR",
-"WHS","WHT","WHU","WHV","WHW","WHX","WHY","WHZ","WIA","WIB","WIC","WID","WIE","WIF","WIG","WIH",
-"WII","WIJ","WIK","WIL","WIM","WIN","WIO","WIP","WIQ","WIR","WIS","WIT","WIU","WIV","WIW","WIX",
-"WIY","WIZ","WJA","WJB","WJC","WJD","WJE","WJF","WJG","WJH","WJI","WJJ","WJK","WJL","WJM","WJN",
-"WJO","WJP","WJQ","WJR","WJS","WJT","WJU","WJV","WJW","WJX","WJY","WJZ","WKA","WKB","WKC","WKD",
-"WKE","WKF","WKG","WKH","WKI","WKJ","WKK","WKL","WKM","WKN","WKO","WKP","WKQ","WKR","WKS","WKT",
-"WKU","WKV","WKW","WKX","WKY","WKZ","WLA","WLB","WLC","WLD","WLE","WLF","WLG","WLH","WLI","WLJ",
-"WLK","WLL","WLM","WLN","WLO","WLP","WLQ","WLR","WLS","WLT","WLU","WLV","WLW","WLX","WLY","WLZ",
-"WMA","WMB","WMC","WMD","WME","WMF","WMG","WMH","WMI","WMJ","WMK","WML","WMM","WMN","WMO","WMP",
-"WMQ","WMR","WMS","WMT","WMU","WMV","WMW","WMX","WMY","WMZ","WNA","WNB","WNC","WND","WNE","WNF",
-"WNG","WNH","WNI","WNJ","WNK","WNL","WNM","WNN","WNO","WNP","WNQ","WNR","WNS","WNT","WNU","WNV",
-"WNW","WNX","WNY","WNZ","WOA","WOB","WOC","WOD","WOE","WOF","WOG","WOH","WOI","WOJ","WOK","WOL",
-"WOM","WON","WOO","WOP","WOQ","WOR","WOS","WOT","WOU","WOV","WOW","WOX","WOY","WOZ","WPA","WPB",
-"WPC","WPD","WPE","WPF","WPG","WPH","WPI","WPJ","WPK","WPL","WPM","WPN","WPO","WPP","WPQ","WPR",
-"WPS","WPT","WPU","WPV","WPW","WPX","WPY","WPZ","WQA","WQB","WQC","WQD","WQE","WQF","WQG","WQH",
-"WQI","WQJ","WQK","WQL","WQM","WQN","WQO","WQP","WQQ","WQR","WQS","WQT","WQU","WQV","WQW","WQX",
-"WQY","WQZ","WRA","WRB","WRC","WRD","WRE","WRF","WRG","WRH","WRI","WRJ","WRK","WRL","WRM","WRN",
-"WRO","WRP","WRQ","WRR","WRS","WRT","WRU","WRV","WRW","WRX","WRY","WRZ","WSA","WSB","WSC","WSD",
-"WSE","WSF","WSG","WSH","WSI","WSJ","WSK","WSL","WSM","WSN","WSO","WSP","WSQ","WSR","WSS","WST",
-"WSU","WSV","WSW","WSX","WSY","WSZ","WTA","WTB","WTC","WTD","WTE","WTF","WTG","WTH","WTI","WTJ",
-"WTK","WTL","WTM","WTN","WTO","WTP","WTQ","WTR","WTS","WTT","WTU","WTV","WTW","WTX","WTY","WTZ",
-"WUA","WUB","WUC","WUD","WUE","WUF","WUG","WUH","WUI","WUJ","WUK","WUL","WUM","WUN","WUO","WUP",
-"WUQ","WUR","WUS","WUT","WUU","WUV","WUW","WUX","WUY","WUZ","WVA","WVB","WVC","WVD","WVE","WVF",
-"WVG","WVH","WVI","WVJ","WVK","WVL","WVM","WVN","WVO","WVP","WVQ","WVR","WVS","WVT","WVU","WVV",
-"WVW","WVX","WVY","WVZ","WWA","WWB","WWC","WWD","WWE","WWF","WWG","WWH","WWI","WWJ","WWK","WWL",
-"WWM","WWN","WWO","WWP","WWQ","WWR","WWS","WWT","WWU","WWV","WWW","WWX","WWY","WWZ","WXA","WXB",
-"WXC","WXD","WXE","WXF","WXG","WXH","WXI","WXJ","WXK","WXL","WXM","WXN","WXO","WXP","WXQ","WXR",
-"WXS","WXT","WXU","WXV","WXW","WXX","WXY","WXZ","WYA","WYB","WYC","WYD","WYE","WYF","WYG","WYH",
-"WYI","WYJ","WYK","WYL","WYM","WYN","WYO","WYP","WYQ","WYR","WYS","WYT","WYU","WYV","WYW","WYX",
-"WYY","WYZ","WZA","WZB","WZC","WZD","WZE","WZF","WZG","WZH","WZI","WZJ","WZK","WZL","WZM","WZN",
-"WZO","WZP","WZQ","WZR","WZS","WZT","WZU","WZV","WZW","WZX","WZY","WZZ","XAA","XAB","XAC","XAD",
-"XAE","XAF","XAG","XAH","XAI","XAJ","XAK","XAL","XAM","XAN","XAO","XAP","XAQ","XAR","XAS","XAT",
-"XAU","XAV","XAW","XAX","XAY","XAZ","XBA","XBB","XBC","XBD","XBE","XBF","XBG","XBH","XBI","XBJ",
-"XBK","XBL","XBM","XBN","XBO","XBP","XBQ","XBR","XBS","XBT","XBU","XBV","XBW","XBX","XBY","XBZ",
-"XCA","XCB","XCC","XCD","XCE","XCF","XCG","XCH","XCI","XCJ","XCK","XCL","XCM","XCN","XCO","XCP",
-"XCQ","XCR","XCS","XCT","XCU","XCV","XCW","XCX","XCY","XCZ","XDA","XDB","XDC","XDD","XDE","XDF",
-"XDG","XDH","XDI","XDJ","XDK","XDL","XDM","XDN","XDO","XDP","XDQ","XDR","XDS","XDT","XDU","XDV",
-"XDW","XDX","XDY","XDZ","XEA","XEB","XEC","XED","XEE","XEF","XEG","XEH","XEI","XEJ","XEK","XEL",
-"XEM","XEN","XEO","XEP","XEQ","XER","XES","XET","XEU","XEV","XEW","XEX","XEY","XEZ","XFA","XFB",
-"XFC","XFD","XFE","XFF","XFG","XFH","XFI","XFJ","XFK","XFL","XFM","XFN","XFO","XFP","XFQ","XFR",
-"XFS","XFT","XFU","XFV","XFW","XFX","XFY","XFZ","XGA","XGB","XGC","XGD","XGE","XGF","XGG","XGH",
-"XGI","XGJ","XGK","XGL","XGM","XGN","XGO","XGP","XGQ","XGR","XGS","XGT","XGU","XGV","XGW","XGX",
-"XGY","XGZ","XHA","XHB","XHC","XHD","XHE","XHF","XHG","XHH","XHI","XHJ","XHK","XHL","XHM","XHN",
-"XHO","XHP","XHQ","XHR","XHS","XHT","XHU","XHV","XHW","XHX","XHY","XHZ","XIA","XIB","XIC","XID",
-"XIE","XIF","XIG","XIH","XII","XIJ","XIK","XIL","XIM","XIN","XIO","XIP","XIQ","XIR","XIS","XIT",
-"XIU","XIV","XIW","XIX","XIY","XIZ","XJA","XJB","XJC","XJD","XJE","XJF","XJG","XJH","XJI","XJJ",
-"XJK","XJL","XJM","XJN","XJO","XJP","XJQ","XJR","XJS","XJT","XJU","XJV","XJW","XJX","XJY","XJZ",
-"XKA","XKB","XKC","XKD","XKE","XKF","XKG","XKH","XKI","XKJ","XKK","XKL","XKM","XKN","XKO","XKP",
-"XKQ","XKR","XKS","XKT","XKU","XKV","XKW","XKX","XKY","XKZ","XLA","XLB","XLC","XLD","XLE","XLF",
-"XLG","XLH","XLI","XLJ","XLK","XLL","XLM","XLN","XLO","XLP","XLQ","XLR","XLS","XLT","XLU","XLV",
-"XLW","XLX","XLY","XLZ","XMA","XMB","XMC","XMD","XME","XMF","XMG","XMH","XMI","XMJ","XMK","XML",
-"XMM","XMN","XMO","XMP","XMQ","XMR","XMS","XMT","XMU","XMV","XMW","XMX","XMY","XMZ","XNA","XNB",
-"XNC","XND","XNE","XNF","XNG","XNH","XNI","XNJ","XNK","XNL","XNM","XNN","XNO","XNP","XNQ","XNR",
-"XNS","XNT","XNU","XNV","XNW","XNX","XNY","XNZ","XOA","XOB","XOC","XOD","XOE","XOF","XOG","XOH",
-"XOI","XOJ","XOK","XOL","XOM","XON","XOO","XOP","XOQ","XOR","XOS","XOT","XOU","XOV","XOW","XOX",
-"XOY","XOZ","XPA","XPB","XPC","XPD","XPE","XPF","XPG","XPH","XPI","XPJ","XPK","XPL","XPM","XPN",
-"XPO","XPP","XPQ","XPR","XPS","XPT","XPU","XPV","XPW","XPX","XPY","XPZ","XQA","XQB","XQC","XQD",
-"XQE","XQF","XQG","XQH","XQI","XQJ","XQK","XQL","XQM","XQN","XQO","XQP","XQQ","XQR","XQS","XQT",
-"XQU","XQV","XQW","XQX","XQY","XQZ","XRA","XRB","XRC","XRD","XRE","XRF","XRG","XRH","XRI","XRJ",
-"XRK","XRL","XRM","XRN","XRO","XRP","XRQ","XRR","XRS","XRT","XRU","XRV","XRW","XRX","XRY","XRZ",
-"XSA","XSB","XSC","XSD","XSE","XSF","XSG","XSH","XSI","XSJ","XSK","XSL","XSM","XSN","XSO","XSP",
-"XSQ","XSR","XSS","XST","XSU","XSV","XSW","XSX","XSY","XSZ","XTA","XTB","XTC","XTD","XTE","XTF",
-"XTG","XTH","XTI","XTJ","XTK","XTL","XTM","XTN","XTO","XTP","XTQ","XTR","XTS","XTT","XTU","XTV",
-"XTW","XTX","XTY","XTZ","XUA","XUB","XUC","XUD","XUE","XUF","XUG","XUH","XUI","XUJ","XUK","XUL",
-"XUM","XUN","XUO","XUP","XUQ","XUR","XUS","XUT","XUU","XUV","XUW","XUX","XUY","XUZ","XVA","XVB",
-"XVC","XVD","XVE","XVF","XVG","XVH","XVI","XVJ","XVK","XVL","XVM","XVN","XVO","XVP","XVQ","XVR",
-"XVS","XVT","XVU","XVV","XVW","XVX","XVY","XVZ","XWA","XWB","XWC","XWD","XWE","XWF","XWG","XWH",
-"XWI","XWJ","XWK","XWL","XWM","XWN","XWO","XWP","XWQ","XWR","XWS","XWT","XWU","XWV","XWW","XWX",
-"XWY","XWZ","XXA","XXB","XXC","XXD","XXE","XXF","XXG","XXH","XXI","XXJ","XXK","XXL","XXM","XXN",
-"XXO","XXP","XXQ","XXR","XXS","XXT","XXU","XXV","XXW","XXX","XXY","XXZ","XYA","XYB","XYC","XYD",
-"XYE","XYF","XYG","XYH","XYI","XYJ","XYK","XYL","XYM","XYN","XYO","XYP","XYQ","XYR","XYS","XYT",
-"XYU","XYV","XYW","XYX","XYY","XYZ","XZA","XZB","XZC","XZD","XZE","XZF","XZG","XZH","XZI","XZJ",
-"XZK","XZL","XZM","XZN","XZO","XZP","XZQ","XZR","XZS","XZT","XZU","XZV","XZW","XZX","XZY","XZZ",
-
-"YAA","YAB","YAC","YAD",
-"YAE","YAF","YAG","YAH","YAI","YAJ","YAK","YAL","YAM","YAN","YAO","YAP","YAQ","YAR","YAS","YAT",
-"YAU","YAV","YAW","YAX","YAY","YAZ","YBA","YBB","YBC","YBD","YBE","YBF","YBG","YBH","YBI","YBJ",
-"YBK","YBL","YBM","YBN","YBO","YBP","YBQ","YBR","YBS","YBT","YBU","YBV","YBW","YBX","YBY","YBZ",
-"YCA","YCB","YCC","YCD","YCE","YCF","YCG","YCH","YCI","YCJ","YCK","YCL","YCM","YCN","YCO","YCP",
-"YCQ","YCR","YCS","YCT","YCU","YCV","YCW","YCX","YCY","YCZ","YDA","YDB","YDC","YDD","YDE","YDF",
-"YDG","YDH","YDI","YDJ","YDK","YDL","YDM","YDN","YDO","YDP","YDQ","YDR","YDS","YDT","YDU","YDV",
-"YDW","YDX","YDY","YDZ","YEA","YEB","YEC","YED","YEE","YEF","YEG","YEH","YEI","YEJ","YEK","YEL",
-"YEM","YEN","YEO","YEP","YEQ","YER","YES","YET","YEU","YEV","YEW","YEX","YEY","YEZ","YFA","YFB",
-"YFC","YFD","YFE","YFF","YFG","YFH","YFI","YFJ","YFK","YFL","YFM","YFN","YFO","YFP","YFQ","YFR",
-"YFS","YFT","YFU","YFV","YFW","YFX","YFY","YFZ","YGA","YGB","YGC","YGD","YGE","YGF","YGG","YGH",
-"YGI","YGJ","YGK","YGL","YGM","YGN","YGO","YGP","YGQ","YGR","YGS","YGT","YGU","YGV","YGW","YGX",
-"YGY","YGZ","YHA","YHB","YHC","YHD","YHE","YHF","YHG","YHH","YHI","YHJ","YHK","YHL","YHM","YHN",
-"YHO","YHP","YHQ","YHR","YHS","YHT","YHU","YHV","YHW","YHX","YHY","YHZ","YIA","YIB","YIC","YID",
-"YIE","YIF","YIG","YIH","YII","YIJ","YIK","YIL","YIM","YIN","YIO","YIP","YIQ","YIR","YIS","YIT",
-"YIU","YIV","YIW","YIX","YIY","YIZ","YJA","YJB","YJC","YJD","YJE","YJF","YJG","YJH","YJI","YJJ",
-"YJK","YJL","YJM","YJN","YJO","YJP","YJQ","YJR","YJS","YJT","YJU","YJV","YJW","YJX","YJY","YJZ",
-"YKA","YKB","YKC","YKD","YKE","YKF","YKG","YKH","YKI","YKJ","YKK","YKL","YKM","YKN","YKO","YKP",
-"YKQ","YKR","YKS","YKT","YKU","YKV","YKW","YKX","YKY","YKZ","YLA","YLB","YLC","YLD","YLE","YLF",
-"YLG","YLH","YLI","YLJ","YLK","YLL","YLM","YLN","YLO","YLP","YLQ","YLR","YLS","YLT","YLU","YLV",
-"YLW","YLX","YLY","YLZ","YMA","YMB","YMC","YMD","YME","YMF","YMG","YMH","YMI","YMJ","YMK","YML",
-"YMM","YMN","YMO","YMP","YMQ","YMR","YMS","YMT","YMU","YMV","YMW","YMX","YMY","YMZ","YNA","YNB",
-"YNC","YND","YNE","YNF","YNG","YNH","YNI","YNJ","YNK","YNL","YNM","YNN","YNO","YNP","YNQ","YNR",
-"YNS","YNT","YNU","YNV","YNW","YNX","YNY","YNZ","YOA","YOB","YOC","YOD","YOE","YOF","YOG","YOH",
-"YOI","YOJ","YOK","YOL","YOM","YON","YOO","YOP","YOQ","YOR","YOS","YOT","YOU","YOV","YOW","YOX",
-"YOY","YOZ","YPA","YPB","YPC","YPD","YPE","YPF","YPG","YPH","YPI","YPJ","YPK","YPL","YPM","YPN",
-"YPO","YPP","YPQ","YPR","YPS","YPT","YPU","YPV","YPW","YPX","YPY","YPZ","YQA","YQB","YQC","YQD",
-"YQE","YQF","YQG","YQH","YQI","YQJ","YQK","YQL","YQM","YQN","YQO","YQP","YQQ","YQR","YQS","YQT",
-"YQU","YQV","YQW","YQX","YQY","YQZ","YRA","YRB","YRC","YRD","YRE","YRF","YRG","YRH","YRI","YRJ",
-"YRK","YRL","YRM","YRN","YRO","YRP","YRQ","YRR","YRS","YRT","YRU","YRV","YRW","YRX","YRY","YRZ",
-"YSA","YSB","YSC","YSD","YSE","YSF","YSG","YSH","YSI","YSJ","YSK","YSL","YSM","YSN","YSO","YSP",
-"YSQ","YSR","YSS","YST","YSU","YSV","YSW","YSX","YSY","YSZ","YTA","YTB","YTC","YTD","YTE","YTF",
-"YTG","YTH","YTI","YTJ","YTK","YTL","YTM","YTN","YTO","YTP","YTQ","YTR","YTS","YTT","YTU","YTV",
-"YTW","YTX","YTY","YTZ","YUA","YUB","YUC","YUD","YUE","YUF","YUG","YUH","YUI","YUJ","YUK","YUL",
-"YUM","YUN","YUO","YUP","YUQ","YUR","YUS","YUT","YUU","YUV","YUW","YUX","YUY","YUZ","YVA","YVB",
-"YVC","YVD","YVE","YVF","YVG","YVH","YVI","YVJ","YVK","YVL","YVM","YVN","YVO","YVP","YVQ","YVR",
-"YVS","YVT","YVU","YVV","YVW","YVX","YVY","YVZ","YWA","YWB","YWC","YWD","YWE","YWF","YWG","YWH",
-"YWI","YWJ","YWK","YWL","YWM","YWN","YWO","YWP","YWQ","YWR","YWS","YWT","YWU","YWV","YWW","YWX",
-"YWY","YWZ","YXA","YXB","YXC","YXD","YXE","YXF","YXG","YXH","YXI","YXJ","YXK","YXL","YXM","YXN",
-"YXO","YXP","YXQ","YXR","YXS","YXT","YXU","YXV","YXW","YXX","YXY","YXZ","YYA","YYB","YYC","YYD",
-"YYE","YYF","YYG","YYH","YYI","YYJ","YYK","YYL","YYM","YYN","YYO","YYP","YYQ","YYR","YYS","YYT",
-"YYU","YYV","YYW","YYX","YYY","YYZ","YZA","YZB","YZC","YZD","YZE","YZF","YZG","YZH","YZI","YZJ",
-"YZK","YZL","YZM","YZN","YZO","YZP","YZQ","YZR","YZS","YZT","YZU","YZV","YZW","YZX","YZY","YZZ",
-
-"ZAA","ZAB","ZAC","ZAD",
-"ZAE","ZAF","ZAG","ZAH","ZAI","ZAJ","ZAK","ZAL","ZAM","ZAN","ZAO","ZAP","ZAQ","ZAR","ZAS","ZAT",
-"ZAU","ZAV","ZAW","ZAX","ZAY","ZAZ","ZBA","ZBB","ZBC","ZBD","ZBE","ZBF","ZBG","ZBH","ZBI","ZBJ",
-"ZBK","ZBL","ZBM","ZBN","ZBO","ZBP","ZBQ","ZBR","ZBS","ZBT","ZBU","ZBV","ZBW","ZBX","ZBY","ZBZ",
-"ZCA","ZCB","ZCC","ZCD","ZCE","ZCF","ZCG","ZCH","ZCI","ZCJ","ZCK","ZCL","ZCM","ZCN","ZCO","ZCP",
-"ZCQ","ZCR","ZCS","ZCT","ZCU","ZCV","ZCW","ZCX","ZCY","ZCZ","ZDA","ZDB","ZDC","ZDD","ZDE","ZDF",
-"ZDG","ZDH","ZDI","ZDJ","ZDK","ZDL","ZDM","ZDN","ZDO","ZDP","ZDQ","ZDR","ZDS","ZDT","ZDU","ZDV",
-"ZDW","ZDX","ZDY","ZDZ","ZEA","ZEB","ZEC","ZED","ZEE","ZEF","ZEG","ZEH","ZEI","ZEJ","ZEK","ZEL",
-"ZEM","ZEN","ZEO","ZEP","ZEQ","ZER","ZES","ZET","ZEU","ZEV","ZEW","ZEX","ZEY","ZEZ","ZFA","ZFB",
-"ZFC","ZFD","ZFE","ZFF","ZFG","ZFH","ZFI","ZFJ","ZFK","ZFL","ZFM","ZFN","ZFO","ZFP","ZFQ","ZFR",
-"ZFS","ZFT","ZFU","ZFV","ZFW","ZFX","ZFY","ZFZ","ZGA","ZGB","ZGC","ZGD","ZGE","ZGF","ZGG","ZGH",
-"ZGI","ZGJ","ZGK","ZGL","ZGM","ZGN","ZGO","ZGP","ZGQ","ZGR","ZGS","ZGT","ZGU","ZGV","ZGW","ZGX",
-"ZGY","ZGZ","ZHA","ZHB","ZHC","ZHD","ZHE","ZHF","ZHG","ZHH","ZHI","ZHJ","ZHK","ZHL","ZHM","ZHN",
-"ZHO","ZHP","ZHQ","ZHR","ZHS","ZHT","ZHU","ZHV","ZHW","ZHX","ZHY","ZHZ","ZIA","ZIB","ZIC","ZID",
-"ZIE","ZIF","ZIG","ZIH","ZII","ZIJ","ZIK","ZIL","ZIM","ZIN","ZIO","ZIP","ZIQ","ZIR","ZIS","ZIT",
-"ZIU","ZIV","ZIW","ZIX","ZIY","ZIZ","ZJA","ZJB","ZJC","ZJD","ZJE","ZJF","ZJG","ZJH","ZJI","ZJJ",
-"ZJK","ZJL","ZJM","ZJN","ZJO","ZJP","ZJQ","ZJR","ZJS","ZJT","ZJU","ZJV","ZJW","ZJX","ZJY","ZJZ",
-"ZKA","ZKB","ZKC","ZKD","ZKE","ZKF","ZKG","ZKH","ZKI","ZKJ","ZKK","ZKL","ZKM","ZKN","ZKO","ZKP",
-"ZKQ","ZKR","ZKS","ZKT","ZKU","ZKV","ZKW","ZKX","ZKY","ZKZ","ZLA","ZLB","ZLC","ZLD","ZLE","ZLF",
-"ZLG","ZLH","ZLI","ZLJ","ZLK","ZLL","ZLM","ZLN","ZLO","ZLP","ZLQ","ZLR","ZLS","ZLT","ZLU","ZLV",
-"ZLW","ZLX","ZLY","ZLZ","ZMA","ZMB","ZMC","ZMD","ZME","ZMF","ZMG","ZMH","ZMI","ZMJ","ZMK","ZML",
-"ZMM","ZMN","ZMO","ZMP","ZMQ","ZMR","ZMS","ZMT","ZMU","ZMV","ZMW","ZMX","ZMY","ZMZ","ZNA","ZNB",
-"ZNC","ZND","ZNE","ZNF","ZNG","ZNH","ZNI","ZNJ","ZNK","ZNL","ZNM","ZNN","ZNO","ZNP","ZNQ","ZNR",
-"ZNS","ZNT","ZNU","ZNV","ZNW","ZNX","ZNY","ZNZ","ZOA","ZOB","ZOC","ZOD","ZOE","ZOF","ZOG","ZOH",
-"ZOI","ZOJ","ZOK","ZOL","ZOM","ZON","ZOO","ZOP","ZOQ","ZOR","ZOS","ZOT","ZOU","ZOV","ZOW","ZOX",
-"ZOY","ZOZ","ZPA","ZPB","ZPC","ZPD","ZPE","ZPF","ZPG","ZPH","ZPI","ZPJ","ZPK","ZPL","ZPM","ZPN",
-"ZPO","ZPP","ZPQ","ZPR","ZPS","ZPT","ZPU","ZPV","ZPW","ZPX","ZPY","ZPZ","ZQA","ZQB","ZQC","ZQD",
-"ZQE","ZQF","ZQG","ZQH","ZQI","ZQJ","ZQK","ZQL","ZQM","ZQN","ZQO","ZQP","ZQQ","ZQR","ZQS","ZQT",
-"ZQU","ZQV","ZQW","ZQX","ZQY","ZQZ","ZRA","ZRB","ZRC","ZRD","ZRE","ZRF","ZRG","ZRH","ZRI","ZRJ",
-"ZRK","ZRL","ZRM","ZRN","ZRO","ZRP","ZRQ","ZRR","ZRS","ZRT","ZRU","ZRV","ZRW","ZRX","ZRY","ZRZ",
-"ZSA","ZSB","ZSC","ZSD","ZSE","ZSF","ZSG","ZSH","ZSI","ZSJ","ZSK","ZSL","ZSM","ZSN","ZSO","ZSP",
-"ZSQ","ZSR","ZSS","ZST","ZSU","ZSV","ZSW","ZSX","ZSY","ZSZ","ZTA","ZTB","ZTC","ZTD","ZTE","ZTF",
-"ZTG","ZTH","ZTI","ZTJ","ZTK","ZTL","ZTM","ZTN","ZTO","ZTP","ZTQ","ZTR","ZTS","ZTT","ZTU","ZTV",
-"ZTW","ZTX","ZTY","ZTZ","ZUA","ZUB","ZUC","ZUD","ZUE","ZUF","ZUG","ZUH","ZUI","ZUJ","ZUK","ZUL",
-"ZUM","ZUN","ZUO","ZUP","ZUQ","ZUR","ZUS","ZUT","ZUU","ZUV","ZUW","ZUX","ZUY","ZUZ","ZVA","ZVB",
-"ZVC","ZVD","ZVE","ZVF","ZVG","ZVH","ZVI","ZVJ","ZVK","ZVL","ZVM","ZVN","ZVO","ZVP","ZVQ","ZVR",
-"ZVS","ZVT","ZVU","ZVV","ZVW","ZVX","ZVY","ZVZ","ZWA","ZWB","ZWC","ZWD","ZWE","ZWF","ZWG","ZWH",
-"ZWI","ZWJ","ZWK","ZWL","ZWM","ZWN","ZWO","ZWP","ZWQ","ZWR","ZWS","ZWT","ZWU","ZWV","ZWW","ZWX",
-"ZWY","ZWZ","ZXA","ZXB","ZXC","ZXD","ZXE","ZXF","ZXG","ZXH","ZXI","ZXJ","ZXK","ZXL","ZXM","ZXN",
-"ZXO","ZXP","ZXQ","ZXR","ZXS","ZXT","ZXU","ZXV","ZXW","ZXX","ZXY","ZXZ","ZYA","ZYB","ZYC","ZYD",
-"ZYE","ZYF","ZYG","ZYH","ZYI","ZYJ","ZYK","ZYL","ZYM","ZYN","ZYO","ZYP","ZYQ","ZYR","ZYS","ZYT",
-"ZYU","ZYV","ZYW","ZYX","ZYY","ZYZ","ZZA","ZZB","ZZC","ZZD","ZZE","ZZF","ZZG","ZZH","ZZI","ZZJ",
-"ZZK","ZZL","ZZM","ZZN","ZZO","ZZP","ZZQ","ZZR","ZZS","ZZT","ZZU","ZZV","ZZW","ZZX","ZZY","ZZZ"
-]
-
-doublets = [ 
-"AA","AB","AC","AD","AE","AF","AG","AH","AI","AJ","AK","AL","AM","AN","AO","AP",
-"AQ","AR","AS","AT","AU","AV","AW","AX","AY","AZ","BA","BB","BC","BD","BE","BF",
-"BG","BH","BI","BJ","BK","BL","BM","BN","BO","BP","BQ","BR","BS","BT","BU","BV",
-"BW","BX","BY","BZ","CA","CB","CC","CD","CE","CF","CG","CH","CI","CJ","CK","CL",
-"CM","CN","CO","CP","CQ","CR","CS","CT","CU","CV","CW","CX","CY","CZ","DA","DB",
-"DC","DD","DE","DF","DG","DH","DI","DJ","DK","DL","DM","DN","DO","DP","DQ","DR",
-"DS","DT","DU","DV","DW","DX","DY","DZ","EA","EB","EC","ED","EE","EF","EG","EH",
-"EI","EJ","EK","EL","EM","EN","EO","EP","EQ","ER","ES","ET","EU","EV","EW","EX",
-"EY","EZ","FA","FB","FC","FD","FE","FF","FG","FH","FI","FJ","FK","FL","FM","FN",
-"FO","FP","FQ","FR","FS","FT","FU","FV","FW","FX","FY","FZ","GA","GB","GC","GD",
-"GE","GF","GG","GH","GI","GJ","GK","GL","GM","GN","GO","GP","GQ","GR","GS","GT",
-"GU","GV","GW","GX","GY","GZ","HA","HB","HC","HD","HE","HF","HG","HH","HI","HJ",
-"HK","HL","HM","HN","HO","HP","HQ","HR","HS","HT","HU","HV","HW","HX","HY","HZ",
-"IA","IB","IC","ID","IE","IF","IG","IH","II","IJ","IK","IL","IM","IN","IO","IP",
-"IQ","IR","IS","IT","IU","IV","IW","IX","IY","IZ","JA","JB","JC","JD","JE","JF",
-"JG","JH","JI","JJ","JK","JL","JM","JN","JO","JP","JQ","JR","JS","JT","JU","JV",
-"JW","JX","JY","JZ","KA","KB","KC","KD","KE","KF","KG","KH","KI","KJ","KK","KL",
-"KM","KN","KO","KP","KQ","KR","KS","KT","KU","KV","KW","KX","KY","KZ","LA","LB",
-"LC","LD","LE","LF","LG","LH","LI","LJ","LK","LL","LM","LN","LO","LP","LQ","LR",
-"LS","LT","LU","LV","LW","LX","LY","LZ","MA","MB","MC","MD","ME","MF","MG","MH",
-"MI","MJ","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX",
-"MY","MZ","NA","NB","NC","ND","NE","NF","NG","NH","NI","NJ","NK","NL","NM","NN",
-"NO","NP","NQ","NR","NS","NT","NU","NV","NW","NX","NY","NZ","OA","OB","OC","OD",
-"OE","OF","OG","OH","OI","OJ","OK","OL","OM","ON","OO","OP","OQ","OR","OS","OT",
-"OU","OV","OW","OX","OY","OZ","PA","PB","PC","PD","PE","PF","PG","PH","PI","PJ",
-"PK","PL","PM","PN","PO","PP","PQ","PR","PS","PT","PU","PV","PW","PX","PY","PZ",
-"QA","QB","QC","QD","QE","QF","QG","QH","QI","QJ","QK","QL","QM","QN","QO","QP",
-"QQ","QR","QS","QT","QU","QV","QW","QX","QY","QZ","RA","RB","RC","RD","RE","RF",
-"RG","RH","RI","RJ","RK","RL","RM","RN","RO","RP","RQ","RR","RS","RT","RU","RV",
-"RW","RX","RY","RZ","SA","SB","SC","SD","SE","SF","SG","SH","SI","SJ","SK","SL",
-"SM","SN","SO","SP","SQ","SR","SS","ST","SU","SV","SW","SX","SY","SZ","TA","TB",
-"TC","TD","TE","TF","TG","TH","TI","TJ","TK","TL","TM","TN","TO","TP","TQ","TR",
-"TS","TT","TU","TV","TW","TX","TY","TZ","UA","UB","UC","UD","UE","UF","UG","UH",
-"UI","UJ","UK","UL","UM","UN","UO","UP","UQ","UR","US","UT","UU","UV","UW","UX",
-"UY","UZ","VA","VB","VC","VD","VE","VF","VG","VH","VI","VJ","VK","VL","VM","VN",
-"VO","VP","VQ","VR","VS","VT","VU","VV","VW","VX","VY","VZ","WA","WB","WC","WD",
-"WE","WF","WG","WH","WI","WJ","WK","WL","WM","WN","WO","WP","WQ","WR","WS","WT",
-"WU","WV","WW","WX","WY","WZ","XA","XB","XC","XD","XE","XF","XG","XH","XI","XJ",
-"XK","XL","XM","XN","XO","XP","XQ","XR","XS","XT","XU","XV","XW","XX","XY","XZ",
-"YA","YB","YC","YD","YE","YF","YG","YH","YI","YJ","YK","YL","YM","YN","YO","YP",
-"YQ","YR","YS","YT","YU","YV","YW","YX","YY","YZ","ZA","ZB","ZC","ZD","ZE","ZF",
-"ZG","ZH","ZI","ZJ","ZK","ZL","ZM","ZN","ZO","ZP","ZQ","ZR","ZS","ZT","ZU","ZV",
-"ZW","ZX","ZY","ZZ"
-]
-
-
-
-
-def get_sha256( text):
-  if ver2 < 25:
-    hasher = mhash.MHASH(mhash.MHASH_SHA256)
-  else:
-    # standard library hashlib since Python 2.5
-    hasher = hashlib.sha256()
-
-  hasher.update( text)
-  return hasher.digest()
-
-
-def triplet1( key):
-  b0 = ord( key[0])
-  b1 = ord( key[1]) & 0x3f
-  h = b0 | b1 << 8
-  return triplets[h]
-
-def triplet2( a):
-  b0 = ord( a[1]) & 0xc0   # 1100 0000
-  b1 = ord(a[2])           # 1111 1111
-  b2 = ord(a[3]) & 0x0f    # 0000 1111
-  h = ( b0 | b1 << 8  | b2 << 16 ) >> 6
-  return triplets[h];
-
-
-def triplet3( a):
-  b0 = ord(a[3]) & 0xf0    # 1111 0000
-  b1 = ord(a[4])           # 1111 1111
-  b2 = ord(a[5]) & 0x03    # 0000 0011
-  h =  (b0 | b1 << 8  | b2 << 16 ) >> 4
-  return triplets[h];
-
-def triplet4( a):
-  b0 = ord(a[5]) & 0xfc    # 1111 1100 
-  b1 = ord(a[6])           # 1111 1111 
-  h =  (b0 | b1 << 8 ) >> 2
-  return triplets[h];
-
-def dublet_28_to_36( a):
-  b0 = ord(a[3]) & 0xf0    # 1111 0000 
-  b1 = ord(a[4]) & 0x1f    # 0001 1111 
-  h =  ( b0 | b1 << 8 ) >> 4
-  return doublets[h];
-
-def dublet_56_to_64( a):
-  b0 = ord(a[7])           # 1111 1111 
-  b1 = ord(a[8]) & 0x01    # 0000 0001 
-  h =  ( b0 | b1 << 8);
-  return doublets[h];
-
-def major_digest( major):
-  dig = get_sha256( major)
-  part1 = triplet1( dig)
-  part2 = triplet2( dig)
-  part3 = triplet3( dig)
-  part4 = triplet4( dig)
-  part5 = dublet_56_to_64( dig)
-  ret = part1+part2+part3+part4+part5
-  return ret
-
-def minor_digest( minor):
-  dig = get_sha256( minor)
-  part1 = triplet1( dig)
-  part2 = triplet2( dig)
-  part3 = dublet_28_to_36( dig)
-  ret = part1+part2+part3
-  return ret
-
-def compute_inchi_check( key):
-  assert type( key) == type( "") or type( key) == type( u"")
-  if key.startswith( "InChIKey="):
-    key = key[9:]
-  m = re.match( "^([A-Z]{14})-([A-Z]{9})$", key)
-  if not m:
-    raise Exception( "The submitted string does not have the format of an base of InChIKey ('^[A-Z]{14}-[A-Z]{9}$')")
-  base = m.group(1) + m.group(2)
-  weight_vector = (1,3,5,7,9,11,15,17,19,21,23,25,1,3,5,7,9,11,15,17,19,21,23)
-  base_vector = [ord(x) for x in base]
-  new_check_num = sum( map( operator.mul, weight_vector, base_vector)) % 26
-  new_check = chr(new_check_num+65)
-  return new_check
-
-
-def flag( version, parts):
-  assert type( version) == type( 0) and 1 <= version <= 3
-  flag = 0
-  starts = set( [x[0] for x in parts])
-  if starts & set( "tbms"):
-    flag |= 0x01
-  if starts & set( "f"):
-    flag |= 0x02
-  if starts & set( "i"):
-    flag |= 0x04
-  version_flag_set = ["ABCDEFGH","IJKLMNOP","QRSTUVWX"]
-  return version_flag_set[int(version)-1][flag]
-
-
-def key_from_inchi( inp):
-  assert type( inp) == type( "") or type( inp) == type( u"")
-  if inp.startswith("InChI="):
-    inp = inp[6:]
-  parts = inp.split( "/")
-  version = parts[0]
-  if not version.isdigit():
-    if len( parts) == 1:
-      raise Exception( "Invalid InChI string '%s'" % inp)
-    else:
-      raise Exception( "Invalid data in version part of InChI - '%s' in '%s'" % (version, inp))
-  elif version not in "123":
-    raise Exception( "Unsupported InChI version '%s' in '%s'" % (version, inp))
-  del parts[0]
-  i = 1
-  next = True
-  parts_major = [parts[0]]
-  # sort the layers into a major and minor part - these are hashed separately
-  while next:
-    if i >= len( parts):
-      break
-    if parts[i][0] in "chq":
-      parts_major.append( parts[i])
-      i += 1
-    else:
-      next = False
-  major = "/".join( parts_major)
-  parts_minor = parts[i:]
-  minor = "/".join( parts_minor)
-  minor = minor and "/"+minor or minor
-  if len( minor) < 255: # interesting property of the original algorithm
-    minor = 2*minor
-  base = major_digest( major) + "-" + minor_digest( minor) + flag( int(version), parts_minor)
-  check = compute_inchi_check( base)
-  return base+check
-
-
-def check_inchi_key( key):
-  """checks the InChIKey using the algorithm described in the manual to InChI 1.02beta"""
-  assert type( key) == type( "") or type( key) == type( u"")
-  if key.startswith( "InChIKey="):
-    key = key[9:]
-  m = re.match( "^([A-Z]{14})-([A-Z]{9})([A-Z])$", key)
-  if not m:
-    raise Exception( "The submitted string does not have the format of an InChIKey ('[A-Z]{14}-[A-Z]{10}')")
-  base = m.group(1) + "-" + m.group(2)
-  orig_check = m.group(3)
-  new_check = compute_inchi_check( base)
-  if new_check != orig_check:
-    return False
-  return True
-
-
-if __name__ == "__main__":
-  inp = "InChI=1/C97H158N8O66S/c1-27-54(126)66(138)69(141)88(151-27)149-25-46-76(62(134)50(101-30(4)117)84(152-46)105-47(125)11-9-8-10-12-98-26-172)161-85-51(102-31(5)118)63(135)73(42(21-112)157-85)162-89-70(142)79(165-93-83(68(140)58(130)39(18-109)156-93)167-87-53(104-33(7)120)65(137)75(44(23-114)159-87)164-91-72(144)81(60(132)41(20-111)154-91)171-97(95(147)148)14-35(122)49(100-29(3)116)78(169-97)56(128)37(124)16-107)61(133)45(160-89)24-150-92-82(67(139)57(129)38(17-108)155-92)166-86-52(103-32(6)119)64(136)74(43(22-113)158-86)163-90-71(143)80(59(131)40(19-110)153-90)170-96(94(145)146)13-34(121)48(99-28(2)115)77(168-96)55(127)36(123)15-106/h27,34-46,48-93,106-114,121-124,126-144H,8-25H2,1-7H3,(H,99,115)(H,100,116)(H,101,117)(H,102,118)(H,103,119)(H,104,120)(H,105,125)(H,145,146)(H,147,148)/t27-,34-,35-,36+,37+,38+,39+,40+,41+,42+,43+,44+,45+,46+,48+,49+,50+,51+,52+,53+,54+,55+,56+,57+,58+,59-,60-,61+,62+,63+,64+,65+,66+,67-,68-,69-,70-,71+,72+,73+,74+,75+,76+,77+,78+,79-,80-,81-,82-,83-,84+,85-,86-,87-,88+,89-,90-,91-,92-,93+,96-,97-/m0/s1"
-  out = "InChIKey=FCLSYSXPOHISFP-WNQFAWBRBF"
-  ret = key_from_inchi( inp)
-  print ret
-  print ret == out
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/inchi.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/inchi.py
--- bkchem-0.12.5/bkchem/oasa/oasa/inchi.py	2008-10-14 09:44:51.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/inchi.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,942 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-from plugin import plugin
-from molecule import molecule
-import periodic_table as pt
-import molfile
-import misc
-
-import re
-
-import operator
-import time
-import xml.dom.minidom as dom
-import dom_extensions
-import string
-import os
-import popen2
-import coords_generator
-from oasa_exceptions import oasa_not_implemented_error, oasa_inchi_error, oasa_unsupported_inchi_version_error
-import select
-import sys
-from stereochemistry import cis_trans_stereochemistry
-
-class inchi( plugin):
-
-  name = "inchi"
-  read = 1
-  write = 0
-
-  proton_donors = ['O','F','Cl','Br','I','N','S']
-  proton_acceptors = ['P','N','S','O']
-  electron_donors = ['O','N','S','P']
-  electron_acceptors = ['B']
-
-  stereo_remap = {"+": cis_trans_stereochemistry.OPPOSITE_SIDE,
-                  "-": cis_trans_stereochemistry.SAME_SIDE,
-                  "?": cis_trans_stereochemistry.UNDEFINED,
-                  "u": cis_trans_stereochemistry.UNDEFINED,
-                  }
-
-
-  def __init__( self, structure=None):
-    self.structure = structure
-    self._no_possibility_to_improve = False
-    self.cleanup()
-
-  def cleanup( self):
-    self.charge = 0
-    self.forced_charge = 0
-    self._protonation_dealt_with_already = 0
-    self._added_hs = set()
-
-  def set_structure( self, structure):
-    self.structure = structure
-
-  def get_structure( self):
-    return self.structure
-
-
-
-  def split_layers( self, text):
-    # try if we have a xml document in hand
-    try:
-      doc = dom.parseString( text)
-    except:
-      # it seems not to be the case
-      if text.startswith( "InChI="):
-        text = text[6:]
-      layers = re.split( "/", text.strip())
-      # fixed hydrogen layer is marked stupidly by f/h :(, we have to deal with it
-      i = None
-      for l in layers:
-        if l.startswith("f"):
-          i = layers.index( l)
-          break
-      if i != None:
-        del layers[i]
-        layers[i] = 'f'+layers[i][1:]
-      return layers
-    structs = doc.getElementsByTagName( 'structure')
-    if not structs:
-      raise "no structures found in xml string %s" % text
-    struct = structs[0]
-    layers = []
-    for name in ('version','formula','connections','H'):
-      try:
-        layer = struct.getElementsByTagName( name)[0]
-      except IndexError:
-        raise "no '%s' tag found in xml string" % name
-      layers.append( dom_extensions.getTextFromElement( layer))
-    return layers
-
-
-
-  def get_layer( self, prefix):
-    for l in self.layers:
-      if l.startswith( prefix):
-        return l[1:]
-
-  def get_atom_with_inchi_number( self, number):
-    assert type( number) == int
-    for v in self.structure.vertices:
-      if v.properties_['inchi_number'] == number:
-        return v
-
-  def read_inchi( self, text):
-    try:
-      self._read_inchi( text)
-    except AssertionError:
-      raise oasa_inchi_error( "Localization of bonds, charges or movable hydrogens failed")
-    except:
-      raise
-    
-
-  def _read_inchi( self, text):
-    if not text:
-      raise oasa_inchi_error( "No inchi was given")
-    self.structure = molecule()
-    self.layers = self.split_layers( text)
-    # version support (very crude)
-    self.version = self._check_version( self.layers[0])
-    if not self.version:
-      raise oasa_unsupported_inchi_version_error( self.layers[0])
-    elif str( self.version[0]) != '1' or str( self.version[1]) != '0':
-      raise oasa_unsupported_inchi_version_error( self.layers[0])
-    
-    self.hs_in_hydrogen_layer = self.get_number_of_hydrogens_in_hydrogen_layer()
-    self.read_sum_layer()
-    self.read_connectivity_layer()
-    self._charge_mover = self._move_charge_somewhere_else()
-    repeat = True
-    run = 0
-    # we have to repeat this step in order to find the right positioning of movable hydrogens
-    while repeat and not self._no_possibility_to_improve:
-      # cleanup
-      for h in self._added_hs:
-        self.structure.remove_vertex( h)
-      for b in self.structure.edges:
-        b.order = 1
-      for v in self.structure.vertices:
-        v.symbol = v.symbol
-        v.charge = 0
-      self.cleanup()
-
-      # the code itself
-      run += 1
-      assert run < 50
-      self._deal_with_notorious_groups()
-      self.process_forced_charges()
-      self.read_hydrogen_layer( run=run)
-      self.read_charge_layer()
-      self.read_p_layer()
-      self.deal_with_da_bonds()
-      #self._deal_with_valencies()
-      self.compensate_for_forced_charges()
-      self.structure.add_missing_bond_orders()
-      #self.read_double_bond_stereo_layer()
-
-      # here we check out if the molecule seems ok
-      fvs = [v for v in self.structure.vertices if v.free_valency]
-      if not fvs and not filter( None, [not v.order for v in self.structure.edges]):
-        repeat = False
-      else:
-        if len( fvs) == 1:
-          a = fvs[0]
-          a.symbol = a.symbol # this resets the valency
-          a.raise_valency_to_senseful_value()
-          if not a.free_valency:
-            repeat = False
-
-      if repeat and self._no_possibility_to_improve and self.charge:
-        try:
-          self._charge_mover.next()
-        except StopIteration:
-          pass
-        else:
-          self._no_possibility_to_improve = False
-          run = 0
-
-    if repeat and self._no_possibility_to_improve:
-##       if len( filter( None, [v.free_valency for v in self.structure.vertices])) == 1:
-##         print
-##         print [(v.symbol, v.valency, v.free_valency)  for v in self.structure.vertices if v.free_valency], filter( None, [not v.order for v in self.structure.edges]), text
-##       if sum( [v.charge for v in self.structure.vertices]) != self.charge:
-##         print "Charge problem", sum( [v.charge for v in self.structure.vertices]), self.charge
-      #pass
-      raise oasa_inchi_error( "Localization of bonds, charges or movable hydrogens failed")
-#    print >> sys.stderr, "run:", run
-
-
-
-  def read_sum_layer( self):
-    if "." in self.layers[1]:
-      raise oasa_not_implemented_error( "INChI", "multiple compound systems are not supported by the library")
-
-    form = pt.formula_dict( self.layers[1])
-    processed_hs = 0 #for diborane and similar compounds we must process some Hs here
-    j = 0
-    for k in form.sorted_keys():
-      for i in range( form[k]):
-        if k == 'H':
-          # we want to process only the Hs that are not in the h-layer
-          if processed_hs >= form[k] - self.hs_in_hydrogen_layer:
-            continue
-          else:
-            processed_hs += 1
-        j += 1
-        a = self.structure.create_vertex()
-        a.symbol = k
-        self.structure.add_vertex( a)
-        a.properties_['inchi_number'] = j
-
-
-
-  def read_connectivity_layer( self):
-    layer = self.get_layer( "c")
-    if not layer:
-      return 
-    chunks = re.split( "([0-9]*)", layer)
-    chunks = filter( None, chunks)
-    chunks = filter( lambda x: x!='-', chunks)
-    last_atom = None
-    bracket_openings = []
-    for c in chunks:
-      if c == '(':
-        bracket_openings.append( last_atom)
-      elif c == ')':
-        last_atom = bracket_openings.pop(-1)
-      elif c == ",":
-        last_atom = bracket_openings.pop(-1)
-        bracket_openings.append( last_atom)
-      else:
-        try:
-          i = int( c)
-        except:
-          raise ValueError, "unexpected character %s in the connectivity layer" % c 
-        # atom
-        if last_atom:
-          self.structure.add_edge( last_atom-1, i-1)
-        last_atom = i
-
-
-  def read_hydrogen_layer( self, run=0):
-    layer = self.get_layer( "h")
-    # check presence of the layer
-    if not layer:
-      return
-
-    fixed_layer = self.get_layer( "f")
-    if fixed_layer:
-      # we can skip movable hydrogens
-      self._read_simple_hydrogen_layer( fixed_layer)
-    else:
-      re_for_brackets = "\([H\d,\-]+?\)"
-      brackets = re.findall( re_for_brackets, layer)
-      for bracket in brackets:
-        self._process_moving_hydrogen( bracket[1:-1], run=run)
-
-    # we improve only in the _process_moving_hydrogen so if it was not called there is no possibility for improvement
-    if not self._added_hs or fixed_layer:
-      self._no_possibility_to_improve = True
-      
-    self._read_simple_hydrogen_layer( layer)
-
-
-
-  def _read_simple_hydrogen_layer( self, layer):
-    """just takes the layer and adds hydrogens according to what it seas,
-    it does not care about moving hydrogens and stuff"""
-
-    re_for_brackets = "\([H\d,\-]+?\)"
-    layer = re.sub( re_for_brackets, "", layer)  # clean the brackets out
-
-    for vs, num in self._parse_h_layer( layer):
-      for v in vs:
-        for j in range( num):
-          h = self.structure.create_vertex()
-          h.symbol = 'H'
-          self.structure.add_vertex( h)
-          self.structure.add_edge( v-1, h)
-          self._added_hs.add( h)
-
-
-
-  def read_charge_layer( self):
-    layer = self.get_layer( "q")
-    if not layer:
-      return
-
-    charge = int( layer[1:]) - self.forced_charge
-    self.charge = int( layer[1:])
-
-
-    if charge > 0:
-      # at first we try to put positive charge on atoms with negative free_valency
-      change = True
-      steps = 0
-      while charge and change:
-        steps += 1
-        assert steps < 10
-        change = False
-        for v in self.structure.vertices:
-          if v.free_valency == -1:
-            if charge > 0:
-              v.charge = -v.free_valency
-            else:
-              v.charge = v.free_valency
-            charge -= v.charge
-            change = True
-            break
-
-    if charge < 0:
-      # we try to put negative charge on orphan atoms
-      steps = 0
-      change = True
-      while charge and change:
-        steps += 1
-        assert steps < 10
-        change = False
-        for v in self.structure.vertices:
-          if v.free_valency and sum( [n.free_valency for n in v.neighbors]) < v.free_valency:
-            charge = self._valency_to_charge( v, charge)
-            change = True
-            break
-
-    # this could be rewritten to put charges to places where a segment with odd number of free_valencies is
-    # this would be more general than counting the free_valencies for the whole molecule
-    free_valencies = sum( [a.free_valency for a in self.structure.vertices])
-    if free_valencies % 2:
-      # odd number of free_valencies means we have to put the charge somewhere to make a free_valency there
-      change = True
-      steps = 0
-
-      while charge and change:
-        change = False
-        steps += 1
-        assert steps < 10
-
-        for v in self.structure.vertices:
-          if v.symbol != 'C' and not v.free_valency and filter( None, [n.free_valency for n in v.neighbors]):
-            v.charge = charge
-            charge = 0
-            change = True
-            break
-
-    # then we do the others, at first trying heteroatoms
-    steps = 0
-    change = True
-    while charge and change:
-      change = False
-      steps += 1
-      assert steps < 10
-
-      for v in self.structure.vertices:
-        if v.free_valency and v.symbol != 'C':
-          charge = self._valency_to_charge( v, charge)
-          change = True
-          break
-
-
-    # then we try the carbon atoms as well
-    steps = 0
-    change = True
-    while charge and change:
-      steps += 1
-      assert steps < 10
-
-      change = False
-      for v in self.structure.vertices:
-        if v.free_valency:
-          charge = self._valency_to_charge( v, charge)
-          change = True
-          break
-
-    if charge:
-      raise oasa_exceptions.oasa_inchi_error( "The molecular charge could not be allocated to any atom (%d)." % charge)
-
-
-
-  def read_p_layer( self):
-    layer = self.get_layer( "p")
-    if not layer:
-      return
-
-
-    p = int( layer[1:]) - self._protonation_dealt_with_already
-    self.charge += p
-    charges = self.forced_charge
-    old_p = p
-
-    # at first add protons to negative charges
-    for v in self.structure.vertices:
-      if p > 0:
-        if v.charge < 0:
-          h = self.structure.create_vertex()
-          h.symbol = 'H'
-          self.structure.add_vertex( h)
-          self.structure.add_edge( v, h)
-          p -= 1
-          v.charge += 1
-          self._protonation_dealt_with_already += 1
-
-    if self._protonation_dealt_with_already:
-      # p was solved above
-      dp = self._protonation_dealt_with_already - sum( [v.charge for v in self.structure.vertices])
-      if dp > 0:
-        # there were no forced charges, so we are not in a zwittrion state, huh, who does not understand that?!
-        for v in self.structure.vertices:
-          if dp > 0 and v.symbol in self.proton_acceptors:
-            v.charge += 1
-            dp -= 1
-
-    # then come the other additional protones
-    while p:
-      old_p = p
-      for v in self.structure.vertices:
-        if p > 0:
-          if v.symbol in self.proton_acceptors:
-            h = self.structure.create_vertex()
-            h.symbol = 'H'
-            self.structure.add_vertex( h)
-            self.structure.add_edge( v, h)
-            self._added_hs.add( h)
-            p -= 1
-            if charges:
-              # the charge is already present as a forced charge
-              charges -= 1
-            else:
-              # the charge can normaly be delt with
-              v.charge += 1
-            break
-        elif p < 0:
-          if v.symbol in self.proton_donors:
-            hs = [n for n in v.neighbors if n.symbol=='H']
-            if hs:
-              h = hs[0]
-              self.structure.remove_vertex( h)
-              p += 1
-              if charges:
-                # the charge is already present as a forced charge
-                charges += 1
-              else:
-                # the charge can normaly be delt with
-                v.charge -= 1
-              break
-      assert p < old_p
-
-
-  def read_double_bond_stereo_layer( self):
-    def get_lowest_numbered_neighbor( atom, verbotten_atom):
-      neighs = [(n.properties_.get('inchi_number',1000000), n)
-                for n in atom.neighbors
-                if n is not verbotten_atom and "inchi_number" in n.properties_]
-      if neighs:
-        neighs.sort( reverse=True)
-        return neighs[0][1]
-      else:
-        # we take the only one that is there (if any)
-        neighs = [n for n in atom.neighbors if n is not verbotten_atom]
-        if neighs:
-          return neighs[0]
-        else:
-          raise oasa_inchi_error( "No neigbors on atom with stereo information!")
-        
-
-    
-    layer = self.get_layer( "b")
-    if not layer:
-      return
-    for a1,a2,sign in re.findall( "(\d+)-(\d+)([-+?u])", layer):
-      atom1 = self.get_atom_with_inchi_number( int( a1))
-      atom2 = self.get_atom_with_inchi_number( int( a2))
-      bond = self.structure.get_edge_between( atom1, atom2)
-      # we need neighbors with lowest inchi_number
-      neigh1 = get_lowest_numbered_neighbor( atom1, atom2)
-      neigh2 = get_lowest_numbered_neighbor( atom2, atom1)
-      st = cis_trans_stereochemistry( references=[neigh1,atom1,atom2,neigh2],
-                                      value=self.stereo_remap[sign])
-      self.structure.add_stereochemistry( st)
-
-
-
-  def _valency_to_charge( self, v, charge):
-    """sets charge of the atom so that it has minimal free_valency,
-    returns the 'unused' charge in case it is not comsumed whole"""
-    ch = misc.signum( charge) * min( abs( charge), v.free_valency)
-    if charge < 0:
-      charge += ch
-      v.charge = -ch
-    else:
-      charge -= ch
-      v.charge = ch
-    return charge
-        
-
-  def get_number_of_hydrogens_in_hydrogen_layer( self):
-    # version check
-    layer = self.get_layer( "h")
-    if not layer:
-      return 0
-
-    # check if we can handle it
-    if "*" in layer or ";" in layer:
-      raise oasa_not_implemented_error( "INChI", "multiple compound systems are not supported by the library")
-
-    ret = 0
-
-    re_for_brackets = "\([H\d,\-]+?\)"
-    brackets = re.findall( re_for_brackets, layer)
-    for bracket in brackets:
-      ret += self._get_hs_in_moving_hydrogen( bracket[1:-1])
-    layer = re.sub( re_for_brackets, "", layer)  # clean the brackets out
-
-    for vs, num in self._parse_h_layer( layer):
-      ret += len( vs) * num
-
-    return ret
-
-
-  def _get_hs_in_moving_hydrogen( self, chunk):
-    chks = chunk.split( ',')
-    if len( chks[0]) > 1:
-      if chks[0][1:].endswith( "-"):
-        hs = int( chks[0][1:-1] or 1)  # number of atoms 
-      else:
-        hs = int( chks[0][1:])  # number of atoms
-    else:
-      hs = 1
-    return hs
-    
-
-
-  def _check_version( self, ver):
-    """returns a tuple of two version numbers"""
-    if "Beta" in ver:
-      return None
-    v = ver.strip(string.ascii_lowercase+string.ascii_uppercase)
-    if "." in v:
-      return v.split('.')
-    else:
-      return v, 0
-
-
-  def _process_moving_hydrogen( self, chunk, run=0):
-    chks = chunk.split( ',')
-    if len( chks[0]) > 1:
-      if chks[0][1:].endswith("-"):
-        hs = int( chks[0][1:-1] or 1) + 1  # number of atoms 
-        self._protonation_dealt_with_already += 1
-      else:
-        hs = int( chks[0][1:])  # number of atoms
-    else:
-      hs = 1
-
-    vertices = [self.structure.vertices[ int( i)-1] for i in chks[1:]]
-    vs = vertices
-    take = hs
-
-    # here we generate still shorter combinations of vertices that receive hydrogens
-    # because of applying them in circles in the next step, we simulate the case
-    # where more than one hydrogen is put to one atom
-    while (run > misc.x_over_y( len( vs), take)) and take:
-      run -= misc.x_over_y( len( vs), take)
-      take -= 1
-    if not take:
-      self._no_possibility_to_improve = True
-      return []
-    variations = misc.gen_variations( vs, take)
-    for i in range( run):
-      vs = variations.next()
-        
-    while hs:
-      for v in vs:
-        h = self.structure.create_vertex()
-        h.symbol = 'H'
-        self.structure.add_vertex( h)
-        self.structure.add_edge( v, h)
-        self._added_hs.add( h)
-        hs -= 1
-        if not hs:
-          break
-
-
-
-  def _split_h_layer( self, layer):
-    was_h = False
-    chunks = []
-    chunk = ""
-    for c in layer:
-      if c == 'H':
-        was_h = True
-        chunk += c
-      elif c == "," and was_h:
-        was_h = False
-        chunks.append( chunk)
-        chunk = ""
-      else:
-        chunk += c
-    if chunk:
-      chunks.append( chunk)
-    return chunks
-
-
-  def _parse_h_layer( self, layer):
-    chunks = self._split_h_layer( layer)
-    for chunk in chunks:
-      try:
-        head, tail = chunk.split( 'H')
-      except Exception, e:
-        raise oasa_inchi_error( "error in hydrogen layer - missing H symbol")
-      num_h = tail and int( tail) or 1
-      vertices = []
-      for p in head.split( ","):
-        if "-" in p:
-          try:
-            a, b = map( int, p.split("-"))
-          except Exception, e:
-            raise oasa_inchi_error( "error in hydrogen layer - non-number character(s) present in atom range specification")
-          vertices.extend( range( a, b+1))
-        else:
-          try:
-            vertices.append( int( p))
-          except Exception, e:
-            raise oasa_inchi_error( "error in hydrogen layer - non-number character(s) present in atom specification")
-
-      yield vertices, num_h
-
-
-  def process_forced_charges( self):
-    """this marks the charges that are forced by the connectivity and thus helps
-    process zwitrions"""
-    forced_charge = 0
-    for v in self.structure.vertices:
-      if v.symbol in ("N","S") and v.free_valency == -1:
-        v.charge = 1
-        forced_charge += 1
-
-        
-    self.forced_charge = forced_charge
-
-
-  def compensate_for_forced_charges( self):
-    """if there were foced charges and they the molecule should not have any charge,
-    we have to take care of it here"""
-    charge = self.charge - self.forced_charge
-    old_charge = charge
-    while not self.charge and charge:
-      for chunk in self.structure._gen_free_valency_connected_components():
-        if len( chunk) % 2:
-          if len( chunk) == 1:
-            vs = chunk
-          else:
-            vs = [v for v in chunk if len( set( v.neighbors) & chunk) == 1]
-          for v in vs:
-            if charge < 0:
-              if v.symbol in self.proton_donors and v.free_valency:
-                v.charge -= 1
-                charge += 1
-            elif charge > 0:
-              if v.symbol in self.proton_acceptors:
-                v.charge += 1
-                charge -= 1
-            if not charge:
-              return
-##       if old_charge == charge:
-##         print "AAAAA", self.layers
-      assert old_charge != charge
-                
-        
-  def deal_with_da_bonds( self):
-    """deal with donor-acceptor bonds, this fixes mostly boron containing compounds"""
-    for v in self.structure.vertices:
-      if v.free_valency < 0 and v.symbol in self.electron_acceptors:
-        ns = [n for n in v.neighbors if n.symbol in self.electron_donors]
-        if ns:
-          n = ns[0]
-          n.charge += 1
-          v.charge -= 1
-
-  def _move_charge_somewhere_else( self):
-    if self.charge < 0:
-      pass
-    elif self.charge == 1:
-      for v in self.structure.vertices:
-        if v.symbol in self.proton_acceptors:
-          v.charge = 1
-          yield None
-
-          
-  def _deal_with_notorious_groups( self):
-    """some groups such as NO2, SO3H etc. need the valency of the central atom to be risen,
-    this is done here"""
-    for v in self.structure.vertices:
-      if v.symbol == "N" and v.degree > 2:
-        for n in v.neighbors:
-          if n.symbol == "O" and n.degree == 1:
-            v.raise_valency()
-            break
-      elif v.symbol == "S" and v.symbol > 2:
-        for n in v.neighbors:
-          if n.symbol == "O" and n.degree == 1:
-            v.raise_valency()
-
-
-  def _deal_with_valencies( self):
-    go = True
-    i = 0
-    while go:
-      i += 1
-      if i> 100:
-        print "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
-        break
-      go = False
-      # set all valency_raise to zero
-      for v in self.structure.vertices:
-        v.properties_['valency_raise'] = 0
-      # mark vertices with the demands to raise valency
-      for comp in self.structure._gen_free_valency_connected_components():
-#        print map( str, comp)
-        if sum( [v.free_valency for v in comp]) % 2:
-          go = True
-          for v in comp:
-            for ne in v.neighbors:
-              if ne not in comp:
-                ne.properties_['valency_raise'] += 1
-      # raise the valency of the most asked for atom
-      vs = [(v.properties_['valency_raise'], v) for v in self.structure.vertices]
-      vs.sort()
-      vs.reverse()
-      for (x, v) in vs:
-#        print x, v, v.valency, v.free_valency
-        if x and v.raise_valency():
-          break
-##           if v.free_valency == 0 and v.symbol in self.proton_acceptors:
-##             if v.charge == 0:
-##               v.charge = 1
-##             elif v.charge == 1:
-##               if v.raise_valency():
-##                 v.charge = 0
-##                 break
-##           elif v.raise_valency():
-##             break
-#          print x, v, v.symbol, v.degree, v.valency, v.free_valency
-#          print [e.order for e in v.neighbor_edges]
-#          break
-#        elif x == 0:
-#          break
-        
-
-def _run_command( command, input):
-  if os.name == "nt":
-    return _run_command_nt( command, input)
-  p = popen2.Popen4( command)
-  out, inp = p.fromchild, p.tochild
-  inp.write( input)
-  inp.close()
-  outf = out.fileno()
-  result = ""
-  try:
-    while 1:
-      ready = select.select( [outf], [], [])
-      if ready[0]:
-        text = os.read( ready[0][0], 1024)
-        if not text:
-          break
-        result += text
-      time.sleep(0.01)
-  finally:
-    out.close()
-    p.poll()
-    del p
-  return result
-
-def _run_command_nt( command, input):
-  out, inp = popen2.popen4( command)
-  inp.write( input)
-  inp.close()
-  text = out.read()
-  return text
-
-
-def generate_inchi_and_inchikey( m, program=None, fixed_hs=True, ignore_key_error=False):
-  """ignore the case when InChIKey cannot be generated to some reason
-  (no mhash library and old InChI program)"""
-  if not program:
-    import config
-    program = config.Config.inchi_binary_path
-  mf = molfile.mol_to_text( m)
-  if os.name == 'nt':
-    options = "/AUXNONE /STDIO /Key" + (fixed_hs and " /FixedH" or "")
-  else:
-    options = "-AUXNONE -STDIO -Key" + (fixed_hs and " -FixedH" or "")
-  command = ' '.join( ('"'+os.path.abspath( program)+'"', options))
-  text = _run_command( command, mf)
-  inchi = ""
-  key = ""
-  warnings = []
-  for line in text.splitlines():
-    if line.startswith( "Warning"):
-      warnings.append(line.strip() + "\n")
-    elif line.startswith( "End of file detected"):
-      pass
-    elif line.startswith( "InChIKey="):
-      key = line.strip()[9:]
-      break
-    elif line.startswith( "InChI="):
-      inchi = inchi + line.strip()
-    elif line.startswith( "Error"):
-      break
-  if not inchi:
-    raise oasa_inchi_error( "InChI program did not create any output InChI")
-  if not key:
-    # probably old version of the InChI software
-    try:
-      import inchi_key
-    except ImportError:
-      if ignore_key_error:
-        key = None
-      else:
-        raise oasa_inchi_error( "InChIKey could not be generated - inchi_key module failed to load properly.")
-    else:
-      key = inchi_key.key_from_inchi( inchi)
-  return inchi, key, warnings
-
-
-def generate_inchi( m, program=None, fixed_hs=True):
-  inchi, key, warnings = generate_inchi_and_inchikey( m, program=program, fixed_hs=fixed_hs, ignore_key_error=True)
-  return inchi, warnings
-
-def generate_inchi_key( m, program=None, fixed_hs=True):
-  inchi, key, warnings = generate_inchi_and_inchikey( m, program=program, fixed_hs=fixed_hs)
-  return key, warnings
-
-
-  
-##################################################
-# MODULE INTERFACE
-
-reads_text = 1
-reads_files = 1
-writes_text = 1
-writes_files = 1
-
-def text_to_mol( text, include_hydrogens=True, mark_aromatic_bonds=False, calc_coords=1):
-  try:
-    inc = inchi()
-    inc.read_inchi( text)
-    mol = inc.structure
-    #mol.add_missing_bond_orders()
-    if not include_hydrogens:
-      mol.remove_unimportant_hydrogens()
-    if calc_coords:
-      coords_generator.calculate_coords( mol, bond_length=calc_coords)
-    if mark_aromatic_bonds:
-      mol.mark_aromatic_bonds()
-    return mol
-  except oasa_inchi_error:
-    raise
-  except oasa_not_implemented_error:
-    raise
-  except:
-    raise
-
-def file_to_mol( f):
-  return text_to_mol( f.read())
-
-
-def mol_to_text( mol, program=None, fixed_hs=True):
-  return generate_inchi( mol, program=program, fixed_hs=fixed_hs)[0]
-
-
-def mol_to_file( mol, f):
-  f.write( mol_to_text( mol))
-
-
-
-#
-##################################################
-
-
-
-
-##################################################
-# DEMO
-
-if __name__ == '__main__':
-
-  import smiles
-
-  def main( text, cycles):
-    t1 = time.time()
-    for jj in range( cycles):
-      mol = text_to_mol( text, calc_coords=True, include_hydrogens=False)
-      print map( str, [b for b in mol.bonds if b.order == 0])
-      print "  smiles: ", smiles.mol_to_text( mol)
-      print "  inchi:  ", mol_to_text( mol, fixed_hs=True)
-      print "  charge: ", sum( [a.charge for a in mol.vertices])
-      print "  mw:     ", mol.weight
-    t1 = time.time() - t1
-    print 'time per cycle', round( 1000*t1/cycles, 2), 'ms'
-
-  repeat = 3
-  inch = "InChI=1/C6H10/c1-3-5-6-4-2/h3-6H,1-2H3/b5-3-,6-4+" #1/C6H6/c1-2-3-4-5-6-1/h1-6H"
-  print "oasa::INCHI DEMO"
-  print "converting following inchi into smiles (%d times)" % repeat
-  print "  inchi:   %s" % inch
-  
-  #import profile
-  #profile.run( 'main( inch, repeat)')
-  main( inch, repeat)
-
-
-# DEMO END
-##################################################
-
-
-
-##################################################
-## TODO
-
-## rename layer to sublayer
-
-
-##################################################
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/__init__.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/__init__.py
--- bkchem-0.12.5/bkchem/oasa/oasa/__init__.py	2008-05-26 06:31:55.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/__init__.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,101 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import sys
-if not (sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3)):
-  raise ImportError, "Python version %d.%d is lower than 2.3 which is needed by OASA" % sys.version_info[0:2]
-
-
-import atom
-import bond
-import molecule
-import smiles
-import coords_generator
-import coords_optimizer
-import molfile
-import inchi
-import cdml
-import graph
-import linear_formula
-import periodic_table
-import config
-import query_atom
-import chem_vertex
-import oasa_exceptions
-import name_database
-import subsearch
-import svg_out
-import stereochemistry
-
-atom = atom.atom
-molecule = molecule.molecule
-bond = bond.bond
-query_atom = query_atom.query_atom
-chem_vertex = chem_vertex.chem_vertex
-
-all = ['atom','bond','molecule','smiles','coords_generator','molfile','inchi','graph',
-       "linear_formula",'periodic_table','config','coords_optimizer','chem_vertex',
-       'query_atom','oasa_exceptions','name_database',"subsearch", "svg_out",
-       'stereochemistry']
-
-try:
-  import cairo_out
-except:
-  CAIRO_AVAILABLE = False
-else:
-  all.append( "cairo_out")
-  CAIRO_AVAILABLE = True
-
-# inchi_key
-try:
-  import inchi_key
-except Exception, e:
-  #print >> sys.stderr, "Module inchi_key could not be loaded - inchi_key related features will be disabled\nSee the error message for more info:\n  %s" % e
-  INCHI_KEY_AVAILABLE = False
-else:
-  all.append( "inchi_key")
-  INCHI_KEY_AVAILABLE = True
-
-# structure_database requires sqlite
-try:
-  import structure_database
-except Exception, e:
-  #print >> sys.stderr, "Module structure_database could not be loaded - structure_database related features will be disabled\nSee the error message for more info:\n  %s" % e
-  STRUCTURE_DATABASE_AVAILABLE = False
-else:
-  all.append( "structure_database")
-  STRUCTURE_DATABASE_AVAILABLE = True
-
-# pybel
-try:
-  import pybel_bridge
-except Exception, e:
-  #print >> sys.stderr, "The 'pybel_bridge' python module could not be loaded - oasa-pybel integration will be disabled\nSee the error message for more info:\n  %s" % e
-  PYBEL_AVAILABLE = False
-else:
-  all.append( "pybel_bridge")
-  PYBEL_AVAILABLE = True  
-
-
-__all__ = all
-
-
-
-
-  
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/known_groups.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/known_groups.py
--- bkchem-0.12.5/bkchem/oasa/oasa/known_groups.py	2008-02-19 04:11:40.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/known_groups.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,66 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-
-cdml_to_smiles = {'Me': 'C',
-                  'Ph': 'c1ccccc1',
-                  'Et': 'CC',
-                  'OCH3': 'OC',
-                  'NO2': 'N(=O)=O',
-                  'COOH': 'C(=O)O',
-                  'COOCH3': 'C(=O)OC',
-                  'CN': 'C#N',
-                  'SO3H': 'S(=O)(=O)O',
-                  'PPh3': 'P(c1ccccc1)(c2ccccc2)c3ccccc3',
-                  'OMe': 'OC',
-                  'COCl': 'C(=O)Cl',
-                  'CH2OH': 'CO'
-                  }
-
-
-name_to_smiles = {'Me': 'C',
-                  'Ph': 'c1ccccc1',
-                  'Et': 'CC',
-                  'Pr': 'CCC',
-                  'Bn': 'Cc1ccccc1',
-                  'Bz': 'C(=O)c1ccccc1',
-                  'Ac': 'C(=O)C',
-                  'Ms': 'S(=O)(=O)C',
-                  'Tf': 'S(=O)(=O)C(F)(F)F',
-                  'Ts': 'S(=O)(=O)c1ccc(C)cc1',
-                  'Bu': 'CCCC',
-
-                  'iPr':   'C(C)C',
-                  'tBu':   'C(C)(C)C',
-                  'Boc':   'C(=O)OC(C)(C)C',
-                  'Alloc': 'C(=O)OCC=C',
-                  'Z':     'C(=O)OCc1ccccc1',
-                  'Fmoc':  'C(=O)OCC1c2ccccc2-c3c1cccc3',
-#                  'DMAM':  '
-                  'CE':    'CCC#N',
-                  'MOP':   'CC1=CC(OC)=CC=N1=O',
-                  'Tr':    'C(c1ccccc1)(c2ccccc2)c3ccccc3',
-                  'MMTr':  'C(c1ccc(OC)cc1)(c2ccccc2)c3ccccc3',
-                  'DMTr':  'C(c1ccc(OC)cc1)(c2ccc(OC)cc2)c3ccccc3',
-                  'TMS':   'Si(C)(C)C',
-                  'TBDMS': 'Si(C)(C)C(C)(C)C',
-                  'TBDPS': 'Si(C(C)(C)C)(c1ccccc1)c2ccccc2',
-                  
-                  }
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/linear_formula.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/linear_formula.py
--- bkchem-0.12.5/bkchem/oasa/oasa/linear_formula.py	2008-10-14 09:10:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/linear_formula.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,318 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-from config import Config
-
-import re
-from periodic_table import periodic_table
-import coords_generator
-import misc
-from known_groups import name_to_smiles
-import smiles
-from oasa_exceptions import oasa_invalid_atom_symbol
-
-
-class linear_formula( object):
-
-  def __init__( self, text="", start_valency=0, end_valency=0, mol=None):
-    """valency specifies the already occupied valency of the submited formula,
-    it is usually used when parsing groups"""
-    self.molecule = None
-    if text:
-      self.parse_text( text, start_valency=start_valency, end_valency=end_valency, mol=mol)
-
-
-  def parse_text( self, text, start_valency=0, end_valency=0, mol=None):
-    text = self.expand_abbrevs( text)
-    mol = self.parse_form( text, start_valency=start_valency, mol=mol, reverse=False)
-    if mol:
-      # are there any atoms?
-      if not mol.vertices:
-        return None
-      
-      # now we check if the structure is complete
-      for v in mol.vertices:
-        if v.free_valency:
-          # we permit free valency at the last atom if end_valency was given
-          if not (v == self.get_last_free_atom(mol) and v.free_valency == end_valency):
-            return None
-
-      if start_valency:
-        mol.remove_vertex( mol.vertices[0]) # remove the dummy
-
-      # are there any atoms, again?
-      if not mol.vertices:
-        return None
-
-      # is the molecule connected?
-      if not mol.is_connected():
-        return None
-
-      # remove hydrogens, but only if the molecule is not completely made of them :)
-      mol.remove_unimportant_hydrogens()
-
-      # set some objects attributes for later reading
-      self.molecule = mol
-      if start_valency:
-        self.first_atom = mol.vertices[0]
-      else:
-        self.first_atom = None
-      if end_valency:
-        self.last_atom = self.get_last_free_atom(mol)
-      else:
-        self.last_atom = None
-      return mol
-
-
-  def parse_form( self, text, start_valency=0, mol=None, reverse=False):
-    form = text
-
-    # the code itself
-    if not mol:
-      mol = Config.create_molecule()
-
-    # create the dummy atom
-    if start_valency:
-      last_atom = mol.create_vertex()
-      last_atom.valency = start_valency
-      mol.add_vertex( last_atom)
-    else:
-      last_atom = None
-
-    # check if there are branches in the formula
-    if "(" not in form:
-      # there are no subbranches
-      chunks = re.split( "([A-Z][a-z]?[0-9]?[+-]?)", form)
-      if reverse:
-        chunks = reverse_chunks( chunks)
-      for chunk in chunks:
-        if chunk:
-          atms = self.chunk_to_atoms( chunk, mol)
-          if atms == None:
-            return None
-          last_atom = self.get_last_free_atom( mol)
-          for a in atms:
-            mol.add_vertex( a)
-            if last_atom:
-              max_val = min( last_atom.free_valency, a.free_valency, 3)
-              if max_val <= 0 and last_atom.free_valency <= 0:
-                if last_atom.raise_valency():
-                  max_val = min( last_atom.free_valency, a.free_valency, 3)
-              b = mol.create_edge()
-              b.order = max_val
-              mol.add_edge( last_atom, a, b)
-            else:
-              last_atom = a
-    else:
-      for chunk, count in gen_formula_fragments( form, reverse=reverse):
-        if chunk:
-          last_atom = self.get_last_free_atom( mol)
-          do_linear = False # should we string the fragments rather than adding them all to the last atom
-          if last_atom and last_atom.free_valency < count:
-            do_linear = True
-
-          for j in range( count):
-            if chunk[0] == "!":
-              # the form should be a smiles
-              m = smiles.text_to_mol( chunk[1:], calc_coords=0)
-              m.add_missing_hydrogens()
-              hs = [v for v in m.vertices[0].neighbors if v.symbol == 'H']
-              m.disconnect( hs[0], m.vertices[0])
-              m.remove_vertex( hs[0])
-              smile = True
-            else:
-              val = last_atom and 1 or 0
-              m = self.parse_form( chunk, start_valency=val, mol=mol.create_graph())
-              smile = False
-            if not m:
-              return None
-
-            if not last_atom:
-              # !!! this should not happen in here
-              mol.insert_a_graph( m)
-              # if there are multiple chunks without a previous atom, we should create a linear fragment
-              #last_atom = [v for v in mol.vertices if v.free_valency > 0][-1]
-            else:
-              if not smile:
-                m.remove_vertex( m.vertices[0]) # remove the dummy
-              mol.insert_a_graph( m)
-              b = mol.create_edge()
-              mol.add_edge( last_atom, m.vertices[0], b)
-              if do_linear:
-                last_atom = m.vertices[0]
-
-    return mol
-        
-
-
-  def chunk_to_atoms( self, chunk, mol):
-    m = re.match( "([A-Z][a-z]?)([0-9])?([+-])?", chunk)    
-    if m:
-      name = m.group( 1)
-      number = m.group( 2) and int( m.group(2)) or 1
-      sign = m.group( 3) and (int( m.group(3)+'1')) or 0
-      ret = []
-      for i in range( number):
-        v = mol.create_vertex()
-        try:
-          v.symbol = name
-        except oasa_invalid_atom_symbol:
-          return None
-        v.charge = sign
-        ret.append( v)
-      return ret
-
-
-  def get_last_free_atom( self, mol):
-    # check if there is something with a free valency
-    if not mol.vertices:
-      return None
-    atoms = [o for o in misc.reverse( mol.vertices)]
-    for a in atoms:
-      if a.free_valency > 0:
-        return a
-    # if its not the case
-    for i, a in enumerate( atoms[0:-1]):
-      b = a.get_edge_leading_to( atoms[i+1])
-      if b and b.order > 1:
-        b.order -= 1
-        return a
-    # well, we cannot do anything else
-    return atoms[-1]
-
-
-  def expand_abbrevs( self, text):
-    # at first sort the text according to length, so that the longest are expanded first
-    # (MMTr and not Tr)
-    keys = [(len( k), k) for k in name_to_smiles.keys()]
-    keys.sort()
-    keys.reverse()
-    for l, key in keys:
-      val = name_to_smiles[ key]
-      text = text.replace( key, "(!%s)" % val)
-    return text
-
-
-def reverse_chunks( chunks):
-  chunks.reverse()
-  for i in range( 0, len( chunks), 2):
-    chunks[i], chunks[i+1] = chunks[i+1],chunks[i]
-  return chunks
-
-
-def gen_formula_fragments( formula, reverse=False):
-  chunks = list( gen_formula_fragments_helper( formula))
-  if reverse:
-    chunks.reverse()
-  i = 0
-  while i < len( chunks):
-    chunk, brack = chunks[ i]
-    if brack and i < len( chunks) - 1:
-      next, nbrack = chunks[i+1]
-      if not nbrack:
-        count, rest = split_number_and_text( next)
-        if count == None:
-          yield chunk, 1
-        else:
-          chunks[i+1] = (rest, nbrack)
-          if not rest:
-            i += 1
-          yield chunk, count
-      else:
-        yield chunk, 1
-    else:
-      yield chunk, 1
-    i += 1
-
-
-def gen_formula_fragments_helper( formula):
-  opened_brackets = 0
-  to_ret = []
-  for ch in formula:
-    if ch not in "()":
-      to_ret.append( ch)
-    elif ch == "(":
-      if opened_brackets == 0:
-        if to_ret:
-          yield ''.join( to_ret), False
-          to_ret = []
-      else:
-        to_ret.append( "(")
-      opened_brackets += 1
-    elif ch == ")":
-      opened_brackets -= 1
-      if opened_brackets == 0:
-        if to_ret:
-          yield ''.join( to_ret), True
-          to_ret = []
-      else:
-        to_ret.append( ")")
-  if to_ret:
-    yield ''.join( to_ret), False
-
-
-
-def split_number_and_text( txt):
-  last = None
-  for i in range( len( txt)):
-    try:
-      last = int( txt[0:i+1])
-    except ValueError:
-      return last, txt[i:]
-  return last, ""
-    
-
-
-def reverse_formula( text):
-  all_chunks = []
-  form = text
-  if "(" not in form:
-    chunks = re.split( "([A-Z][a-z]?[0-9]?[+-]?)", form)
-    for chunk in chunks:
-      if chunk:
-        all_chunks.append( chunk)
-  else:
-    for chunk, count in gen_formula_fragments_helper( form):
-      if count:
-        all_chunks.append(")")
-      if chunk:
-        all_chunks.extend( reverse_formula( chunk))
-      if count:
-        all_chunks.append("(")
-#
-#        if count:
-#          all_chunks.append( str(count))
-
-  print all_chunks
-  #all_chunks.reverse()
-  return all_chunks
-  
-  
-if __name__ == "__main__":
-  form = 'CH3(CH2)7'
-  #form = 'CH3((CH2)2)2O'
-
-  a = linear_formula( form , start_valency=0, end_valency=1)
-  if a.molecule:
-    m = a.molecule
-    coords_generator.calculate_coords( m)
-    coords_generator.show_mol( m)
-  else:
-    print "nothing"
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/misc.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/misc.py
--- bkchem-0.12.5/bkchem/oasa/oasa/misc.py	2008-02-19 04:11:40.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/misc.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,232 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of BKchem - a chemical drawing program
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-#
-#
-#
-#--------------------------------------------------------------------------
-
-"""module containing miscelanous functions used in BKchem that don't
-fit anywhere else. Does not contain any objects"""
-
-import math
-import time
-import string
-from warnings import warn
-import re
-import types
-import base64
-
-
-def intersection( a, b):
-  "returns intersection of 2 lists"
-  ret = []
-  for i in a:
-    if i in b:
-      ret.append( i)
-  return ret
-
-
-def difference( a,b):
-  "returns difference of 2 lists ( a-b)"
-  ret = list( a)  # needed for type conversion of tuple for instance
-  for i in b:
-    if i in ret:
-      ret.remove( i)
-  return ret
-
-
-def signum( a):
-  if a == 0:
-    return 0
-  elif a < 0:
-    return -1
-  else:
-    return 1
-
-
-def filter_unique( items):
-  #t1 = time.time()
-  ret = []
-  for item in items:
-    if item not in ret:
-      ret.append( item)
-  #print time.time() - t1
-  return ret
-
-
-#def sub_all_numbers( text):
-#  return re.sub( "\d+", '<sub>\g<0><\\sub>', text)
-
-def reverse_molecule_formula( formula):
-  pass
-  
-
-def normalize_coords( coords):
-  x1, y1, x2, y2 = coords
-  if x2 < x1:
-    x2, x1 = x1, x2
-  if y2 < y1:
-    y2, y1 = y1, y2
-  return (x1, y1, x2, y2)
-
-
-def list_difference( list):
-  """return a list of differences between list members,
-  the list is by 1 shorter than the original"""
-  ret = []
-  for i in range( len( list)-1):
-    ret.append( list[i]-list[i+1])
-  return ret
-
-
-def split_number_and_unit( txt):
-  try:
-    v = float( txt)
-    return v, ''
-  except:
-    pass
-  cutter = re.compile( "([+-]?\d*\.?\d*)\s*([a-zA-Z]*)")
-  if txt:
-    a = cutter.match( txt)
-    if a and a.group(1):
-      return float( a.group(1)), a.group(2)
-  return None, None
-
-
-def lazy_apply( function, arguments):
-  """similar to apply but returns a callable (lambda) that performs the apply when called."""
-  return lambda: apply( function, arguments)
-
-
-
-
-def extend_bbox( bbox, pixels=1):
-  minx = min( (bbox[0], bbox[2]))
-  maxx = max( (bbox[0], bbox[2]))
-  miny = min( (bbox[1], bbox[3]))
-  maxy = max( (bbox[1], bbox[3]))
-  return minx-pixels, miny-pixels, maxx+pixels, maxy+pixels
-
-
-def smallest_common_bbox( bboxes):
-  _x0, _y0, _x1, _y1 = None, None, None, None
-  for (x0, y0, x1, y1) in bboxes:
-    minx = min( x0, x1)
-    maxx = max( x0, x1)
-    miny = min( y0, y1)
-    maxy = max( y0, y1)
-    if not _x0 or minx < _x0:
-      _x0 = minx
-    if not _x1 or maxx > _x1:
-      _x1 = maxx
-    if not _y0 or miny < _y0:
-      _y0 = miny
-    if not _y1 or maxy > _y1:
-      _y1 = maxy
-  return _x1, _y1, _x0, _y0
-
-      
-    
-
-
-
-def has_one_value_only( iterable):
-  if not iterable:
-    return 0
-  a = iterable[0]
-  for i in iterable:
-    if a != i:
-      return 0
-  return 1
-
-
-
-def plural_or_singular( iterable):
-  """useful for string construction such as 'you have %d apple%s' % (len(apples), plural_or_singular( apples)"""
-  if len( iterable) == 1:
-    return ''
-  else:
-    return 's'
-
-
-def len_and_ending( iterable):
-  return (len( iterable), plural_or_singular( iterable))
-
-
-def reverse( iterable):
-  for i in range( len( iterable)-1, -1, -1):
-    yield iterable[i]
-
-
-
-# some helper, higher order functions
-
-map_functions = lambda funcs, value: zip( apply, funcs, len(funcs)*[value])
-
-something_true = lambda vals: len( filter( None, vals))
-
-some_apply = lambda func, vals: something_true( map( func, vals))
-
-
-
-# some results
-
-isinstance_of_one = lambda obj, parents: some_apply( lambda x: isinstance( obj, x), parents)
-
-
-  
-
-def gen_variations(items, n):
-  if n==0:
-    yield []
-  else:
-    for i in xrange( len(items)-n+1):
-      for v in gen_variations(items[i+1:],n-1):
-        yield [items[i]]+v
-
-
-def gen_combinations( items, n):
-  if n==0:
-    yield []
-  else:
-    for i in xrange( len( items)):
-      for v in gen_combinations( [x for x in items if x!=items[i]], n-1):
-        yield [items[i]]+v
-
-
-def x_over_y( x, y):
-  return factorial( x) / factorial( y) / factorial( x-y)
-
-
-def factorial( x):
-  ret = 1
-  for i in range( 2, x+1):
-    ret *= i
-  return ret
-
-
-def gen_variations_and_one( items, length):
-  for i in items:
-    vars = gen_variations( [x for x in items if x!=i], length-1)
-    for vs in vars:
-      yield vs + [i]
-
-
-#print x_over_y( 11, 7)
-#print len( list( gen_variations( [1,2,3,4,5,6,7,8,9,10,11,12], 8)))
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/molecule.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/molecule.py
--- bkchem-0.12.5/bkchem/oasa/oasa/molecule.py	2008-10-14 09:33:32.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/molecule.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,948 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-import graph
-from atom import atom
-from query_atom import query_atom
-from bond import bond
-import copy
-import common
-import operator
-import misc
-import periodic_table as PT
-import math
-import transform3d
-
-
-class molecule( graph.graph):
-
-
-  def __init__( self, vertices =[]):
-    graph.graph.__init__( self, vertices=vertices)
-    # aliases
-    self.atoms = self.vertices
-    self.bonds = self.edges
-    self.stereochemistry = []
-
-  def __str__( self):
-    return "molecule, %d atoms, %d bonds" % (len( self.vertices), len( self.edges))
-
-  def create_vertex( self, vertex_class=None):
-    if not vertex_class:
-      return atom()
-    else:
-      return vertex_class()
-
-  def create_edge( self):
-    return bond()
-
-  def create_graph( self):
-    import config
-    return config.Config.molecule_class()
-
-  def add_stereochemistry( self, stereo):
-    self.stereochemistry.append( stereo)
-
-  def remove_stereochemistry( self, stereo):
-    if not stereo in self.stereochemistry:
-      raise ValueError, "cannot remove non-existent stereochemistry information"
-    self.stereochemistry.remove( stereo)
-
-  # analytics
-
-  # override of graphs method to add stereochemistry support
-  def get_disconnected_subgraphs( self):
-    out = graph.graph.get_disconnected_subgraphs( self)
-    for part in out:
-      for st in self.stereochemistry:
-        if set( st.references) <= set( part.vertices):
-          part.add_stereochemistry( st)
-    return out
-
-  def get_mol_weight( self):
-    w = 0
-    for v in self.vertices:
-      w += v.weight
-      if v.free_valency > 0:
-        w += v.free_valency * PT.periodic_table['H']['weight']
-    return w
-
-  weight = property( get_mol_weight, None, None, "molecular weight")
-
-
-  def get_charge( self):
-    return sum( [a.charge for a in self.vertices])
-
-  charge = property( get_charge, None, None, "Net charge of the molecule")
-
-
-  def get_formula_dict( self):
-    """returns a formula dict as defined in the periodic_table.py::formula_dict"""
-    comp = PT.formula_dict()
-    for a in self.atoms:
-      comp += a.get_formula_dict()
-    return comp
-
-
-
-  def add_missing_hydrogens( self):
-    hs = set()
-    for v in copy.copy( self.vertices):
-      for i in range( v.free_valency):
-        h = self.create_vertex()
-        h.symbol = 'H'
-        self.add_vertex( h)
-        self.add_edge( h, v)
-        hs.add( h)
-    return hs
-
-
-  def add_missing_bond_orders( self, retry=False):
-    """retry means to try a different approach because the last one was not successful"""
-    # we must at first find orphans that will force raising of valence of their neigbors
-    # NO2 is a typical example
-    [v.raise_valency_to_senseful_value() for v in self.vertices if v.free_valency < 0]
-
-    #print [sum( [xxx.free_valency for xxx in x]) for x in self._gen_free_valency_connected_components()]
-
-##     print [map( str, n) for n in [v.neighbors for v in self.vertices if v.symbol == "N"]]
-##     print [v.charge for v in self.vertices if v.symbol == "N"]
-
-    fix = True
-    while fix:
-      fix = False
-      for v in self.vertices:
-        if sum( [n.free_valency for n in v.neighbors]) < v.free_valency:
-          for n in v.neighbors:
-            if n.raise_valency():
-              fix = True
-              break
-        if fix:
-          break
-
-    #print [v.valency for v in self.vertices if v.symbol == "N"]
-
-##     if retry:
-##       for ring in self.get_smallest_independent_cycles_e():
-##         if not filter( lambda x: x<=0, [v.free_valency for v in self.edge_subgraph_to_vertex_subgraph( ring)]):
-##           for e in ring:
-##             e.order = 4
-##       self.localize_aromatic_bonds()
-
-    # now we process it
-    processed = [1]
-    step = 0
-    while processed:
-      processed = []
-      step += 1
-      assert step < 100 # there is something strange if we reach this number
-      for b in [bo for bo in self.edges if min( [a.free_valency for a in bo.vertices])]:
-        a1, a2 = b.get_vertices()
-        as1 = [a for a in a1.get_neighbors() if a.free_valency > 0]
-        as2 = [a for a in a2.get_neighbors() if a.free_valency > 0]
-        if len( as1) == 1 or len( as2) == 1:
-          b.order += min( [a.free_valency for a in b.vertices])
-          processed.append( b)
-
-      # we process non-bridge bonds first, so that their fixation does not
-      # force unsolvable possitioning of bonds in rings
-      if not processed:
-        for b in self.edges:
-          i = min( [a.free_valency for a in b.vertices])
-          if i and not self.is_edge_a_bridge( b):
-            processed = [b]
-            b.order += i
-            break
-
-      if not processed:
-        for b in self.edges:
-          i = min( [a.free_valency for a in b.vertices])
-          if i:
-            processed = [b]
-            b.order += i
-            break
-
-
-  def _gen_free_valency_connected_components( self):
-    done = set()
-    vs = set( copy.copy( self.vertices))
-    while vs:
-      now = set()
-      v = vs.pop()
-      if v.free_valency:
-        now.add( v)
-        new = set( now)
-        while new:
-          added = False
-          now_new = set()
-          for v in new:
-            for n in v.neighbors:
-              if n.free_valency and not n in done:
-                now.add( n)
-                done.add( n)
-                now_new.add( n)
-          new = now_new
-        vs -= now
-        yield now
-      else:
-        done.add( v)
-
-
-
-
-  def mark_aromatic_bonds( self):
-    rings = self.get_all_cycles()   #self.get_smallest_independent_cycles()
-    solved = [1]
-    # we need to repeat it to mark such things as 'badly' drawn naphtalene (no double bond in the centre) 
-    while solved:
-      solved = []
-      for aring in rings:
-        els = [self._get_atoms_possible_aromatic_electrons( a, aring) for a in aring]
-        if () in els:
-          continue
-        for comb in common.gen_combinations_of_series( els):
-          if sum( comb) % 4 == 2:
-            bring = self.vertex_subgraph_to_edge_subgraph( aring)
-            for b in bring:
-              b.aromatic = 1
-              #b.order = 4
-            solved.append( aring)
-            break
-      for r in solved:
-        rings.remove( r)
-
-
-
-
-  def _get_atoms_possible_aromatic_electrons( self, at, ring):
-    out = set()
-    if at.charge > 0 and at.symbol not in PT.accept_cation:
-      out.add( 0)
-    elif at.charge < 0:
-      out.add( 2)
-    if at.symbol in PT.accept_cation and not at.charge > 0:
-      out.add( 2)
-    if at.symbol in ('B','Al'):
-      out.add( 0)
-    #if at.multiplicity == 2:
-    #  out.add( 1)
-    for b, a in at.get_neighbor_edge_pairs():
-      if b.order > 1 and (a in ring or b.aromatic):
-        out.add( 1)
-      elif b.order > 1:
-        out.add( 0)
-    return tuple( out)
-
-
-
-
-  def localize_aromatic_bonds( self):
-    """localizes aromatic bonds (does not relocalize already localized ones),
-    for those that are not aromatic but marked so
-    (it is for instance possible to misuse 'cccc' in smiles to create butadiene)
-    they will be properly localized but marked as non-aromatic"""
-    erings = self.get_smallest_independent_cycles_e()
-    # filter off rings without aromatic bonds
-    erings = filter( lambda x: len( [b for b in x if b.aromatic]), erings)
-    rings = map( self.edge_subgraph_to_vertex_subgraph, erings)
-    # sort rings
-    rings.sort( lambda x,y: len(y)%2 - len(x)%2) # odd size rings first
-    last_rings = []
-    while rings:
-      # we have to continue with the neighbor rings of the last_ring (or the rings before)
-      intersection = None
-      if last_rings:
-        aring = None
-        while last_rings:
-          last_ring = last_rings.pop( -1)
-          for ring in rings:
-            intersection = ring & last_ring
-            if intersection:
-              aring = ring
-              last_rings.append( last_ring)
-              break
-          if aring:
-            break
-        if not aring:
-          aring = rings.pop(0)
-        else:
-          rings.remove( aring)
-      else:
-        aring = rings.pop(0)
-      last_rings.append( aring)
-      # convert ring from set to list
-      aring = self.sort_vertices_in_path( aring, start_from=intersection and intersection.pop() or None)
-      # taken from mark_aromatic_bonds
-      els = [self._get_atoms_possible_aromatic_electrons( a, aring) for a in aring]
-      if () in els:
-        continue  # misuse of aromatic bonds (e.g. by smiles) or e.g. tetrahydronaphtalene
-      for comb in common.gen_combinations_of_series( els):
-        if sum( comb) % 4 == 2:
-          aring.append( aring[0])
-          comb.append( comb[0])
-          # at first we process the bonds that are surely single (like C-S-C in thiophene)
-          already_set = None
-          for i in range( len( aring) -1):
-            if comb[i] + comb[i+1] != 2:
-              # these bonds must be single
-              b = aring[i].get_edge_leading_to( aring[i+1])
-              b.order = 1
-              already_set = i
-          if already_set != None:
-            # we reorder the comb and aring to start from the already set bonds
-            j = already_set + 1
-            aring = aring[j:len(aring)] + aring[1:j]
-            aring.insert( 0, aring[-1])
-            comb = comb[j:len(comb)] + comb[1:j]
-            comb.insert( 0, comb[-1])
-          i = 0
-          while i+1 < len( aring):
-            if comb[i] + comb[i+1] == 2:
-              b = aring[i].get_edge_leading_to( aring[i+1])
-              assert b != None # should be
-              # to assure alternating bonds
-              bs1 = [bo for bo in aring[i].get_neighbor_edges() if bo.order == 2 and bo.aromatic and bo!=b]
-              bs2 = [bo for bo in aring[i+1].get_neighbor_edges() if bo.order == 2 and bo.aromatic and bo!=b]
-              if len( bs1) == 0 and len( bs2) == 0:
-                b.order = 2
-              else:
-                b.order = 1
-            i += 1
-          break
-
-    self.localize_fake_aromatic_bonds()
-
-
-
-
-  def localize_fake_aromatic_bonds( self):
-    to_go = [b for b in self.bonds if b.order == 4]
-
-    processed = []
-    for b in to_go:
-      if not min( [a.free_valency for a in b.vertices]):
-        b.order = 1
-        processed.append( b)
-    to_go = misc.difference( to_go, processed)
-
-    while to_go:
-      # find the right bond
-      b = None
-      for bo in to_go:
-        bs1, bs2 = bo.get_neighbor_edges2()
-        if not bs1 or len( [e for e in bs1 if e.order == 1]) > 0 and len( [e for e in bs1 if e.order == 2]) == 0 \
-           or not bs2 or len( [e for e in bs2 if e.order == 1]) > 0 and len( [e for e in bs2 if e.order == 2]) == 0:
-          b = bo
-          break
-        # new start for iteration
-      if not b:
-        for bo in self.edges:
-          if not [e for e in bo.get_neighbor_edges() if e.order != 4]:
-            b = bo
-            break
-      if not b:
-        b = to_go.pop(0)
-      # the code itself
-      b.order = 2
-      b.aromatic = 0
-      for bo in b.get_neighbor_edges():
-        if bo.order == 4:
-          bo.order = 1
-          bo.aromatic = 0
-      # next turn
-      to_go = [b for b in self.bonds if b.order == 4]
-
-
-  def remove_unimportant_hydrogens( self):
-    """removes all H atoms that don't have any special properties
-    or are not part of a hydrogen only molecule."""
-    only_hydrogens = True
-    for v in self.vertices:
-      if v.symbol != "H":
-        only_hydrogens = False
-        break
-    if not only_hydrogens:
-      for v in copy.copy( self.vertices):
-        if v.symbol == 'H' and v.degree <= 1 and not v.isotope:
-          self.remove_vertex( v)
-
-
-
-  def remove_all_hydrogens( self):
-    """removes all H atoms"""
-    for v in copy.copy( self.vertices):
-      if v.symbol == 'H' and v.degree <= 1:
-        self.remove_vertex( v)
-
-
-
-  def _get_atom_distance_matrix( self, a):
-    self.mark_vertices_with_distance_from( a)
-    big_out = []
-    i = 0
-    while 1:
-      out = [a.symbol_number for a in self.vertices if a.properties_['d'] == i]
-      if i > 0:
-        out2 = []
-        for a in self.vertices:
-          if a.properties_['d'] == i-1:
-            out2 += a.get_hydrogen_count() * [1]
-        out += out2
-      if out:
-        out.sort()
-        # out.reverse()
-        big_out.append( tuple( out))
-      else:
-        return big_out
-      i += 1
-
-
-
-
-  def get_symmetry_unique_atoms( self):
-    out = []
-    vs = copy.copy( self.vertices)
-    for v in vs:
-      out.append( [i for i in self._get_atom_distance_matrix( v)])
-    while out:
-      k = vs.pop( 0)
-      v = out.pop( 0)
-      o = [k]
-      proc_vs = []
-      proc_out = []
-      while out:
-        k2 = vs.pop( 0)
-        v2 = out.pop( 0)
-        if v2 == v:
-          o.append( k2)
-        else:
-          proc_vs.append( k2)
-          proc_out.append( v2)
-      yield o
-      out = proc_out
-      vs = proc_vs
-
-
-
-
-  def number_atoms_uniquely( self):
-    out = {}
-    for v in self.vertices:
-      out[v] = [i for i in self._get_atom_distance_matrix( v)]
-      v.properties_['distance_matrix'] = out[v]
-    ms = out.values()
-    #self.remove_all_hydrogens()
-    ms.sort()
-    i = 1
-    ret = []
-    for m in ms:
-      ret.append( out.keys()[ out.values().index( m)])
-      #if ret.symbol != 'H':
-      #  print "%3d: " % i, ret, ret.free_valency
-      i += 1
-    return ret
-
-
-
-
-  def _read_file( self, name="/home/beda/oasa/oasa/mol.graph"):
-    self.vertices = []
-    self.edges = set()
-    f = file( name, 'r')
-    vs = f.readline()
-    for i in vs.split(' '):
-      if i != '\n':
-        v = self.create_vertex()
-        v.symbol = i
-        self.add_vertex( v)
-    for l in f.readlines():
-      o, a, b = l.split(' ')
-      e = self.create_edge()
-      e.order = int( o)
-      self.add_edge( self.vertices[int(a)], self.vertices[int(b)], e=e)
-    f.close()
-
-
-
-
-  def find_longest_mostly_carbon_chain( self):
-    if len( self.vertices) < 2:
-      return copy.copy( self.vertices)
-    ends = [v for v in self.vertices if v.get_degree() == 1]
-    paths = []
-    for e1 in ends:
-      for e2 in ends:
-        if e1 != e2:
-          path = self.find_path_between( e1, e2)
-          paths.append( path)  # could be optimized by not storing all the paths (once we know how to weight the chains)
-    # for now we don't take care of chain composition
-    l = 0
-    p = None
-    for path in paths:
-      if len( path) > l:
-        l = len( path)
-        p = path
-    return p
-
-
-
-
-
-  # --- the fragment matching routines ---
-
-  def select_matching_substructures( self, other, implicit_freesites=False, auto_cleanup=True):
-    """select fragments that match the complete molecule 'other' and yield them
-    as lists of atoms in the order of other.vertices; however when other has
-    explicit hydrogens that match implicit hydrogens on self the length of the
-    returned fragment might be shorter of the matched implicit hydrogens;
-    
-    IF YOU DON'T LET THE GENERATOR RUN TO THE END OR SET THE auto_cleanup TO FALSE, YOU HAVE TO
-    RUN clean_after_search MANUALLY NOT TO CLUTTER THE STRUCTURE"""
-
-    # at first decide if we need to add implicit hydrogens to self
-    add_implicit = False
-    for v in other.vertices:
-      if (isinstance( v, atom) and v.symbol == 'H') or \
-         (isinstance( v, atom) and v.explicit_hydrogens > 0) or \
-         (isinstance( v, query_atom) and ('H' in v.symbols or 'R' in v.symbols)):
-        add_implicit = True
-        break
-    if add_implicit:
-      added_hs = self.add_missing_hydrogens()
-      for h in added_hs:
-        h.properties_['implicit_hydrogen'] = True
-    # here we add explicit hydrogens to other, we call them implicit_hydrogen to make it
-    # work the same way as implicit_hydrogen
-    for v in other.vertices:
-      for i in range( v.explicit_hydrogens):
-        h = other.create_vertex()
-        other.add_vertex( h)
-        h.symbol = 'H'
-        e = other.create_edge()
-        e.order = 1
-        other.add_edge( v, h, e=e)
-        h.properties_['implicit_hydrogen'] = True
-      v.explicit_hydrogens = 0  # make it 0 and after search put it back
-    # for implicit_freesites we add the free_sites here
-    if implicit_freesites:
-      for v in other.vertices:
-        v.properties_['old_free_sites'] = v.free_sites
-        v.free_sites = v.free_valency
-
-    # then we create the dicts for storing threads in each of the atoms
-    i = 0 
-    for a in other.vertices:
-      a.properties_['subsearch'] = {}
-    for e in other.edges | self.edges:
-      e.properties_['subsearch'] = {}
-    # here we select the vertex to start from
-    vs = [v for v in other.vertices if isinstance( v, atom)]
-    sym = common.least_common_item( [v.symbol for v in vs])
-    v = [v for v in vs if v.symbol == sym][0]
-    for a in self.vertices:
-      a.properties_['subsearch'] = {}
-      if v.matches( a):
-        i += 1
-        a.properties_['subsearch'][i] = v
-        v.properties_['subsearch'][i] = a
-
-    # now we can proceed with the search
-    yielded = set()
-    for thread in self._mark_matching_threads( v, other):
-      vs = [v.properties_['subsearch'][thread] for v in other.vertices]
-      # for symetrical fragments we have to get rid of copies (O1=N=O2 and O2=N=O1)
-      vsset = frozenset( vs)
-      if vsset not in yielded:
-        if self._freesites_match( other, thread):
-          yield [v for v in vs if not 'implicit_hydrogen' in v.properties_.keys()]
-      yielded.add( vsset)
-
-    if auto_cleanup:
-      self.clean_after_search( other)
-
-
-
-  def clean_after_search( self, other):
-    # for implicit_freesites we restore original free_sites here
-    for v in other.vertices:
-      if "old_free_sites" in v.properties_:
-        v.free_sites = v.properties_['old_free_sites']
-        del v.properties_['old_free_sites']
-    # finally we remove the added implicit hydrogens
-    hs = [v for v in self.vertices if 'implicit_hydrogen' in v.properties_.keys()]
-    for v in hs:
-      del v.properties_['implicit_hydrogen']
-      self.remove_vertex( v)
-    # we also remove implicit_hydrogen (that represent atom.explicit_hydrogens) on other
-    # and also set the explicit_hydrogens count back to the original atoms
-    for v in other.vertices:
-      if 'implicit_hydrogen' not in v.properties_:
-        v.explicit_hydrogens = len( [h for h in v.neighbors if 'implicit_hydrogen' in h.properties_])
-    hs = [v for v in other.vertices if 'implicit_hydrogen' in v.properties_.keys()]
-    for v in hs:
-      del v.properties_['implicit_hydrogen']
-      other.remove_vertex( v)
-    for v in self.vertices + other.vertices:
-      del v.properties_['subsearch']
-    for e in self.edges | other.edges:
-      del e.properties_['subsearch']
-    
-      
-  def _mark_matching_threads( self, v, other):
-    """v is other vertex, other is the other molecule"""
-    thread = 0
-    threads = v.properties_['subsearch'].keys()
-    while threads:
-      thread = min( threads)
-      threads.remove( thread)
-
-      mirror = v.properties_['subsearch'][thread]
-      for e, n in v.get_neighbor_edge_pairs():
-        if thread not in n.properties_['subsearch']:
-          candidates = set()
-          for me, mn in mirror.get_neighbor_edge_pairs():
-            if thread not in mn.properties_['subsearch'] and mn.matches( n) and me.matches( e) and thread not in e.properties_['subsearch']:
-              candidates.add( (mn, me, e))
-
-          if candidates:
-            if len( candidates) > 1:
-              new_threads = self._spawn_thread( other, thread, len( candidates)-1)
-              ths = [thread] + new_threads
-            else:
-              ths = [thread]
-            for c, me, e in candidates:
-              th = ths.pop()
-              n.properties_['subsearch'][th] = c
-              c.properties_['subsearch'][th] = n
-              e.properties_['subsearch'][th] = me
-              me.properties_['subsearch'][th] = e
-            [x for x in self._mark_matching_threads( n, other)] # just make the generator run
-            if thread not in v.properties_['subsearch']:
-              # the thread already died
-              break
-            else:
-              pass
-          else:
-            self._delete_thread( other, thread)
-            break
-        # for proper handling of rings we have to check also the ones that are in this thread already
-        elif thread not in e.properties_['subsearch']:
-          me = self.get_edge_between( mirror, n.properties_['subsearch'][thread]) 
-          if me and e.matches( me):
-            e.properties_['subsearch'][thread] = me
-            me.properties_['subsearch'][thread] = e
-          else:
-            self._delete_thread( other, thread)
-            break
-        else:
-          pass
-         
-      threads = [i for i in v.properties_['subsearch'].keys() if i >= thread]
-      if thread in threads:
-        threads.remove( thread)
-        yield thread
-          
-
-
-  def _spawn_thread( self, other, thread, number):
-    my_vs = [v for v in self.vertices if thread in v.properties_['subsearch']]
-    my_es = [e for e in self.edges if thread in e.properties_['subsearch']]
-    other_vs = [v for v in other.vertices if thread in v.properties_['subsearch']]
-    other_es = [e for e in other.edges if thread in e.properties_['subsearch']]
-    max_thread = max( [max( v.properties_['subsearch'].keys()) for v in other.vertices if v.properties_['subsearch']])
-    for i in range( max_thread +1, max_thread +number +1, 1):
-      for v in my_vs + my_es + other_vs + other_es:
-        v.properties_['subsearch'][ i] = v.properties_['subsearch'][thread]
-    return range( max_thread +1, max_thread +number +1, 1)
-
-
-
-  def _delete_thread( self, other, thread):
-    for v in self.vertices + other.vertices:
-      try:
-        del v.properties_['subsearch'][thread]
-      except KeyError:
-        pass
-
-
-  def _freesites_match( self, other, thread):
-    for v in other.vertices:
-      mirror = v.properties_['subsearch'][thread]
-      unmatched_ns = [n for n in mirror.neighbors if thread not in n.properties_['subsearch']] # and not n.symbol == 'H']
-      # if there is more unmatched neighbors then free-site it does not match (Hs don't count)
-      if not len( unmatched_ns)+mirror.explicit_hydrogens <= v.free_sites:
-        return False
-    return True
-
-
-  def contains_substructure( self, other, implicit_freesites=True):
-    found = False
-    for i in self.select_matching_substructures( other, implicit_freesites=implicit_freesites, auto_cleanup=False):
-      found = True
-      break
-    self.clean_after_search( other)
-    return found
-
-
-  # // --- end of the fragment matching routines ---
-
-  def detect_stereochemistry_from_coords( self, omit_rings=True):
-    import stereochemistry,geometry
-    def add_neighbor_double_bonds( bond, path):
-      for _e in bond.get_neighbor_edges():
-        if _e.order == 2 and _e not in path:
-          path.append( _e)
-          add_neighbor_double_bonds( _e, path)
-    # double bonds
-    # detect clusters of double bonds
-    double_paths = []
-    processed = set()
-    for e in self.edges:
-      if e.order == 2 and e not in processed:
-        if omit_rings and not self.is_edge_a_bridge( e):
-          continue
-        path = [e]
-        add_neighbor_double_bonds( e, path)
-        if len( path) % 2:
-          double_paths.append( path)
-          processed |= set( path)
-    # detect config on these paths
-    for path in double_paths:
-        vertices = []
-        for bond in path:
-          vertices.extend( bond.vertices)
-        ends = [v for v in vertices if vertices.count(v) == 1]
-        if len( ends) != 2: # two ends is the only thing we are prepared to handle
-          continue
-        end1, end2 = ends
-        # set stereochemistry for all neighbors of both ends
-        for e1,n1 in end1.get_neighbor_edge_pairs():
-          plane1 = geometry.plane_normal_from_3_points( (n1.x,n1.y,n1.z),(end1.x,end1.y,end1.z),(end2.x,end2.y,end2.z))
-          if plane1 == None:
-            continue # some coords were missing
-          if not e1 in path:
-            for e2,n2 in end2.get_neighbor_edge_pairs():
-              if not e2 in path:
-                plane2 = geometry.plane_normal_from_3_points( (end1.x,end1.y,end1.z),(end2.x,end2.y,end2.z),(n2.x,n2.y,n2.z))
-                #cos_angle = geometry.same_or_oposite_side( plane1, plane2)
-                cos_angle = geometry.angle_between_planes( plane1, plane2)
-                if cos_angle < 0:
-                  value = stereochemistry.cis_trans_stereochemistry.OPPOSITE_SIDE
-                else:
-                  value = stereochemistry.cis_trans_stereochemistry.SAME_SIDE
-                if len( path) == 1:
-                  center = path[0]
-                else:
-                  center = None
-                refs = [n1,end1,end2,n2]
-                st = stereochemistry.cis_trans_stereochemistry( center=center, value=value, references=refs)
-                to_remove = None
-                to_add = None
-                for st1 in self.stereochemistry:
-                  if set( st1.references) == set( st.references):
-                    if st.value == st1.value:
-                      break
-                    else:
-                      to_remove = st1
-                      break
-                else:
-                  self.add_stereochemistry( st)
-                if to_remove:
-                  self.remove_stereochemistry( to_remove)
-
-
-  def mark_morgan( self):
-    old_morgs = 0
-    for v in self.vertices:
-      v.properties_['new_morgan'] = v.degree #sum( [n.degree for n in v.neighbors])
-    new_morgs = len( set( [v.properties_['new_morgan'] for v in self.vertices]))
-
-    while new_morgs > old_morgs:
-      for v in self.vertices:
-        v.properties_['morgan'] = v.properties_['new_morgan'] 
-
-      for v in self.vertices:
-        v.properties_['new_morgan'] = v.properties_['morgan'] + sum( [n.properties_['morgan'] for n in v.neighbors])
-      old_morgs = new_morgs
-      new_morgs = len( set( [v.properties_['new_morgan'] for v in self.vertices]))
-      
-    print "morgan", old_morgs, [v.properties_['morgan'] for v in self.vertices]
-
-
-  ## some geometry related things
-
-  def normalize_bond_length( self, bond_length=30):
-    """make the average bond-length be bond_length by scaling the structure up"""
-    if not self.edges or len( self.vertices) < 2:
-      return False
-
-    minx = None
-    maxx = None
-    miny = None
-    maxy = None
-    # atoms
-    for v in self.vertices:
-      if not maxx or v.x > maxx:
-        maxx = v.x
-      if not minx or v.x < minx:
-        minx = v.x
-      if not miny or v.y < miny:
-        miny = v.y
-      if not maxy or v.y > maxy:
-        maxy = v.y
-
-    scale = bond_length / self.get_mean_bond_length()
-    movex = (maxx+minx)/2
-    movey = (maxy+miny)/2
-    trans = transform3d.transform3d()
-    trans.set_move( -movex, -movey, 0)
-    trans.set_scaling( scale)
-    trans.set_move( movex, movey, 0)
-    for v in self.atoms:
-      v.x, v.y, v.z = trans.transform_xyz( v.x, v.y, v.z)
-    return True
-
-
-  def get_mean_bond_length( self):
-    """returns the mean bond length of bonds in the molecule"""
-    if len( self.edges) == 0:
-      return None
-    bond_length = 0.0
-    for e in self.edges:
-      v1, v2 = e.vertices
-      bond_length += math.sqrt( (v1.x-v2.x)**2 + (v1.y-v2.y)**2)
-    bl = bond_length / len( self.edges)
-    return bl
-
-  def get_structure_hash( self):
-    vs = self.number_atoms_uniquely()
-    ret = []
-    for v in vs:
-      ret.append( str(v.properties_['distance_matrix']))
-    res = "*".join( ret)
-    import sha
-    ss = sha.new()
-    ss.update( res)
-    return ss.hexdigest()
-
-  def create_CIP_digraph( self, center):
-    """creates a digraph according to rules described in CIP paper."""
-    assert center in self.vertices
-    from graph.digraph import digraph
-    dg = digraph()
-    dg.add_vertex( center.copy())
-    return dg
-
-
-def the_right_sorting_function( t1, t2):
-  for i,l in enumerate( t1):
-    k = t2[i]
-    for j in range( len( l)):
-      if j > len( k)-1:
-        return 1
-      if l[j] > k[j]:
-        return 1
-      elif l[j] < k[j]:
-        return -1
-    if len( l) < len( k):
-      return -1
-  return 0
-
-
-
-
-def equals( mol1, mol2, level=0):
-  """don't forget to put all hydrogens and bond orders to the bonds
-     level 1 - number of atoms and bonds,
-     level 2 - the number of atoms with same symbols is the same,
-     level 3 - the whole connecivity (no stereo)
-     """
-  # level 1
-  if not level or level >= 1:
-    if not len( mol1.vertices) == len( mol2.vertices):
-      return False
-    if not len( mol1.edges) == len( mol2.edges):
-      return False
-  # level 2
-  if not level or level >= 2:
-    symbols1 = [v.symbol for v in mol1.vertices]
-    symbols1.sort()
-    symbols2 = [v.symbol for v in mol2.vertices]
-    symbols2.sort()
-    if symbols2 != symbols1:
-      return False
-  # level 3
-  if not level or level >= 3:
-    vs1 = mol1.number_atoms_uniquely()
-    vs2 = mol2.number_atoms_uniquely()
-    for i, v1 in enumerate( vs1):
-      v2 = vs2[ i]
-      if v1.properties_['distance_matrix'] != v2.properties_['distance_matrix']:
-        return False
-  return True
-
-#import psyco
-#psyco.profile()
-
-##################################################
-# DEMO
-
-if __name__ == '__main__':
-
-  def main():
-    g = molecule()
-    g._read_file()
-
-    for b in g.edges:
-      print g.is_edge_a_bridge( b),
-
-##     g.add_missing_hydrogens()
-
-##     for v in g.vertices:
-##   #    print v.symbol, v.is_chiral()
-##       if v.symbol:
-##         ## for a in v.get_neighbors_CIP_sorted():
-##         ##   print a, [na.symbol for na in a.get_neighbors()]
-##         if v.is_chiral():
-##           print v.symbol, [na.symbol for na in v.get_neighbors_CIP_sorted()]
-
-
-
-  import time
-  t = time.time()
-  main()
-  print round( 1000*(time.time()-t), 2), 'ms'
-
-# DEMO END
-##################################################
-
-
-
-##################################################
-# TODO
-
-# azulene is wrongly localized
-# 0,2 must alternate in aromatic compound
-# radical has 1 el in _get_atoms_possible_aromatic_electrons
-# test the equals function
-
-##################################################
-
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/molfile.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/molfile.py
--- bkchem-0.12.5/bkchem/oasa/oasa/molfile.py	2008-10-14 04:24:10.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/molfile.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,292 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-from plugin import plugin
-from molecule import molecule
-
-
-
-class molfile( plugin):
-
-  name = "molfile"
-  read = 1
-  write = 1
-
-  def __init__( self, structure=None):
-    self.structure = structure
-
-  def set_structure( self, structure):
-    self.structure = structure
-
-  def get_structure( self):
-    return self.structure
-
-  def read_file( self, file):
-    self._read_header( file)
-    self._read_body( file)
-
-  def _read_header( self, file):
-    for i in range( 3):
-      file.readline()
-
-  def _read_body( self, file):
-    atoms = read_molfile_value( file, 3, conversion=int)
-    bonds = read_molfile_value( file, 3, conversion=int)
-    # nothing more interesting
-    file.readline()
-    # read the structure
-    self.structure = molecule()
-    for i in range( atoms):
-      a = self._read_atom( file)
-      self.structure.add_vertex( v=a)
-    for k in range( bonds):
-      b, i, j = self._read_bond( file)
-      self.structure.add_edge( i, j, e=b)
-    for line in file:
-      if line.strip() == "M  END":
-        break
-      if line.strip().startswith( "M  "):
-        self._read_property( line.strip())
-      #print line.strip()
-
-  def _read_atom( self, file):
-    x = read_molfile_value( file, 10, conversion=float)
-    y = read_molfile_value( file, 10, conversion=float)
-    z = read_molfile_value( file, 10, conversion=float)
-    read_molfile_value( file, 1) # empty space
-    symbol = read_molfile_value( file, 3)
-    mass_diff = read_molfile_value( file, 2)
-    charge, multi = self._read_molfile_charge( read_molfile_value( file, 3, conversion=int))
-    file.readline() # next line please
-    ret = self.structure.create_vertex()
-    ret.coords = (x,y,z)
-    ret.charge = charge
-    ret.symbol = symbol
-    ret.multiplicity = multi
-    return ret
-
-  def _read_bond( self, file):
-    a1 = read_molfile_value( file, 3, conversion=int) -1 # molfiles index from 1
-    a2 = read_molfile_value( file, 3, conversion=int) -1 # how lamish :)
-    order = read_molfile_value( file, 3, conversion=int)
-    type = read_molfile_value( file, 3, conversion=int)
-    type_remap = { 0: 'n', 1: 'w', 6: 'h', 4: 'a'}
-    type = type_remap.get( type, 'n')
-    file.readline() # next line please
-    b = self.structure.create_edge()
-    b.order = order
-    b.type = type
-    return b, a1, a2
-
-  def _read_property( self, prop):
-    import re
-    m = re.match( "M\s+RAD\s+(\d+)(.*)", prop)
-    if m:
-      for at,rad in re.findall( "(\d+)\s+(\d+)", m.group( 2)):
-        index = int( at)
-        multi = int( rad)
-        self.structure.vertices[index-1].multiplicity = multi
-    
-
-  def write_file( self, file):
-    """file should be a writable file object"""
-    if not self.structure:
-      raise "No structure to write"
-    self._write_header( file)
-    self._write_counts_line( file)
-    self._write_body( file)
-    self._write_m_lines( file)
-    file.write( 'M  END\n')
-
-  def get_text( self):
-    return ''.join( (self._get_header(), self._get_counts_line(), self._get_body(), 'M  END'))
-
-  def _write_header( self, file):
-    file.write( self._get_header())
-
-  def _get_header( self):
-    return "\n\n\n"
-
-  def _write_counts_line( self, file):
-    file.write( self._get_counts_line())
-
-  def _get_counts_line( self):
-    atoms = len( self.structure.vertices)
-    bonds = len( self.structure.edges)
-    atom_lists = 0
-    fff = 0 # obsolete
-    chiral = 0
-    stexts = 0
-    obsolete = "  0  0  0  0"
-    extras = 999
-    mol_version = " V2000"
-    #         1  2 3 4 5 6 7 8 9
-    return "%3d%3d%3d%3d%3d%3d%s%s%s\n" % (atoms,bonds,atom_lists,fff,chiral,stexts,obsolete,extras,mol_version)
-
-  def _write_body( self, file):
-    file.write( self._get_body())
-
-  def _get_body( self):
-    lines = []
-    for a in self.structure.vertices:
-      lines.append( self._get_atom_line( a))
-    for b in self.structure.edges:
-      lines.append( self._get_bond_line( b))
-    return '\n'.join( lines)+'\n'
-
-
-  def _get_atom_line( self, a):
-    x = a.get_x()
-    y = a.get_y()
-    z = a.get_z()
-    symbol = a.symbol
-    mass_diff = 0
-    charge = self._get_molfile_charge( a.charge, a.multiplicity)
-    rest = "  0  0  0  0  0  0  0  0  0  0"
-    #            1    2     3     4  5  6 7
-    return "%10.4f%10.4f%10.4f %-3s%2d%3d%s" % (x,y,z,symbol,mass_diff,charge,rest)
-
-  def _get_bond_line( self, b):
-    v1, v2 = b.get_vertices()
-    a1 = self.structure.vertices.index( v1) +1
-    a2 = self.structure.vertices.index( v2) +1
-    order = b.order
-    type_remap = { 'n': 0, 'w': 1, 'h': 6, 'a': 4, 'b': 0, 'd': 0}
-    type = type_remap.get( b.type, 0)
-    rest = "  0  0  0"
-    #         1  2  3  4 5
-    return "%3d%3d%3d%3d%s" % (a1,a2,order,type,rest)
-
-  def _write_m_lines( self, file):
-    m_lines = self._get_m_lines()
-    if m_lines:
-      file.write( "\n".join( m_lines) + "\n")
-
-  def _get_m_lines( self):
-    # radicals
-    radicals = {}
-    i = 1
-    for v in self.structure.vertices:
-      if v.multiplicity != 1:
-        radicals[i] = v.multiplicity
-      i += 1
-    if radicals:
-      assert len( radicals) <= 8
-      nums = radicals.keys()
-      nums.sort()
-      rads = " ".join( ["%3d %3d" % (n,radicals[n]) for n in nums])
-      rad_line = "M  RAD%3d %s" % (len(nums), rads)
-    else:
-      rad_line = None
-    # return m_lines
-    m_lines = filter( None, [rad_line])
-    return m_lines
-
-  def _read_molfile_charge( self, value):
-    if value == 0:
-      return (0,1)
-    ch = -(value-4)
-    multi = ch == 0 and 2 or 1
-    return (ch, multi)
-
-  def _get_molfile_charge( self, charge, multiplicity):
-    if charge == 0:
-      if multiplicity == 1:
-        return 0
-      else:
-        return 4
-    else:
-      return 4-charge
-
-
-def read_molfile_value( file, length, strip=1, conversion=None):
-  """reads specified number of characters, if strip strips whitespace,
-  if conversion (a fuction taking one string argument) applies it;
-  if empty string is obtained after stripping 0 is returned"""
-  str = file.read( length)
-  if strip:
-    str = str.strip()
-  if str == "":
-    return 0
-  if conversion:
-    str = conversion( str)
-  return str
-
-  
-
-
-##################################################
-# MODULE INTERFACE
-
-from StringIO import StringIO
-
-reads_text = 1
-reads_files = 1
-writes_text = 1
-writes_files = 1
-
-def mol_to_text( mol):
-  m = molfile()
-  m.structure = mol
-  return m.get_text()
-
-def mol_to_file( mol, f):
-  m = molfile()
-  m.structure = mol
-  m.write_file( f)
-
-def file_to_mol( f):
-  m = molfile()
-  m.read_file( f)
-  return m.structure
-
-def text_to_mol( text):
-  return file_to_mol( StringIO( text))
-
-#
-##################################################
-  
-
-if __name__ == "__main__":
-
-  import sys
-
-  if len( sys.argv) < 1:
-    print "you must supply a filename"
-    sys.exit()
-
-  # parsing of the file
-
-  file_name = sys.argv[1]
-  f = file( file_name, 'r')
-  mol = file_to_mol( f)
-  f.close()
-
-  for a in  mol.atoms:
-    print a.x, a.y
-
-  import time
-
-  t = time.time()
-  lens = map( len, mol.get_smallest_independent_cycles())
-  lens.sort()
-  print lens
-  print time.time() -t 
-  print "total %d rings" % len( lens)
-  print mol
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/name_database.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/name_database.py
--- bkchem-0.12.5/bkchem/oasa/oasa/name_database.py	2008-02-19 04:11:40.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/name_database.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,91 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import os, sys
-import anydbm
-
-
-class Config:
-    database_file = os.path.abspath( os.path.join( os.path.dirname( __file__), "names.db"))
-
-
-def normalize_inchi( inchi):
-    if inchi.startswith("InChI="):
-        return inchi[6:]
-    else:
-        return inchi
-    
-
-def compound_to_database_string( c):
-    c['inchi'] = normalize_inchi( c['inchi'])
-    return "%(inchi)s %(cid)s ### %(name)s\n" % c
-
-
-def database_string_to_compound( line):
-    a,name = line.split( "###")
-    inchi, cid = a.split()
-    return {'inchi':inchi.strip(), 'cid':cid.strip(), 'name':name.strip()}
-    
-
-def mydb_to_gdbm( infilename, outfilename):
-    import gdbm
-    infile = file( infilename, "r")
-    base = gdbm.open( outfilename, "n")
-    for line in infile:
-        rec = database_string_to_compound( line)
-        base[ rec['inchi']] = rec['cid'] + " " + rec['name']
-        
-    
-
-def get_compound_from_database( inchi, database_file=None):
-    inchi = normalize_inchi( inchi)
-    for fname in (database_file, Config.database_file):
-        if fname and os.path.exists(fname):
-            break
-    else:
-        raise Exception("Name database not found")
-    base = anydbm.open( fname)
-    if base.has_key( inchi):
-        cid, name = base[ inchi].split( " ", 1)
-        return {'inchi': inchi, 'cid': cid, 'name': name}
-    else:
-        return None
-
-
-def name_molecule( mol, database_file=None):
-    """tries to find name for an OASA molecule in the database,
-    it requires InChI generation to work"""
-    import inchi
-    inch = inchi.mol_to_text( mol)
-    return get_compound_from_database( inch, database_file=database_file)
-
-
-if __name__ == "__main__":
-    import sys
-    if len( sys.argv) > 1:
-        fname = sys.argv[1]
-        if os.path.exists( fname):
-            try:
-                mydb_to_gdbm( fname, Config.database_file)
-            except:
-                print "given file must be a text file with one compound per line in format 'InChI CID ### name'"
-        else:
-            print "you must supply a valid filename to update the database or no argument for a test to run"
-    else:
-        print get_compound_from_database( "1/C4H10/c1-3-4-2/h3-4H2,1-2H3")
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/oasa_exceptions.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/oasa_exceptions.py
--- bkchem-0.12.5/bkchem/oasa/oasa/oasa_exceptions.py	2008-04-25 05:21:34.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/oasa_exceptions.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,123 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-
-class oasa_error( Exception):
-
-  def __init__( self, *args, **kw):
-    Exception.__init__( self, *args, **kw)    
-
-
-
-
-class oasa_periodic_table_error( oasa_error):
-  """exception for reporting periodic_table related error"""
-
-  def __init__( self, id, value, symbol=None):
-    oasa_error.__init__(self)
-    self.id = id
-    self.value = value
-
-  def __str__( self):
-    return "OASA periodic_table error, id=%s, value=%s" % (self.id, self.value)
-
-
-
-
-class oasa_invalid_atom_symbol( oasa_error):
-  """exception for reporting invalid atom symbol use"""
-
-  def __init__( self, value, symbol):
-    oasa_error.__init__(self)
-    self.value = value
-    self.symbol = symbol
-
-  def __str__( self):
-    return "Symbol '%s' not allowed (%s)" % (self.symbol, self.value)
-
-
-
-class oasa_invalid_value( oasa_error):
-  """exception for reporting invalid values"""
-
-  def __init__( self, meaning, value):
-    oasa_error.__init__(self)
-    self.value = value
-    self.meaning = meaning
-
-  def __str__( self):
-    return "The value for '%s' is not allowed (%s)" % (self.meaning, self.value)
-
-
-
-class oasa_not_implemented_error( oasa_error):
-
-  def __init__( self, where, what):
-    oasa_error.__init__(self)
-    self.where = where
-    self.what = what
-
-  def __str__( self):
-    return "'Not implemented' error in %s: %s" % (self.where, self.what)
-
-
-class oasa_inchi_error( oasa_error):
-
-  def __init__( self, what):
-    oasa_error.__init__(self)
-    self.what = what
-
-  def __str__( self):
-    return "InChI error: %s" % self.what
-
-
-
-class oasa_unsupported_inchi_version_error( oasa_error):
-  
-  def __init__( self, version):
-    oasa_error.__init__(self)
-    self.version = version
-
-  def __str__( self):
-    return "The InChI has an unsupported version: %s" % self.version
-
-
-
-class oasa_smiles_error( oasa_error):
-  
-  def __init__( self, value):
-    oasa_error.__init__(self)
-    self.value = value
-
-  def __str__( self):
-    return "SMILES Error: %s" % self.value
-
-
-class oasa_stereochemistry_error( oasa_error):
-  
-  def __init__( self, value):
-    oasa_error.__init__(self)
-    self.value = value
-
-  def __str__( self):
-    return "Stereochemistry Error: %s" % self.value
-
-
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/periodic_table.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/periodic_table.py
--- bkchem-0.12.5/bkchem/oasa/oasa/periodic_table.py	2008-11-25 06:43:09.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/periodic_table.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,346 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-#
-#
-#
-#--------------------------------------------------------------------------
-
-from __future__ import generators
-#from generators import lazy_map, take
-import re
-import operator
-import types
-
-
-"""periodic table as a dictionary, plus functions for molecular
-formula manipulation and computation"""
-
-periodic_table = {
-"H"  : {"ord":   1, "weight":   1.0079, "exact_mass":   1.00782503, "en": 2.10, "els":  1, "valency": (1,)},
-"He" : {"ord":   2, "weight":   4.0026, "exact_mass":   4.00260325, "en": 0.00, "els":  8, "valency": (0, 2)},
-"Li" : {"ord":   3, "weight":   6.9410, "exact_mass":   7.01600455, "en": 0.98, "els":  1, "valency": (1,)},
-"Be" : {"ord":   4, "weight":   9.0122, "exact_mass":   9.01218220, "en": 1.57, "els":  2, "valency": (2,)},
-"B"  : {"ord":   5, "weight":  10.8110, "exact_mass":  11.00930540, "en": 2.04, "els":  3, "valency": (3,)},
-"C"  : {"ord":   6, "weight":  12.0107, "exact_mass":  12.00000000, "en": 2.55, "els":  4, "valency": (4, 2)},
-"N"  : {"ord":   7, "weight":  14.0067, "exact_mass":  14.00307400, "en": 3.04, "els":  5, "valency": (3, 5)},
-"O"  : {"ord":   8, "weight":  15.9994, "exact_mass":  15.99491462, "en": 3.44, "els":  6, "valency": (2,)},
-"F"  : {"ord":   9, "weight":  18.9984, "exact_mass":  18.99840322, "en": 3.98, "els":  7, "valency": (1,)},
-"Ne" : {"ord":  10, "weight":  20.1797, "exact_mass":  19.99244018, "en": 0.00, "els":  8, "valency": (0, 2)},
-"Na" : {"ord":  11, "weight":  22.9898, "exact_mass":  22.98976928, "en": 0.93, "els":  1, "valency": (1,)},
-"Mg" : {"ord":  12, "weight":  24.3050, "exact_mass":  23.98504170, "en": 1.31, "els":  2, "valency": (2,)},
-"Al" : {"ord":  13, "weight":  26.9815, "exact_mass":  26.98153863, "en": 1.61, "els":  3, "valency": (3,)},
-"Si" : {"ord":  14, "weight":  28.0855, "exact_mass":  27.97692653, "en": 1.91, "els":  4, "valency": (4,)},
-"P"  : {"ord":  15, "weight":  30.9738, "exact_mass":  30.97376163, "en": 2.19, "els":  5, "valency": (3, 5)},
-"S"  : {"ord":  16, "weight":  32.0650, "exact_mass":  31.97207100, "en": 2.58, "els":  6, "valency": (2, 4, 6)},
-"Cl" : {"ord":  17, "weight":  35.4530, "exact_mass":  34.96885268, "en": 3.16, "els":  7, "valency": (1, 3, 5, 7)},
-"Ar" : {"ord":  18, "weight":  39.9480, "exact_mass":  39.96238312, "en": 0.00, "els":  8, "valency": (0, 2)},
-"K"  : {"ord":  19, "weight":  39.0983, "exact_mass":  38.96370668, "en": 0.82, "els":  1, "valency": (1,)},
-"Ca" : {"ord":  20, "weight":  40.0780, "exact_mass":  39.96259098, "en": 1.00, "els":  2, "valency": (2,)},
-"Sc" : {"ord":  21, "weight":  44.9559, "exact_mass":  44.95591190, "en": 1.36, "els":  3, "valency": (3, 1)},
-"Ti" : {"ord":  22, "weight":  47.8670, "exact_mass":  47.94794630, "en": 1.54, "els":  4, "valency": (4, 3)},
-"V"  : {"ord":  23, "weight":  50.9415, "exact_mass":  50.94395950, "en": 1.63, "els":  5, "valency": (2, 4, 5)},
-"Cr" : {"ord":  24, "weight":  51.9961, "exact_mass":  51.94050750, "en": 1.66, "els":  6, "valency": (2, 3, 6)},
-"Mn" : {"ord":  25, "weight":  54.9380, "exact_mass":  54.93804510, "en": 1.55, "els":  7, "valency": (2, 3, 4, 6, 7)},
-"Fe" : {"ord":  26, "weight":  55.8450, "exact_mass":  55.93493750, "en": 1.83, "els":  8, "valency": (0, 2, 3)},
-"Co" : {"ord":  27, "weight":  58.9332, "exact_mass":  58.93319500, "en": 1.88, "els":  8, "valency": (2, 3)},
-"Ni" : {"ord":  28, "weight":  58.6934, "exact_mass":  57.93534290, "en": 1.91, "els":  8, "valency": (2, 3)},
-"Cu" : {"ord":  29, "weight":  63.5460, "exact_mass":  62.92959750, "en": 1.90, "els":  1, "valency": (0, 1, 2)},
-"Zn" : {"ord":  30, "weight":  65.3900, "exact_mass":  63.92914220, "en": 1.65, "els":  2, "valency": (2,)},
-"Ga" : {"ord":  31, "weight":  69.7230, "exact_mass":  68.92557360, "en": 1.81, "els":  3, "valency": (3,)},
-"Ge" : {"ord":  32, "weight":  72.6400, "exact_mass":  73.92117780, "en": 2.01, "els":  4, "valency": (4,)},
-"As" : {"ord":  33, "weight":  74.9216, "exact_mass":  74.92159650, "en": 2.18, "els":  5, "valency": (3, 5)},
-"Se" : {"ord":  34, "weight":  78.9600, "exact_mass":  79.91652130, "en": 2.55, "els":  6, "valency": (2, 4, 6)},
-"Br" : {"ord":  35, "weight":  79.9040, "exact_mass":  78.91833710, "en": 2.96, "els":  7, "valency": (1, 3, 5)},
-"Kr" : {"ord":  36, "weight":  83.8000, "exact_mass":  83.91150700, "en": 0.00, "els":  8, "valency": (0, 2)},
-"Rb" : {"ord":  37, "weight":  85.4678, "exact_mass":  84.91178974, "en": 0.82, "els":  1, "valency": (1,)},
-"Sr" : {"ord":  38, "weight":  87.6200, "exact_mass":  87.90561210, "en": 0.95, "els":  2, "valency": (2,)},
-"Y"  : {"ord":  39, "weight":  88.9059, "exact_mass":  88.90584830, "en": 1.22, "els":  3, "valency": (3,)},
-"Zr" : {"ord":  40, "weight":  91.2240, "exact_mass":  89.90470440, "en": 1.33, "els":  4, "valency": (4,)},
-"Nb" : {"ord":  41, "weight":  92.9064, "exact_mass":  92.90637810, "en": 1.60, "els":  5, "valency": (3, 5)},
-"Mo" : {"ord":  42, "weight":  95.9400, "exact_mass":  97.90540820, "en": 2.16, "els":  6, "valency": (3, 5, 6)},
-"Tc" : {"ord":  43, "weight":  98.9063, "exact_mass":  97.90721600, "en": 1.90, "els":  7, "valency": (5, 7)},
-"Ru" : {"ord":  44, "weight": 101.0700, "exact_mass": 101.90434930, "en": 2.20, "els":  8, "valency": (3, 4, 6, 8)},
-"Rh" : {"ord":  45, "weight": 102.9055, "exact_mass": 102.90550400, "en": 2.28, "els":  8, "valency": (3, 4)},
-"Pd" : {"ord":  46, "weight": 106.4200, "exact_mass": 105.90348600, "en": 2.20, "els":  8, "valency": (2, 4)},
-"Ag" : {"ord":  47, "weight": 107.8682, "exact_mass": 106.90509700, "en": 1.93, "els":  1, "valency": (1,)},
-"Cd" : {"ord":  48, "weight": 112.4110, "exact_mass": 113.90335850, "en": 1.69, "els":  2, "valency": (2,)},
-"In" : {"ord":  49, "weight": 114.8180, "exact_mass": 114.90387800, "en": 1.78, "els":  3, "valency": (3,)},
-"Sn" : {"ord":  50, "weight": 118.7100, "exact_mass": 119.90219470, "en": 1.96, "els":  4, "valency": (2, 4)},
-"Sb" : {"ord":  51, "weight": 121.7600, "exact_mass": 120.90381570, "en": 2.05, "els":  5, "valency": (3, 5)},
-"Te" : {"ord":  52, "weight": 127.6000, "exact_mass": 129.90622440, "en": 2.10, "els":  6, "valency": (2, 4, 6)},
-"I"  : {"ord":  53, "weight": 126.9045, "exact_mass": 126.90447300, "en": 2.66, "els":  7, "valency": (1, 3, 5, 7)},
-"Xe" : {"ord":  54, "weight": 131.2930, "exact_mass": 131.90415350, "en": 2.60, "els":  8, "valency": (0, 2)},
-"Cs" : {"ord":  55, "weight": 132.9055, "exact_mass": 132.90545190, "en": 0.79, "els":  1, "valency": (1,)},
-"Ba" : {"ord":  56, "weight": 137.2370, "exact_mass": 137.90524720, "en": 0.89, "els":  2, "valency": (2,)},
-"La" : {"ord":  57, "weight": 138.9055, "exact_mass": 138.90635330, "en": 1.10, "els":  3, "valency": (3,)},
-"Hf" : {"ord":  72, "weight": 178.4900, "exact_mass": 179.94655000, "en": 1.30, "els":  4, "valency": (4,)},
-"Ta" : {"ord":  73, "weight": 180.9479, "exact_mass": 180.94799580, "en": 1.50, "els":  5, "valency": (5,)},
-"W"  : {"ord":  74, "weight": 183.8400, "exact_mass": 183.95093120, "en": 2.36, "els":  6, "valency": (6,)},
-"Re" : {"ord":  75, "weight": 186.2070, "exact_mass": 186.95575310, "en": 1.90, "els":  7, "valency": (7,)},
-"Os" : {"ord":  76, "weight": 190.2300, "exact_mass": 191.96148070, "en": 2.20, "els":  8, "valency": (4, 6, 8)},
-"Ir" : {"ord":  77, "weight": 192.2170, "exact_mass": 192.96292640, "en": 2.20, "els":  8, "valency": (3, 4, 6)},
-"Pt" : {"ord":  78, "weight": 195.0780, "exact_mass": 194.96479110, "en": 2.28, "els":  8, "valency": (2, 4)},
-"Au" : {"ord":  79, "weight": 196.9665, "exact_mass": 196.96656870, "en": 2.54, "els":  1, "valency": (1, 3)},
-"Hg" : {"ord":  80, "weight": 200.5900, "exact_mass": 201.97064300, "en": 2.00, "els":  2, "valency": (1, 2)},
-"Tl" : {"ord":  81, "weight": 204.3833, "exact_mass": 204.97442750, "en": 2.04, "els":  3, "valency": (1, 3)},
-"Pb" : {"ord":  82, "weight": 207.2000, "exact_mass": 207.97665210, "en": 2.33, "els":  4, "valency": (2, 4)},
-"Bi" : {"ord":  83, "weight": 208.9804, "exact_mass": 208.98039870, "en": 2.02, "els":  5, "valency": (3, 5)},
-"Po" : {"ord":  84, "weight": 208.9824, "exact_mass": 208.98243040, "en": 2.00, "els":  6, "valency": (2, 4, 6)},
-"At" : {"ord":  85, "weight": 209.9871, "exact_mass": 209.98714800, "en": 2.20, "els":  7, "valency": (1, 7)},
-"Rn" : {"ord":  86, "weight": 222.0176, "exact_mass": 222.01757770, "en": 0.00, "els":  8, "valency": (0, 2)},
-"Ra" : {"ord":  88, "weight": 226.0254, "exact_mass": 226.02540980, "en": 0.90, "els":  2, "valency": (2,)},
-"U"  : {"ord":  92, "weight": 238.0289, "exact_mass": 238.05078820, "en": 1.38, "els":  6, "valency": (3, 4, 5, 6)},
-"X": {'query': True, 'ord': 300, 'key': '"X"', 'weight': 0, 'valency': (1,)},          # halogen
-"Q": {'query': True, 'ord': 301, 'key': '"Q"', 'weight': 0, 'valency': (1, 2, 3, 4)},  # anything not H or C
-"A": {'query': True, 'ord': 302, 'key': '"A"', 'weight': 0, 'valency': (1, 2, 3, 4)},  # anything not H
-"R": {'query': True, 'ord': 303, 'key': '"R"', 'weight': 0, 'valency': (1, 2, 3, 4)},  # anything
-}
-
-# elements that accept cations and raise their valency; for each element a valency is specified
-# because this property is valency (oxidation state) specific 
-accept_cation = {'N': 3, 'P': 3, 'O': 2, 'S': 2, 'Se': 2}
-
-# elements that accept anions and raise their valency; for each element a valency is specified
-# because this property is valency (oxidation state) specific 
-accept_anion = {'B': 3, 'Al': 3, 'P': 5}
-
-
-class composition_dict( dict):
-  """special dict that automatically converts itself to human readable composition on str()"""
-  def __str__( self):
-    ret = ''
-    for n in ('C','H'):
-      if n in self:
-        if ret:
-          ret += ', '
-        ret += "%s: %2.3f%%" % (n, self[n])
-    k = self.keys()
-    k.sort()
-    for n in self:
-      if n not in ('C','H'):
-        if ret:
-          ret += ', '
-        ret += "%s: %2.3f%%" % (n, self[n])
-    return ret
-
-class formula_dict( dict):
-  """special dict that automatically converts itself to human readable
-  formula on str(). Implements += for convenient formula concatenation"""
-
-  def __init__( self, form=None):
-    dict.__init__( self)
-    ## incomplete means that there were some problems to fully convert a formula to this dict
-    self.incomplete = 0
-    if type( form) in (types.StringType, types.UnicodeType):
-      self.read_formula_string( form)
-    elif type( form) == types.DictType:
-      for key, val in form.iteritems():
-        if key in periodic_table and type( val) == types.IntType:
-          self[ key] = val
-        else:
-          raise ValueError, "some of the dictionary entries are not valid for formula_dict (%s => %s)" % (str( key), str( val))
-  
-  def __str__( self, reverse=0):
-    sum = ''
-    k = self.sorted_keys()
-    if reverse:
-      k.reverse()
-    for s in k:
-      if self[s] == 1:
-        num = ''
-      else:
-        num = str( self[s])
-      sum += s+num
-    return sum
-
-  def __iadd__( self, other):
-    for s in other:
-      if s in self:
-        self[s] += other[s]
-      else:
-        self[s] = other[s]
-    return self
-
-  def __add__( self, other):
-    ret = formula_dict()
-    for form in (self, other):
-      for s in form:
-        if s in ret:
-          ret[s] += form[s]
-        else:
-          ret[s] = form[s]
-    return ret
-
-  def __mul__( self, other):
-    if not type( other) == types.IntType:
-      raise TypeError, "formula_dict can be only multiplied by an integer"
-    res = formula_dict()
-    for key in self.keys():
-      res[key] = other * self[key]
-    return res
-
-
-  def get_element_fraction( self, element):
-    if element in self:
-      return self[element]*periodic_table[element]['weight']/self.get_molecular_weight()
-    return 0
-
-  def get_molecular_weight( self):
-    tot = 0
-    for i in self:
-      tot += self[i]* periodic_table[i]['weight']
-    return tot
-
-  def get_exact_molecular_mass( self):
-    tot = 0
-    for i in self:
-      tot += self[i]* periodic_table[i]['exact_mass']
-    return tot
-
-  
-  def keys_in_order( self):
-    return self.sorted_keys()
-
-  def sorted_keys( self):
-    k = self.keys()
-    ret = []
-    if 'C' in k:
-      for a in ('C','H'):
-        if a in k:
-          ret.append( a)
-          k.remove( a)
-      k.sort()
-      return ret+k
-    else:
-      k.sort()
-      return k
-    
-
-  def read_formula_string( self, form):
-    is_formula = re.compile("^([A-Z][a-z]?[0-9]*)*$")
-    #form = "".join( form.split("."))
-    form = form.replace( ".", "")
-    if not is_formula.match( form):
-      return None
-    chunks = re.split( "([A-Z][a-z]*)", form)
-    del chunks[0]
-    for i in range( 0, len( chunks), 2):
-      if chunks[i] in self:
-        if chunks[i+1] == '':
-          j = 1
-        else:
-          j = int( chunks[i+1])
-        self[ chunks[i]] += j
-      elif chunks[i] in periodic_table:
-        if chunks[i+1] == '':
-          j = 1
-        else:
-          j = int( chunks[i+1])
-        self[ chunks[i]] = j
-      else:
-        self.incomplete = 1
-
-  def get_html_repr_as_string( self, outer_element=None, reverse=0):
-    sum = ''
-    k = self.sorted_keys()
-    if reverse:
-      k.reverse()
-    for s in k:
-      if self[s] == 1:
-        num = ''
-      else:
-        num = '<sub>%d</sub>' % self[s]
-      sum += s+num
-    if outer_element:
-      return '<%s>%s</%s>' % (outer_element, sum, outer_element)
-    return sum
-    
-  def is_saturated_alkyl_chain( self):
-    if (self.sorted_keys() == ['C','H']) and (self['H'] == 2*self['C']+1):
-      return 1
-    else:
-      return 0
-
-  def to_tuple( self):
-    return tuple( reduce( operator.add, [[k,self[k]] for k in self.sorted_keys()], []))
-
-
-    
-def dict_to_composition( form):
-  w = form.get_molecular_weight()
-  ret = composition_dict()
-  for s in form:
-    ret[ s] = form.get_element_fraction(s) * 100
-  return ret
-
-def formula_to_weight( formula):
-  return formula_dict( formula).get_molecular_weight()
-
-def formula_to_formula( formula):
-  return str( formula_dict( formula))
-
-def formula_to_composition( formula):
-  return dict_to_composition( formula_to_dict( formula))
-
-
-## other support functions
-
-def text_to_hydrogenated_atom( text):
-  a = re.match( '^([a-z]{1,2})(h)(\d*)$', text.lower())
-  if a:
-    atom = a.group( 1)
-    hydrogens = a.group( 3)
-  else:
-    a = re.match( '^(h)(\d*)([a-z]{1,2})$', text.lower())
-    if a:
-      atom = a.group( 3)
-      hydrogens = a.group( 2)
-    else:
-      return None
-
-  if atom.capitalize() in periodic_table:
-    ret = formula_dict()
-    ret[ atom.capitalize()] = 1
-    if hydrogens:
-      ret[ 'H'] = int( hydrogens)
-    else:
-      ret[ 'H'] = 1
-    return ret
-  else:
-    return None
-
-
-
-def gen_bit_masks( length):
-  ret = length * [0]
-  yield ret
-  for i in xrange( 2 ** length):
-    ret[0] += 1
-    for j in xrange( length):
-      if ret[j] == 2:
-        ret[j] = 0
-        if j == length-1:
-          raise StopIteration
-        else:
-          ret[j+1] += 1
-      else:
-        break
-    yield ret
-  
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/plugin.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/plugin.py
--- bkchem-0.12.5/bkchem/oasa/oasa/plugin.py	2008-02-19 04:11:40.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/plugin.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,42 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-class plugin:
-  """the basic plugin interface"""
-
-  name = "plugin"
-  read = 0
-  write = 0
-
-  def __init__( self):
-    pass
-
-  def set_structure( self, structure):
-    pass
-
-  def get_structure( self, structure):
-    pass
-
-  def read_file( self, file):
-    pass
-
-  def write_file( self, file):
-    pass
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/pybel_bridge.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/pybel_bridge.py
--- bkchem-0.12.5/bkchem/oasa/oasa/pybel_bridge.py	2008-10-14 09:46:12.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/pybel_bridge.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,223 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-"""This module is intended for integration of OpenBabel/Pybel into OASA.
-It provides functionality for translation of OASA molecules into Pybel molecules."""
-
-import pybel, openbabel
-from molecule import molecule
-from atom import atom
-from bond import bond
-from periodic_table import periodic_table as PT
-num2symbol = dict( [(v['ord'],k) for k,v in PT.iteritems()])
-
-
-class PybelConverter( object):
-
-  ## -------------------- Pybel to OASA --------------------
-
-  @classmethod
-  def pybel_to_oasa_atom( self, patom):
-    oatom = atom()
-    psymbol = num2symbol[patom.atomicnum]
-    oatom.symbol = psymbol
-    oatom.x, oatom.y, oatom.z = patom.coords
-    return oatom
-
-  @classmethod
-  def pybel_to_oasa_bond( self, pbond):
-    obond = bond()
-    obond.order = pbond.GetBondOrder()
-    return obond
-
-  @classmethod
-  def pybel_to_oasa_molecule_with_atom_map( self, pmol):
-    omol = molecule()
-    patom_idx2oatom = {}
-    for pa in pmol.atoms:
-      oa = self.pybel_to_oasa_atom( pa)
-      omol.add_vertex( oa)
-      patom_idx2oatom[ pa.idx] = oa
-    for pb in openbabel.OBMolBondIter( pmol.OBMol):
-      ob = self.pybel_to_oasa_bond( pb)
-      i1 = pb.GetBeginAtomIdx()
-      i2 = pb.GetEndAtomIdx()
-      oa1 = patom_idx2oatom[ i1]
-      oa2 = patom_idx2oatom[ i2]
-      omol.add_edge( oa1, oa2, ob)
-    return omol, patom_idx2oatom
-
-  @classmethod
-  def pybel_to_oasa_molecule( self, *args):
-    omol, patom_idx2oatom = self.pybel_to_oasa_molecule_with_atom_map( *args)
-    return omol
-
-  ## // -------------------- Pybel to OASA --------------------
-
-  ## -------------------- OASA to Pybel --------------------
-
-  @classmethod
-  def oasa_to_ob_atom( self, oatom):
-    num = PT[ oatom.symbol]['ord']
-    obatom = openbabel.OBAtom()
-    obatom.SetAtomicNum( num)
-    obatom.SetVector( oatom.x, oatom.y, oatom.z)
-    #patom = pybel.Atom()
-    #patom.atomicnum = num
-    return obatom
-
-  @classmethod
-  def oasa_to_ob_bond( self, obond):
-    pbond = openbabel.OBBond()
-    pbond.SetBondOrder( obond.order)
-    return pbond
-
-  @classmethod
-  def oasa_to_pybel_molecule_with_atom_map( self, omol):
-    obmol = openbabel.OBMol()
-    oatom2obatom_idx = {}
-    for i,oa in enumerate( omol.atoms):
-      oba = self.oasa_to_ob_atom( oa)
-      oba.SetIdx( i+1)
-      obmol.AddAtom( oba)
-      oatom2obatom_idx[ oa] = i+1
-    for ob in omol.bonds:
-      oa1, oa2 = ob.vertices
-      idx1 = oatom2obatom_idx[ oa1]
-      idx2 = oatom2obatom_idx[ oa2]
-      obmol.AddBond( idx1, idx2, ob.order)
-    pmol = pybel.Molecule( obmol)
-    return pmol, oatom2obatom_idx
-
-  @classmethod
-  def oasa_to_pybel_molecule( self, *args):
-    omol, patom_idx2oatom = self.oasa_to_pybel_molecule_with_atom_map( *args)
-    return omol
-
-  ## // -------------------- OASA to Pybel --------------------
-
-  ## -------------------- conversion --------------------
-
-  @classmethod
-  def read_text( self, format, text):
-    """returns a list of OASA molecules from a string"""
-    obc = openbabel.OBConversion()
-    if not obc.SetInFormat( format):
-        raise ValueError, "invalid format %s" % format
-    obmol = openbabel.OBMol()
-    ok = obc.ReadString( obmol, text)
-    mols = []
-    while ok:
-      mols.append( self.pybel_to_oasa_molecule( pybel.Molecule( obmol)))
-      obmol = openbabel.OBMol()
-      ok = obc.Read( obmol)
-    return mols
-
-  ## // -------------------- conversion --------------------
-
-  ## -------------------- conversion support --------------------
-  ## not needed - pybel provides this functionality, I just overlooked it
-  
-  @classmethod
-  def get_supported_input_formats( self):
-    conv = openbabel.OBConversion()
-    ret = []
-    for form in conv.GetSupportedInputFormat():
-      form_id, desc = [x.strip() for x in form.split("--")]
-      ret.append( (form_id, desc))
-    return ret
-
-  @classmethod
-  def get_supported_output_formats( self):
-    conv = openbabel.OBConversion()
-    ret = []
-    for form in conv.GetSupportedOutputFormat():
-      form_id, desc = [x.strip() for x in form.split("--")]
-      ret.append( (form_id, desc))
-    return ret
-
-  ## // -------------------- conversion support --------------------
-
-class ForceFieldOptimizer( object):
-  """provides the forcefield interface for oasa molecules"""
-  
-  def __init__( self, mol, forcefield_name="Ghemical"):
-    self.mol = mol
-    self._amol, self._oatom2patom_idx = PybelConverter.oasa_to_pybel_molecule_with_atom_map( self.mol)
-    self._patom_idx2oatom = dict( [(v,k) for k,v in self._oatom2patom_idx.iteritems()])
-    self.forcefield_name = forcefield_name
-    self.ff = openbabel.OBForceField.FindForceField( self.forcefield_name)
-    self.ff.Setup( self._amol.OBMol)
-    
-  def conjugate_gradients( self, step_size=10, max_steps=100):
-    """partial result will be yielded after the 'step_size' number of iteration has
-    been performed. When false is yielded, we did not reach convergence criteria yet.
-    'max_steps' is the number of repetitions of this, so the real number of iterations
-    is 'max_steps' * 'step_size'
-    """
-    self.ff.ConjugateGradientsInitialize( step_size * max_steps)
-    for i in range( max_steps):
-      do_continue = self.ff.ConjugateGradientsTakeNSteps( step_size)
-      yield not do_continue
-      if not do_continue:
-        break
-
-  def update_coords( self):
-    self.ff.UpdateCoordinates( self._amol.OBMol)
-    for patom in self._amol:
-      oatom = self._patom_idx2oatom[ patom.idx]
-      oatom.coords = patom.coords
-    
-
-
-if __name__ == "__main__":
-  import time
-  
-  pmol = pybel.readstring("smi", "CC(=O)O")
-  omol = PybelConverter.pybel_to_oasa_molecule( pmol)
-  print omol
-  import smiles
-  c = smiles.converter()
-  print c.mols_to_text( [omol])
-
-  #print get_supported_input_formats()
-  #print get_supported_output_formats()
-
-  if False:
-    import molfile
-    f = file( "tbu-benzen.mol", "r")
-    mol = molfile.file_to_mol( f)
-    f.close()
-
-    print PybelConverter.oasa_to_pybel_molecule( mol)
-
-    t = time.time()
-    ff = ForceFieldOptimizer( mol)
-    gen = ff.conjugate_gradients()
-    for step in gen:
-      print step, ff.ff.Energy()
-      ff.update_coords()
-      print [a.coords for a in mol.atoms]
-    print time.time() - t
-
-  a = pybel.readstring( "smi", "CCCC\nCCC")
-
-  for mol in PybelConverter.read_text( "smi", "CCCC\nCCC"):
-    print mol
-  
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/query_atom.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/query_atom.py
--- bkchem-0.12.5/bkchem/oasa/oasa/query_atom.py	2008-10-14 09:46:12.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/query_atom.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,139 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import sys
-sys.path.append( '../')
-
-import graph
-import periodic_table as PT
-from common import is_uniquely_sorted
-
-import copy
-from warnings import warn
-
-import re
-from atom import atom
-
-from chem_vertex import chem_vertex
-from oasa_exceptions import oasa_invalid_atom_symbol
-
-
-
-class query_atom( chem_vertex):
-
-  ## ("value","charge","x","y","z","multiplicity","valency","charge","free_sites")
-  attrs_to_copy = chem_vertex.attrs_to_copy + ("symbols",)
-
-  def __init__( self, coords=None):
-    chem_vertex.__init__( self, coords=coords)
-    self.symbols = set()
-    self.free_sites = 0
-
-
-  def matches( self, other):
-    if not isinstance( other, atom):
-      return False
-    #if isinstance( other, query_atom):
-    #  raise ValueError, "query atoms on the recieving side are not supported"
-    # halogens
-    for sym in self.symbols:
-      if sym == other.symbol:
-        return True
-      elif sym == "X":
-        if other.symbol in ("F","Cl","Br","I"):
-          return True
-      # Q (any except H,C)
-      elif sym == "Q":
-        if other.symbol not in "HC":
-          return True
-      # A (any except H)
-      elif sym == "A":
-        if other.symbol != "H":
-          return True
-      # R - anything
-      elif sym == "R":
-        return True
-
-    return False
-
-
-  ## PROPERTIES
-
-
-  # symbol
-  def _set_symbol( self, symbol):
-    if symbol in PT.periodic_table.keys():
-      if not "query" in PT.periodic_table[ symbol]:
-        warn( "Setting normal atom symbol to a query_atom instance, do you mean it?")
-      self.symbols = set( [symbol])
-    else:
-      self.symbols = self.parse_query_definition( symbol)
-
-    self._symbol = symbol
-
-
-  def _get_symbol( self):
-    return self._symbol
-
-  symbol = property( _get_symbol, _set_symbol, None, "atom symbol")
-
-
-
-
-
-  # free_sites
-  def _set_free_sites( self, free_sites):
-    self._free_sites = free_sites
-
-  def _get_free_sites( self):
-    return self._free_sites
-
-  free_sites = property( _get_free_sites, _set_free_sites, None, "atoms free_sites")
-
-
-
-      
-  def __str__( self):
-    return "query atom '%s'" % str( self.symbol)
-
-
-
-
-  def is_query_definition( self, text):
-    matcher = re.compile( "\[([A-Z][a-z]?,)*[A-Z][a-z]?\]")
-    return matcher.match( text) and True or False
-
-  is_query_definition = classmethod( is_query_definition)
-
-
-  def parse_query_definition( self, text):
-    if self.is_query_definition( text):
-      syms = set( map( str, text[1:-1].split(",")))
-      for sym in syms:
-        if sym not in PT.periodic_table.keys():
-          raise oasa_invalid_atom_symbol( "invalid symbol in query definition", sym)
-      return syms
-    else:
-      raise oasa_invalid_atom_symbol( "not valid query definition", text)
-
-  parse_query_definition = classmethod( parse_query_definition)
-
-
-
-#print query_atom.parse_query_definition( "[Ar,Cl,X]")
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/reaction.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/reaction.py
--- bkchem-0.12.5/bkchem/oasa/oasa/reaction.py	2008-02-19 07:44:47.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/reaction.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,60 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-from molecule import molecule
-
-
-class reaction( object):
-  """Reaction representation"""
-
-
-  def __init__( self, reactants=None, products=None, reagents=None):
-    self.reactants = reactants or []
-    self.products = products or []
-    self.reagents = reagents or []
-
-
-
-class reaction_component( object):
-  """Represents one component of a reaction"""
-
-  def __init__( self, mol=None, stoichiometry=1):
-    self.stoichiometry = stoichiometry
-    self.molecule = mol
-
-  def _set_molecule( self, mol):
-    assert isinstance( mol, molecule)
-    self._molecule = mol
-
-  def _get_molecule( self):
-    return self._molecule
-
-  molecule = property( _get_molecule, _set_molecule)
-
-  def _set_stoichiometry( self, stoich):
-    assert type( stoich) in (int, float)
-    self._stoichiometry = stoich
-
-  def _get_stoichiometry( self):
-    return self._stoichiometry
-
-  stoichiometry = property( _get_stoichiometry, _set_stoichiometry)
-
-  def __str__( self):
-    return "%s * (%s)" % (self.stoichiometry, self.molecule)
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/smiles.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/smiles.py
--- bkchem-0.12.5/bkchem/oasa/oasa/smiles.py	2008-10-14 09:46:12.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/smiles.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,774 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-from plugin import plugin
-from molecule import molecule, equals
-import periodic_table as PT
-import oasa_exceptions
-import reaction
-import stereochemistry
-
-
-from config import Config
-
-import re
-
-import operator
-import time
-
-class smiles( plugin):
-
-  name = "smiles"
-  read = 1
-  write = 1
-
-  smiles_to_oasa_bond_recode = {'-': 1, '=': 2, '#': 3, ':': 4, ".": 0, "\\": 1, "/": 1}
-  oasa_to_smiles_bond_recode = {1: '', 2: '=', 3: '#', 4:''}
-
-  def __init__( self, structure=None):
-    self.structure = structure
-
-  def recode_oasa_to_smiles_bond( self, b):
-    if b.aromatic:
-      return ''
-    elif b in self._stereo_bonds_to_others:
-      others = [(e,st) for e,st in self._stereo_bonds_to_others[b] if e in self._stereo_bonds_to_code]
-      if not others:
-        code = "\\"
-      else:
-        # other bonds enforce encoding of this one, we select the first one,
-        # bacause there is nothing we can do if there are clashing constrains anyway
-        other, st = others[0]
-        end1,inside1,inside2,end2 = st.references
-        if set( other.vertices) == set( [end1,inside1]):
-          v1 = inside1
-          v2 = end1
-        else:
-          v1 = inside2
-          v2 = end2
-        last_order = self._processed_atoms.index( v2) - self._processed_atoms.index( v1)
-        last_code = self._stereo_bonds_to_code[ other] == "\\" and 1 or -1
-        relation = st.value == st.OPPOSITE_SIDE and -1 or 1
-        if relation*last_code*last_order < 0:
-          code = "/"
-        else:
-          code = "\\"
-      self._stereo_bonds_to_code[ b] = code
-      return code
-    else:
-      if b.order == 1:
-        a1, a2 = b.vertices
-        if 'aromatic' in a1.properties_ and 'aromatic' in a2.properties_:
-          # non-aromatic bond connecting two aromatic rings, we need to return -
-          return '-'
-      return self.oasa_to_smiles_bond_recode[ b.order]
-
-  def set_structure( self, structure):
-    self.structure = structure
-
-  def get_structure( self):
-    return self.structure
-
-  def read_smiles( self, text):
-    mol = Config.create_molecule()
-    text = "".join( text.split())
-    is_text = re.compile("^[A-Z][a-z]?$")
-    # internally revert \/ bonds before numbers, this makes further processing much easier
-    text = re.sub( r"([\\/])([0-9])", lambda m: (m.group(1)=="/" and "\\" or "/")+m.group(2), text)
-    # // end
-    chunks = re.split( "(\[.*?\]|[A-Z][a-z]?|%[0-9]{1,2}|[^A-Z]|[a-z])", text)
-    chunks = self._check_the_chunks( chunks)
-    last_atom = None
-    last_bond = None
-    numbers = {}
-    bracket_openings = []
-    for c in chunks:
-      # atom
-      if is_text.match( c) or c.islower() or c[0] == "[":
-        a = mol.create_vertex()
-        if c[0] == "[":
-          # atom spec in square brackets
-          self._parse_atom_spec( c, a)
-        else:
-          # just atom symbol
-          if c.islower():
-            symbol = c.upper()
-            a.properties_['aromatic'] = 1
-          else:
-            symbol = c
-          a.symbol = symbol
-
-        mol.add_vertex( a)
-        if last_bond: # and not (not 'aromatic' in a.properties_ and last_bond.aromatic):
-          mol.add_edge( last_atom, a, e=last_bond)
-          last_bond = None
-        elif last_atom:
-          b = mol.add_edge( last_atom, a)
-          if 'aromatic' in a.properties_:
-            # aromatic bond
-            b.order = 4
-            b.type = 'n'
-        last_atom = a
-        last_bond = None
-      # bond
-      elif c in r'-=#:.\/':
-        order = self.smiles_to_oasa_bond_recode[ c]
-        last_bond = mol.create_edge()
-        last_bond.order = order
-        last_bond.type = 'n'
-        if c in r'\/':
-          last_bond.properties_['stereo'] = c
-      # ring closure
-      elif c.isdigit():
-        if c in numbers:
-          if last_bond:
-            b = last_bond
-          else:
-            b = mol.create_edge()
-            if "aromatic" in numbers[c].properties_:
-              b.order = 4
-          mol.add_edge( last_atom, numbers[c], e=b)
-          last_bond = None
-          del numbers[ c]
-        else:
-          numbers[c] = last_atom
-          last_bond = None
-      elif c == '(':
-        bracket_openings.append( last_atom)
-      elif c == ')':
-        last_atom = bracket_openings.pop(-1)
-
-    ## FINISH
-    for a in mol.vertices:
-      if not "explicit_valency" in a.properties_:
-        a.raise_valency_to_senseful_value()
-      else:
-        # detect radicals (but not biradicals - problem of triplet vs. singlet)
-        del a.properties_['explicit_valency']
-        if a.valency - a.occupied_valency == 1:
-          a.multiplicity += 1
-        else:
-          a.valency = a.occupied_valency
-      try:
-        del a.properties_['aromatic']
-      except:
-        pass
-
-    # stereochemistry
-    self._process_stereochemistry( mol)
-
-    if len(mol.vertices) == 0:
-      mol = None
-    self.structure = mol 
-
-
-  def _parse_atom_spec( self, c, a):
-    """c is the text spec,
-    a is an empty prepared vertex (atom) instance"""
-    bracketed_atom = re.compile("^\[(\d*)([A-z][a-z]?)(.*?)\]")
-    m = bracketed_atom.match( c)
-    if m:
-      isotope, symbol, rest = m.groups()
-    else:
-      raise ValueError( "unparsable square bracket content '%s'" % c)
-    if symbol.islower():
-      symbol = symbol.upper()
-      a.properties_['aromatic'] = 1
-    a.symbol = symbol
-    if isotope:
-      a.isotope = int( isotope)
-    # hydrogens
-    _hydrogens = re.search( "H(\d*)", rest)
-    h_count = 0
-    if _hydrogens:
-      if _hydrogens.group(1):
-        h_count = int( _hydrogens.group(1))
-      else:
-        h_count = 1
-    a.explicit_hydrogens = h_count
-    # charge
-    charge = 0
-    # one possible spec of charge
-    _charge = re.search( "[-+]{2,10}", rest)
-    if _charge:
-      charge = len( _charge.group(0))
-      if _charge.group(0)[0] == "-":
-        charge *= -1
-    # second one, only if the first one failed
-    else:
-      _charge = re.search( "([-+])(\d?)", rest)
-      if _charge:
-        if _charge.group(2):
-          charge = int( _charge.group(2))
-        else:
-          charge = 1
-        if _charge.group(1) == "-":
-          charge *= -1
-    a.charge = charge
-    # stereo
-    _stereo = re.search( "@+", rest)
-    if _stereo:
-      stereo = _stereo.group(0)
-      a.properties_['stereo'] = stereo
-    # using [] means valency is explicit
-    a.properties_['explicit_valency'] = True
-
-  def _check_the_chunks( self, chunks):
-    is_text = re.compile("^[A-Z][a-z]?$")
-    is_long_num = re.compile( "^%[0-9]{1,2}$")
-    ret = []
-    for c in chunks:
-      if not c:
-        continue
-      if is_text.match( c):
-        if (not c in PT.periodic_table and len(c)==2) or c == "Sc": # Sc is S-c not scandium
-          a,b = c
-          ret.append( a)
-          ret.append( b)
-        else:
-          ret.append( c)
-      elif is_long_num.match( c):
-        ret.append( str( int( c[1:])))
-      else:
-        ret.append( c)
-    return ret
-
-
-
-  def _process_stereochemistry( self, mol):
-    ## process stereochemistry
-    ## double bonds
-    def get_stereobond_direction( end_atom, inside_atom, bond, init):
-      position = mol.vertices.index( end_atom) - mol.vertices.index( inside_atom)
-      char = bond.properties_['stereo'] == "\\" and 1 or -1
-      direction = (position * char * init) < 0 and "up" or "down"
-      return direction
-    def get_end_and_inside_vertex_from_edge_path( edge, path):
-      a1,a2 = edge.vertices
-      if len( [e for e in a1.neighbor_edges if e in path]) == 1:
-        return a1, a2
-      return a2, a1
-    
-    stereo_edges = [e for e in mol.edges if "stereo" in e.properties_]
-    paths = []
-    for i,e1 in enumerate( stereo_edges):
-      for e2 in stereo_edges[i+1:]:
-        path = mol.get_path_between_edges( e1, e2)
-        path2 = path[1:-1]
-        if len( path2)%2 and not [_e for _e in path2 if _e.order != 2]:
-          # only odd number of double bonds, double bonds only
-          for _e in path[1:-1]:
-            if not mol.is_edge_a_bridge_fast_and_dangerous( _e):
-              break
-          else:
-            # only stereo related to non-cyclic bonds
-            paths.append( path)
-    
-    for path in paths:
-      bond1 = path[0]
-      end_atom1,inside_atom1 = get_end_and_inside_vertex_from_edge_path( bond1, path)
-      bond2 = path[-1]
-      end_atom2,inside_atom2 = get_end_and_inside_vertex_from_edge_path( bond2, path)
-      d1 = get_stereobond_direction( end_atom1, inside_atom1, bond1, -1)
-      d2 = get_stereobond_direction( end_atom2, inside_atom2, bond2, -1)
-      if d1 == d2:
-        value = stereochemistry.cis_trans_stereochemistry.SAME_SIDE
-      else:
-        value = stereochemistry.cis_trans_stereochemistry.OPPOSITE_SIDE
-      if len( path) == 3:
-        center = path[1]
-      else:
-        center = None
-      refs = [end_atom1,inside_atom1,inside_atom2,end_atom2]
-      st = stereochemistry.cis_trans_stereochemistry( center=center, value=value, references=refs)
-      mol.add_stereochemistry( st)
-
-    for e in mol.edges:
-      if 'stereo' in e.properties_:
-        del e.properties_['stereo']
-
-
-  def get_smiles( self, mol):
-    if not mol.is_connected():
-      raise oasa_exceptions.oasa_not_implemented_error( "SMILES", "Cannot encode disconnected compounds, such as salts etc.")
-    #mol = molec.copy()
-    self.ring_joins = []
-    self._processed_atoms = []
-    self.branches = {}
-    self._stereo_bonds_to_code = {} # for bond it will contain character it uses
-    self._stereo_bonds_to_others = {} # for bond it will contain the other bonds
-    # at first we mark all the atoms with aromatic bonds
-    # it is much simple to do it now when all the edges are present
-    # we can make use of the properties attribute of the vertex
-    for e in mol.edges:
-      if e.aromatic:
-        for v in e.vertices:
-          v.properties_[ 'aromatic'] = 1
-    # stereochemistry information preparation
-    for st in mol.stereochemistry:
-      end1, inside1, inside2, end2 = st.references
-      e1 = end1.get_edge_leading_to( inside1)
-      e2 = end2.get_edge_leading_to( inside2)
-      self._stereo_bonds_to_others[ e1] = self._stereo_bonds_to_others.get( e1, []) + [(e2, st)]
-      self._stereo_bonds_to_others[ e2] = self._stereo_bonds_to_others.get( e2, []) + [(e1, st)]
-
-    ret = ''.join( [i for i in self._get_smiles( mol)])
-    mol.reconnect_temporarily_disconnected_edges()
-    # this is needed because the way temporarily_disconnected edges are handled is not compatible with the way smiles
-    # generation works - it splits the molecule while reusing the same atoms and bonds and thus disconnected bonds accounting fails
-    for e in mol.edges:
-      e.disconnected = False
-    return ret
-
-
-
-  def _get_smiles( self, mol, start_from=None):
-    # single atoms
-    if len( mol.vertices) == 1:
-      v = mol.vertices[0]
-      yield self._create_atom_smiles( v)
-      for e in self.ring_joins:
-        if v in e.get_vertices():
-          yield self.recode_oasa_to_smiles_bond( e)
-          yield str( self.ring_joins.index( e) +1)
-      return
-    while not (is_line( mol) and (not start_from or start_from.get_degree() <= 1)):
-      if is_pure_ring( mol):
-        if start_from:
-          self.ring_joins.append( mol.temporarily_disconnect_edge( start_from.neighbor_edges[0]))
-        else:
-          self.ring_joins.append( mol.temporarily_disconnect_edge( list( mol.edges)[0]))
-      else:
-        e, mol, branch_vertex, branch = self.disconnect_something( mol, start_from=start_from)
-        if branch_vertex:
-          if branch_vertex in self.branches:
-            self.branches[ branch_vertex].append((e, branch))
-          else:
-            self.branches[ branch_vertex] = [(e, branch)]
-        else:
-          self.ring_joins.append( e)
-    try:
-      start, end = filter( lambda x: x.get_degree() == 1, mol.vertices)
-    except:
-      #print filter( lambda x: x.get_degree() == 1, mol.vertices)
-      raise "shit"
-    if start_from == end:
-      start, end = end, start
-    v = start
-    last = None
-    was_end = False
-    while True:
-      yield self._create_atom_smiles( v)
-      # the atom
-      for e in self.ring_joins:
-        if v in e.get_vertices():
-          _b = self.recode_oasa_to_smiles_bond( e)
-          if _b not in "/\\":
-            yield _b
-          yield str( self.ring_joins.index( e) +1)
-      # branches
-      if v in self.branches:
-        for edg, branch in self.branches[ v]:
-          yield '('
-          yield self.recode_oasa_to_smiles_bond( edg)
-          v1, v2 = edg.vertices
-          vv = (v1 != v) and v1 or v2
-          for i in self._get_smiles( branch, start_from=vv):
-            yield i
-          yield ')'
-      # bond leading to the neighbor
-      for e, neighbor in v.get_neighbor_edge_pairs():
-        if neighbor != last:
-          yield self.recode_oasa_to_smiles_bond( e)
-          last = v
-          v = neighbor
-          break
-      if was_end:
-        break
-      if v == end:
-        was_end = True
-
-
-  def _create_atom_smiles( self, v):
-    self._processed_atoms.append( v)
-    if 'aromatic' in v.properties_.keys():
-      symbol = v.symbol.lower()
-    else:
-      symbol = v.symbol
-
-    if v.isotope or v.charge != 0 or v.valency != PT.periodic_table[ v.symbol]['valency'][0] or 'stereo' in v.properties_ or v.multiplicity != 1:
-      # we must use square bracket
-      isotope = v.isotope and str( v.isotope) or ""
-      # charge
-      if v.charge:
-        sym = v.charge < 0 and "-" or "+"
-        charge = sym + (abs( v.charge) > 1 and str( abs( v.charge)) or "")
-      else:
-        charge = ""
-      # explicit hydrogens
-      if v.explicit_hydrogens or v.valency != PT.periodic_table[ v.symbol]['valency'][0]:
-        num_h = v.valency - v.occupied_valency
-        h_spec = (num_h and "H" or "") + (num_h > 1 and str( num_h) or "")
-      else:
-        h_spec = ""
-      # stereo
-      stereo = v.properties_.get( "stereo", "")
-      return "[%s%s%s%s%s]" % (isotope, symbol, stereo, h_spec, charge)
-    else:
-      # no need to use square brackets
-      return symbol
-
-
-  def disconnect_something( self, mol, start_from=None):
-    """returns (broken edge, resulting mol, atom where mol was disconnected, disconnected branch)"""
-    # we cannot do much about this part
-    if start_from and start_from.get_degree() != 1:
-      for e,n in start_from.get_neighbor_edge_pairs():
-        if n.get_degree() > 2:
-          mol.temporarily_disconnect_edge( e)
-          return e, mol, None, None
-      mol.temporarily_disconnect_edge( e)
-      return e, mol, None, None
-    # at first try to find edges for which degree of neighbors is bigger
-    # than [2,2] and at best they are not bridges
-    # when no non-bridges are present use the other ones
-    #
-    # the edges with crowded atoms
-    for e in mol.edges:
-      d1, d2 = [x.get_degree() for x in e.get_vertices()]
-      if d1 > 2 and d2 > 2 and not mol.is_edge_a_bridge_fast_and_dangerous( e):
-        mol.temporarily_disconnect_edge( e)
-        return e, mol, None, None
-    # the other valuable non-bridge edges
-    for e in mol.edges:
-      d1, d2 = [x.get_degree() for x in e.get_vertices()]
-      if (d1 > 2 or d2 > 2) and not mol.is_edge_a_bridge_fast_and_dangerous( e):
-        mol.temporarily_disconnect_edge( e)
-        return e, mol, None, None
-    # there are no non-bridges
-    # we want to split off the smallest possible chunks
-    min_size = None
-    the_right_edge = None
-    the_right_mol = None
-    the_right_branch = None
-    the_right_branch_atom = None
-    ring_joints_in_branch = 1000
-    ring_join_vertices = set( reduce( operator.add, [e.vertices for e in self.ring_joins], []))
-    for e in mol.edges:
-      d1, d2 = [x.get_degree() for x in e.get_vertices()]
-      if d1 > 2 or d2 > 2:
-        ps = mol.get_pieces_after_edge_removal( e)
-        if len( ps) == 1:
-          print "impossible"
-          continue
-        lenghts = map( len, ps)
-        ms = min( lenghts)
-        p1, p2 = ps
-        the_mol = (len( p1) < len( p2)) and p2 or p1
-        the_branch = (p1 == the_mol) and p2 or p1
-        ring_joints = len( [i for i in the_branch if i in ring_join_vertices])
-        if not min_size or ms < min_size or ring_joints_in_branch > ring_joints:
-          min_size = ms
-          the_right_edge = e
-          the_right_mol = the_mol
-          the_right_branch = the_branch
-          ring_joints_in_branch = ring_joints
-    if the_right_edge:
-      # what is possible to make here instead in the loop is made here
-      # it saves time
-      v1, v2 = the_right_edge.vertices
-      mol.temporarily_disconnect_edge( the_right_edge)
-      the_right_branch_atom = (v1 in the_right_mol) and v1 or v2
-      the_right_mol = mol.get_induced_subgraph_from_vertices( the_right_mol)
-      the_right_branch = mol.get_induced_subgraph_from_vertices( the_right_branch)
-      return (the_right_edge,
-              the_right_mol,
-              the_right_branch_atom,
-              the_right_branch)
-    #print mol, mol.is_connected()
-    raise "fuck, how comes!?"
-
-
-
-
-  def disconnect_something_simple( self, mol, start_from=None):
-    """returns (broken edge, resulting mol, atom where mol was disconnected, disconnected branch)"""
-    # we cannot do much about this part
-    if not mol.is_connected():
-      print "unconnected ", mol
-    if start_from and start_from.get_degree() > 1:
-      e = start_from._neighbors.keys()[0]
-      mol.disconnect_edge( e)
-      ps = [i for i in mol.get_connected_components()]
-      if len( ps) == 1:
-        return e, mol, None, None
-      else:
-        p1 = mol.get_induced_subgraph_from_vertices( ps[0])
-        p2 = mol.get_induced_subgraph_from_vertices( ps[1])
-        v1, v2 = e.get_vertices()
-        p = (start_from in p1.vertices) and p1 or p2
-        px = (p1 == p) and p2 or p1
-        v = (v1 in p.vertices) and v1 or v2
-        return e, p, v, px
-
-    for e in mol.edges:
-      d1, d2 = [x.get_degree() for x in e.get_vertices()]
-      if (d1 > 2 or d2 > 2):
-        mol.disconnect_edge( e)
-        ps = [i for i in mol.get_connected_components()]
-        if len( ps) == 1:
-          return e, mol, None, None
-        else:
-          p1 = mol.get_induced_subgraph_from_vertices( ps[0])
-          p2 = mol.get_induced_subgraph_from_vertices( ps[1])
-          v1, v2 = e.get_vertices()
-          if start_from:
-            p = (start_from in p1.vertices) and p1 or p2
-            px = (p1 == p) and p2 or p1
-            v = (v1 in p.vertices) and v1 or v2
-            return e, p, v, px
-
-          v = (v1 in p1.vertices) and v1 or v2
-          return e, p1, v, p2
-    print mol, mol.is_connected(), ',', map( len, mol.get_connected_components()), ',', start_from
-    raise "fuck, how comes!?"
-
-
-
-def is_line( mol):
-  """all degrees are 2 except of two with degree 1"""
-  if len( mol.vertices) == 1:
-    return True
-  ones = 0
-  for v in mol.vertices:
-    d = v.get_degree() 
-    if d == 1:
-      if ones == 2:
-        return False
-      ones += 1
-    elif d != 2:
-      return False
-  if ones == 2:
-    return True
-  return False
-
-def is_pure_ring( mol):
-  return filter( lambda x: x.get_degree() != 2, mol.vertices) == []
-
-
-##################################################
-## MODULE INTERFACE - newstyle
-
-from converter_base import converter_base
-
-class smiles_converter( converter_base):
-
-  # standard converter attrs
-  reads_text = True
-  writes_text = True
-  reads_files = True
-  writes_files = True
-
-  default_configuration = {"R_GENERATE_COORDS": True,
-                           "R_BOND_LENGTH": 1,
-                           "R_LOCALIZE_AROMATIC_BONDS": True,
-                           
-                           "W_AROMATIC_BOND_AUTODETECT": True,
-                           "W_INDIVIDUAL_MOLECULE_SEPARATOR": ".",
-                           "W_DETECT_STEREO_FROM_COORDS": True,
-                           }
-
-  def __init__( self):
-    converter_base.__init__( self)
-
-  def mols_to_text( self, structures):
-    converter_base.mols_to_text( self, structures)
-    sm = smiles()
-    ret = []
-    for mol in structures:
-      if self.configuration["W_AROMATIC_BOND_AUTODETECT"]:
-        mol.mark_aromatic_bonds()
-      if self.configuration["W_DETECT_STEREO_FROM_COORDS"]:
-        mol.detect_stereochemistry_from_coords()
-      ret.append( sm.get_smiles( mol))
-    self.last_status = self.STATUS_OK
-    return self.configuration["W_INDIVIDUAL_MOLECULE_SEPARATOR"].join( ret)
-
-  def read_text( self, text):
-    # reaction support
-    if ">" in text:
-      if text.count(">") != 2:
-        raise oasa_exceptions.oasa_smiles_error( "Only two '>' characters are supported in SMILES reactions. %d present" % text.count(">"))
-      parts = text.split( ">")
-      res = []
-      for part in parts:
-        res.append( self._read_string( part))
-        if not self.last_status == self.STATUS_OK:
-          res = []
-          break
-      if not res:
-        self.result = []
-        return []
-      else:
-        react = reaction.reaction()
-        for p in res[0]:
-          react.reactants.append( reaction.reaction_component( p))
-        for p in res[2]:
-          react.products.append( reaction.reaction_component( p))
-        for p in res[1]:
-          react.reagents.append( reaction.reaction_component( p))
-        self.result = [react]
-        return self.result
-    else:
-      return self._read_string( text)
-
-  def _read_string( self, text):
-    converter_base.read_text( self, text)
-    sm = smiles()
-    sm.read_smiles( text)
-    mol = sm.structure
-    if mol == None:
-      return []
-    zero_bonds = [e for e in mol.edges if e.order == 0]
-    for b in zero_bonds:
-      mol.disconnect_edge( b)
-    mols = mol.get_disconnected_subgraphs()
-    for mol in mols:
-      if self.configuration["R_LOCALIZE_AROMATIC_BONDS"]:
-        mol.localize_aromatic_bonds()
-        for b in mol.bonds:
-          b.aromatic = 0
-      if self.configuration["R_GENERATE_COORDS"]:
-        coords_generator.calculate_coords( mol, bond_length=self.configuration['R_BOND_LENGTH'])
-    self.result = mols
-    self.last_status = self.STATUS_OK
-    return mols
-
-  def mols_to_file( self, structures, f):
-    converter_base.mols_to_file( self, structures, f)
-    f.write( self.mols_to_text( structures))
-
-  def read_file( self, f):
-    converter_base.read_file( self, f)
-    mols = []
-    for line in f:
-      mol = self.text_to_mols( line)
-      mols.extend( mol)
-    return mols
-    
-converter = smiles_converter
-
-# END OF MODULE INTERFACE
-##################################################
-
-##################################################
-## MODULE INTERFACE - oldstyle
-
-import coords_generator
-
-reads_text = True
-writes_text = True
-reads_files = True
-writes_files = True
-
-def mol_to_text( structure):
-  sm = smiles()
-  structure.mark_aromatic_bonds()
-  return sm.get_smiles( structure)
-
-def text_to_mol( text, calc_coords=1):
-  sm = smiles()
-  sm.read_smiles( text)
-  mol = sm.structure
-  mol.localize_aromatic_bonds()
-  for b in mol.bonds:
-    b.aromatic = 0
-  if calc_coords:
-    coords_generator.calculate_coords( mol, bond_length=calc_coords)
-  return mol
-
-def mol_to_file( mol, f):
-  f.write( mol_to_text( mol))
-  
-def file_to_mol( f):
-  return text_to_mol( f.read())
-
-
-# END OF MODULE INTERFACE
-##################################################
-
-
-
-##################################################
-# DEMO
-
-if __name__ == '__main__':
-
-  import sys
-
-  def main( text, cycles):
-    t = time.time()
-    conv = converter()
-    for j in range( cycles):
-      mols = conv.read_text( text)
-      for mol in mols:
-        mol.remove_unimportant_hydrogens()
-        print "  summary formula:   ", mol.get_formula_dict()        
-      text = conv.mols_to_text( mols)
-      print "  generated SMILES:   %s" % text
-      print "  --"
-    t = time.time()-t
-    print 'time per cycle', round( 1000*t/cycles, 2), 'ms'
-
-  repeat = 3
-
-  if not len( sys.argv) > 1:
-    text = "COc5ccc4c2sc(cc2nc4c5)-c(cc1nc3c6)sc1c3ccc6OC"  #"ccc4ccc2cc1cc3ccccc3cc1cc2c4"
-    text = "C1CC=CC1"
-  else:
-    text = sys.argv[1]
-
-  print "oasa::SMILES DEMO"
-  print "converting following smiles to smiles (%d times)" % repeat
-  print "  starting with:      %s" % text
-  print "  --------------------"
-  main( text, repeat)
-
-# DEMO END
-##################################################
-
-
-
-
-
-
-##################################################
-# TODO
-
-# last branch does not need to be branch
-# optimize for either speed or human-readability
-# at first get rid of the edges common to two or more rings, not critical
-# handling of the start_from in disconnect_something as in disconnect_something_simple
-# could in disconnect_something happen that a start_from will be returned in a branch?
-# if I do "print m.get_smiles( m.structure)" the ring counting does not work after that
-## the transformation can be destructive!!!  - check
-
-## THIS IS A PROBLEM : C=1ccC=2C=1C=CC=CC=2  (should be azulene)
-
-# E/Z stereo is ignored in rings
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/stereochemistry.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/stereochemistry.py
--- bkchem-0.12.5/bkchem/oasa/oasa/stereochemistry.py	2008-05-28 10:09:15.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/stereochemistry.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,80 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import oasa_exceptions
-
-
-class stereochemistry( object):
-
-  def __init__( self, center=None, references=None, value=None):
-    self.center = center
-    if not references:
-      self.references = []
-    else:
-      self.references = references
-    self.value = value
-
-  # value property
-  def _get_value( self):
-    return self._value
-  def _set_value( self, value):
-    self._value = value
-  value = property( _get_value, _set_value)
-
-  # references property
-  def _get_references( self):
-    return self._references
-  def _set_references( self, references):
-    self._references = references
-  references = property( _get_references, _set_references)
-
-  # center property
-  def _get_center( self):
-    return self._center
-  def _set_center( self, center):
-    self._center = center
-  center = property( _get_center, _set_center)
-
-
-
-class cis_trans_stereochemistry( stereochemistry):
-
-  UNDEFINED = 0
-  OPPOSITE_SIDE = 1
-  SAME_SIDE = 2
-
-  # override of value
-  def _set_value( self, value):
-    if value not in (self.SAME_SIDE, self.OPPOSITE_SIDE):
-      raise oasa_exceptions.oasa_stereochemistry_error( "invalid stereochemistry identifier '%s'" % value)
-    super( self.__class__, self)._set_value( value)
-  value = property( stereochemistry._get_value, _set_value)
-
-  # references overriden
-  def _set_references( self, references):
-    if len( references) != 4:
-      raise oasa_exceptions.oasa_stereochemistry_error( "wrong number of references in stereochemistry specification '%s'" % len( references))
-    super( self.__class__, self)._set_references( references)
-  references = property( stereochemistry._get_references, _set_references)
-
-  def get_other_end( self, ref):
-    if not ref in self.references:
-      raise ValueError, "submitted object is not referenced in this stereochemistry object."
-    ref1, _r1, _r2, ref2 = self.references
-    return ref is ref1 and ref2 or ref1
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/structure_database.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/structure_database.py
--- bkchem-0.12.5/bkchem/oasa/oasa/structure_database.py	2008-09-19 07:25:27.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/structure_database.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,332 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import os, sys, re
-import oasa_exceptions
-try:
-    from pysqlite2 import dbapi2 as sqlite
-except ImportError, e:
-    raise Exception( "The required pysqlite module could not be loaded. More info here: '%s'" % e)
-
-import inchi
-
-class Config:
-    database_file = os.path.abspath( os.path.join( os.path.dirname( __file__), "structures.db"))
-
-
-def create_database():
-  connection = sqlite.connect( Config.database_file)
-  c = connection.cursor()
-  c.execute( "DROP TABLE IF EXISTS structures;")
-  c.execute( """CREATE TABLE structures (
-  id INTEGER PRIMARY KEY,
-  name TEXT,
-  inchikey TEXT,
-  smiles TEXT);""")
-  connection.commit()
-  c.execute( """CREATE TABLE synonyms (
-  id INTEGER,
-  synonym TEXT);""")
-  connection.commit()
-  c.close()
-  connection.close()
-
-
-def normalize_inchi( inchi):
-    if inchi.startswith("InChI="):
-        return inchi[6:]
-    else:
-        return inchi
-
-def _open_infile( infilename):
-    if os.path.isfile( infilename):
-        if infilename.endswith(".gz"):
-            import gzip
-            f = gzip.open( infilename, "r")
-        else:
-            f = file( infilename, "r")
-        return f
-    else:
-        return None
-
-def fill_database( infilename, name_cutoff=26, atom_count_cutoff=100):
-    """compound is added if either name length of atom_count is below the
-    corresponding cutoff value"""
-    added = 0
-    ignored = 0
-    connection = sqlite.connect( Config.database_file)
-    c = connection.cursor()
-    f = _open_infile( infilename)
-    if not f:
-        raise ValueError( "File does not exist:", infilename)
-    f.readline() # skip the first line
-    i = 0
-    for line in f:
-        if i % 10000 == 0:
-            print "done %8d, added %8d, ignored %8d" % (i,added,ignored)
-        values = [x.strip() for x in line.strip().split("\t")]
-        if len( values) != 4:
-            print >> sys.stderr, "Ignoring line:", line,
-            continue
-        cid, inchikey, smiles, name = [x.strip() for x in line.strip().split("\t")]
-        c.execute( "DELETE FROM structures WHERE id=?", (cid,))
-        if len( name) <= name_cutoff and len( [x for x in smiles if x.isupper()]) <= atom_count_cutoff and _allow_molecule( name, smiles):
-            c.execute( "INSERT INTO structures (id,name,inchikey,smiles) VALUES (?,?,?,?);", (cid, name, inchikey, smiles))
-            added += 1
-        else:
-            ignored += 1
-        if i % 100 == 0:
-            connection.commit()
-        i += 1
-    f.close()
-    connection.commit()
-    connection.close()
-    return added, ignored
-
-
-def get_compounds_from_database( database_file=None, **kw):
-    """easy to use interface to the SQL - keyword arguments are converted to
-    corresponding SQL commands.
-    Examples:
-    get_compounds_from_database( smiles='C1CCCCC1')
-    get_compounds_from_database( inchi='1/C4H10/c1-3-4-2/h3-4H2,1-2H3')    
-    """
-    for fname in (database_file, Config.database_file):
-        if fname and os.path.exists(fname):
-            break
-    else:
-        raise oasa_exceptions.oasa_error("Structure database not found. Try running 'python structure_database.py structures.txt.gz' in oasa directory to create the database file from default source.")
-
-    if 'inchi' in kw:
-        if not 'inchikey' in kw:
-            import inchi_key
-            kw['inchikey'] = inchi_key.key_from_inchi( kw['inchi'])
-        del kw['inchi']
-    search = ["%s=?" % k for k in kw.keys()]
-    values = kw.values()
-    tables = "structures"
-    if "synonym" in kw:
-        tables = "structures, synonyms"
-        search.append( "structures.id=synonyms.id")
-    if search:
-        sql = "SELECT structures.* FROM %s WHERE %s" % (tables, " AND ".join( search))
-    else:
-        sql = "SELECT structures.* FROM %s" % (tables)
-    connection = sqlite.connect( Config.database_file)
-    c = connection.cursor()
-    try:
-        c.execute( sql, values)
-    except sqlite.OperationalError, e:
-        raise oasa_exceptions.oasa_error( "Error reading from structure database: '%s'" % e)
-    ret = []
-    c2 = connection.cursor()
-    for row in c:
-        c2.execute( "SELECT synonym FROM synonyms WHERE id=%d" % row[0])
-        synonyms = list( c2)
-        ret.append( row+([x[0] for x in synonyms],))
-    c.close()
-    c2.close()
-    connection.close()
-    return ret
-
-def find_molecule_in_database( mol, database_file=None):
-    """tries to find oasa.molecule mol in the database by using its InChiKey"""
-    inchikey = inchi.generate_inchi_key( mol)[0]
-    res = get_compounds_from_database( inchikey=inchikey)
-    return res
-
-def _allow_molecule( name, smile):
-    if smile.count("-]") > 2:
-        # more than 2 negative charges
-        return False
-    if smile.count("+]") > 2:
-        # more than 2 positive charges
-        return False
-    if re.search( "\[\d", smile):
-        return False
-    if smile.count(".") > 2:
-        return False
-    disallowed = ["Tl","U","Ra","Rn","At","Po","Bi","Ta","W","Hf","La","Y","Sr","Nb","Zr","Eu","Lu","Ho","Ce","Nd","Dy","Th","Gd","Yb","Sm",'Es','Np','Pu','Pr','Md','Pm','Pa','Tb','Bk','Cm','Er','Cf','Fm','No',"Am","Ac","Tm","Lr","Fr"]
-    for ch in disallowed:
-        if ch in smile:
-            return False
-    # try smiles
-##     try:
-##         smiles.text_to_mol( smile, calc_coords=False)
-##     except oasa_exceptions.oasa_invalid_atom_symbol, e:
-##         return False
-    return True
-    
-def filter_src_file( infilename, name_cutoff=26, atom_count_cutoff=1000):
-    """print only those lines in infilename that would be allowed
-    in structure_database using current settings"""
-    added = 0
-    ignored = 0
-    f = _open_infile( infilename)
-    if not f:
-        raise ValueError
-    f.readline() # skip the first line
-    for line in f:
-        values = [x.strip() for x in line.strip().split("\t")]
-        if len( values) != 4:
-            print >> sys.stderr, "Ignoring line:", line,
-            continue
-        cid, inchikey, smiles, name = [x.strip() for x in line.strip().split("\t")]
-        if len( name) <= name_cutoff and len( [x for x in smiles if x.isupper()]) <= atom_count_cutoff and _allow_molecule( name, smiles):
-            print line[:-1]
-            added += 1
-        else:
-            ignored += 1
-    f.close()
-    return added, ignored
-
-def _is_cid_in_db( cid, cursor):
-    cursor.execute( "SELECT id FROM structures WHERE id=%s" % cid)
-    if list( cursor):
-        return True
-    else:
-        return False
-
-def add_synonyms_old( fname, only_first=3):
-    """this version checks each cid against databse, one by one,
-    this is slow, but requires less memory"""
-    f = _open_infile( fname)
-    connection = sqlite.connect( Config.database_file)
-    c = connection.cursor()
-    c2 = connection.cursor()
-    i = 0
-    last_cid = None
-    line_count = 0
-    for line in f:
-        line_count += 1
-        parts = line.split()
-        cid = parts[0]
-        if last_cid != cid:
-            last_cid == cid
-            count = 1
-            exists = _is_cid_in_db( cid, c2)
-        else:
-            count += 1
-        if exists and count <= only_first:
-            name = " ".join( parts[1:])
-            c.execute( "INSERT INTO synonyms (id,name) VALUES (?,?);", (cid, name))
-            i += 1
-            if i % 500 == 0:
-                connection.commit()
-            if i % 10000 == 0:
-                print "Added %d" % i
-        if line_count % 100000 == 0:
-            print "-- %dk lines --" % (line_count // 1000)
-        if line_count % 1000000 == 0:
-            break
-    connection.commit()
-    return i    
-
-
-def add_synonyms( fname, only_first=3):
-    """this version takes all cids into the memory for later comparison and is therefore
-    much faster, it can however consume much memory for big databases"""
-    f = _open_infile( fname)
-    connection = sqlite.connect( Config.database_file)
-    c = connection.cursor()
-    c2 = connection.cursor()
-    i = 0
-    last_cid = None
-    line_count = 0
-    c.execute( "SELECT id FROM structures;")
-    cids = set( [row[0] for row in c])
-    for line in f:
-        line_count += 1
-        parts = line.split()
-        cid = int( parts[0])
-        if last_cid != cid:
-            last_cid = cid
-            count = 1
-            exists = cid in cids
-        else:
-            count += 1
-        if exists and count <= only_first:
-            name = " ".join( parts[1:])
-            c.execute( "INSERT INTO synonyms (id,synonym) VALUES (?,?);", (cid, name))
-            i += 1
-            if i % 500 == 0:
-                connection.commit()
-            if i % 10000 == 0:
-                print "Added %d" % i
-        if line_count % 1000000 == 0:
-            print "-- %dk lines --" % (line_count // 1000)
-        #if line_count % 1000000 == 0:
-        #    break
-    connection.commit()
-    return i    
-
-
-
-if __name__ == "__main__":
-    import sys
-##     a,i = filter_src_file( 'selected_names_name50_inchi100.clean.txt')
-##     print >> sys.stderr, a, i
-##     sys.exit()
-
-    from optparse import OptionParser
-    op = OptionParser(usage="python %prog -c command [options] [structure file]")
-
-    op.add_option( "-c", "--command", action="store",
-                   dest="command", default="test",
-                   help="command - what to do; one of 'test','update','rebuild','synonyms'")
-    op.add_option( "-s", "--synonyms", action="store",
-                   dest="synonyms", default="",
-                   help="file containing synonyms")
-    (options, args) = op.parse_args()
-
-    if options.command == "test":
-        print get_compounds_from_database( inchi="1/C4H10/c1-3-4-2/h3-4H2,1-2H3")
-        print get_compounds_from_database( smiles="O")
-        import smiles
-        print find_molecule_in_database( smiles.text_to_mol("C1CCC=CC1"))
-        print find_molecule_in_database( smiles.text_to_mol("c1ccccc1C"))
-        print get_compounds_from_database( synonym="toluene")
-    elif options.command in ('update','rebuild','synonyms'):
-        if not options.command == 'synonyms':
-            if len( args) >= 1:
-                fname = args[0]
-            else:
-                print >> sys.stderr, "You must supply a valid filename of a file containing structures to be read."
-                sys.exit()
-            if options.command == 'rebuild':
-                if os.path.exists( Config.database_file):
-                    try:
-                        os.remove( Config.database_file)
-                    except Exception, e:
-                        print >> sys.stderr, "Old database file could not be removed. Reason: %s" % e
-                        print >> sys.stderr, "Quitting.."
-                        sys.exit()
-                create_database()
-            added, ignored = fill_database( fname)
-            print "Added: %d, Ignored: %d" % (added, ignored)
-        if options.synonyms:
-            print "Adding synonyms"
-            #import profile
-            #profile.run( 'add_synonyms( options.synonyms)')
-            i = add_synonyms( options.synonyms)
-            #i = 0
-            print "Added %d synonyms" % i
-    else:
-        if options.command not in ("test","rebuild","update"):
-            print >> sys.argv, "Invalid action '%s'" % options.command
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/subsearch_data.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/subsearch_data.py
--- bkchem-0.12.5/bkchem/oasa/oasa/subsearch_data.py	2008-11-05 03:43:10.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/subsearch_data.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,306 +0,0 @@
-## automatically generated file - may be overwritten at any time
-structures = [
-('aldehyde', 'carbonyl group', 'HC=O', []) ,
-('ketone', 'carbonyl group', 'CC(=O)C', [1, 4]) ,
-('carboxylic acid', 'carboxyl group', 'HOC=O', []) ,
-('carboxylate anion', 'carboxyl group', '[O-]C=O', []) ,
-('ester', 'carboxyl group', 'COC=O', [1]) ,
-('anhydride', 'anhydride', 'O=COC=O', []) ,
-('acyl chloride', 'acyl chloride', 'O=CCl', []) ,
-('acyl flouride', 'acyl flouride', 'O=CF', []) ,
-('acyl bromide', 'acyl bromide', 'O=CBr', []) ,
-('acyl iodide', 'acyl iodide', 'O=CI', []) ,
-('nitrile', 'nitrile', 'C#N', []) ,
-('amide', 'amide', 'C(=O)N', []) ,
-('alkyl chloride', 'alkyl chloride', 'C-Cl', [1]) ,
-('alkyl bromide', 'alkyl bromide', 'C-Br', [1]) ,
-('alkyl fluoride', 'alkyl fluoride', 'C-F', [1]) ,
-('alkyl iodide', 'alkyl iodide', 'C-I', [1]) ,
-('alcohol', 'hydroxy group', 'C-[OH]', [1]) ,
-('alcoxide anion', 'alkoxide', 'C-[O-]', [1]) ,
-('ether', 'ether', 'C-O-C', [1, 3]) ,
-('thiol', 'thiol', 'C-[SH]', [1]) ,
-('thiolate anion', 'thiolate', 'C-[S-]', [1]) ,
-('thioether', 'thioether', 'C-S-C', [1, 3]) ,
-('primary amine', 'amino group', 'C-[NH2]', [1]) ,
-('secondary amine', 'amino group', 'C-[NH]-C', [1, 3]) ,
-('tertiary amine', 'amino group', 'CN(C)C', [1, 3, 4]) ,
-('quarternary ammonium salt', 'amino group', 'C[N+](C)(C)C', [1, 3, 4, 5]) ,
-('borane', 'borane', 'C-[BH2]', [1]) ,
-('borane', 'borane', 'C-[BH]-C', [1, 3]) ,
-('borane', 'borane', 'C-B(C)C', [1, 3, 4]) ,
-('nitro compound', 'nitro group', 'C-N(=O)=O', [1]) ,
-('sulfonic acid', 'sulfonyl group', 'C-S(=O)(=O)O', [1]) ,
-('sulfate anion', 'sulfate', 'C-S(=O)(=O)[O-]', [1]) ,
-]
-rings = [
-('benzene', 'C1=CC=CC=C1', '350796d8048c08e039528ff682c2886fb617caaf') ,
-('pyridine', 'C1=CC=NC=C1', 'cf57c9c221dcef058588e5718cc0f4d6cff5d9cc') ,
-('thiolane', 'C1CCSC1', '17e910702bcda3fb1ee721f24bddd3f04c5ff9b8') ,
-('piperazine', 'C1CNCCN1', '84846557d99c1bc92e8e2f839f438c1124608c0e') ,
-('cyclopropane', 'C1CC1', '0d4d50316ada08de65c5ad9bdbe6b4f69c44344f') ,
-('oxirane', 'C1CO1', '6f929e37b37d323fc6dc236bd574f23014f9cc58') ,
-('1H-pyrrole', 'C1=CNC=C1', '29e0fc670f81762d5654a0293a6699dfaa01b50b') ,
-('oxolane', 'C1CCOC1', '18ee72a2a84912feffeb31dc1405e7d018c95f0e') ,
-('furan', 'C1=COC=C1', '3d7f9eb442fc790ee6dfd16d715041f710a2e90a') ,
-('thiophene', 'C1=CSC=C1', '348f4f456ec4f9ea7c148b9b588bcd58fb4d905c') ,
-('cyclohexane', 'C1CCCCC1', '431733eb3addf4944ce5bedb02524754ea459a70') ,
-('cyclohexene', 'C1CCC=CC1', '0023eb1833d1884c2e8123996cc38b69bae3becc') ,
-('piperidine', 'C1CCNCC1', '8fb354481a85c3017b0ffe644c7cdf0be99aa530') ,
-('morpholine', 'C1COCCN1', '0da779e5e31bfbfb025f6e4c176d3291a3c1d68a') ,
-('azepane', 'C1CCCNCC1', 'ff46e572cbe148da06eb262e89f15b682b2f5227') ,
-('cyclopentene', 'C1CC=CC1', '40517ae1a62ef40c8dbe7546312298dabf35850c') ,
-('oxane', 'C1CCOCC1', '36ad6ab83ec9c6d43251967854bd0dc84325a778') ,
-('aziridine', 'C1CN1', '54f2a460a8357bb5e5ecbc2f2d51dae167a256dc') ,
-('cyclobutane', 'C1CCC1', 'e97ad0643bc6ba7b72d701695d05185a55199291') ,
-('thietane', 'C1CSC1', 'b2d1abc89d0d540ff4dfaf7e35f74f0e65395e36') ,
-('cyclopentane', 'C1CCCC1', '4ca53e63f47ae1c479fedcee520bbecfe7017b82') ,
-('pyridazine', 'C1=CC=NN=C1', '2855bb8bb1cc163a8e4b6add5642c1d9f37073c6') ,
-('pyrimidine', 'C1=CN=CN=C1', '959bceeb09fbb4f48e46f3582d39b6beccdd27fd') ,
-('pyrazine', 'C1=CN=CC=N1', '0a2d9f7e5b882c36eb7d875b01be66ad7fdc98ca') ,
-('cycloheptane', 'C1CCCCCC1', 'c77a5b0844a6a6f884bd0137d9d4ca3f75bc56b5') ,
-('cyclooctane', 'C1CCCCCCC1', '14f4b482684afd368b6fab21d5010fc18e7e36ff') ,
-('cyclodecane', 'C1CCCCCCCCC1', 'f0ac50914527659ad2613cdcd95da846ac328247') ,
-('cyclododecane', 'C1CCCCCCCCCCC1', '51261d03082ed6a57cfbe27c54f87cb01bc0c6a5') ,
-('thiirane', 'C1CS1', '9af082c9d2cd9b93716df861f2ac1ba1a439226d') ,
-('azetidine', 'C1CNC1', 'f7f6de01b36bed0ef503e453965da50ff54eec92') ,
-('oxetane', 'C1COC1', '7a5f0c189b422f154f6dd74cd2ba0338bb327339') ,
-('oxepane', 'C1CCCOCC1', '0385e44b970128e523484e2bbb247337bcf2e12e') ,
-('cycloheptene', 'C1CCC=CCC1', '4e1d399678ea912ed47b635df708b27895850507') ,
-('cyclooctatetraene', 'C1=CC=CC=CC=C1', '16731e44f910160a6670509592f883e3ead4e357') ,
-('cyclooctene', 'C1CCCC=CCC1', '99bca2a4525fd8bd4b407bb0b29c5e40c3a93913') ,
-('cyclodecene', 'C1CCCCC=CCCC1', '46797f868adb9aa5b634083701a7230ddff5bd35') ,
-('azocane', 'C1CCCNCCC1', '04a249f214d4d31b9488cf2674682dfdc04e08ef') ,
-('cyclododecene', 'C1CCCCCC=CCCCC1', '99049ae96447c87becf0a83b7291e69bcf3f8505') ,
-('thiane', 'C1CCSCC1', '0fcce6b3d6f5e5d3bbae896d926ff67ebd44c27c') ,
-('azecane', 'C1CCCCNCCCC1', '27a623104ec68af70355c2ce66ac6493b6c2e128') ,
-('pyrrolidine', 'C1CCNC1', '997bd7dc868ea899cb064de29b6f096a459445c0') ,
-('thiomorpholine', 'C1CSCCN1', '68e58d8fd06ba90a84701b7ed187f4a5cc710561') ,
-('thiadiazole', 'C1=CSN=N1', 'b6fbe37f0950f8bd1637330cbcc08d1397071a02') ,
-('cyclotetradecane', 'C1CCCCCCCCCCCCC1', '51ee0d72c6e802ea649701ad4641d65ee711ee81') ,
-('cyclopentadecane', 'C1CCCCCCCCCCCCCC1', '86f3d9da03d0ddb76560a105c2e943174e1755e8') ,
-('cyclohexadecane', 'C1CCCCCCCCCCCCCCC1', 'ab6827c71f1e330cb56a6da147d157ea8b5585c8') ,
-('cyclobutene', 'C1CC=C1', '801c06baea3fd40bd10a95845bb9fbec44392883') ,
-('phospholane', 'C1CCPC1', '5d1c6067c37a19a88b7b766e149fec4de7aac54f') ,
-('thiepane', 'C1CCCSCC1', '2fd7591c33b05c6e097e199de637eb17801be2a4') ,
-('pyrazolidine', 'C1CNNC1', '577a673e5d3d568ab3b3379d69c9a7a7da324406') ,
-('dithiolane', 'C1CSSC1', 'ded6dbb77dda101cdbd2e1683b8e70737ca60cc1') ,
-('azonane', 'C1CCCCNCCC1', '451d9f0d0037cc6b4c3b4ad2cd76672b8b139d70') ,
-('hexathiepane', 'C1SSSSSS1', '45f5a5aa67df5e1c370156076681804dc2afb87d') ,
-('azaboretidine', 'B1CCN1', '774654a93af38313389d3c20a5a64a0d67efaaba') ,
-('dioxetane', 'C1COO1', '982ef947b7bc7c43514a14c4172714a8d2bebadf') ,
-('phosphinine', 'C1=CC=PC=C1', '135c35e52dfae56eacbedf706d88fb85c43d23b0') ,
-('triazine', 'C1=CN=NN=C1', 'a1af4d8c97e8b076a7143a73891842a25b94040c') ,
-('cyclopropene', 'C1C=C1', 'd1f3db031419f8c22f6f7c500992f01e722ecb75') ,
-('trithiane', 'C1CSSSC1', '134d7d93d8858632f4014889a086c4d9df504954') ,
-('cyclononane', 'C1CCCCCCCC1', '66e6c25e69eda88d7bd27929b78e10c6e3de3d96') ,
-('cycloundecane', 'C1CCCCCCCCCC1', '2e90d677a596d98387272806222e2a412680e68f') ,
-('cyclotridecane', 'C1CCCCCCCCCCCC1', 'cd63ad2dcee7fa54c252d36b070a484e0ad6e59e') ,
-('cycloheptadecane', 'C1CCCCCCCCCCCCCCCC1', 'cf535eecb259e341659e4f4e1cd56b721cc1e193') ,
-('cyclooctadecane', 'C1CCCCCCCCCCCCCCCCC1', 'c21f462611d018f7c3421ff246e188c7abe68f91') ,
-('diazinane', 'C1CCNNC1', '3a4dafe6d15aeb9ba8d32a0ff7928b333eccdbcb') ,
-('dithiane', 'C1CCSSC1', '87c14e137db7023dffdfc04ababe2350a236a19c') ,
-('dioxepane', 'C1CCOOCC1', 'b8985532e75bdbfeb1f89c38c003151de4cbaa2c') ,
-('cycloundecyne', 'C1CCCCC#CCCCC1', 'f3706b74c5045ef990a2387782bc2b81edc5d8c7') ,
-('cyclobutadiene', 'C1=CC=C1', 'a600e8a75d931a6d7eb2bcd8999c418ab08b346c') ,
-('cyclododecyne', 'C1CCCCCC#CCCCC1', '443f57fe0cb9b6fb1fc1dc95ed9244e5eee344e5') ,
-('cyclooctyne', 'C1CCCC#CCC1', 'bf0ef0e62fe7392f42e36f1e31c8a67e681f0046') ,
-('cyclodecyne', 'C1CCCCC#CCCC1', '650a888751e6b496022ee42405f3297177d0a7d9') ,
-('dioxolane', 'C1COOC1', 'b26104001ab13f31d096ad8dff1c9e665bb07dcc') ,
-('phosphinane', 'C1CCPCC1', '0a8cc7bf4ffe603977712d57bac305cc680b7a4e') ,
-('oxathiolane', 'C1COSC1', '4d20d194fa55b745d4f01401019fffea9a911c63') ,
-('dioxane', 'C1CCOOC1', 'd96577e0fbbd49e3f112bf3be4914ce0ae29ba75') ,
-('dioxocane', 'C1CCCOOCC1', 'cd5cfcfa6681b5bc07a5ddb0ff99269a2e0b43b4') ,
-('cyclononyne', 'C1CCCC#CCCC1', '044a001e9c37cf41ccba1e0a306bf9845219e503') ,
-('dithiete', 'C1=CSS1', '0f93f7d4f0f4229fd95bda655265968dcdb4512e') ,
-('oxazinane', 'C1CCONC1', 'aa65ea759150de6e715fa071f3e1b34e5f50416e') ,
-('oxirene', 'C1=CO1', '2c836bcf1d872fe7a2206089ac63599d755e5b93') ,
-('oxonane', 'C1CCCCOCCC1', 'f90e9088e9da9c3035fd8160a4fb5b80f3394679') ,
-('thiocane', 'C1CCCSCCC1', '3824ce63ab7add7310957fb862efdd1f224cd0ff') ,
-('imidazolidine', 'C1CNCN1', '6b8b5e6ba3ef5ec0b47769c32cf6c71fa8a17997') ,
-('dioxirane', 'C1OO1', '14d8dc16520e67e60161e5980bd7b66182fe382e') ,
-('cycloicosane', 'C1CCCCCCCCCCCCCCCCCCC1', 'f83f352fb286c8f8196441716e464732fae15baf') ,
-('cyclotetracosane', 'C1CCCCCCCCCCCCCCCCCCCCCCC1', '813de51ca763c71756ebefaafa049883b8e846e3') ,
-('dithiepane', 'C1CCSSCC1', '2860adab37f68c6ac4d76fb3700580e8f14b543d') ,
-('oxadithiole', 'C1=CSSO1', 'b55537faa488df8529f2a4ded6c560c410968ab7') ,
-('tetrathiepane', 'C1CSSSSC1', 'be64ca38faea662875fd211a4312c2dbb3a327a6') ,
-('dithiocane', 'C1CCCSSCC1', '8e9ad5c8fe1b3e5845857f49f3277b89ce2e0461') ,
-('trithiolane', 'C1CSSS1', '8d153a99f1e39e4b2f084d19e00f2d26b78e1f80') ,
-('tetrathiine', 'C1=CSSSS1', 'd4791f2954ab58a34b49450147067cc73885aa1b') ,
-('tetrathiane', 'C1CSSSS1', '385385a3f4441c84f1d1997abdd315c84402fbd5') ,
-('oxathietane', 'C1CSO1', 'b2fe37b651ac8133b4c6d6725355e0b430f450cf') ,
-('diaziridine', 'C1NN1', '4f538486d12df7d88605346dcc5ec5c19bcfa186') ,
-('tetrazolidine', 'C1NNNN1', '9f78cf3fbb3524148a620de2233f497e6829c426') ,
-('oxazirene', 'C1=NO1', '8cf80d062b1b24c165c8dab8f8db344390d407f1') ,
-('thiirene', 'C1=CS1', '6c69bc937c11775b5e14dadf47ae2d9357f67d71') ,
-('cyclobutatetraene', 'C=1=C=C=C1', '961cd73ed9bb150af834c1f0f53778352af9842e') ,
-('pentazine', 'C1=NN=NN=N1', '355086e0fe424b0ee6e6e1f7b58cbd579b3517e5') ,
-('oxepine', 'C1=CC=COC=C1', 'cdcf5031b069c89472ea145e4d4dc190f8e17c1b') ,
-('oxadiazole', 'C1=CON=N1', '5aae07d09ecdc69362b3d1050127bac612c72e3c') ,
-('cycloheptyne', 'C1CCC#CCC1', '2827288cd65a0e1ba0aad43d0d7bfa65c9c8c84a') ,
-('borinine', 'B1=CC=CC=C1', 'e1e1b60e3d204e73088193fa93553910130206a4') ,
-('cyclohexyne', 'C1CCC#CC1', 'dcb5ebafcb32befb46b2aba7393871813b6658fd') ,
-('cyclotridecyne', 'C1CCCCCC#CCCCCC1', '1f737da455b3305283e49d1eca61320eabf7fecf') ,
-('thionane', 'C1CCCCSCCC1', '297a2a259a078df7e5111712d81b8d8ad698aac2') ,
-('dioxazine', 'C1=COON=C1', '91dc380b4410faa1174dac90b2cde032889ef1a5') ,
-('dithiine', 'C1=CSSC=C1', 'dcec36bd6e9dcdb8066d7616038860cd7d0cb5eb') ,
-('cyclohexacosane', 'C1CCCCCCCCCCCCCCCCCCCCCCCCC1', 'cd8fc040b9b526588dd9f0920b0822d8c3c9871a') ,
-('dithietane', 'C1CSS1', '7eeca53bbdd3bfddbedabbb8faf8a2ec0b328348') ,
-('tetrazine', 'C1=CN=NN=N1', '7cf758d32dd71d641703429489ee22e64fbeea13') ,
-('thiazepine', 'C1=CC=NSC=C1', '16fed730a19c040eda525e52b0002964cf558698') ,
-('thiepine', 'C1=CC=CSC=C1', '4019c280163f87d00cbed500f891fed25bd203a5') ,
-('oxathiane', 'C1CCSOC1', '9a9c05cbc93c34a351b8a9bb1b7b5b1fc6914f91') ,
-('diazocane', 'C1CCCNNCC1', '1f71308fa16c925352a8321c12c6005d8dfaa34f') ,
-('trithiepane', 'C1CCSSSC1', '3a0550857269121171d4cfa02b4a60e391008f8e') ,
-('oxocane', 'C1CCCOCCC1', 'f810d3c6d7033ca9d544f22caacc458ec3586d13') ,
-('trithionane', 'C1CCCSSSCC1', '343040278bd1da3b558df9dc94d865b0cace1984') ,
-('diazepane', 'C1CCNNCC1', '2df73e7034e5449cc5ef63b21d5d8d5ca7999d84') ,
-('dithionane', 'C1CCCSSCCC1', '3a1ec9f7282eab0b935d2af57fdd3ffe032f0e3f') ,
-('oxonine', 'C1=CC=COC=CC=C1', 'f49d94491c44b840dd0df00e5b836f328d0e1b3e') ,
-('thionine', 'C1=CC=CSC=CC=C1', '9ec69f6b3dc4a2a093b3a21653b49ab3a3770cf4') ,
-('cyclohenicosane', 'C1CCCCCCCCCCCCCCCCCCCC1', '2973544d8f8196cd188373364a3d814edb9a90cc') ,
-('cyclodocosane', 'C1CCCCCCCCCCCCCCCCCCCCC1', 'fe1ead29e43ffcbf2cd523de0d225416a250b9f8') ,
-('cyclotricosane', 'C1CCCCCCCCCCCCCCCCCCCCCC1', '119b5f17605d30602e61fbb19b4af2839ec60e2b') ,
-('oxazepine', 'C1=CC=NOC=C1', '57ab48a5b9e6cd15eb2a3de800aa1c45653b6d65') ,
-('thiatriazole', 'C1=NN=NS1', '4a4eefec8072df698e17aef07683b17a4828d96f') ,
-('oxecane', 'C1CCCCOCCCC1', 'd89283583bb20cc014e25f6065442ace7ffa0ecf') ,
-('phosphepane', 'C1CCCPCC1', 'fa0dc617ad23b30170f59c85952ac45f3fd280de') ,
-('cyclodecapentaene', 'C1=CC=CC=CC=CC=C1', '4169cf9eb9e401cf5213a2a36cd57f631843c4d0') ,
-('dioxine', 'C1=COOC=C1', '635f53e36526a0a7739cf658748b3fd56e952df4') ,
-('phosphirane', 'C1CP1', '3a448f3692483c4d9848411779fef0d322370934') ,
-('oxaziridine', 'C1NO1', '715029842446df199ac78a81d16f32f15fd21280') ,
-('triazinane', 'C1CNNNC1', 'dfde7e3d21db5538b768592e02676187e0c4167d') ,
-('pentathiepane', 'C1CSSSSS1', '3e8584bb02f40e13387ce008f53b5f56d30aae1a') ,
-('diphospholane', 'C1CPPC1', '9e874a73f57247f11967f34318c7810b45ea3f43') ,
-('azaphosphiridine', 'C1NP1', '66044516871e9ad5e1fc38eaf61e32c8fade3334') ,
-('oxadiazepane', 'C1CCONNC1', '5e5cff7ebf29b7bde84fb602aa36579ef954f2db') ,
-('dioxaphospholane', 'C1CPOO1', 'f5b4ba19aeac0177345200a384322895597c397d') ,
-('tetrathiolane', 'C1SSSS1', '636168f3883d7dcd603ca1841d58347ed904212c') ,
-('dioxathiolane', 'C1CSOO1', 'db310b8d6a4f2f5de3dd8da794c56e9f1f285226') ,
-('dithiazolidine', 'C1CSSN1', 'c9d59e75872fa09d054d580415da90907fd95e99') ,
-('dithiazinane', 'C1CNSSC1', 'f12a1fbaf7c6af33b2c10f79827eec2b7515dd56') ,
-('thiazetidine', 'C1CSN1', '1d5158a5e94d7120b6aa6a9971b75ea478ded917') ,
-('diazaborinine', 'B1=CC=CN=N1', 'f1eeb5081c91763ab861b6f91174e0fea245eca3') ,
-('cyclopropyne', 'C1C#C1', '5cdc00b7c84f012faf32667fb41a666131f0d380') ,
-('oxazetidine', 'C1CON1', '9e92f5f2c1a5ddeba2e6942b39185bb55aea2f3b') ,
-('hexaoxecane', 'C1CCOOOOOOC1', 'aa985362e8f0251701aca17edd11c156b4407424') ,
-('triazete', 'C1=NN=N1', 'edf50618d184779e0b0f0fb577b23fcceb412242') ,
-('oxadiazolidine', 'C1CONN1', '3a61a99d3ef0e01e4d1178ca9165fa748891ec43') ,
-('azaphospholidine', 'C1CNPC1', 'bbbb2d6d166f64f4c1abf0f43809a928ed1d92b8') ,
-('diazetidine', 'C1CNN1', '53c8dbfcf7c0e4f7d11cc353098fde0696261ddf') ,
-('hexathiocane', 'C1CSSSSSS1', '2d1ca01e0f1d74c57956eb330de3136502c0b47e') ,
-('oxathiirane', 'C1OS1', '110bb8900719882698322a2a482b58884b697115') ,
-('oxazonane', 'C1CCCNOCCC1', '2fa4399ae035509c892450109ac9319afee7f6dc') ,
-('oxathiazinane', 'C1CNSOC1', '587722dfa53b283dd48f72237ab5eaa14955aca0') ,
-('oxadiazinane', 'C1CNNOC1', '6c5353238ffe463bc0f68df6db49cfba1d3d7779') ,
-('oxaphosphepine', 'C1=CC=POC=C1', '4990add5c563d2732e7ef9c0a9737b75c9bcb833') ,
-('azaborinine', 'B1=NC=CC=C1', 'c5608dd6c838e533b27fe79ce67897e54e69aaa9') ,
-('trioxetane', 'C1OOO1', '8b53555333fff059db3977adc7246d50f0accf40') ,
-('oxathiine', 'C1=COSC=C1', '57ef13e1e7ff73b7ab75da35ab1e45fd143bc937') ,
-('diphosphetane', 'C1CPP1', '8458108160ffdba74fcade3b55fa08d4621daf8b') ,
-('dithiadiazine', 'C1=CSSN=N1', '0cdeb2316f6f9770157ee8dc485362ce2fe49fc2') ,
-('triazaborinine', 'B1=CC=NN=N1', 'b094bce4fdc27e8ec136eadb3342ecf6439215d6') ,
-('dioxatriazine', 'C1=NN=NOO1', '21f6196c1610891139b3274ef0d7514dac6f06d0') ,
-('trioxane', 'C1COOOC1', '522e8a3d04d891ed21d440c5db0de92f41c99bf9') ,
-('dioxaphosphinine', 'C1=COOP=C1', 'ed4b8ddb7b836bfea5c0869db6f19e31a17ec7fc') ,
-('dioxazolidine', 'C1COON1', '6cea07386b7ed0462fb3d967eb6475f58606a83c') ,
-('dioxaphosphinane', 'C1COOPC1', 'e48686307c03dc4e2beba3e7f5baf7cb276c215f') ,
-('oxazaphosphinane', 'C1CONPC1', 'b633f8c5a3532d0b14d4901572fddc2d992792f6') ,
-('tetraoxecane', 'C1CCCOOOOCC1', '303d769622ea32fd9942a59c69652e2c1b1ffb2b') ,
-('thiazepane', 'C1CCNSCC1', '5427cfdb1f1a1259f2d97e86142255fbeb16032d') ,
-('oxazepane', 'C1CCNOCC1', 'e9de69f1bb6d04af47123bb26ba59033fc12fb59') ,
-('trithiazole', 'C1=NSSS1', 'b51693d0b45cf5143b8d93c9992dfd8daeb9503a') ,
-('oxazaphospholidine', 'C1CPNO1', '7b304fa46adf627e4319a7cbf81c1ab14fb34550') ,
-('dithiaphosphetane', 'C1PSS1', 'd9d0754bf2ed9862525c361a75d189ae40a9dc51') ,
-('cyclobutyne', 'C1CC#C1', '22eafeec6ab23da517a42a852b69ce8e2f72c781') ,
-('cyclooctadecyne', 'C1CCCCCCCCC#CCCCCCCC1', '0091a553fe3cddb3ada8a474b47addd0e3b61d86') ,
-('thiazocane', 'C1CCCSNCC1', '5d16343e31e84d28dd164faa25b0366d0709dd51') ,
-('oxazocane', 'C1CCCONCC1', 'badc1ba6a20263389ff0e39cf40e17e023a004b8') ,
-('azete', 'C1=CN=C1', '71c5464586b37647a6bfbb6129eab332827c9da9') ,
-('thiadiphosphinane', 'C1CPPSC1', '391b958ce0fd68dad4e158a2442c26754025c8f5') ,
-('hexaoxocane', 'C1COOOOOO1', '23584e4502e4d0ef5f081c22ed79f1fba458d854') ,
-('oxathiadiazine', 'C1=COSN=N1', 'c43f8bd421c1e398ea7bc273be15f75adbe772e6') ,
-('oxadithiane', 'C1COSSC1', '193bb93ecb815d6d74c9e98734ea41d465bfeca2') ,
-('dioxazepane', 'C1CCOONC1', '1c88f4337a309538eff2bdd2aaaa236336f56861') ,
-('diazaphosphinane', 'C1CNNPC1', '66f183b133681b1afdf4bdaea70e0c1a651652a2') ,
-('dioxadiazecane', 'C1CCCOONNCC1', '1c8f5eab329af07b4570b5f4c7c2ae6db2d46a0f') ,
-('dioxadiazonane', 'C1CCNNOOCC1', '5c9daa9121f71cb15ee1b031f21e5848af2df8bf') ,
-('dioxadiazocane', 'C1CCOONNC1', 'efc1a204dc7c21702f187dc156588fec6f43e667') ,
-('oxadiazecane', 'C1CCCNNOCCC1', '585510072c8e0c9cfac51db25c622bb2be9fc0c2') ,
-('oxadiazonane', 'C1CCCONNCC1', '221b6da6d3a3aada0f12a90d965c28e6288b7dce') ,
-('oxadiazocane', 'C1CCNNOCC1', 'acead1b345487fdae399ff97045ee91366298a6c') ,
-('dioxazaborinine', 'B1=NOOC=C1', '030f0983333da5470200390f87ca76b65ca08c81') ,
-('dioxecane', 'C1CCCCOOCCC1', 'e0294012a81603b0a77e0d88847dc9e960621f97') ,
-('dioxazinane', 'C1CNOOC1', '5a6cc06023c137f0f7b9b3c11cece0bd603d26aa') ,
-('oxathiepane', 'C1CCOSCC1', '371c969316608f47f1c7090fda71284feb669dc8') ,
-('thiaziridine', 'C1NS1', 'c0b88d6ebc38b3aeb8bfbe301b09279c0443144e') ,
-('trioxazolidine', 'C1NOOO1', '5f3c6fe44e62fccce5aa856416018173300b2b0a') ,
-('tetrazocane', 'C1CCNNNNC1', '63388a7ab542aa8d42cbb7b1c22bcaa233bdb261') ,
-('tetraoxane', 'C1COOOO1', '544c64fcb3dd03c3aad864dacc127f3b65c7b90c') ,
-('triazecane', 'C1CCCNNNCCC1', '9490e0ed8f1791e391e26bb8b89a56911a6eddf2') ,
-('dioxocine', 'C1=CC=COOC=C1', 'b8d2746ba5fe8d040e2248651300978514edc529') ,
-('cyclononadecane', 'C1CCCCCCCCCCCCCCCCCC1', '7f937f8028b7096bed706dd6dce7c6b207c67d3e') ,
-('thiazinane', 'C1CCSNC1', '8fb6179aedb4d70bd9bba28559bbe6f1a484e971') ,
-('azadiborinine', 'B1=CC=CN=B1', '680fd74b389ce272569cd58931c0f820af9693d9') ,
-('oxatetrathiane', 'C1OSSSS1', 'c7d26e4cac672ce8249511ca244de80d7b6bc509') ,
-('oxatriazole', 'C1=NN=NO1', 'a08ba137c2fa6059a440a892db8b73eeb19bf655') ,
-('dithiirane', 'C1SS1', 'f90fe2671584c5761388a026856ae140e21e18c7') ,
-('oxathiazine', 'C1=COSN=C1', 'ecf57d3aef19b75cc4b9a1d227f3dc62cf76ff44') ,
-('dithiazine', 'C1=CSSN=C1', 'e2ec6255ce4f957b7a0687d6a687128cb14c16f4') ,
-('thiadiazepine', 'C1=CN=NSC=C1', '37d8ab83307a220c52cdf655793500b274b0d2ac') ,
-('triazolidine', 'C1CNNN1', '83374cb6d6936e5e0e0f1db02ee350888c4de666') ,
-('oxadiazepine', 'C1=CN=NOC=C1', 'f6c4a58a86462ee7f874ba61ce7e958b473edef6') ,
-('tetrazonane', 'C1CCNNNNCC1', 'a87bd0bf3616220e955efcbffec5f01e44e37716') ,
-('thiadiazolidine', 'C1CSNN1', '6854276625fc20d6ede0a7a3afbc0f402b209c9c') ,
-('thiadiazepane', 'C1CCSNNC1', '5d18321fd58647b0f043c6a3abddf8481e1f6850') ,
-('thiadiazinane', 'C1CNNSC1', '0d82137ec152069685ff0438d13efc543c37d31c') ,
-('triazonane', 'C1CCCNNNCC1', '97d5d32aa687f65cbe69f89c8f3492ccde5f7f65') ,
-('oxathiazolidine', 'C1COSN1', '0f7b095cb0cba97bee79ff3fa6c1c2570a7ade48') ,
-('dioxazocine', 'C1=CC=NOOC=C1', '16a01f0ed74d90b104ab32afaf0ae9309fc6141a') ,
-('trioxolane', 'C1COOO1', '485f6481058ecc384ea4d59ecce5613ff0e30fac') ,
-('dithiadiphosphinane', 'C1CSSPP1', '3cc665eed72417b07b5133c51941d19d304b8dfa') ,
-('diphosphinane', 'C1CCPPC1', '7ef5852616d3c724a0b35cb9764bb8f358ae072b') ,
-('diphosphepane', 'C1CCPPCC1', '20c0aa7d09532a45f1342080398a5f0e5d4cffe6') ,
-('thiazonane', 'C1CCCNSCCC1', '3a27b0523db52326917da97a1d15ec0e5c1d1e13') ,
-('tetrazinane', 'C1CNNNN1', '81abe199380d51d88414b1385f6f51b49549a167') ,
-('trithiole', 'C1=CSSS1', '9204e479bd47a1e7a44ce8a2d8930fba40805b46') ,
-('trioxole', 'C1=COOO1', '6ff1f27d2eee426adcbbe81a47ce81ec44aa9c3b') ,
-('oxadithiolane', 'C1CSSO1', '112aa6a6bb089c6cb62a53057dca158a1e70d16f') ,
-('trioxepane', 'C1CCOOOC1', 'cf3b4298b5554a1fa231cc78e2dba6ccd6436f41') ,
-('diazaphospholidine', 'C1CPNN1', '2e74649eb87672d23f242f9b4f12a007c050afdd') ,
-('dioxaborinine', 'B1=CC=COO1', 'bef359904126ce3f4699930a5b9d41671fe3827e') ,
-('tetrazecane', 'C1CCCNNNNCC1', 'ed1abc4c9823a8786d4ebb9f0a74e395fc1e8955') ,
-('dioxathiane', 'C1COOSC1', '50154b3a766e04da5debb23f23b703cfbe018732') ,
-('oxadithiazole', 'C1=NSSO1', '8dbb4edb6cee44dae6c526580e9e360fb6e91742') ,
-('oxathiaphospholane', 'C1CPSO1', '0827fbd02a7b4345198259617cb34af0357469c2') ,
-('dioxaphosphocine', 'C1=CC=POOC=C1', '581ffacbef62f591556f7b9eb1dd0b88fcac2791') ,
-('thiatriazolidine', 'C1NNNS1', 'b154a07a59dbef28df1057d845fe310983768f44') ,
-('oxatriazolidine', 'C1NNNO1', 'e354367317f6027f75a541516f543190c6c85dd3') ,
-('diazonane', 'C1CCCNNCCC1', 'ff04eed2d94092952561106545e4a3cb8e913466') ,
-('triazepane', 'C1CCNNNC1', '439f8f0f9b04a588292680344a5e96b06a3b9dd3') ,
-('triphosphinane', 'C1CPPPC1', '907e429ec178db85e99a661df31a1a6a4005b5db') ,
-('dioxete', 'C1=COO1', 'bc12a49992d17807de6798dc7fd11a67ab09762f') ,
-('oxaphospholane', 'C1COPC1', 'a6cca1c3f2929b11e3dcdeb1ca4cc5e0a6fbd185') ,
-('trioxazole', 'C1=NOOO1', 'ea253efca5d4bf9987ee0d41d6109c298a74eb23') ,
-('triphosphinine', 'C1=CP=PP=C1', 'cdfe8af6dfd6d4dc25c450607867e48b354a6801') ,
-('oxathiocine', 'C1=CC=CSOC=C1', 'fb49d5ea2b73dfa0a9c94ac2b207757509c838cd') ,
-('azaphosphepane', 'C1CCNPCC1', 'eb74fd435a478a70fd2a9a195b94665345a88120') ,
-('dioxaphosphepane', 'C1CCPOOC1', '06397f029c0cb79f4d68500b0d1dfd9a7d67a4b8') ,
-('azaphosphinane', 'C1CCPNC1', '70e3ef4924d0055a402f57065dc552bcbf90f9ee') ,
-('triazetidine', 'C1NNN1', 'da5ec424bba6b7ab69ea4fa992125eae8b248209') ,
-('dioxaphosphonane', 'C1CCCPOOCC1', 'd30ec082388e5d8e20aa0cc5abb16397f2217571') ,
-('pentazecane', 'C1CCNNNNNCC1', 'ff09f109b2265802230ed82dbc49aab2293b6df1') ,
-('thiadiazonane', 'C1CCCSNNCC1', '27e002205d75e109bf5dd0987c623ed5e9cdd82e') ,
-('thiadiazocane', 'C1CCNNSCC1', '546170f8c647cf582b53ed3a73db17d55d6353e1') ,
-('tetrathiecane', 'C1CCCSSSSCC1', '92f2b03540740806b778e2afab19af56cd3f04cf') ,
-('oxazecane', 'C1CCCCONCCC1', 'f81e12755c6bb4dfb881e58209a7d6469f4eaf3a') ,
-('oxathiazocane', 'C1CCNSOCC1', '0f815be8e796692e2519651332d03578f33bfec3') ,
-('diazecane', 'C1CCCCNNCCC1', '97c7d7f99033994f0723b113a8531735fe65e493') ,
-('phosphetane', 'C1CPC1', '00194707c5ebc3653d7497d107811b312b9abf8a') ,
-('trioxocane', 'C1CCOOOCC1', '34c09eb9a24bf8e280495ff3594bb5c155fdfc19') ,
-('oxaphosphetane', 'C1CPO1', '95f6e955f3967cdca678530d1f49ddba069951a8') ,
-('phosphecane', 'C1CCCCPCCCC1', 'f6ec97611287768b60598d87275c708147b1894a') ,
-('phosphocane', 'C1CCCPCCC1', 'f5ed7c2040ea55bff0cdf27ce2d3816d024e306d') ,
-('phosphonane', 'C1CCCCPCCC1', '2c608a8f710660ae48421f0e68b780274738c645') ,
-('tetrazocine', 'C1=CN=NN=NC=C1', '2c5b47cd32e43d55b00dbdbf99077d8392f3579a') ,
-]
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/subsearch.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/subsearch.py
--- bkchem-0.12.5/bkchem/oasa/oasa/subsearch.py	2008-10-14 09:46:12.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/subsearch.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,387 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import smiles
-from graph.digraph import digraph
-
-import os
-
-class substructure_search_manager( object):
-
-  substructure_def_file = os.path.join( os.path.dirname( __file__), "subsearch_data.txt")
-  ring_def_file = os.path.join( os.path.dirname( __file__), "subsearch_rings.txt")
-
-  def __init__( self):
-    self.structures = digraph()  # graph describing the relations between individual structures
-    self.search_trees = []  # list of substructure instances that for a tree
-    self.rings = {}
-    self.fill_data()
-
-  def fill_data( self):
-    import subsearch_data
-    for struct in subsearch_data.structures:
-      compound_type, name, smiles_string, to_ignore = struct
-      sub = substructure( name, compound_type, smiles=smiles_string, atoms_to_ignore=to_ignore)
-      v = self.structures.create_vertex()
-      v.value = sub
-      self.structures.add_vertex( v)
-    for ring_desc in subsearch_data.rings:
-      name, smiles_string, ring_hash = ring_desc
-      rng = ring( name, smiles_string, ring_hash=ring_hash)
-      self.rings[ ring_hash] = rng
-    # the following takes some time
-    self._analyze_structure_dependencies()
-    
-
-  def _read_structure_file( self, name=""):
-    """may be used to read data directly from source txt files.
-    is deprecated in favor of automatically build subsearch_data.py module"""
-    f = file( name or self.substructure_def_file, "r")
-    for line in f:
-      l = line.strip()
-      if l and not l.startswith( "#"):
-        parts = l.split(";")
-        if len( parts) < 3:
-          raise ValueError( "wrong line in data file: '%s'" % l)
-        compound_type, name, smiles_string = [x.strip() for x in parts[:3]]
-        if len( parts) > 3:
-          to_ignore = parts[3].strip()
-        else:
-          to_ignore = ""
-        to_ignore = map( int, filter( None, to_ignore.split(",")))
-        if not name.strip():
-          name = compound_type
-        sub = substructure( name, compound_type, smiles=smiles_string, atoms_to_ignore=to_ignore)
-        v = self.structures.create_vertex()
-        v.value = sub
-        self.structures.add_vertex( v)
-    self._analyze_structure_dependencies()
-
-  def _read_ring_file( self, name=""):
-    """may be used to read data directly from source txt files.
-    is deprecated in favor of automatically build subsearch_data.py module"""
-    f = file( name or self.ring_def_file, "r")
-    for line in f:
-      l = line.strip()
-      if l and not l.startswith( "#"):
-        parts = [x.strip() for x in l.split(";")]
-        if len( parts) != 3:
-          raise ValueError( "wrong line in data file: '%s'" % l)
-        name, smiles_string, ring_hash = parts
-        rng = ring( name, smiles_string, ring_hash=ring_hash)
-        self.rings[ ring_hash] = rng
-
-  def _analyze_structure_dependencies( self):
-    for v1 in self.structures.vertices:
-      for v2 in self.structures.vertices:
-        if v1 is not v2:
-          sub1 = v1.value
-          sub2 = v2.value
-          if sub1.structure.contains_substructure( sub2.structure):
-            self.structures.add_edge( v2, v1)
-
-  def _find_head_structures( self):
-    for v1 in self.structures.vertices:
-      v1.properties_['in_links'] = []
-      for v2 in self.structures.vertices:
-        if v1 in v2.neighbors:
-          v1.properties_['in_links'].append( v2.value)
-    heads = []
-    for v in self.structures.vertices:
-      if len( v.properties_['in_links']) == 0:
-        # we have got a top of the tree
-        heads.append( v)
-    return heads
-  
-  def _compute_search_trees( self):
-    heads = self._find_head_structures()
-    assert heads
-    for head in heads:
-      d = self.structures.mark_vertices_with_distance_from( head)
-      dv = [(v.properties_['d'],v) for v in self.structures.vertices if 'd' in v.properties_]
-      dv.sort( reverse=True)
-      for d,v in dv:
-        for d,parent in dv:
-          if parent.value in v.properties_['in_links']:
-            if v.value not in parent.value.children:
-              # this ensures only one copy of a substructure in a tree
-              parent.value.children.append( v.value)
-            break
-    return [h.value for h in heads]
-
-  def which_substructure_is_more_specific( self, s1, s2):
-    """returns 1 if s1 is more specific,
-               2 if s2 is more specific,
-               3 if both are the same,
-               4 if the relationship cannot be established
-               (there is not connection between s1 and s2 in the
-               substructure interdependency graph - this signifies
-               a problem, probably in the matching algorithm
-               """
-    if s1 is s2:
-      return 3
-    # just find vertices corresponding to s1 and s2
-    v1 = None
-    v2 = None
-    for v in self.structures.vertices:
-      if v.value == s1:
-        v1 = v
-      elif v.value == s2:
-        v2 = v
-    assert v1
-    assert v2
-    p1 = self.structures.path_exists( v1, v2)
-    p2 = self.structures.path_exists( v2, v1)
-    if (p1 and p2) or (not p1 and not p2):
-      # both paths exist or none does - this should not happen
-      return 4
-    if p1:
-      return 2
-    if p2:
-      return 1
-
-  def find_substructures_in_mol( self, mol):
-    # get the hits
-    hits2 = []
-    for v in self.structures.vertices:
-      struct = v.value
-      ms = struct.find_matches( mol)
-      hits2 += ms
-    # weed out the hits that match inside a ring
-    ring_hits = self.find_rings_in_mol( mol)
-    hits = []
-    for hit in hits2:
-      keep = True
-      for rhit in ring_hits:
-        if set(rhit.get_significant_atoms()) & set(hit.get_significant_atoms()):
-          keep = False
-          break
-      if keep:
-        hits.append( hit)
-    # weed out overlapping hits - leave only the most significant ones
-    hit_num = len( hits)
-    to_delete = True
-    while to_delete:
-      to_delete = []
-      for i,hit1 in enumerate( hits):
-        for hit2 in hits[i+1:]:
-          if hit1 is not hit2:
-            if set(hit1.get_significant_atoms()) & set(hit2.get_significant_atoms()):
-              winner = self.which_substructure_is_more_specific( hit1.substructure, hit2.substructure)
-              if winner == 1:
-                to_delete.append( hit2)
-              elif winner == 2:
-                to_delete.append( hit1)
-              elif winner == 3:
-                pass # we preserve both hits
-              else:
-                raise ValueError( "Relationship between competing fragments could not be established,\nthere is probaly an error in the substructure matching code.")
-        if to_delete:
-          break
-      hits = [hit for hit in hits if not hit in to_delete]
-    return ring_hits + hits
-
-  def find_rings_in_mol( self, mol, detect_aromatic=True):
-    hits = []
-    if detect_aromatic:
-      # this is necessary to correctly process fused aromatic rings
-      # with improperly localized bonds
-      mol.mark_aromatic_bonds()
-    for ering in mol.get_smallest_independent_cycles_e():
-      vring = mol.edge_subgraph_to_vertex_subgraph( ering)
-      ring_mol = mol.get_new_induced_subgraph( vring, ering)
-      # here we need to take care of aromatic bonds
-      # this is needed to properly match naphthalene, etc.
-      if detect_aromatic:
-        for e in ring_mol.edges:
-          if e.aromatic:
-            e.order = 4
-        ring_mol.localize_aromatic_bonds()
-      ring_hash = ring_mol.get_structure_hash()
-      if ring_hash in self.rings:
-        hit = ring_match( vring, self.rings[ ring_hash])
-      else:
-        ring_obj = ring( None, smiles.mol_to_text( ring_mol), ring_hash=ring_hash)
-        self.rings[ ring_hash] = ring_obj
-        hit = ring_match( vring, ring_obj)
-      hits.append( hit)
-    return hits
-
-  @classmethod
-  def _data_files_to_python_module( self, structure_file=None, ring_file=None):
-    out = file( "subsearch_data.py", "w")
-    f = file( structure_file or self.substructure_def_file, "r")
-    print >> out, "## automatically generated file - may be overwritten at any time"
-    print >> out, "structures = ["
-    for line in f:
-      l = line.strip()
-      if l and not l.startswith( "#"):
-        parts = [x.strip() for x in l.split(";")]
-        if len( parts) < 3:
-          print >> sys.stderr, "Invalid line in src file:", line[:-1]
-        elif len( parts) == 3:
-          parts.append( "")
-        to_ignore = map( int, filter( None, parts[3].split(",")))
-        parts[3] = to_ignore
-        if not parts[1]:
-          parts[1] = parts[0]
-        print >> out, tuple(parts), ","
-    print >> out, "]"
-    f.close()
-    f = file( ring_file or self.ring_def_file, "r")
-    print >> out, "rings = ["
-    for line in f:
-      l = line.strip()
-      if l and not l.startswith( "#"):
-        parts = [x.strip() for x in l.split(";")]
-        print >> out, tuple(parts), ","
-    print >> out, "]"
-    f.close()
-    out.close()
-
-
-class substructure( object):
-
-  def __init__( self, name, compound_type, smiles="", atoms_to_ignore=None):
-    self.name = name
-    self.compound_type = compound_type
-    self.structure = None
-    if smiles:
-      self.read_smiles( smiles, atoms_to_ignore=atoms_to_ignore)
-    self.children = []
-
-  def __str__( self):
-    return "<Substructure: %s: %s>" % (self.name, self.smiles_string)
-
-  def read_smiles( self, smiles_string, atoms_to_ignore=None):
-    self.smiles_string = smiles_string.strip()
-    self.structure = smiles.text_to_mol( smiles_string, calc_coords=False)
-    if atoms_to_ignore:
-      self.atoms_to_ignore = [self.structure.vertices[x-1] for x in atoms_to_ignore]
-    else:
-      self.atoms_to_ignore = []
-
-  def find_matches( self, mol):
-    ret = []
-    ms = list( mol.select_matching_substructures( self.structure, implicit_freesites=True, auto_cleanup=False))
-    for atoms in ms:
-      num = min( atoms[0].properties_['subsearch'].keys())
-      atoms_in_fragment = [a.properties_['subsearch'][num] for a in atoms]
-      ret.append( substructure_match( atoms, atoms_in_fragment, self))
-    mol.clean_after_search( self.structure)
-    return ret
-
-
-class ring( object):
-
-  def __init__( self, name, smiles, ring_hash=None):
-    self.name = name
-    self.compound_type = "ring"
-    self.smiles_string = smiles.strip()
-    if ring_hash:
-      self.ring_hash = ring_hash
-    else:
-      self.ring_hash = None # read smiles and generate hash here?
-
-  def __str__( self):
-    return "<Ring: %s: %s>" % (self.name, self.smiles_string)
-
-
-class substructure_match( object):
-
-  def __init__( self, atoms_found, atoms_searched, substruct):
-    """atoms_found are atoms in the molecule we searched in,
-    atoms_searched are atoms in the fragment we used for search,
-     (atoms_searched and atoms_found are guaranteed to have the same order, thus
-      allowing matching between the two structures),
-    substruct is the substructure instance"""
-    self.substructure = substruct
-    self.atoms_found = atoms_found
-    self.atoms_searched = atoms_searched
-
-  def __str__( self):
-    return "<Match of %s with %d atoms (%d significant)>" % (self.substructure, len( self.atoms_found), len( self.get_significant_atoms()))
-
-  def get_significant_atoms( self):
-    ret = []
-    for i,af in enumerate( self.atoms_found):
-      atms = self.atoms_searched[i]
-      if atms not in self.substructure.atoms_to_ignore:
-        ret.append( af)
-    return ret
-
-
-class ring_match( object):
-
-  def __init__( self, atoms_found, ring_obj):
-    self.substructure = ring_obj
-    self.atoms_found = atoms_found
-
-  def __str__( self):
-    return "<RingMatch of %s>" % (self.substructure)
-
-  def get_significant_atoms( self):
-    return self.atoms_found
-
-
-  
-if __name__ == "__main__":
-  # update the subsearch_data.py module
-  substructure_search_manager._data_files_to_python_module()
-  import time
-  t = time.time()
-  ssm = substructure_search_manager()
-  #ssm._read_structure_file()
-  #ssm._read_ring_file()
-  
-  print "Read_structure_file: %.1fms" % (1000*(time.time() - t))
-  t = time.time()
-  
-  print ssm.structures
-  #print ssm.structures.is_connected()
-  dump = ssm.structures.get_graphviz_text_dump()
-  f = file( "dump.dot", "w")
-  f.write( dump)
-  f.close()
-
-  print "Graphviz dump: %.1fms" % (1000*(time.time() - t))
-  t = time.time()
-
-  def print_tree( x, l):
-    print l*" ", x #, x.children
-    for ch in x.children:
-      print_tree( ch, l+2)
-  
-  #for tree in ssm._compute_search_trees():
-  #  print_tree( tree, 0)
-
-  #"c1cccc2c1cccc2
-  #text = "COc5ccc4c2sc(cc2nc4c5)-c(cc1nc3c6)sc1c3ccc6OC"
-  text = 'C(=O)OCC'
-  #text2 = 'C(=O)[O-]'
-  mol = smiles.text_to_mol( text, calc_coords=False)
-  #m2 = smiles.text_to_mol( text2, calc_coords=False)
-  #print "XXXX", mol.contains_substructure( m2)
-
-  subs = ssm.find_substructures_in_mol( mol)
-  for sub in subs:
-    print sub
-
-  print "Substructure search: %.1fms" % (1000*(time.time() - t))
-  t = time.time()
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/svg_out.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/svg_out.py
--- bkchem-0.12.5/bkchem/oasa/oasa/svg_out.py	2008-10-14 04:24:33.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/svg_out.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,248 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import xml.dom.minidom as dom
-import dom_extensions
-import transform
-import geometry
-import math
-import misc
-import operator
-import copy
-
-
-class svg_out:
-
-  show_hydrogens_on_hetero = False
-  margin = 15
-  line_width = 2
-  bond_width = 6
-  # should individual parts of an edge be grouped together
-  group_items = True
-
-
-  def __init__( self):
-    pass
-
-  def mol_to_svg( self, mol, before=None, after=None):
-    """before and after should be methods or functions that will take one
-    argument - svg_out instance and do whatever it wants with it - usually
-    adding something to the resulting DOM tree"""
-    self.document = dom.Document()
-    top = dom_extensions.elementUnder( self.document,
-                                       "svg",
-                                       attributes=(("xmlns", "http://www.w3.org/2000/svg"),
-                                                   ("version", "1.0")))
-    self.top = dom_extensions.elementUnder( top, "g",
-                                            attributes=(("stroke", "#000"),
-                                                        ("stroke-width", "1.0")))
-
-    x1, y1, x2, y2 = None, None, None, None
-    for v in mol.vertices:
-      if x1 == None or x1 > v.x:
-        x1 = v.x
-      if x2 == None or x2 < v.x:
-        x2 = v.x
-      if y1 == None or y1 > v.y:
-        y1 = v.y
-      if y2 == None or y2 < v.y:
-        y2 = v.y
-
-    w = int( x2 - x1 + 2*self.margin)
-    h = int( y2 - y1 + 2*self.margin)
-    top.setAttribute( "width", str( w))
-    top.setAttribute( "height", str( h))
-
-    self.transformer = transform.transform()
-    self.transformer.set_move( -x1+self.margin, -y1+self.margin)
-
-    self.molecule = mol
-
-    if before:
-      before( self)
-
-    for e in copy.copy( mol.edges):
-      self._draw_edge( e)
-    for v in mol.vertices:
-      self._draw_vertex( v)
-
-    if after:
-      after( self)
-
-    return self.document
-
-
-  def paper_to_canvas_coord( self, x):
-    return x
-
-
-  def prepare_dumb_transformer( self):
-    tr = transform.transform()
-    tr.set_scaling( self.paper_to_canvas_coord( 1))
-    return tr
-
-
-  def _create_parent( self, item, top):
-    if self.group_items:
-      parent = dom_extensions.elementUnder( top, "g")
-      if 'svg_id' in item.properties_:
-        parent.setAttribute( "id", item.properties_['svg_id'])
-    else:
-      parent = self.top
-    return parent
-      
-
-  def _draw_edge( self, e):
-    v1, v2 = e.vertices
-    start = self.transformer.transform_xy( v1.x, v1.y)
-    end = self.transformer.transform_xy( v2.x, v2.y)
-    parent = self._create_parent( e, self.top)
-
-    if e.order == 1:
-      self._draw_line( parent, start, end, line_width=self.line_width)
-
-    if e.order == 2:
-      side = 0
-      # find how to center the bonds
-      # rings have higher priority in setting the positioning
-      for ring in self.molecule.get_smallest_independent_cycles():
-        if v1 in ring and v2 in ring:
-          side += reduce( operator.add, [geometry.on_which_side_is_point( start+end, (self.transformer.transform_xy( a.x,a.y))) for a in ring if a!=v1 and a!=v2])
-      # if rings did not decide, use the other neigbors
-      if not side:
-        for v in v1.neighbors + v2.neighbors:
-          if v != v1 and v!= v2:
-            side += geometry.on_which_side_is_point( start+end, (self.transformer.transform_xy( v.x, v.y)))
-      if side:
-        self._draw_line( parent, start, end, line_width=self.line_width)
-        x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], self.bond_width*misc.signum( side))
-        self._draw_line( parent, (x1, y1), (x2, y2), line_width=self.line_width)
-      else:
-        for i in (1,-1):
-          x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], i*self.bond_width*0.5)
-          self._draw_line( parent, (x1, y1), (x2, y2), line_width=self.line_width)
-        
-        
-    elif e.order == 3:
-      self._draw_line( parent, start, end, line_width=self.line_width)
-      for i in (1,-1):
-        x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], i*self.bond_width*0.7)
-        self._draw_line( parent, (x1, y1), (x2, y2), line_width=self.line_width)
-    
-
-  def _draw_vertex( self, v):
-    parent = self._create_parent( v, self.top)
-
-    if v.symbol != "C" or v.charge != 0 or v.multiplicity != 1:
-      x = v.x - 5
-      y = v.y + 6
-      x1 = x
-      x2 = x + 12
-      y1 = y - 12
-      y2 = y + 2
-      text = v.symbol
-      if self.show_hydrogens_on_hetero:
-        if v.free_valency == 1:
-          text += "H"
-        elif v.free_valency > 1:
-          text += "H%d" % v.free_valency
-      # charge
-      if v.charge == 1:
-        text += "+"
-      elif v.charge == -1:
-        text += "-"
-      elif v.charge > 1:
-        text += str( v.charge) + "+"
-      elif v.charge < -1:
-        text += str( v.charge)
-      # radicals
-      if v.multiplicity in (2,3):
-        self._draw_circle( parent, self.transformer.transform_xy((x2+x1)/2,y-17), fill_color="#000", opacity=1, radius=3)
-        if v.multiplicity == 3:
-          self._draw_circle( parent, self.transformer.transform_xy((x2+x1)/2,y+5), fill_color="#000", opacity=1, radius=3)  
-      self._draw_rectangle( parent, self.transformer.transform_4( (x1, y1, x2, y2)), fill_color="#fff")
-      self._draw_text( parent, self.transformer.transform_xy(x,y), text)
-
-
-  def _draw_line( self, parent, start, end, line_width=1, capstyle=""):
-    x1, y1 = start
-    x2, y2 = end
-    line = dom_extensions.elementUnder( parent, 'line',
-                                        (( 'x1', str( x1)),
-                                         ( 'y1', str( y1)),
-                                         ( 'x2', str( x2)),
-                                         ( 'y2', str( y2))))
-
-
-  def _draw_text( self, parent, xy, text, font_name="Arial", font_size=16):
-    x, y = xy
-    dom_extensions.textOnlyElementUnder( parent, "text", text,
-                                         (( "x", str( x)),
-                                          ( "y", str( y)),
-                                          ( "font-family", font_name),
-                                          ( "font-size", str( font_size)),
-                                          ( 'fill', "#000")))
-
-
-  def _draw_rectangle( self, parent, coords, fill_color="#fff", stroke_color="#fff"):
-    x, y, x2, y2 = coords
-    dom_extensions.elementUnder( parent, 'rect',
-                                 (( 'x', str( x)),
-                                  ( 'y', str( y)),
-                                  ( 'width', str( x2-x)),
-                                  ( 'height', str( y2-y)),
-                                  ( 'fill', fill_color),
-                                  ( 'stroke', stroke_color)))
-
-  def _draw_circle( self, parent, xy, radius=5, fill_color="#fff", stroke_color="#fff", id="", opacity=0):
-    x, y = xy
-    el = dom_extensions.elementUnder( parent, 'ellipse',
-                                      (( 'cx', str( x)),
-                                       ( 'cy', str( y)),
-                                       ( 'rx', str( radius)),
-                                       ( 'ry', str( radius)),
-                                       ( 'stroke-width', "1"),
-                                       ( 'fill', fill_color),
-                                       ( 'stroke', stroke_color),
-                                       ( 'fill-opacity', str(opacity)),
-                                       ( 'stroke-opacity', str(opacity)),
-                                      ))
-    if id:
-      el.setAttribute( "id", id)
-
-
-def mol_to_svg( mol, filename):
-  c = svg_out()
-  tree = c.mol_to_svg( mol)
-  f = file( filename, "w")
-  f.write( tree.toxml())
-  f.close()
-
-
-if __name__ == "__main__":
-
-  #import inchi
-  #mol = inchi.text_to_mol( "1/C7H6O2/c8-7(9)6-4-2-1-3-5-6/h1-5H,(H,8,9)", include_hydrogens=False, calc_coords=30)
-  #import smiles
-  #mol = smiles.text_to_mol( "CC[CH]", calc_coords=40)
-  import molfile
-  mol = molfile.file_to_mol( file( "/home/beda/bkchem/bkchem/untitled0.mol", "r"))
-  mol_to_svg( mol, "output.svg")
-
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/tests.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/tests.py
--- bkchem-0.12.5/bkchem/oasa/oasa/tests.py	2008-09-22 09:47:08.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/tests.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,265 +0,0 @@
-
-
-import smiles
-import molfile
-from time import time
-
-
-def test( function, times):
-    t1 = time()
-    for i in range( times):
-        function()
-    return time() - t1
-
-def test_method( obj, method, args, times):
-    t1 = time()
-    for i in range( times):
-        getattr( obj, method)( *args)
-    return time() - t1
-
-
-
-def is_edge_a_bridge_speed():
-    mol = smiles.text_to_mol( "CCCC(CCC)CCCC1CC2CC1C2")
-    t1 = time()
-    for i in range( 100):
-        b = 0
-        for e in mol.edges:
-            b += mol.is_edge_a_bridge( e)
-    print b
-    return time() - t1
-
-
-sssr_smiles = "C1CC2CC1CC2"
-
-def sssr_old():
-    mol = smiles.text_to_mol( sssr_smiles)
-    t1 = time()
-    cs = mol.get_smallest_independent_cycles_e()
-    print len( cs)
-    return time() - t1
-
-def sssr():
-    mol = smiles.text_to_mol( sssr_smiles)
-    t1 = time()
-    cs = mol.get_smallest_independent_cycles_e_new()
-    print len( cs)
-    return time() - t1
-
-    
-
-
-def find_all_cycles_speed():
-    pass
-
-
-def stereo():
-    sms = ["C\C=C\C","C\C=C/C"]
-    c = smiles.converter()
-    c.configuration['R_GENERATE_COORDS'] = True
-    for i,sm in enumerate( sms):
-        fname = "pokus%d.mol" % i
-        m = c.read_text( sm)[0]
-        print [(a.x,a.y) for a in m.vertices]
-        f = file( fname, "w")
-        molfile.mol_to_file( m, f)
-
-def stereo2():
-    sms = ["C\C=C\C","C\C=C/C"]
-    c = smiles.converter()
-    c.configuration['R_GENERATE_COORDS'] = True
-    for i,sm in enumerate( sms):
-        m = c.read_text( sm)[0]
-        print m.stereochemistry
-
-
-def stereo_from_coords():
-    f = file( "ez_stereo2.mol", "r")
-    mol = molfile.file_to_mol( f)
-    f.close()
-    mol.detect_stereochemistry_from_coords()
-    print smiles.mol_to_text( mol)
-
-def stereo_from_coords2():
-    f = file( "cis_trans_cycles.mol", "r")
-    mol = molfile.file_to_mol( f)
-    f.close()
-    c = smiles.converter()
-    print c.mols_to_text( [mol])
-
-
-def aromaticity():
-    mol = smiles.text_to_mol( "C1=C[CH][CH]C=C1")
-    #print [v.multiplicity for v in mol.vertices]
-    mol.mark_aromatic_bonds()
-    print [e.aromatic for e in mol.edges]
-    
-def smiles_from_db():
-    import structure_database
-    import time, sys
-    c = smiles.converter()
-    c.configuration['R_GENERATE_COORDS'] = True
-    i = 0
-    errors, ok = 0, 0
-    t = time.time()
-    for hit in structure_database.get_compounds_from_database():
-        i += 1
-        try:
-            mol = c.read_text( hit[3])
-        except:
-            print >> sys.stderr, "CRASH:", hit[3]
-            errors += 1
-        else:
-            ok += 1
-        if i % 100 == 0:
-            tm = time.time() - t
-            print "%d, %d ok, %d crashes;  %.1f per second" % (i, ok, errors, i/tm)
-
-def smiles_compression():
-    import structure_database
-    import time, sys, zlib
-    sms = []
-    f = file( "smiles.txt", "r")
-    for line in f:
-        sms.append( line.strip())
-    f.close()
-    i = 0
-    length = 0
-    comp_length = 0
-    c = zlib.compressobj()
-    for sm in sms:
-        length += len( sm)
-        i += 1
-        comp_length += len( c.compress( sm))
-    comp_length += len( c.flush(zlib.Z_SYNC_FLUSH))
-    print "Ratio:", 1.0 * comp_length / length
-
-    import random
-    total_compressed = 0
-    total_sm = 0
-    for sm in random.sample( sms, 10000):
-        total_sm += len( sm)
-        compressed = c.compress( sm)
-        compressed += c.flush(zlib.Z_SYNC_FLUSH)
-        total_compressed += len( compressed)
-        #print "RAW", 1.0*len( compressed) / len( sm)
-    print "Radio for individual SMILES:", 1.0*total_compressed / total_sm
-
-
-def cairo_out_test():
-    import molfile
-    import time
-    cid = 22646404
-    #cid = 1373132
-    #cid = 18305969
-    #cid = 19815256
-    f = file( "%d.rot.mol" % cid, "r")
-    mol = molfile.file_to_mol( f)
-    f.close()
-    import cairo_out
-    mol.normalize_bond_length( 25)
-    mol.remove_unimportant_hydrogens()
-    c = cairo_out.cairo_out( scaling=1.0, margin=10, font_size=16,
-                             bond_second_line_shortening=0.15, bond_width=6,
-                             line_width=2.0, antialias_drawing=True,
-                             antialias_text=True)
-    #c.background_color = (1,1,0.6)
-    c.show_hydrogens_on_hetero = True
-    t = time.time()
-    c.mol_to_cairo( mol, "%d.png" % cid)
-    print time.time()-t
-
-
-def cairo_out_test2():
-    import smiles
-    #mol = smiles.text_to_mol( "COC(=O)CNC(C1=CC=CC=C1)C2=C(C=CC(=C2)Br)NC(=O)C3=CC(=CC=C3)Cl")
-    #mol = smiles.text_to_mol( "c1nccc2c1cncn2")
-    mol = smiles.text_to_mol( "BNOLi")
-    import cairo_out
-    mol.normalize_bond_length( 30)
-    mol.remove_unimportant_hydrogens()
-    c = cairo_out.cairo_out( color_bonds=True, color_atoms=True)
-    c.show_hydrogens_on_hetero = True
-    c.font_size = 20
-    c.mol_to_cairo( mol, "test.png")
-
-def cairo_out_test_wedge():
-    import molfile
-    import time
-    f = file( "untitled0.mol", "r")
-    mol = molfile.file_to_mol( f)
-    f.close()
-    import cairo_out
-    mol.normalize_bond_length( 30)
-    mol.remove_unimportant_hydrogens()
-    c = cairo_out.cairo_out( scaling=1.0, antialias_text=True, color_bonds=True, color_atoms=True)
-    t = time.time()
-    c.mol_to_cairo( mol, "untitled0.png")
-    print time.time()-t
-
-def test_inchi_stereo():
-    import inchi
-    f = file( "stereobonds.txt", "r")
-    for line in f:
-        orig = line.strip()
-        try:
-            mol = inchi.text_to_mol( orig, calc_coords=True, include_hydrogens=False)
-        except:
-            #print "cannot read inchi", orig
-            continue
-        new = inchi.mol_to_text( mol)
-        if new != orig:
-            pass
-            print "problem"
-            print orig
-            print new
-            print "------------------------------"
-        else:
-            pass
-            #print "ok", orig
-    f.close()
-
-def test_CIP():
-    import smiles
-    m = smiles.text_to_mol( "HC(C(CCC)CCC)(C1CC1C)C2CC2")
-    from atom import atom
-    a = m.atoms[1]
-    print a.is_chiral()
-    print [m.atoms.index( x) for x in a.get_neighbors_CIP_sorted()]
-
-
-def test_CIP_2():
-    import smiles
-    m = smiles.text_to_mol( "HC(CC)(O)C")
-    from atom import atom
-    a = m.atoms[1]
-    dg = m.create_CIP_digraph( a)
-    print dg
-
-
-
-def test_3d_rot():
-    import geometry
-    p = [0,4,0]
-    t = geometry.create_transformation_to_coincide_point_with_z_axis( [0,0,0], p)
-    print t.transform_xyz( *p)
-    print t.transform_xyz( 0,0,1)
-
-#print "new", sssr()
-#print "old", sssr_old()
-#print is_edge_a_bridge_speed()
-
-#stereo_from_coords2()
-#aromaticity()
-
-#smiles_from_db()
-#smiles_compression()
-
-#cairo_out_test()
-#cairo_out_test2()
-#cairo_out_test_wedge()
-
-#test_inchi_stereo()
-
-test_CIP_2()
-#test_3d_rot()
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/transform3d.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/transform3d.py
--- bkchem-0.12.5/bkchem/oasa/oasa/transform3d.py	2008-02-19 04:11:40.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/transform3d.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,154 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of BKchem - a chemical drawing program
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-
-"""this module provides basic coordinate transformations in 3D based on matrix algebra"""
-
-from math import cos, sin, pi
-
-class transform3d:
-  """this class provides basic higher-level interface for coordinate transforms"""
-  def __init__( self, mat = None):
-    if not mat:
-      self.mat = matrix( [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]])
-    else:
-      self.mat = matrix( mat)
-
-  def transform_xyz( self, x, y, z):
-    x1, y1, z1, one = self.mat.get_multiplied2( [[x],[y],[z],[1]])
-    return x1[0], y1[0], z1[0]
-
-  def transform_xy( self, x, y):
-    """is used only for compatibility with 2D transform, assumes z=0,
-    should be used only in cases where it is sure that starting and
-    result positions have z0 (pi rotation around one axis etc.)"""
-    x0, y0, z0 = self.transform_xyz( x, y, 0)
-    return x0, y0
-
-  def transform_xy_flat_list( self, coords):
-    """ONLY X,Y TRANSFORM, for compatibility with 2D transform!!!,
-    transforms a list that cointains alternating x,y values (not list of pairs
-    as self.transform_list)"""
-    ret = []
-    for j in range( 0, len( coords), 2):
-      x, y = self.transform_xy( coords[j], coords[j+1])
-      ret.append( x)
-      ret.append( y)
-    return ret
-
-
-  def transform_list( self, l):
-    ret = []
-    for line in l:
-      ret.append( self.transform_xyz( line[0], line[1], line[2]))
-    return ret
-
-  def map_transform( self, l):
-    "this method can be used with map() function"
-    return self.transform_xyz( l[0], l[1], l[2])
-
-  def set_move( self, dx, dy, dz):
-    "add an moving step to transformation matrix"
-    self.mat = matrix( mat=self.mat.get_multiplied( [[1,0,0,dx],[0,1,0,dy],[0,0,1,dz],[0,0,0,1]]))
-
-  def set_rotation( self, xa, ya, za):
-    "add an rotation step to transformation matrix"
-    self.set_rotation_x( xa)
-    self.set_rotation_y( ya)
-    self.set_rotation_z( za)
-    
-  def set_rotation_x( self, xa):
-    mat = [[1,0,0,0],
-           [0, cos(xa), sin(xa), 0],
-           [0, -sin(xa), cos(xa), 0],
-           [0,0,0,1]]
-    self.mat = matrix( mat=self.mat.get_multiplied( mat))
-
-  def set_rotation_y( self, ya):
-    mat = [[cos(ya), 0, -sin(ya), 0],
-           [0, 1, 0, 0],
-           [sin(ya), 0, cos(ya), 0],
-           [0,0,0,1]]
-    self.mat = matrix( mat=self.mat.get_multiplied( mat))
-
-  def set_rotation_z( self, za):
-    mat = [[cos(za), sin(za), 0, 0],
-           [-sin(za), cos(za), 0, 0],
-           [0,0,1,0],
-           [0,0,0,1]]
-    self.mat = matrix( mat=self.mat.get_multiplied( mat))
-
-  def set_scaling_xyz( self, mx, my, mz):
-    "add an scaling step to transformation matrix"
-    self.mat = matrix( mat=self.mat.get_multiplied( [[mx,0,0,0],[0,my,0,0],[0,0,mz,0],[0,0,0,1]]))
-
-  def set_scaling( self, scale):
-    "add an scaling step to transformation matrix, same scaling for both dimensions"
-    self.set_scaling_xyz( scale, scale, scale)
-
-
-
-class matrix:
-  "this class provides common operations for matrix of 3x3 elements"
-
-  def __init__( self, mat):
-    self.mat = mat
-
-  def get_transposed( self):
-    m = self.mat
-    ret = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
-    for i in range( len( m)):
-      for j in range( len( m)):
-        ret[j][i] = m[i][j]
-    return ret
-
-  def get_inverse( self):
-    pass
-
-  def get_multiplied2( self, multi):
-    "returns matrix multiplied by multi"
-    m = []
-    if type( multi) != type([]):
-      for i in range( len( self.mat)):
-        m.append([])
-        for j in range( len( self.mat)):
-          m[i].append( multi*self.mat[i][j])
-    else:
-      for i in range( len( self.mat)):
-        m.append([])
-        for j in range( len( multi[0])):
-          m[i].append( self.mat[i][0]*multi[0][j] + self.mat[i][1]*multi[1][j] + self.mat[i][2]*multi[2][j] + self.mat[i][3]*multi[3][j])
-    return  m
-
-  def get_multiplied( self, multi):
-    "returns matrix multiplied by multi"
-    m = []
-    if type( multi) != type([]):
-      for i in range( len( self.mat)):
-        m.append([])
-        for j in range( len( self.mat)):
-          m[i].append( multi*self.mat[i][j])
-    else:
-      for i in range( len( multi)):
-        m.append([])
-        for j in range( len( self.mat)):
-          m[i].append( multi[i][0]*self.mat[0][j] + multi[i][1]*self.mat[1][j] + multi[i][2]*self.mat[2][j] +  multi[i][3]*self.mat[3][j])
-    return  m
-
-
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/transform.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/transform.py
--- bkchem-0.12.5/bkchem/oasa/oasa/transform.py	2008-05-05 09:36:28.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/transform.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,135 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of BKchem - a chemical drawing program
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-#
-#
-#
-#--------------------------------------------------------------------------
-
-"""this module provides basic coordinate transformations based on matrix algebra"""
-
-from math import cos, sin, pi
-
-class transform:
-  """this class provides basic higher-level interface for coordinate transforms"""
-  def __init__( self, mat = None):
-    if not mat:
-      self.mat = matrix( [[1,0,0],[0,1,0],[0,0,1]])
-    else:
-      self.mat = matrix( mat)
-
-  def transform_xy( self, x, y):
-    x1, y1, one = self.mat.get_multiplied2( [[x],[y],[1]])
-    return x1[0], y1[0]
-
-  def transform_4( self, list):
-    "for items that have 4 coordinates (lines, rectangles) this transforms 'em all"
-    x1, y1, one = self.mat.get_multiplied2( [[list[0]],[list[1]],[1]])
-    x2, y2, one = self.mat.get_multiplied2( [[list[2]],[list[3]],[1]])
-    return (x1[0], y1[0], x2[0], y2[0])
-
-  def transform_list( self, l):
-    ret = []
-    for line in l:
-      ret.append( self.transform_xy( line[0], line[1]))
-    return ret
-
-  def transform_xy_flat_list( self, coords):
-    """transforms a list that cointains alternating x,y values (not list of pairs
-    as self.transform_list)"""
-    ret = []
-    for j in range( 0, len( coords), 2):
-      x, y = self.transform_xy( coords[j], coords[j+1])
-      ret.append( x)
-      ret.append( y)
-    return ret
-
-
-  def map_transform( self, l):
-    "this method can be used with map() function"
-    return self.transform_xy( l[0], l[1])
-
-  def set_move( self, dx, dy):
-    "add an moving step to transformation matrix"
-    self.mat = matrix( mat=self.mat.get_multiplied( [[1,0,dx],[0,1,dy],[0,0,1]]))
-
-  def set_rotation( self, angle):
-    "add an rotation step to transformation matrix"
-    self.mat = matrix( mat=self.mat.get_multiplied( [[cos(angle),-sin(angle),0],[sin(angle),cos(angle),0],[0,0,1]]))
-
-  def set_scaling_xy( self, mx, my):
-    "add an scaling step to transformation matrix"
-    self.mat = matrix( mat=self.mat.get_multiplied( [[mx,0,0],[0,my,0],[0,0,1]]))
-
-  def set_scaling( self, scale):
-    "add an scaling step to transformation matrix, same scaling for both dimensions"
-    self.mat = matrix( mat=self.mat.get_multiplied( [[scale,0,0],[0,scale,0],[0,0,1]]))
-
-
-class matrix:
-  "this class provides common operations for matrix of 3x3 elements"
-
-  def __init__( self, mat):
-    self.mat = mat
-
-  def get_transposed( self):
-    m = self.mat
-    ret = [[0,0,0],[0,0,0],[0,0,0]]
-    for i in range( len( m)):
-      for j in range( len( m)):
-        ret[j][i] = m[i][j]
-    return ret
-
-  def get_inverse( self):
-    pass
-
-  def get_multiplied2( self, multi):
-    "returns matrix multiplied by multi"
-    m = []
-    if type( multi) != type([]):
-      for i in range( len( self.mat)):
-        m.append([])
-        for j in range( len( self.mat)):
-          m[i].append( multi*self.mat[i][j])
-    else:
-      for i in range( len( self.mat)):
-        m.append([])
-        for j in range( len( multi[0])):
-          m[i].append( self.mat[i][0]*multi[0][j] + self.mat[i][1]*multi[1][j] + self.mat[i][2]*multi[2][j])
-    return  m
-
-  def get_multiplied( self, multi):
-    "returns matrix multiplied by multi"
-    m = []
-    if type( multi) != type([]):
-      for i in range( len( self.mat)):
-        m.append([])
-        for j in range( len( self.mat)):
-          m[i].append( multi*self.mat[i][j])
-    else:
-      for i in range( len( multi)):
-        m.append([])
-        for j in range( len( self.mat)):
-          m[i].append( multi[i][0]*self.mat[0][j] + multi[i][1]*self.mat[1][j] + multi[i][2]*self.mat[2][j])
-    return  m
-
-  def get_determinant( self):
-    m = self.mat
-    return (((m[0][0] * m[1][1] * m[2][2]) + (m[0][1] * m[1][2] * m[2][0]) + (m[0][2] * m[1][0] * m[2][1])) -
-            ((m[2][1] * m[1][2] * m[0][0]) + (m[2][2] * m[1][0] * m[0][1]) + (m[2][0] * m[1][1] * m[0][2])))
-                                                                                         
diff -uNr bkchem-0.12.5/bkchem/oasa/oasa/unittests.py bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/unittests.py
--- bkchem-0.12.5/bkchem/oasa/oasa/unittests.py	2008-05-19 10:28:53.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/oasa/unittests.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,401 +0,0 @@
-#--------------------------------------------------------------------------
-#     This file is part of OASA - a free chemical python library
-#     Copyright (C) 2003-2008 Beda Kosata <beda@zirael.org>
-
-#     This program is free software; you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation; either version 2 of the License, or
-#     (at your option) any later version.
-
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-
-#     Complete text of GNU GPL can be found in the file gpl.txt in the
-#     main directory of the program
-
-#--------------------------------------------------------------------------
-
-import unittest
-import linear_formula
-import smiles
-import molecule
-
-# helper functions
-
-def create_test(i,name):
-  def test(obj):
-    getattr(obj, name)(i)
-  return test
-
-
-## linear formula testing
-
-class TestLinearFormula(unittest.TestCase):
-
-  formulas = [("CH3COOH","CC(=O)O",0,0),
-              ("CH3C(CH3)3","CC(C)(C)C",0,0),
-              ("CH2C(CH3)3","CC(C)(C)C",1,0),
-              ("(CH2)7Cl","CCCCCCCCl",1,0),
-              ]
-    
-  def _testformula(self, num):
-    l = linear_formula.linear_formula()
-    linear, smile, start_valency, end_valency = self.formulas[num]
-    m1 = l.parse_text( linear, start_valency=start_valency, end_valency=end_valency) 
-    m2 = smiles.text_to_mol( smile)
-    self.assert_(molecule.equals(m1,m2,level=3))
-
-# this creates individual test for formulas
-for i in range( len( TestLinearFormula.formulas)):
-  setattr( TestLinearFormula, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-## // linear formula testing
-
-
-
-## substructure testing
-
-class TestSubstructure(unittest.TestCase):
-
-  formulas = [("CC(=O)C","CC(=O)C",True),
-              ("CCCC","CCC",True),
-              ("CC(=O)C","C=O",True),
-              ("C=O","CC(=O)C",False),
-              ("C(=O)H","C=O",True),
-              ("C=O","C(=O)H",True),      # implicit hydrogens work
-              ("C(=O)O","C(=O)OH",True),  # implicit hydrogens work
-              ("C(=O)OH","C(=O)O",True),
-              ("C(=O)OC","C(=O)OH",False),  # explicit hydrogens work
-              ("C(=O)OC","C(=O)[OH]",False),  # explicit hydrogens in atom specs work
-              ("C(=O)OC","C(=O)O",True),
-              #
-              ("CC(=O)H","C(=O)H",True),
-              # charges
-              ("C(=O)[O-]","C(=O)O",True),              
-              ("C(=O)O","C(=O)[O-]",False),
-              ("C(=O)[O-]","C(=O)[OH]",False),
-              ("C(=O)[OH]","C(=O)[O-]",False),
-              ("C(=O)[O-]","C(=O)OH",False),
-              ("C(=O)OCC","C(=O)[O-]",False),
-              ]
-    
-  def _testformula(self, num):
-    smile1, smile2, result = self.formulas[num]
-    m1 = smiles.text_to_mol( smile1)
-    m2 = smiles.text_to_mol( smile2)
-    self.assert_(m1.contains_substructure(m2)==result)
-
-# this creates individual test for substructures
-for i in range( len( TestSubstructure.formulas)):
-  setattr( TestSubstructure, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-## // substructure testing
-
-
-## SMILES equality testing
-
-class TestEqualSMILES(unittest.TestCase):
-
-  formulas = [("Sc1ccccc1","S-c1ccccc1",True),  # check Sc (in PT scandium) bug
-              ("Oc1ccccc1","O-c1ccccc1",True),
-              ("c1ccccc1","C:1:C:C:C:C:C:1", True),
-              ("c1ccccc1","[CH]:1:[CH]:[CH]:[CH]:[CH]:[CH]:1", True),
-              ("c1cscc1","C1=C-S-C=C1", True),
-              ("c1ccccc1","C1=CC=CC=C1", True),
-              ("c1ccccc1","C=1C=CC=CC=1", True),
-              ("C=1CC=1","C1C=C1", True),
-              ("C12CC%01CC2","C12CC1CC2", True),
-              ("C%01%02CC%01CC2","C12CC1CC2", True),
-              ("H","[H]", False),
-              ("C","[2H]C", False),
-              ("[C]","[CH0]", True),
-              ]
-    
-  def _testformula(self, num):
-    smile1, smile2, result = self.formulas[num]
-    m1 = smiles.text_to_mol( smile1)
-    m2 = smiles.text_to_mol( smile2)
-    self.assert_(molecule.equals(m1,m2,level=3)==result)
-
-# this creates individual test for substructures
-for i in range( len( TestEqualSMILES.formulas)):
-  setattr( TestEqualSMILES, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-## // SMILES equality testing
-
-
-## SMILES reading testing
-
-class TestSMILESReading(unittest.TestCase):
-
-  formulas = [("Sc1ccccc1",("C6H6S",)),
-              ("Oc1ccccc1",("C6H6O",)),
-              ("[Na+].[Cl-]", ("Na","Cl")),
-              ("[O-]c1ccccc1.[Na+]",("C6H5O","Na")),
-              ("O=C[O-].[NH4+]",("CHO2","H4N")),
-              ("c1ccccc1-c1ccccc1",("C12H10",)),
-              ("c1cscc1",("C4H4S",)),
-              ]
-    
-  def _testformula(self, num):
-    smile1, sum_forms = self.formulas[num]
-    conv = smiles.converter()
-    mols = conv.read_text( smile1)
-    for i,mol in enumerate( mols):
-      self.assert_( i < len( sum_forms))
-      self.assertEqual( str( mol.get_formula_dict()), sum_forms[i]) 
-
-  def test_empty_smiles( self):
-    conv = smiles.converter()
-    for text in ("", "  "):
-      mols = conv.read_text( "")
-      self.assertEqual( mols, [])
-
-
-# this creates individual test for substructures
-for i in range( len( TestSMILESReading.formulas)):
-  setattr( TestSMILESReading, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-## // SMILES reading testing
-
-
-## SMILES Reaction support
-
-class TestSMILESReactionSupport(unittest.TestCase):
-  
-  def test1(self):
-    """tests handling of reactions by the SMILES reader on a preparation of methyl-formate"""
-    c = smiles.converter()
-    reacts = c.read_text( "O=CO.CO>[H+]>O=COC.O")
-    self.assertEqual( reacts, c.result)
-    self.assertEqual( c.last_status, c.STATUS_OK)
-    self.assertEqual( len( reacts), 1)
-    react = reacts[0]
-    self.assertEqual( len( react.reactants), 2)
-    self.assertEqual( len( react.reactants[0].molecule.atoms), 3)
-    self.assertEqual( len( react.reactants[1].molecule.atoms), 2)
-    self.assertEqual( len( react.reagents[0].molecule.atoms), 1)
-    self.assertEqual( len( react.products[0].molecule.atoms), 4)
-    self.assertEqual( len( react.products[1].molecule.atoms), 1)
-    self.assertEqual( str( react.products[0].molecule.get_formula_dict()), "C2H4O2")
-
-  def test2(self):
-    """test reactions with some empty parts"""
-    c = smiles.converter()
-    reacts = c.read_text( "C=C.[H][H]>>CC")
-    self.assertEqual( len( reacts), 1)
-    react = reacts[0]
-    self.assertEqual( len( react.reagents), 0)
-    self.assertEqual( len( react.reactants), 2)
-    self.assertEqual( len( react.products), 1)
-
-
-
-
-## // SMILES Reaction support
-
-## Reaction test
-
-import reaction
-
-class TestReactionComponent(unittest.TestCase):
-
-  def test1(self):
-    mol = smiles.text_to_mol( "CCCO")
-    rc = reaction.reaction_component( mol, 2)
-    self.assertEqual( rc.stoichiometry, 2)
-    self.assertRaises( Exception, reaction.reaction_component, mol, "x")
-    self.assertRaises( Exception, rc._set_stoichiometry, "x")
-    self.assertRaises( Exception, reaction.reaction_component, 2, 2)
-    self.assertRaises( Exception, rc._set_molecule, "x")    
-
-
-## Explicit hydrogens, occupied_valency and free_valency testing
-
-class TestValency(unittest.TestCase):
-
-  # (formula, (N explicit_hydrogens, N occupied_valency, N free_valency))
-
-  formulas = [("CN",(0,1,2)),
-              ("C[NH2]",(2,3,0)),
-              ("C[NH]C",(1,3,0)),
-              ("CN(C)C",(0,3,0)),
-              ]
-    
-  def _testformula(self, num):
-    smile1, (explicit_hs, occupied_v, free_v) = self.formulas[num]
-    conv = smiles.converter()
-    mols = conv.read_text( smile1)
-    self.assertEqual( len( mols), 1)
-    mol = mols[0]
-    ns = [v for v in mol.vertices if v.symbol == 'N']
-    self.assertEqual( len( ns), 1)
-    n = ns[0]
-    self.assertEqual( n.explicit_hydrogens, explicit_hs)
-    self.assertEqual( n.free_valency, free_v)
-    self.assertEqual( n.occupied_valency, occupied_v)
-
-# this creates individual test
-for i in range( len( TestValency.formulas)):
-  setattr( TestValency, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-## // Explicit hydrogens, occupied_valency and free_valency testing
-
-
-## Charge computation testing
-
-class TestCharge(unittest.TestCase):
-
-  formulas = [("",()),
-              ("c1ccccc1",(0,)),
-              ("[Na+].[Cl-]", (1,-1)),
-              ("[O-]c1ccccc1.[Na+]",(-1,1)),
-              ("O=C[O-].[NH4+]",(-1,1)),
-              ("[O-]CC[O-].[K+].[K+]", (-2,1,1)),
-              ("[OH-].[OH-].[Ca+2]",(-1,-1,2)),
-              ]
-    
-  def _testformula(self, num):
-    smile1, charges = self.formulas[num]
-    conv = smiles.converter()
-    mols = conv.read_text( smile1)
-    comp_charges = [mol.charge for mol in mols]
-    comp_charges.sort()
-    charges = list( charges)
-    charges.sort()
-    self.assertEqual( comp_charges, charges)
-
-# this creates individual test for substructures
-for i in range( len( TestCharge.formulas)):
-  setattr( TestCharge, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-## // Charge computation testing
-
-
-## Stereochemistry testing
-
-class TestStereo(unittest.TestCase):
-
-  formulas = [("c1ccccc1",()),
-              ("C\C=C/C", (1,)),
-              ("C\C=C/C=C/C=C\C", (-1,1,1)),
-              ("C\C(\O)=C/C=C/C=C\C", (-1,-1,1,1)),
-              ("O\C=C=C=C/N=C/Br", (-1,1)),
-              ("O\C(\N)=C/C=C\C=C\Cl", (-1,-1,1,1))
-              ]
-    
-  def _testformula(self, num):
-    smile1, directions = self.formulas[num]
-    conv = smiles.converter()
-    mols = conv.read_text( smile1)
-    self.assertEqual( len( mols), 1)
-    mol = mols[0]
-    sts = [st.value==st.SAME_SIDE and 1 or -1 for st in mol.stereochemistry]
-    sts.sort()
-    sts = tuple( sts)
-    self.assertEqual( sts, directions)
-
-# this creates individual test for substructures
-for i in range( len( TestStereo.formulas)):
-  setattr( TestStereo, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-
-class TestStereo2(unittest.TestCase):
-  """tests if stereochemistry of structure read from smiles
-  remains the same when recoded back to smiles and read again."""
-
-  formulas = ["C\C=C/C",
-              "N\C=C/C=C/Cl",
-              "O\C(\N)=C/C=C\C=C\Cl",
-              "O\C=C=C=C/N=C/Br",
-              "C/C(Cl)=C(\O)C",
-              ]
-    
-  def _testformula(self, num):
-    def create_st_sum( st):
-      symbols = [a.symbol for a in (st.references[0],st.references[-1])]
-      symbols.sort()
-      return tuple( symbols + [st.value==st.SAME_SIDE and 1 or -1])
-      
-    # round 1
-    smile1 = self.formulas[num]
-    conv = smiles.converter()
-    mols = conv.read_text( smile1)
-    self.assertEqual( len( mols), 1)
-    mol = mols[0]
-    sts1 = [create_st_sum( st) for st in mol.stereochemistry]
-    # round 2
-    smile2 = conv.mols_to_text( [mol])
-    mols = conv.read_text( smile1)
-    self.assertEqual( len( mols), 1)
-    mol = mols[0]
-    sts2 = [create_st_sum( st) for st in mol.stereochemistry]
-    for stsum in sts2:
-      self.assertEqual( (stsum in sts1), True)
-
-# this creates individual test for substructures
-for i in range( len( TestStereo2.formulas)):
-  setattr( TestStereo2, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-
-class TestStereo3(unittest.TestCase):
-  """tests if stereochemistry of structure read from smiles
-  remains the same when coordinates are calculates, stereo information
-  thrown away and recalculated from the coords."""
-
-  formulas = ["C\C=C/C",
-              "N\C=C/C=C/Cl",
-              "O\C(\N)=C/C=C\C=C\Cl",
-              "O\C=C=C=C/N=C/Br",
-              "C/C(Cl)=C(\O)N",
-              ]
-    
-  def _testformula(self, num):
-    def create_st_sum( st):
-      symbols = [a.symbol for a in (st.references[0],st.references[-1])]
-      symbols.sort()
-      return tuple( symbols + [st.value==st.SAME_SIDE and 1 or -1])
-      
-    # round 1
-    smile1 = self.formulas[num]
-    conv = smiles.converter()
-    mols = conv.read_text( smile1)
-    self.assertEqual( len( mols), 1)
-    mol = mols[0]
-    sts1 = [create_st_sum( st) for st in mol.stereochemistry]
-    # round 2
-    mol.stereochemistry = []
-    mol.detect_stereochemistry_from_coords()
-    sts2 = [create_st_sum( st) for st in mol.stereochemistry]
-    for stsum in sts2:
-      if not stsum in sts1:
-        # not necessarily an error, we must count changes
-        changes = 0
-        for (v1,v2) in zip( stsum, sts1[0]):
-          if v1 != v2:
-            changes += 1
-        self.assertEqual( changes % 2, 0)
-      else:
-        self.assertEqual( (stsum in sts1), True)
-
-# this creates individual test for substructures
-for i in range( len( TestStereo3.formulas)):
-  setattr( TestStereo3, "testformula"+str(i+1), create_test(i,"_testformula"))
-
-
-## // Stereo testing
-
-
-
-if __name__ == '__main__':
-  import sys
-  if len( sys.argv) > 1 and sys.argv[1] == "-v":
-    tests = [globals()[k] for k in dir() if type(globals()[k])==type and issubclass( globals()[k], unittest.TestCase)]
-    ss = []
-    for test in tests:
-      s1 = unittest.makeSuite( test)
-      unittest.TextTestRunner(verbosity=2).run(s1)
-  else:
-    unittest.main()
diff -uNr bkchem-0.12.5/bkchem/oasa/setup.py bkchem-0.12.5-nopiddle/bkchem/oasa/setup.py
--- bkchem-0.12.5/bkchem/oasa/setup.py	2008-05-20 03:49:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/setup.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,37 +0,0 @@
-#! /usr/bin/python
-
-import sys
-from distutils.core import setup
-import glob
-import os
-import operator
-try:
-  import py2exe
-except:
-  pass
-
-## A few pre-setup hacks
-#if os.name != 'posix':
-#  sys.path.insert( 0, 'bkchem')
-
-# the setup itself
-
-set = setup(
-  name = 'oasa',
-  version = '0.12.1',
-  description = "OASA is a free cheminformatics library written in Python",
-  author = "Beda Kosata",
-  author_email = "beda@zirael.org",
-  url = "http://bkchem.zirael.org/oasa_en.html",
-  license = "GNU GPL",
-  platforms = ["Unix", "Windows", "hopefully other OSes able to run Python"],
-  long_description = "OASA is a free cheminformatics library written in Python",
-  
-  packages=[ 'oasa', 'oasa/graph'],
-
-  #data_files=[ ('oasa', glob.glob( 'oasa/templates/*.cdml')+glob.glob('templates/*.xml')),
-
-  #windows=['oasa'],
-  #options = {"py2exe": {"packages": ["encodings"]}}
-  )
-
diff -uNr bkchem-0.12.5/bkchem/oasa/test.py bkchem-0.12.5-nopiddle/bkchem/oasa/test.py
--- bkchem-0.12.5/bkchem/oasa/test.py	2008-05-20 03:47:49.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/oasa/test.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,15 +0,0 @@
-import oasa
-#print oasa.CAIRO_AVAILABLE
-
-def cairo_out_test2():
-    #mol = smiles.text_to_mol( "COC(=O)CNC(C1=CC=CC=C1)C2=C(C=CC(=C2)Br)NC(=O)C3=CC(=CC=C3)Cl")
-    #mol = smiles.text_to_mol( "c1nccc2c1cncn2")
-    mol = oasa.smiles.text_to_mol( "OC(O)C(O)C(O)CO")
-    mol.normalize_bond_length( 30)
-    mol.remove_unimportant_hydrogens()
-    c = oasa.cairo_out.cairo_out( color_bonds=True, color_atoms=True)
-    c.show_hydrogens_on_hetero = True
-    c.font_size = 20
-    c.mol_to_cairo( mol, "test.png")
-
-cairo_out_test2()
diff -uNr bkchem-0.12.5/bkchem/plugins/__init__.py bkchem-0.12.5-nopiddle/bkchem/plugins/__init__.py
--- bkchem-0.12.5/bkchem/plugins/__init__.py	2007-10-26 03:08:10.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/__init__.py	2008-11-26 09:25:30.000000000 -0200
@@ -19,7 +19,7 @@
 
 
 __all__ = []
-_names = ['CML','CML2','openoffice','ps_builtin','molfile','pdf_piddle','ps_piddle','pdf_cairo','png_cairo',"odf", "svg_cairo",'ps_cairo']
+_names = ['CML','CML2','openoffice','ps_builtin','molfile','pdf_cairo','png_cairo',"odf", "svg_cairo",'ps_cairo']
 # 'bitmap' and 'gtml' were removed for the release
 
 import sys
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/aigen.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/aigen.py
--- bkchem-0.12.5/bkchem/plugins/piddle/aigen.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/aigen.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,879 +0,0 @@
-#aigen.py
-""" 
-This is a library to generate AI files containing text and graphics.  It is
-part of the Adobe Illustrator format backend for PIDDLE foundation for a
-complete Reporting solution in Python.
-
-(C) Copyright Bill Bedford 1999
-parts (C) Copyright Andy Robinson 1998-1999
-"""
-
-import os
-import sys
-import string
-import time
-import tempfile
-from types import *
-
-
-##############################################################
-#
-#			Constants and declarations
-#
-##############################################################
-
-
-
-StandardEnglishFonts = [
-	'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique',  
-	'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 
-	'Helvetica-BoldOblique',
-	'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic',
-	'Symbol','ZapfDingbats']
-
-AIError = 'AIError'
-AFMDIR = '.'
-
-##############################################################
-#
-#			AI Metrics
-# This is a preamble to give us a stringWidth function.
-# loads and caches AFM files, but won't need to as the
-# standard fonts are there already
-##############################################################
-
-widths = {'courier': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,
-0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,
-600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,
-0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],
-'courier-bold': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,
-0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,
-600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,
-0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],
-'courier-boldoblique': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,
-0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,
-600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,
-0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],
-'courier-oblique': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,
-0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,
-0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,
-600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,
-0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],
-'helvetica': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-278,278,355,556,556,889,667,222,333,333,389,584,278,333,278,278,
-556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,
-1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,
-667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,
-222,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,
-556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,333,556,556,167,556,556,556,556,191,333,556,333,333,500,500,
-0,556,556,556,278,0,537,350,222,333,333,556,1000,1000,0,611,
-0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,
-1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,1000,0,370,0,0,0,0,556,778,1000,365,0,0,0,0,
-0,889,0,0,0,278,0,0,222,611,944,611,0,0,834],
-'helvetica-bold': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-278,333,474,556,556,889,722,278,333,333,389,584,278,333,278,278,
-556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,
-975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,
-667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,
-278,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,
-611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,333,556,556,167,556,556,556,556,238,500,556,333,333,611,611,
-0,556,556,556,278,0,556,350,278,500,500,556,1000,1000,0,611,
-0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,
-1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,1000,0,370,0,0,0,0,611,778,1000,365,0,0,0,0,
-0,889,0,0,0,278,0,0,278,611,944,611,0,0,834],
-'helvetica-boldoblique': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-278,333,474,556,556,889,722,278,333,333,389,584,278,333,278,278,
-556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,
-975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,
-667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,
-278,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,
-611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,333,556,556,167,556,556,556,556,238,500,556,333,333,611,611,
-0,556,556,556,278,0,556,350,278,500,500,556,1000,1000,0,611,
-0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,
-1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,1000,0,370,0,0,0,0,611,778,1000,365,0,0,0,0,
-0,889,0,0,0,278,0,0,278,611,944,611,0,0,834],
-'helvetica-oblique': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-278,278,355,556,556,889,667,222,333,333,389,584,278,333,278,278,
-556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,
-1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,
-667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,
-222,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,
-556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,333,556,556,167,556,556,556,556,191,333,556,333,333,500,500,
-0,556,556,556,278,0,537,350,222,333,333,556,1000,1000,0,611,
-0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,
-1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,1000,0,370,0,0,0,0,556,778,1000,365,0,0,0,0,
-0,889,0,0,0,278,0,0,222,611,944,611,0,0,834],
-'symbol': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-250,333,713,500,549,833,778,439,333,333,500,549,250,549,250,278,
-500,500,500,500,500,500,500,500,500,500,278,278,549,549,549,444,
-549,722,667,722,612,611,763,603,722,333,631,722,686,889,722,722,
-768,741,556,592,611,690,439,768,645,795,611,333,863,333,658,500,
-500,631,549,549,494,439,521,411,603,329,603,549,549,576,521,549,
-549,521,549,603,439,576,713,686,493,686,494,480,200,480,549,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,620,247,549,167,713,500,753,753,753,753,1042,987,603,987,603,
-400,549,411,549,549,713,494,460,549,549,549,549,1000,603,1000,658,
-823,686,795,987,768,768,823,768,768,713,713,713,713,713,713,713,
-768,713,790,790,890,823,549,250,713,603,603,1042,987,603,987,603,
-494,329,790,790,786,713,384,384,384,384,384,384,494,494,494,494,
-0,329,274,686,686,686,384,384,384,384,384,384,494,494,790],
-'times-bold': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-250,333,555,500,500,1000,833,333,333,333,500,570,250,333,250,278,
-500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,
-930,722,667,722,722,667,611,778,778,389,500,778,667,944,722,778,
-611,778,722,556,667,722,722,1000,722,722,667,333,278,333,581,500,
-333,500,556,444,556,444,333,500,556,278,333,556,278,833,556,500,
-556,556,444,389,333,556,500,722,500,500,444,394,220,394,520,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,333,500,500,167,500,500,500,500,278,500,500,333,333,556,556,
-0,500,500,500,250,0,540,350,333,500,500,500,1000,1000,0,500,
-0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,
-1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,1000,0,300,0,0,0,0,667,778,1000,330,0,0,0,0,
-0,722,0,0,0,278,0,0,278,500,722,556,0,0,750],
-'times-bolditalic': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-250,389,555,500,500,833,778,333,333,333,500,570,250,333,250,278,
-500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,
-832,667,667,667,722,667,667,722,778,389,500,667,611,889,722,722,
-611,722,667,556,611,722,667,889,667,611,611,333,278,333,570,500,
-333,500,500,444,500,444,333,500,556,278,278,500,278,778,556,500,
-500,500,389,389,278,556,444,667,500,444,389,348,220,348,570,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,389,500,500,167,500,500,500,500,278,500,500,333,333,556,556,
-0,500,500,500,250,0,500,350,333,500,500,500,1000,1000,0,500,
-0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,
-1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,944,0,266,0,0,0,0,611,722,944,300,0,0,0,0,
-0,722,0,0,0,278,0,0,278,500,722,500,0,0,750],
-'times-italic': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-250,333,420,500,500,833,778,333,333,333,500,675,250,333,250,278,
-500,500,500,500,500,500,500,500,500,500,333,333,675,675,675,500,
-920,611,611,667,722,611,611,722,722,333,444,667,556,833,667,722,
-611,722,611,500,556,722,611,833,611,556,556,389,278,389,422,500,
-333,500,500,444,500,444,278,500,500,278,278,444,278,722,500,500,
-500,500,389,389,278,500,444,667,444,444,389,400,275,400,541,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,389,500,500,167,500,500,500,500,214,556,500,333,333,500,500,
-0,500,500,500,250,0,523,350,333,556,556,500,889,1000,0,500,
-0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,
-889,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,889,0,276,0,0,0,0,556,722,944,310,0,0,0,0,
-0,667,0,0,0,278,0,0,278,500,667,500,0,0,750],
-'times-roman': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-250,333,408,500,500,833,778,333,333,333,500,564,250,333,250,278,
-500,500,500,500,500,500,500,500,500,500,278,278,564,564,564,444,
-921,722,667,667,722,611,556,722,722,333,389,722,611,889,722,722,
-556,722,667,556,611,722,722,944,722,722,611,333,278,333,469,500,
-333,444,500,444,500,444,333,500,500,278,278,500,278,778,500,500,
-500,500,333,389,278,500,500,722,500,500,444,480,200,480,541,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,333,500,500,167,500,500,500,500,180,444,500,333,333,556,556,
-0,500,500,500,250,0,453,350,333,444,444,500,1000,1000,0,444,
-0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,
-1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,889,0,276,0,0,0,0,611,722,889,310,0,0,0,0,
-0,667,0,0,0,278,0,0,278,500,722,500,0,0,750],
-'zapfdingbats': [
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-278,974,961,974,980,719,789,790,791,690,960,939,549,855,911,933,
-911,945,974,755,846,762,761,571,677,763,760,759,754,494,552,537,
-577,692,786,788,788,790,793,794,816,823,789,841,823,833,816,831,
-923,744,723,749,790,792,695,776,768,792,759,707,708,682,701,826,
-815,789,789,707,687,696,689,786,787,713,791,785,791,873,761,762,
-762,759,759,892,892,788,784,438,138,277,415,392,392,668,668,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,732,544,544,910,667,760,760,776,595,694,626,788,788,788,788,
-788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,
-788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,
-788,788,788,788,894,838,1016,458,748,924,748,918,927,928,928,834,
-873,828,924,924,917,930,931,463,883,836,836,867,867,696,696,874,
-0,874,760,946,771,865,771,888,967,888,831,873,927,970,234]
-}
-
-ascent_descent = {'Courier': (629, -157),
- 'Courier-Bold': (626, -142),
- 'Courier-BoldOblique': (626, -142),
- 'Courier-Oblique': (629, -157),
- 'Helvetica': (718, -207),
- 'Helvetica-Bold': (718, -207),
- 'Helvetica-BoldOblique': (718, -207),
- 'Helvetica-Oblique': (718, -207),
- 'Symbol': (0, 0),
- 'Times-Bold': (676, -205),
- 'Times-BoldItalic': (699, -205),
- 'Times-Italic': (683, -205),
- 'Times-Roman': (683, -217),
- 'ZapfDingbats': (0, 0)}
-
-
-def parseAFMfile(filename):
-	"""Returns an array holding the widths of all characters in the font.
-	Ultra-crude parser"""
-	alllines = open(filename, 'r').readlines()
-	# get stuff between StartCharMetrics and EndCharMetrics
-	metriclines = []
-	between = 0
-	for line in alllines:
-		if string.find(string.lower(line), 'endcharmetrics') > -1:
-			between = 0
-			break
-		if between:
-			metriclines.append(line)
-		if string.find(string.lower(line), 'startcharmetrics') > -1:
-			between = 1
-			
-	# break up - very shaky assumption about array size
-	widths = [0] * 255
-	
-	for line in metriclines:
-		chunks = string.split(line, ';')
-		
-		(c, cid) = string.split(chunks[0])
-		(wx, width) = string.split(chunks[1])
-		#(n, name) = string.split(chunks[2])
-		#(b, x1, y1, x2, y2) = string.split(chunks[3])
-		widths[string.atoi(cid)] = string.atoi(width)
-	
-	# by default, any empties should get the width of a space
-	for i in range(len(widths)):
-		if widths[i] == 0:
-			widths[i] == widths[32]
-
-	return widths
-
-
-class FontCache:
-	"""Loads and caches font width information on demand.  Font names
-	converted to lower case for indexing.  Public interface is stringwidth"""
-	def __init__(self):
-		global widths
-		self.__widtharrays = widths
-
-		
-	def loadfont(self, fontname):
-		filename = AFMDIR + os.sep + fontname + '.afm'
-		print 'cache loading',filename
-		assert os.path.exists(filename)
-		widths = parseAFMfile(filename)
-		self.__widtharrays[fontname] = widths
-	def getfont(self, fontname):
-		try:
-			return self.__widtharrays[fontname]
-		except:
-			try:
-				self.loadfont(fontname)
-				return self.__widtharrays[fontname]
-			except:
-				# font not found, use Courier
-				print 'Font',fontname,'not found - using Courier for widths'
-				return self.getfont('courier')
-	
-
-	def stringwidth(self, text, font):
-		widths = self.getfont(string.lower(font))
-		w = 0
-		for char in text:
-			w = w + widths[ord(char)]
-		return w
-	def status(self):
-		#returns loaded fonts
-		return self.__widtharrays.keys()
-		
-TheFontCache = FontCache()
-
-stringwidth = TheFontCache.stringwidth
-
-
-##############################################################
-#
-#			AI Document
-#
-##############################################################
-
-
-class AIDocument:
-	def __init__(self):
-		self.objects = []
-		self.info = AIHeader()  #hang onto it!
-		self.add(self.info)
-		self.transforms = AIStream()
-		
-		self.pages = []
-		self.pagepositions = []
-		self.infopos = 19
-		
-		# position 1
-#		cat = AICatalog()
-#		cat.RefPages = 3
-#		cat.RefOutlines = 2
-#		self.add(cat)
-	
-		# position 2 - outlines
-#		outl = AIOutline()
-#		self.add(outl)
-	
-		# position 3 - pages collection
-#		self.PageCol = AIPageCollection()
-#		self.add(self.PageCol)
-	
-		# positions 4-17 - fonts
-#		fonts = MakeType1Fonts()
-#		for font in fonts:
-#			self.add(font)
-#	
-#		self.fontdict = MakeFontDictionary(4, 15)
-		
-		# position 18 - Info
-
-
-				# testing here - position 19 - an image
-		self.add(AIProlog())
-		self.add(AISetUp())
-#		self.add(AIStream())
-#		self.add(AIImage())
-		self.add(self.transforms)
-
-	def add(self, obj):
-		self.objects.append(obj)
-		obj.doc = self
-
-	def setTitle(self, title):
-		"embeds in AI file"
-		self.info.title = title
-		
-	def setAuthor(self, author):
-		"embedded in AI file"
-		self.info.author = author
-			
-	def setBoundingBox(self, boundingbox):
-		"embeds in AI file"
-		self.transforms.originx = 0
-		self.transforms.originy = 0
-#		self.info.boundingBox = boundingbox
-		lx,ly, ux,uy, tx = boundingbox
-		print 'setBoundingBox', lx,ly, ux,uy, tx
-		print 'setBoundingBox', ux-lx,uy-ly
-		self.info.pagesize = (ux-lx), (uy-ly)
-		##XXX If the ArtSize is smaller than Letter Freehand always draws the 
-		##XXX origin as if the Art board was Letter sized, however the arboard 
-		##XXX is drawn at the same center as a letter sized artboard.
-		##XXX hence this translation 
-		if ux-lx < 612:
-			self.transforms.originx = w = (612 - (ux-lx))/2 +tx
-			lx, ux = lx + w, ux + w
-		if uy-ly < 792:
-			self.transforms.originy = w = (792 - (uy-ly))/2 +tx
-			ly, uy = ly + w, uy + w
-#		print self.transforms
-#		print self.transforms.originx
-#		print self.transforms.originy
-		print 'setBoundingBox', lx,ly, ux,uy
-		self.info.boundingBox = lx, ly, ux, uy
-		self.transforms.height = uy
-		
-	def setPage(self, page):
-#		print 'setPage', page
-		self.transforms.data = page
-		
-			
-	def SaveToFile(self, filename):
-#		print 'SaveToFile', self.transforms.originx, self.transforms.originy
-		f = open(filename, 'w')
-		old_output = sys.stdout
-		sys.stdout = f
-		self.printAI()
-		sys.stdout = old_output
-		f.close()
-	
-	def printXref(self):
-		self.startxref = sys.stdout.tell()
-		print 'xref'
-		print 0,len(self.objects) + 1
-		print '0000000000 65535 f'
-		for pos in self.xref:
-			print '%0.10d 00000 n' % pos
-	
-	def printTrailer(self):
-		print '''%%PageTrailer
-gsave annotatepage grestore showpage
-%%Trailer'''
-#		print '<< /Size %d /Root %d 0 R /Info %d 0 R>>' % (len(self.objects) + 1, 1, self.infopos)
-#		print 'startxref'
-#		print self.startxref
-
-	def printAI(self):
-		"prints it to standard output.  Logs positions for doing trailer"
-#		print "%AI-1.0"
-#		print "%����"
-		i = 1
-		self.xref = []
-#		print self.objects
-		for obj in self.objects:
-#			print 'printAI', obj
-#			pos = sys.stdout.tell()
-#			self.xref.append(pos)
-#			print i, '0 obj'
-			obj.printAI()
-#			print 'endobj'
-#			i = i + 1
-#		self.printXref()
-		self.printTrailer()
-		print "%%EOF",
-
-
-	def addPage(self, page):
-		"""adds page and stream at end.  Maintains pages list"""
-		#page.buildstream()
-		pos = len(self.objects) # work out where added
-		
-#		page.ParentPos = 3   #pages collection
-#		page.info = {'parentpos':3,
-#			'fontdict':self.fontdict,
-#			'contentspos':pos + 2}
-		
-#		self.PageCol.PageList.append(pos+1) 
-#		print 'addPage', self.transforms.setStream((10, 20, 'm')) 
-#		print 'addPage', self.transforms.setStream(page) 
-#		self.page = 
-		self.transforms.data = page
-#		print 'addPage', self.page
-#		self.objects.append(page)
-#		self.objects.append(self.page)
-
-
-
-##############################################################
-#
-#			Utilities
-#
-##############################################################
-
-class OutputGrabber:
-	"""At times we need to put something in the place of standard
-	output.  This grabs stdout, keeps the data, and releases stdout
-	when done.
-	
-	NOT working well enough!"""
-	def __init__(self):
-		self.oldoutput = sys.stdout
-		sys.stdout = self
-		self.closed = 0
-		self.data = []
-	def write(self, x):
-		if not self.closed:
-			self.data.append(x)
-	
-	def getData(self):
-		return string.join(self.data)
-
-	def close(self):
-		sys.stdout = self.oldoutput
-		self.closed = 1
-		
-	def __del__(self):
-		if not self.closed:
-			self.close()
-	
-				
-def testOutputGrabber():
-	gr = OutputGrabber()
-	for i in range(10):
-		print 'line',i
-	data = gr.getData()
-	gr.close()
-	print 'Data...',data
-	
-
-##############################################################
-#
-#			AI Object Hierarchy
-#
-##############################################################
-
-
-
-class AIObject:
-	"Base class for all AI objects"
-	def printAI(self):
-		print '% base AI object'
-	
-		
-class AILiteral(AIObject):
-	" a ready-made one you wish to quote"
-	def __init__(self, text):
-		self.text = text
-	def printAI(self):
-		print self.text
-
-
-
-class AICatalog(AIObject):
-	"requires RefPages and RefOutlines set"
-	def __init__(self):
-		self.template = '''<<
-/Type /Catalog
-/Pages %d 0 R
-/Outlines %d 0 R
->>'''
-	def printAI(self):
-		print self.template % (self.RefPages, self.RefOutlines)
-
-class AIHeader(AIObject):
-	# no features implemented yet
-	def __init__(self):
-		self.title = "untitled"
-		self.author = "anonymous"
-		self.boundingBox = (0, 0, 565, 842)
-		self.pagesize = (565, 842)
-		self.rulerUnits	= 2	
-		now = time.localtime(time.time())
-		self.datestr = time.strftime("%x %I:%M %p", now)
-				
-	def printAI(self):
-		print "%!PS-Adobe-3.0"
-		print "%%Creator: PIDDLE Adobe Illustrator backend"
-		print "%%Title: " +'(%s)' % self.title
-		print "%%For: " +'(%s)' % self.author
-		print "%%CreationDate: " +'(%s)' % self.datestr
-		print "%%DocumentProcessColors: Black"""
-		print '%%BoundingBox: ' + '%s %s %s %s' % self.boundingBox
-		#%%DocumentProcessColors: Cyan Magenta Yellow
-		#%%DocumentCustomColors: (PANTONE 156 CV)
-		#%%RGBCustomColor: red green blue (customcolorname)
-		#%%DocumentFonts: CooperBlack
-		#%%+ Minion-Regular
-		#%%DocumentFiles: WrathOfRalph
-		print "%AI5_FileFormat 3"
-		print "%AI3_ColorUsage: Color"
-		print '%AI5_ArtSize: ' + '%s %s' % self.pagesize
-		print '%AI5_Templatebox: ' + '%s %s' % self.pagesize
-		#%AI7_ImageSettings: flag
-		print '%AI5_TargetResolution: 300'
-		print '%%EndComments'		
-
-
-class AIProlog(AIObject):
-	"null outline, does nothing yet"
-	def __init__(self):
-		self.FontList = []
-	def printAI(self):
-		print '%%BeginProlog'
-		print '%%EndProlog'
-
-class AISetUp(AIObject):
-	"null outline, does nothing yet"
-	def __init__(self):
-		self.FontList = []
-	def printAI(self):
-		print '%%BeginSetup'
-		if self.FontList:
-			pass
-		print '%%EndSetup'
-	
-class AIPageCollection(AIObject):
-	"presumes PageList attribute set (list of integers)"
-	def __init__(self):
-		self.PageList = []
-	def printAI(self):
-		result = '<<\n/Type /Pages\n/Count %d\n/Kids [' % len(self.PageList)
-		for page in self.PageList:
-			result = result + str(page) + ' 0 R '
-		result = result + ']\n>>'
-		print result
-
-#class AIBody(AIObject):
-#	"""The Bastard.  Needs list of Resources etc. Use a standard one for now.
-#	It manages a AIStream object which must be added to the document's list
-#	os objects as well."""
-#	def __init__(self):
-#		self.drawables = []
-#		self.stream = AIStream()
-#		self.hasImages = 0
-#		self.template = """<<
-#/Type /Page
-#/Parent %(parentpos)d 0 R
-#/Resources 
-#	<< 
-#	/Font %(fontdict)s
-#	/ProcSet %(procsettext)s 
-#	>>
-#/MediaBox [0 0 595 842]
-#/Contents %(contentspos)d 0 R
-#>>"""
-#	def printAI(self):
-#		# check for image support
-##		if self.hasImages:
-##			self.info['procsettext'] = '[/AI /Text /ImageC]'
-##		else:
-##			self.info['procsettext'] = '[/AI /Text]'
-##			
-##		print self.template % self.info
-#		print "AIBody.printAI(self)"
-#
-#	def clear(self):
-#		self.drawables = []
-#	
-#	def setStream(self, data):
-#		self.stream.setStream(data)
-#		
-#
-#	#def add(self, drawable):
-#	#	self.drawables.append(drawable)
-#	#
-#	#def buildstream(self):
-#	#	textmode = 0
-#	#	
-#	#	oldout = sys.stdout
-#	#	fn = tempfile.mktemp()
-#	#	#f = open(fn, 'wb')  #AR 19980202
-#	#	f = open(fn, 'w')
-#	#	sys.stdout = f
-#	#	text = 0
-#	#	for obj in self.drawables:
-#	#		if obj.isText():
-#	#			if text == 0:
-#	#				print 'BT'
-#	#				text = 1
-#	#		else:
-#	#			if text == 1:
-#	#				print 'ET'
-#	#				text = 0
-#	#				
-#	#		obj.printAI()
-#	#	if self.drawables[-1].isText():
-#	#		print 'ET'
-#	#	sys.stdout = oldout
-#	#	f.close()
-#	#		
-#	#	self.stream = AIStream()
-#	#	#self.stream.data = open(fn,'r').read()
-#	#	self.stream.data = open(fn,'rb').read()	#AR 19980202
-#
-#		
-#		
-#		
-TestStream = ['q', (72, 720, 'l'), 'S', 'Q', (80, 672, 'm'), (24, 'TL'),
-(('Test Page with no stream'), 'Tj', 'T*')]
-
-
-class AIStream(AIObject):
-	"Used for the contents of a page"
-	def __init__(self):
-		self.data = []
-		self.originx = 0
-		self.originy = 0
-		self.height = 0
-	
-#	def setStream(self, data):
-#		self.data = data
-#		print 'setStream', self.originy, self.originy
-#		print self.data
-		
-	def printAI(self):
-		# test code is useful
-		if self.data == None:
-			self.data = TestStream
-		# the AI length key should contain the length including
-		# any extra LF pairs added by Print on DOS.
-		
-#		lines = len(string.split(self.data,'\n'))
-#		length = len(self.data) + lines   # one extra LF each
-		#length = len(self.data)	#AR 19980202
-#		print 'printAI', self.originx, self.originy
-#		print 'printAI', self.transformAI(self.originx, self.originy, self.height)
-
-			
-#		print '<< /Length %d >>' % length
-		print '''%AI5_BeginLayer
-1 1 1 1 0 0 0 79 128 255 Lb
-(Foreground) Ln'''
-		print self.transformAI(self.originx, self.originy, self.height)
-
-#		print 'XXXX', self.data
-		print '''LB
-%AI5_EndLayer--'''
-
-	def transformAI(self, ox, oy, ty):
-#		print 'transformAI', ox, oy
-#		print 'transformAI', self.data, type(self.data)
-		page = []
-		for line in self.data:
-#			print line
-			if type(line) == TupleType and  len(line) == 3:
-#				print 'x', line
-				line =  line[0]+ ox,  line[1]+oy, line[2]
-				line =  line[0],  oy + ty -line[1], line[2]
-				line = '%f %f %s' % line
-			elif type(line) == TupleType and  len(line) == 7:
-				line =  line[0]+ox,  line[1]+oy, line[2]+ox,  line[3]+oy, line[4]+ox,  line[5]+oy, line[6]
-				line =  line[0],  oy+ty-line[1], line[2],  oy+ty-line[3], line[4],  oy+ty-line[5], line[6]
-				line = '%f %f %f %f %f %f %s' % line
-#			print line
-			page.append(line)
-		return string.join(page, '\n')
-			
-class AIImage(AIObject):
-	def printAI(self):
-		print """<<
-/Type /XObject
-/Subtype /Image
-/Name /Im0
-/Width 24
-/Height 23
-/BitsPerComponent 1
-/ColorSpace /DeviceGray
-/Filter /ASCIIHexDecode
-/Length 174
->>
-stream
-003B00 002700 002480 0E4940 114920 14B220 3CB650
-75FE88 17FF8C 175F14 1C07E2 3803C4 703182 F8EDFC
-B2BBC2 BB6F84 31BFC2 18EA3C 0E3E00 07FC00 03F800
-1E1800 1FF800>
-endstream
-endobj"""
-			
-class AIType1Font(AIObject):
-	def __init__(self, key, font):
-		self.fontname = font
-		self.keyname = key
-		self.template = """<<
-/Type /Font
-/Subtype /Type1
-/Name /%s
-/BaseFont /%s
-/Encoding /WinAnsiEncoding
->>"""
-	def printAI(self):
-		print self.template % (self.keyname, self.fontname)
-
-class AIProcSet(AIObject):
-	def printAI(self):
-		print "[/AI /Text]"
-
-
-
-
-
-
-
-##############################################################
-#
-#			some helpers
-#
-##############################################################
-
-def MakeType1Fonts():
-	"returns a list of all the standard font objects"
-	fonts = []
-	pos = 1
-	for fontname in StandardEnglishFonts:
-		font = AIType1Font('F'+str(pos), fontname)
-		fonts.append(font)
-		pos = pos + 1
-	return fonts
-
-def MakeFontDictionary(startpos, count):
-	"returns a font dictionary assuming they are all in the file from startpos"	
-	dict = "		<< \n"
-	pos = startpos
-	for i in range(count-1):
-		dict = dict + '\t\t/F%d %d 0 R \n' % (i + 1, startpos + i)
-	dict = dict + "		>>\n"
-	return dict
-	
-
-#if __name__ == '__main__':
-#	print 'For test scripts, run test1.py to test7.py'
\ No newline at end of file
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/discipline.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/discipline.py
--- bkchem-0.12.5/bkchem/plugins/piddle/discipline.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/discipline.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,57 +0,0 @@
-from types import *
-from piddle import *
-
-def checkMethods(parentMethod, childMethod):
-	"Make sure the child's method obey's the parent's interface; return 1 if OK."
-	# get the parameter names
-	pf = parentMethod.func_code
-	cf = childMethod.func_code
-	pargs = pf.co_varnames[:pf.co_argcount]
-	cargs = cf.co_varnames[:cf.co_argcount]
-	
-	# make sure they match, at least as far as the parent's go
-	if len(cargs) < len(pargs):
-		print "too few args"
-		return 0	
-	for i in range(len(pargs)):
-		if pargs[i] != cargs[i]:
-			print "arg names don't match"
-			return 0
-
-	# if child has any additional arguments, make sure
-	# they have default values
-	extras = len(cargs) - len(pargs)
-	defs = childMethod.func_defaults
-	if extras and (defs is None or len(defs) < extras):
-		print "need %s defaults, got %s" % (extras, defs)
-		print cargs
-		print pargs
-		return 0
-	
-	# otherwise, it's OK
-	return 1
-	
-def checkClasses(parent, child):
-	"Make sure the child class obeys the parent's interface."
-	
-	parentDir = dir(parent)
-	childDir = dir(child)			
-	for name in childDir:
-		item = getattr(child, name)
-		if type(item) != MethodType or name[0] == '_':
-			pass  # print "     %s is not a public method" % name
-		elif name in parentDir:
-			if not checkMethods(getattr(parent, name).im_func, item.im_func):
-				print "NAUGHTY CHILD disobeys arguments to", name
-			else:
-				print "     %s looks OK" % name
-		else:
-			print "     %s is unique to the child" % name
-
-foo = raw_input("backend to check (e.g., PDF):")
-if foo:
-	canvasname = foo+"Canvas"
-	module = __import__("piddle"+foo, globals(), locals(), [canvasname] )
-	child = getattr(module, canvasname)
-	print "\nChecking %s...\n" % canvasname
-	checkClasses( Canvas, child )
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/__init__.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/__init__.py
--- bkchem-0.12.5/bkchem/plugins/piddle/__init__.py	2004-11-15 09:12:21.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/__init__.py	1969-12-31 21:00:00.000000000 -0300
@@ -1 +0,0 @@
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/pagesizes.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pagesizes.py
--- bkchem-0.12.5/bkchem/plugins/piddle/pagesizes.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pagesizes.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,30 +0,0 @@
-"""pagesizes.py
-
-This module defines a few common page sizes in PIDDLE units (1/72 inch).
-"""
-
-from piddle import cm, inch
-
-_W, _H = (21*cm, 29.7*cm)
-
-A6 = (_W*.5, _H*.5)
-A5 = (_H*.5, _W)
-A4 = (_W, _H)
-A3 = (_H*2, _W)
-A2 = (_W*2, _H*2)
-A1 = (_H*4, _W*2)
-A0 = (_W*4, _H*4)
-
-letter = (8.5*inch, 11*inch)
-legal = (8.5*inch, 17*inch)
-elevenSeventeen = (11*inch, 17*inch)
-
-_BW, _BH = (25*cm, 35.3*cm)
-B6 = (_BW*.5, _BH*.5)
-B5 = (_BH*.5, _BW)
-B4 = (_BW, _BH)
-B3 = (_BH*2, _BW)
-B2 = (_BW*2, _BH*2)
-B1 = (_BH*4, _BW*2)
-B0 = (_BW*4, _BH*4)
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/pdfdoc.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfdoc.py
--- bkchem-0.12.5/bkchem/plugins/piddle/pdfdoc.py	2004-11-16 10:16:50.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfdoc.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,566 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-#pdfdoc.py
-""" 
-PDFgen is a library to generate PDF files containing text and graphics.  It is the 
-foundation for a complete reporting solution in Python.  
-
-The module pdfdoc.py handles the 'outer structure' of PDF documents, ensuring that
-all objects are properly cross-referenced and indexed to the nearest byte.  The 
-'inner structure' - the page descriptions - are presumed to be generated before 
-each page is saved.
-pdfgen.py calls this and provides a 'canvas' object to handle page marking operators.
-piddlePDF calls pdfgen and offers a high-level interface.
-
-(C) Copyright Andy Robinson 1998-1999
-"""
-
-import os
-import sys
-import string
-import time
-import tempfile
-import cStringIO
-from types import *
-from math import sin, cos, pi, ceil
-
-Log = sys.stderr  # reassign this if you don't want err output to console
-
-try:
-    import zlib
-except:
-    Log.write("zlib not available, page compression not available\n")
-
-
-from pdfgeom import bezierArc
-
-import pdfutils
-from pdfutils import LINEEND   # this constant needed in both
-import pdfmetrics
-
-##############################################################
-#
-#            Constants and declarations
-#
-##############################################################
-
-
-
-StandardEnglishFonts = [
-    'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique',  
-    'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 
-    'Helvetica-BoldOblique',
-    'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic',
-    'Symbol','ZapfDingbats']
-
-PDFError = 'PDFError'
-AFMDIR = '.'
-
-A4 = (595.27,841.89)   #default page size
-
-class PDFDocument:
-    """Responsible for linking and writing out the whole document.
-    Builds up a list of objects using add(key, object).  Each of these
-    must inherit from PDFObject and be able to write itself into the file.
-    For cross-linking, it provides getPosition(key) which tells you where
-    another object is, or raises a KeyError if not found.  The rule is that
-    objects should only refer ones previously written to file.
-    """
-    def __init__(self):
-        self.objects = []
-        self.objectPositions = {}
-        
-        self.fonts = MakeType1Fonts()
-
-        #mapping of Postscriptfont names to internal ones;
-        #needs to be dynamically built once we start adding
-        #fonts in.
-        self.fontMapping = {}
-        for i in range(len(StandardEnglishFonts)):
-            psname = StandardEnglishFonts[i]
-            pdfname = '/F%d' % (i+1)
-            self.fontMapping[psname] = pdfname
-        
-            
-        self.pages = []
-        self.pagepositions = []
-        
-        # position 1
-        cat = PDFCatalog()
-        cat.RefPages = 3
-        cat.RefOutlines = 2
-        self.add('Catalog', cat)
-    
-        # position 2 - outlines
-        outl = PDFOutline()
-        self.add('Outline', outl)
-    
-        # position 3 - pages collection
-        self.PageCol = PDFPageCollection()
-        self.add('PagesTreeRoot',self.PageCol)
-    
-        # positions 4-16 - fonts
-        fontstartpos = len(self.objects) + 1
-        for font in self.fonts:
-            self.add('Font.'+font.keyname, font)
-        self.fontdict = MakeFontDictionary(fontstartpos, len(self.fonts))
-        
-        # position 17 - Info
-        self.info = PDFInfo()  #hang onto it!
-        self.add('Info', self.info)
-        self.infopos = len(self.objects)  #1-based, this gives its position
-    
-    
-    def add(self, key, obj):
-        self.objectPositions[key] = len(self.objects)  # its position
-        self.objects.append(obj)
-        obj.doc = self
-        return len(self.objects) - 1  # give its position
-
-    def getPosition(self, key):
-        """Tell you where the given object is in the file - used for
-        cross-linking; an object can call self.doc.getPosition("Page001")
-        to find out where the object keyed under "Page001" is stored."""
-        return self.objectPositions[key]
-    
-    def setTitle(self, title):
-        "embeds in PDF file"
-        self.info.title = title
-        
-    def setAuthor(self, author):
-        "embedded in PDF file"
-        self.info.author = author
-            
-    def setSubject(self, subject):
-        "embeds in PDF file"
-        self.info.subject = subject
-            
-    
-    def printXref(self):
-        self.startxref = sys.stdout.tell()
-        Log.write('xref\n')
-        Log.write("%s %s" % (0,len(self.objects) + 1) )
-        Log.write('0000000000 65535 f')
-        for pos in self.xref:
-            Log.write( '%0.10d 00000 n\n' % pos)
-
-    def writeXref(self, f):
-        self.startxref = f.tell()
-        f.write('xref' + LINEEND)
-        f.write('0 %d' % (len(self.objects) + 1) + LINEEND)
-        f.write('0000000000 65535 f' + LINEEND)
-        for pos in self.xref:
-            f.write('%0.10d 00000 n' % pos + LINEEND)
-
-    
-    def printTrailer(self):
-        print 'trailer'
-        print '<< /Size %d /Root %d 0 R /Info %d 0 R>>' % (len(self.objects) + 1, 1, self.infopos)
-        print 'startxref'
-        print self.startxref
-
-    def writeTrailer(self, f):
-        f.write('trailer' + LINEEND)
-        f.write('<< /Size %d /Root %d 0 R /Info %d 0 R>>' % (len(self.objects) + 1, 1, self.infopos)  + LINEEND)
-        f.write('startxref' + LINEEND)
-        f.write(str(self.startxref)  + LINEEND)
-
-    def SaveToFile(self, filename):
-        fileobj = open(filename, 'wb')
-        self.SaveToFileObject(fileobj)
-        fileobj.close()
-
-    def SaveToFileObject(self, fileobj):
-        """Open a file, and ask each object in turn to write itself to
-        the file.  Keep track of the file position at each point for
-        use in the index at the end"""
-        f = fileobj
-        i = 1
-        self.xref = []
-        f.write("%PDF-1.2" + LINEEND)  # for CID support
-        f.write("%�춾" + LINEEND)
-        for obj in self.objects:
-            pos = f.tell()
-            self.xref.append(pos)
-            f.write(str(i) + ' 0 obj' + LINEEND)
-            obj.save(f)
-            f.write('endobj' + LINEEND)
-            i = i + 1
-        self.writeXref(f)
-        self.writeTrailer(f)
-        f.write('%%EOF')  # no lineend needed on this one!
-        
-        # with the Mac, we need to tag the file in a special
-        #way so the system knows it is a PDF file.
-        #This supplied by Joe Strout
-        if os.name == 'mac':
-            import macfs
-            try: 
-                macfs.FSSpec(filename).SetCreatorType('CARO','PDF ')
-            except:
-                pass
-
-
-    def printPDF(self):
-        "prints it to standard output.  Logs positions for doing trailer"
-        print "%PDF-1.0"
-        print "%�춾"
-        i = 1
-        self.xref = []
-        for obj in self.objects:
-            pos = sys.stdout.tell()
-            self.xref.append(pos)
-            print i, '0 obj'
-            obj.printPDF()
-            print 'endobj'
-            i = i + 1
-        self.printXref()
-        self.printTrailer()
-        print "%%EOF",
-
-    def addPage(self, page):
-        """adds page and stream at end.  Maintains pages list"""
-        #page.buildstream()
-        pos = len(self.objects) # work out where added
-        
-        page.ParentPos = 3   #pages collection
-        page.info = {
-            'parentpos':3,
-            'fontdict':self.fontdict,
-            'contentspos':pos + 2,
-            }
-        
-        self.PageCol.PageList.append(pos+1)  
-        self.add('Page%06d'% len(self.PageCol.PageList), page)
-        #self.objects.append(page)
-        self.add('PageStream%06d'% len(self.PageCol.PageList), page.stream)
-        #self.objects.append(page.stream)
-
-    def hasFont(self, psfontname):
-        return self.fontMapping.has_key(psfontname)
-
-    def getInternalFontName(self, psfontname):
-        try:
-            return self.fontMapping[psfontname]
-        except:
-            raise PDFError, "Font %s not available in document" % psfontname
-
-    def getAvailableFonts(self):
-        fontnames = self.fontMapping.keys()
-        fontnames.sort()
-        return fontnames
-    
-##############################################################
-#
-#            Utilities
-#
-##############################################################
-
-class OutputGrabber:
-    """At times we need to put something in the place of standard
-    output.  This grabs stdout, keeps the data, and releases stdout
-    when done.
-    
-    NOT working well enough!"""
-    def __init__(self):
-        self.oldoutput = sys.stdout
-        sys.stdout = self
-        self.closed = 0
-        self.data = []
-    def write(self, x):
-        if not self.closed:
-            self.data.append(x)
-    
-    def getData(self):
-        return string.join(self.data)
-
-    def close(self):
-        sys.stdout = self.oldoutput
-        self.closed = 1
-        
-    def __del__(self):
-        if not self.closed:
-            self.close()
-    
-                
-def testOutputGrabber():
-    gr = OutputGrabber()
-    for i in range(10):
-        print 'line',i
-    data = gr.getData()
-    gr.close()
-    print 'Data...',data
-    
-
-##############################################################
-#
-#            PDF Object Hierarchy
-#
-##############################################################
-
-
-
-class PDFObject:
-    """Base class for all PDF objects.  In PDF, precise measurement
-    of file offsets is essential, so the usual trick of just printing
-    and redirecting output has proved to give different behaviour on
-    Mac and Windows.  While it might be soluble, I'm taking charge
-    of line ends at the binary level and explicitly writing to a file.
-    The LINEEND constant lets me try CR, LF and CRLF easily to help
-    pin down the problem."""
-    def save(self, file):
-        "Save its content to an open file"
-        file.write('% base PDF object' + LINEEND)
-    def printPDF(self):
-        self.save(sys.stdout)
-    
-
-class PDFLiteral(PDFObject):
-    " a ready-made one you wish to quote"
-    def __init__(self, text):
-        self.text = text
-    def save(self, file):
-        file.write(self.text + LINEEND)
-
-
-
-class PDFCatalog(PDFObject):
-    "requires RefPages and RefOutlines set"
-    def __init__(self):
-        self.template = string.join([
-                        '<<',
-                        '/Type /Catalog',
-                        '/Pages %d 0 R',
-                        '/Outlines %d 0 R',
-                        '>>'
-                        ],LINEEND
-                        )
-    def save(self, file):
-        file.write(self.template % (self.RefPages, self.RefOutlines) + LINEEND)
-
-
-class PDFInfo(PDFObject):
-    """PDF documents can have basic information embedded, viewable from
-    File | Document Info in Acrobat Reader.  If this is wrong, you get
-    Postscript errors while printing, even though it does not print."""
-    def __init__(self):
-        self.title = "untitled"
-        self.author = "anonymous"
-        self.subject = "unspecified"
-
-        now = time.localtime(time.time())
-        self.datestr = '%04d%02d%02d%02d%02d%02d' % tuple(now[0:6])
-
-    def save(self, file):
-        file.write(string.join([
-                "<</Title (%s)",
-                "/Author (%s)",
-                "/CreationDate (D:%s)",
-                "/Producer (PDFgen)",
-                "/Subject (%s)",
-                ">>"
-                ], LINEEND
-            ) % ( 
-    pdfutils._escape(self.title), 
-    pdfutils._escape(self.author), 
-    self.datestr, 
-    pdfutils._escape(self.subject)
-    ) + LINEEND)
-    
-
-
-class PDFOutline(PDFObject):
-    "null outline, does nothing yet"
-    def __init__(self):
-        self.template = string.join([
-                '<<',
-                '/Type /Outlines',
-                '/Count 0',
-                '>>'],
-                LINEEND)
-    def save(self, file):
-        file.write(self.template + LINEEND)
-
-
-class PDFPageCollection(PDFObject):
-    "presumes PageList attribute set (list of integers)"
-    def __init__(self):
-        self.PageList = []
-
-    def save(self, file):
-        lines = [ '<<',
-                '/Type /Pages',
-                '/Count %d' % len(self.PageList),
-                '/Kids ['
-                ]
-        for page in self.PageList:
-            lines.append(str(page) + ' 0 R ')
-        lines.append(']')
-        lines.append('>>')
-        text = string.join(lines, LINEEND)
-        file.write(text + LINEEND)
-
-
-class PDFPage(PDFObject):
-    """The Bastard.  Needs list of Resources etc. Use a standard one for now.
-    It manages a PDFStream object which must be added to the document's list
-    of objects as well."""
-    def __init__(self):
-        self.drawables = []
-        self.pagewidth = 595  #these are overridden by piddlePDF
-        self.pageheight = 842
-        self.stream = PDFStream()
-        self.hasImages = 0
-        self.pageTransitionString = ''  # presentation effects
-        # editors on different systems may put different things in the line end
-        # without me noticing.  No triple-quoted strings allowed!
-        self.template = string.join([
-                '<<',
-                '/Type /Page',
-                '/Parent %(parentpos)d 0 R',
-                '/Resources',
-                '   <<',
-                '   /Font %(fontdict)s',
-                '   /ProcSet %(procsettext)s',
-                '   >>',
-                '/MediaBox [0 0 %(pagewidth)d %(pageheight)d]',  #A4 by default
-                '/Contents %(contentspos)d 0 R',
-                '%(transitionString)s',
-                '>>'],
-            LINEEND)
-    def setCompression(self, onoff=0):
-        "Turns page compression on or off"
-        assert onoff in [0,1], "Page compression options are 1=on, 2=off"
-        self.stream.compression = onoff
-        
-    def save(self, file):
-        self.info['pagewidth'] = self.pagewidth
-        self.info['pageheight'] = self.pageheight
-        # check for image support
-        if self.hasImages:
-            self.info['procsettext'] = '[/PDF /Text /ImageC]'
-        else:
-            self.info['procsettext'] = '[/PDF /Text]'
-        self.info['transitionString'] = self.pageTransitionString
-
-        file.write(self.template % self.info + LINEEND)
-
-    def clear(self):
-        self.drawables = []
-    
-    def setStream(self, data):
-        if type(data) is ListType:
-            data = string.join(data, LINEEND)
-        self.stream.setStream(data)
-
-TestStream = "BT /F6 24 Tf 80 672 Td 24 TL (   ) Tj T* ET"
-
-
-class PDFStream(PDFObject):
-    "Used for the contents of a page"
-    def __init__(self):
-        self.data = None
-        self.compression = 0
-
-    def setStream(self, data):
-        self.data = data
-
-    def save(self, file):
-        #avoid crashes if they wrote nothing in the page
-        if self.data == None:
-             self.data = TestStream
-
-        if self.compression == 1:
-            comp = zlib.compress(self.data)   #this bit is very fast...
-            base85 = pdfutils._AsciiBase85Encode(comp) #...sadly this isn't
-            wrapped = pdfutils._wrap(base85)
-            data_to_write = wrapped
-        else:
-            data_to_write = self.data
-        # the PDF length key should contain the length including
-        # any extra LF pairs added by Print on DOS.
-        
-        #lines = len(string.split(self.data,'\n'))
-        #length = len(self.data) + lines   # one extra LF each
-        length = len(data_to_write) + len(LINEEND)    #AR 19980202
-        if self.compression:
-            file.write('<< /Length %d /Filter [/ASCII85Decode /FlateDecode]>>' % length + LINEEND)
-        else:
-            file.write('<< /Length %d >>' % length + LINEEND)
-        file.write('stream' + LINEEND)
-        file.write(data_to_write + LINEEND)
-        file.write('endstream' + LINEEND)
-
-class PDFImage(PDFObject):
-    # sample one while developing.  Currently, images go in a literals
-    def save(self, file):
-        file.write(string.join([
-                '<<',
-                '/Type /XObject',
-                '/Subtype /Image',
-                '/Name /Im0',
-                '/Width 24',
-                '/Height 23',
-                '/BitsPerComponent 1',
-                '/ColorSpace /DeviceGray',
-                '/Filter /ASCIIHexDecode',
-                '/Length 174',
-                '>>',
-                'stream',
-                '003B00 002700 002480 0E4940 114920 14B220 3CB650',
-                '75FE88 17FF8C 175F14 1C07E2 3803C4 703182 F8EDFC',
-                'B2BBC2 BB6F84 31BFC2 18EA3C 0E3E00 07FC00 03F800',
-                '1E1800 1FF800>',
-                'endstream',
-                'endobj'
-                ], LINEEND) + LINEEND)
-
-class PDFType1Font(PDFObject):
-    def __init__(self, key, font):
-        self.fontname = font
-        self.keyname = key
-        self.template = string.join([
-                    '<<',
-                    '/Type /Font',
-                    '/Subtype /Type1',
-                    '/Name /%s',
-                    '/BaseFont /%s',
-                    '/Encoding /MacRomanEncoding',
-                    '>>'],
-                    LINEEND)
-    def save(self, file):
-        file.write(self.template % (self.keyname, self.fontname) + LINEEND)
-
-
-       
-
-
-
-
-
-##############################################################
-#
-#            some helpers
-#
-##############################################################
-
-def MakeType1Fonts():
-    "returns a list of all the standard font objects"
-    fonts = []
-    pos = 1
-    for fontname in StandardEnglishFonts:
-        font = PDFType1Font('F'+str(pos), fontname)
-        fonts.append(font)
-        pos = pos + 1
-    return fonts
-
-def MakeFontDictionary(startpos, count):
-    "returns a font dictionary assuming they are all in the file from startpos"    
-    dict = "  <<" + LINEEND
-    pos = startpos
-    for i in range(count):
-        dict = dict + '\t\t/F%d %d 0 R ' % (i + 1, startpos + i) + LINEEND
-    dict = dict + "\t\t>>" + LINEEND
-    return dict
-        
-if __name__ == '__main__':
-    print 'For test scripts, run test1.py to test6.py'
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/pdfgen.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfgen.py
--- bkchem-0.12.5/bkchem/plugins/piddle/pdfgen.py	2004-11-16 10:17:15.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfgen.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,1059 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-#pdfgen.py
-""" 
-PDFgen is a library to generate PDF files containing text and graphics.  It is the 
-foundation for a complete reporting solution in Python.  It is also the
-foundation for piddlePDF, the PDF back end for PIDDLE.
-
-Documentation is a little slim right now; run then look at testpdfgen.py
-to get a clue.
-
----------- Licence Terms (same as the Python license) -----------------
-(C) Copyright Robinson Analytics 1998-1999.
-
-Permission to use, copy, modify, and distribute this software and its
-documentation for any purpose and without fee is hereby granted, provided
-that the above copyright notice appear in all copies and that both that
-copyright notice and this permission notice appear in supporting
-documentation, and that the name of Robinson Analytics not be used
-in advertising or publicity pertaining to distribution of the software
-without specific, written prior permission. 
-
-ROBINSON ANALYTICS LTD. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
-SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
-IN NO EVENT SHALL ROBINSON ANALYTICS BE LIABLE FOR ANY SPECIAL, INDIRECT
-OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-PERFORMANCE OF THIS SOFTWARE. 
-
-Progress Reports:
-0.82, 1999-10-27, AR:
-        Fixed some bugs on printing to Postscript.  Added 'Text Object'
-        analogous to Path Object to control entry and exit from text mode.
-        Much simpler clipping API.  All verified to export Postscript and
-        redistill.
-        One limitation still - clipping to text paths is fine in Acrobat
-        but not in Postscript (any level)
-        
-0.81,1999-10-13, AR:
-        Adding RoundRect; changed all format strings to use %0.2f instead of %s,
-        so we don't get exponentials in the output.
-0.8,1999-10-07, AR:  all changed!
-
-2000-02-07: changed all %0.2f's to %0.4f in order to allow precise ploting of graphs that
-        range between 0 and 1 -cwl
-
-"""
-##  0.81    1999-10-13:
-##                
-##
-##
-import os
-import sys
-import string
-import time
-import tempfile
-import cStringIO
-from types import *
-from math import sin, cos, tan, pi, ceil
-
-import pdfutils
-import pdfdoc
-import pdfmetrics
-import pdfgeom
-
-# Robert Kern
-# Constants for closing paths.
-# May be useful if one changes 'arc' and 'rect' to take a
-# default argument that tells how to close the path.
-# That way we can draw filled shapes.
-
-FILL_EVEN_ODD = 0
-FILL_NON_ZERO = 1
-    #this is used by path-closing routines.
-    #map stroke, fill, fillmode -> operator
-    # fillmode: 1 = non-Zero (obviously), 0 = evenOdd
-PATH_OPS = {(0, 0, FILL_EVEN_ODD) : 'n',  #no op
-            (0, 0, FILL_NON_ZERO) : 'n',  #no op
-            (1, 0, FILL_EVEN_ODD) : 'S',  #stroke only
-            (1, 0, FILL_NON_ZERO) : 'S',  #stroke only
-            (0, 1, FILL_EVEN_ODD) : 'f*',  #Fill only
-            (0, 1, FILL_NON_ZERO) : 'f',  #Fill only
-            (1, 1, FILL_EVEN_ODD) : 'B*',  #Stroke and Fill
-            (1, 1, FILL_NON_ZERO) : 'B',  #Stroke and Fill
-            }
-
-close = 'h'
-newpath = 'n'
-stroke = 'S'
-closeStroke = 's'
-nzFill = 'f'
-eoFill = 'f*'
-fillStroke = 'B'
-closeFillStroke = 'b'
-eoFillStroke = 'B*'
-closeEoFillStroke = 'b*'
-
-class Canvas:
-    """This is a low-level interface to the PDF file format.  The plan is to
-    expose the whole pdfgen API through this.  Its drawing functions should have a
-    one-to-one correspondence with PDF functionality.  Unlike PIDDLE, it thinks
-    in terms of RGB values, Postscript font names, paths, and a 'current graphics
-    state'.  Just started development at 5/9/99, not in use yet.
-
-    """
-    def __init__(self,filename,pagesize=(595.27,841.89), bottomup = 1):
-        """Most of the attributes are private - we will use set/get methods
-        as the preferred interface.  Default page size is A4."""
-        self._filename = filename
-        self._doc = pdfdoc.PDFDocument()
-        self._pagesize = pagesize
-        self._currentPageHasImages = 1
-        self._pageTransitionString = ''
-
-        self._pageCompression = 1  #on by default - turn off when debugging!
-        self._pageNumber = 1   # keep a count
-        self._code = []    #where the current page's marking operators accumulate
-        
-        #PostScript has the origin at bottom left. It is easy to achieve a top-
-        #down coord system by translating to the top of the page and setting y
-        #scale to -1, but then text is inverted.  So self.bottomup is used
-        #to also set the text matrix accordingly.  You can now choose your
-        #drawing coordinates.
-        self.bottomup = bottomup
-        if self.bottomup:
-            #set initial font
-            #self._preamble = 'BT /F9 12 Tf 14.4 TL ET'
-            self._preamble = '1 0 0 1 0 0 cm BT /F9 12 Tf 14.4 TL ET'
-        else:
-            #switch coordinates, flip text and set font
-            #self._preamble = '1 0 0 -1 0 %0.4f cm BT /F9 12 Tf 14.4 TL ET' % self._pagesize[1]
-            self._preamble = '1 0 0 -1 0 %0.4f cm BT /F9 12 Tf 14.4 TL ET' % self._pagesize[1]
-
-        #initial graphics state
-        self._x = 0
-        self._y = 0
-        self._fontname = 'Times-Roman'
-        self._fontsize = 12
-        self._textMode = 0  #track if between BT/ET
-        self._leading = 14.4
-        self._currentMatrix = (1., 0., 0., 1., 0., 0.)
-        self._fillMode = 0   #even-odd
-        
-        #text state        
-        self._charSpace = 0
-        self._wordSpace = 0
-        self._horizScale = 100
-        self._textRenderMode = 0
-        self._rise = 0
-        self._textLineMatrix = (1., 0., 0., 1., 0., 0.)
-        self._textMatrix = (1., 0., 0., 1., 0., 0.)
-
-        # line drawing        
-        self._lineCap = 0
-        self._lineJoin = 0
-        self._lineDash = None  #not done
-        self._lineWidth = 0
-        self._mitreLimit = 0
-
-        self._fillColorRGB = (0,0,0)
-        self._strokeColorRGB = (0,0,0)
-
-    def _escape(self, s):
-        """PDF escapes are like Python ones, but brackets need slashes before them too.
-        Use Python's repr function and chop off the quotes first"""
-        s = repr(s)[1:-1]
-        s = string.replace(s, '(','\(')
-        s = string.replace(s, ')','\)')
-        return s
-
-    #info functions - non-standard
-    def setAuthor(self, author):
-        self._doc.setAuthor(author)
-        
-    def setTitle(self, title):
-        self._doc.setTitle(title)
-        
-    def setSubject(self, subject):
-        self._doc.setSubject(subject)
-        
-    def pageHasData(self):
-        "Info function - app can call it after showPage to see if it needs a save"
-        return len(self._code) == 0
-    
-    def showPage(self):
-        """This is where the fun happens"""
-        page = pdfdoc.PDFPage()
-        page.pagewidth = self._pagesize[0]
-        page.pageheight = self._pagesize[1]
-        page.hasImages = self._currentPageHasImages
-        page.pageTransitionString = self._pageTransitionString
-        page.setCompression(self._pageCompression)
-        #print stream
-        page.setStream([self._preamble] + self._code)
-        self._doc.addPage(page)
-        
-        #now get ready for the next one
-        self._pageNumber = self._pageNumber + 1
-        self._code = []    # ready for more...
-        self._currentPageHasImages = 0
-
-    def getPageNumber(self):
-        return self._pageNumber
-        
-    def save(self, filename=None, fileobj=None):
-
-        """Saves the pdf document to fileobj or to file with name filename.
-        If holding data, do a showPage() to save them having to."""
-        
-        if len(self._code):  
-            self.showPage()  # what's the effect of multiple 'showPage's 
-        if fileobj:
-            self._doc.SaveToFileObject(fileobj)
-        elif filename:
-            self._doc.SaveToFile(filename)
-        else:  
-            self._doc.SaveToFile(self._filename)
-
-
-    def setPageSize(self, size):
-        """accepts a 2-tuple in points for paper size for this
-        and subsequent pages"""
-        self._pagesize = size
-        
-
-    def addLiteral(self, s, escaped=1):
-        if escaped==0:
-            s = self._escape(s)
-        self._code.append(s)
-
-
-        ######################################################################
-        #
-        #      coordinate transformations
-        #
-        ######################################################################
-
-
-    def transform(self, a,b,c,d,e,f):
-        """How can Python track this?"""
-        a0,b0,c0,d0,e0,f0 = self._currentMatrix
-        self._currentMatrix = (a0*a+c0*b,    b0*a+d0*b,
-                               a0*c+c0*d,    b0*c+d0*d,
-                               a0*e+c0*f+e0, b0*e+d0*f+f0)
-        self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f cm' % (a,b,c,d,e,f))
-
-    def translate(self, dx, dy):
-        self.transform(1,0,0,1,dx,dy)
-
-    def scale(self, x, y):
-        self.transform(x,0,0,y,0,0)
-
-    def rotate(self, theta):
-        """Canvas.rotate(theta)
-
-        theta is in degrees."""
-        c = cos(theta * pi / 180)
-        s = sin(theta * pi / 180)
-        self.transform(c, s, -s, c, 0, 0)
-
-    def skew(self, alpha, beta):
-        tanAlpha = tan(alpha * pi / 180)
-        tanBeta  = tan(beta  * pi / 180)
-        self.transform(1, tanAlpha, tanBeta, 1, 0, 0)
-
-        ######################################################################
-        #
-        #      graphics state management
-        #
-        ######################################################################
-
-
-
-    def saveState(self):
-        """These need expanding to save/restore Python's state tracking too"""
-        self._code.append('q')
-        
-    def restoreState(self):
-        """These need expanding to save/restore Python's state tracking too"""
-        self._code.append('Q')
-
-        ###############################################################
-        #
-        #   Drawing methods.  These draw things directly without
-        #   fiddling around with Path objects.  We can add any geometry
-        #   methods we wish as long as their meaning is precise and
-        #   they are of general use.
-        #
-        #   In general there are two patterns.  Closed shapes
-        #   have the pattern shape(self, args, stroke=1, fill=0);
-        #   by default they draw an outline only. Line segments come
-        #   in three flavours: line, bezier, arc (which is a segment
-        #   of an elliptical arc, approximated by up to four bezier
-        #   curves, one for each quadrant.
-        #
-        #   In the case of lines, we provide a 'plural' to unroll
-        #   the inner loop; it is useful for drawing big grids
-        ################################################################
-
-
-        #--------first the line drawing methods-----------------------
-    def line(self, x1,y1, x2,y2):
-        "As it says"       
-        self._code.append('n %0.4f %0.4f m %0.4f %0.4f l S' % (x1, y1, x2, y2))
-
-    def lines(self, linelist):
-        """As line(), but slightly more efficient for lots of them -
-        one stroke operation and one less function call"""
-        self._code.append('n')
-        for (x1,y1,x2,y2) in linelist:
-            self._code.append('%0.4f %0.4f m %0.4f %0.4f l' % (x1, y1, x2, y2))
-        self._code.append('S')
-
-    def grid(self, xlist, ylist):
-        """Lays out a grid in current line style.  Suuply list of
-        x an y positions."""
-        assert len(xlist) > 1, "x coordinate list must have 2+ items"
-        assert len(ylist) > 1, "y coordinate list must have 2+ items"
-        lines = []
-        y0, y1 = ylist[0], ylist[-1]
-        x0, x1 = xlist[0], xlist[-1]
-        for x in xlist:
-            lines.append(x,y0,x,y1)
-        for y in ylist:
-            lines.append(x0,y,x1,y)
-        self.lines(lines)
-
-    def bezier(self, x1, y1, x2, y2, x3, y3, x4, y4):
-        "Bezier curve with the four given control points"
-        self._code.append('n %0.4f %0.4f m %0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c S' %
-                          (x1, y1, x2, y2, x3, y3, x4, y4)
-                          )
-    def arc(self, x1,y1, x2,y2, startAng=0, extent=90):
-        """Contributed to piddlePDF by Robert Kern, 28/7/99.
-        Trimmed down by AR to remove color stuff for pdfgen.canvas and
-        revert to positive coordinates.
-        
-        Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
-        starting at startAng degrees and covering extent degrees.   Angles
-        start with 0 to the right (+x) and increase counter-clockwise.
-        These should have x1<x2 and y1<y2.
-
-        The algorithm is an elliptical generalization of the formulae in
-        Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
-
-        pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
-        #move to first point
-        self._code.append('n %0.4f %0.4f m' % pointList[0][:2])
-        for curve in pointList:
-            self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c' % curve[2:])
-        # stroke
-        self._code.append('S')
-
-        #--------now the shape drawing methods-----------------------
-    def rect(self, x, y, width, height, stroke=1, fill=0):
-        "draws a rectangle"
-        self._code.append('n %0.4f %0.4f %0.4f %0.4f re ' % (x, y, width, height)
-                          + PATH_OPS[stroke, fill, self._fillMode])
-        
-    
-    def ellipse(self, x1, y1, x2, y2, stroke=1, fill=0):
-        """Uses bezierArc, which conveniently handles 360 degrees -
-        nice touch Robert"""
-        pointList = pdfgeom.bezierArc(x1,y1, x2,y2, 0, 360)
-        #move to first point
-        self._code.append('n %0.4f %0.4f m' % pointList[0][:2])
-        for curve in pointList:
-            self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c' % curve[2:])
-        #finish
-        self._code.append(PATH_OPS[stroke, fill, self._fillMode])
-
-        
-    def wedge(self, x1,y1, x2,y2, startAng, extent, stroke=1, fill=0):
-        """Like arc, but connects to the centre of the ellipse.
-        Most useful for pie charts and PacMan!"""
-
-        x_cen  = (x1+x2)/2.
-        y_cen  = (y1+y2)/2.
-        pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
-  
-        self._code.append('n %0.4f %0.4f m' % (x_cen, y_cen))
-        # Move the pen to the center of the rectangle
-        self._code.append('%0.4f %0.4f l' % pointList[0][:2])
-        for curve in pointList:
-            self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c' % curve[2:])
-        # finish the wedge
-        self._code.append('%0.4f %0.4f l ' % (x_cen, y_cen))
-        # final operator
-        self._code.append(PATH_OPS[stroke, fill, self._fillMode])
-
-    def circle(self, x_cen, y_cen, r, stroke=1, fill=0):
-        """special case of ellipse"""
-
-        x1 = x_cen - r
-        x2 = x_cen + r
-        y1 = y_cen - r
-        y2 = y_cen + r
-        self.ellipse(x1, y1, x2, y2, stroke, fill)
-
-    def roundRect(self, x, y, width, height, radius, stroke=1, fill=0):
-        """Draws a rectangle with rounded corners.  The corners are
-        approximately quadrants of a circle, with the given radius."""
-        #use a precomputed set of factors for the bezier approximation
-        #to a circle. There are six relevant points on the x axis and y axis.
-        #sketch them and it should all make sense!
-        t = 0.4472 * radius
-        
-        x0 = x
-        x1 = x0 + t
-        x2 = x0 + radius
-        x3 = x0 + width - radius
-        x4 = x0 + width - t
-        x5 = x0 + width
-
-        y0 = y
-        y1 = y0 + t
-        y2 = y0 + radius
-        y3 = y0 + height - radius
-        y4 = y0 + height - t
-        y5 = y0 + height
-
-        self._code.append('n %0.4f %0.4f m' % (x2, y0))
-        self._code.append('%0.4f %0.4f l' % (x3, y0))  # bottom row
-        self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c'
-                         % (x4, y0, x5, y1, x5, y2)) # bottom right
-
-        self._code.append('%0.4f %0.4f l' % (x5, y3))  # right edge
-        self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c'
-                         % (x5, y4, x4, y5, x3, y5)) # top right
-        
-        self._code.append('%0.4f %0.4f l' % (x2, y5))  # top row
-        self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c'
-                         % (x1, y5, x0, y4, x0, y3)) # top left
-        
-        self._code.append('%0.4f %0.4f l' % (x0, y2))  # left edge
-        self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c'
-                         % (x0, y1, x1, y0, x2, y0)) # bottom left
-
-        self._code.append('h')  #close off, although it should be where it started anyway
-        
-    
-        self._code.append(PATH_OPS[stroke, fill, self._fillMode])
-        ##################################################
-        #
-        #  Text methods
-        #
-        # As with graphics, a separate object ensures that
-        # everything is bracketed between  text operators.
-        # The methods below are a high-level convenience.
-        # use PDFTextObject for multi-line text.
-        ##################################################
-
-    def drawString(self, x, y, text):
-        """Draws a string in the current text styles."""
-        #we could inline this for speed if needed
-        t = self.beginText(x, y)
-        t.textLine(text)
-        self.drawText(t)
-
-    def drawRightString(self, x, y, text):
-        """Draws a string right-aligned with the y coordinate"""
-        width = self.stringWidth(text, self._fontname, self._fontsize)
-        t = self.beginText(x - width, y)
-        t.textLine(text)
-        self.drawText(t)
-
-    def drawCentredString(self, x, y, text):
-        """Draws a string right-aligned with the y coordinate.  I
-        am British so the spelling is correct, OK?"""
-        width = self.stringWidth(text, self._fontname, self._fontsize)
-        t = self.beginText(x - 0.5*width, y)
-        t.textLine(text)
-        self.drawText(t)
-  
-    def getAvailableFonts(self):
-        """Returns the list of PostScript font names available.
-        Standard set now, but may grow in future with font embedding."""
-        fontnames = self._doc.getAvailableFonts()
-        fontnames.sort()
-        return fontnames
-
-    def setFont(self, psfontname, size, leading = None):
-        """Sets the font.  If leading not specified, defaults to 1.2 x
-        font size. Raises a readable exception if an illegal font
-        is supplied.  Font names are case-sensitive! Keeps track
-        of font anme and size for metrics."""
-        self._fontname = psfontname
-        self._fontsize = size
-        pdffontname = self._doc.getInternalFontName(psfontname)
-        if leading is None:
-            leading = size * 1.2
-        self._leading = leading
-        self._code.append('BT %s %0.1f Tf %0.1f TL ET' % (pdffontname, size, leading))
-
-    def stringWidth(self, text, fontname, fontsize):
-        "gets width of a string in the given font and size"
-        return pdfmetrics.stringwidth(text, fontname) * 0.001 * fontsize
-        
-    # basic graphics modes
-    def setLineWidth(self, width):
-        self._lineWidth = width
-        self._code.append('%0.4f w' % width)
-
-    def setLineCap(self, mode):
-        """0=butt,1=round,2=square"""
-        assert mode in (0,1,2), "Line caps allowed: 0=butt,1=round,2=square"
-        self._lineCap = mode
-        self._code.append('%d J' % mode)
-        
-    def setLineJoin(self, mode):
-        """0=mitre, 1=round, 2=bevel"""
-        assert mode in (0,1,2), "Line Joins allowed: 0=mitre, 1=round, 2=bevel"
-        self._lineJoin = mode
-        self._code.append('%d j' % mode)
-        
-    def setMiterLimit(self, limit):
-        self._miterLimit = limit
-        self._code.append('%0.4f M' % limit)
-
-    def setDash(self, array=[], phase=0):
-        """Two notations.  pass two numbers, or an array and phase"""
-        if type(array) == IntType or type(array) == FloatType:
-            self._code.append('[%s %s] 0 d' % (array, phase))
-        elif type(array) == ListType or type(Array) == TupleType:
-            assert phase <= len(array), "setDash phase must be l.t.e. length of array"
-            textarray = string.join(map(str, array))
-            self._code.append('[%s] %s d' % (textarray, phase))
-        
-    def setFillColorRGB(self, r, g, b):
-        self._fillColorRGB = (r, g, b)
-        self._code.append('%0.4f %0.4f %0.4f rg' % (r,g,b))
-        
-    def setStrokeColorRGB(self, r, g, b):
-        self._strokeColorRGB = (r, g, b)
-        self._code.append('%0.4f %0.4f %0.4f RG' % (r,g,b))
-        
-    # path stuff - the separate path object builds it    
-    def beginPath(self):
-        """Returns a fresh path object"""
-        return PDFPathObject()
-    
-    def drawPath(self, aPath, stroke=1, fill=0):
-        "Draw in the mode indicated"
-        op = PATH_OPS[stroke, fill, self._fillMode]
-        self._code.append(aPath.getCode() + ' ' + op)
-
-    def clipPath(self, aPath, stroke=1, fill=0):
-        "clip as well as drawing"
-        op = PATH_OPS[stroke, fill, self._fillMode]
-        self._code.append(aPath.getCode() + ' W ' + op)
-
-    def beginText(self, x=0, y=0):
-        """Returns a fresh text object"""
-        return PDFTextObject(self, x, y)
-
-    def drawText(self, aTextObject):
-        """Draws a text object"""
-        self._code.append(aTextObject.getCode())
-        
-        ######################################################
-        #
-        #   Image routines
-        #
-        ######################################################
-    def drawInlineImage(self, image, x,y, width=None,height=None):
-        """Draw a PIL Image into the specified rectangle.  If width and
-        height are omitted, they are calculated from the image size.
-        Also allow file names as well as images.  This allows a
-        caching mechanism"""
-        # print "drawInlineImage: x=%s, y=%s, width = %s, height=%s " % (x,y, width, height)
-        try: 
-            import Image
-        except ImportError:
-            print 'Python Imaging Library not available'
-            return
-        try:
-            import zlib
-        except ImportError:
-            print 'zlib not available'
-            return
-            
-        self._currentPageHasImages = 1
-
-        if type(image) == StringType:
-            if os.path.splitext(image)[1] in ['.jpg', '.JPG']:
-            #directly process JPEG files
-            #open file, needs some error handling!!
-                imageFile = open(image, 'rb')
-                info = self.readJPEGInfo(imageFile)
-                imgwidth, imgheight = info[0], info[1]
-                if info[2] == 1:
-                    colorSpace = 'DeviceGray'
-                elif info[2] == 3:
-                    colorSpace = 'DeviceRGB'
-                else: #maybe should generate an error, is this right for CMYK?
-                    colorSpace = 'DeviceCMYK'
-                imageFile.seek(0)		#reset file pointer
-                imagedata = []
-                imagedata.append('BI')   # begin image
-                # this describes what is in the image itself
-                imagedata.append('/Width %0.4f /Height %0.4f' %(info[0], info[1]))
-                imagedata.append('/BitsPerComponent 8')
-                imagedata.append('/ColorSpace /%s' % colorSpace)
-                imagedata.append('/Filter [ /ASCII85Decode /DCTDecode]')
-                imagedata.append('ID')   
-                #write in blocks of (??) 60 characters per line to a list
-                compressed = imageFile.read()
-                encoded = pdfutils._AsciiBase85Encode(compressed)
-                outstream = cStringIO.StringIO(encoded)
-                dataline = outstream.read(60)
-                while dataline <> "":
-                    imagedata.append(dataline)
-                    dataline = outstream.read(60)
-                imagedata.append('EI')
-            else:
-                if not pdfutils.cachedImageExists(image):
-                    pdfutils.cacheImageFile(image)
-                #now we have one cached, slurp it in
-                cachedname = os.path.splitext(image)[0] + '.a85'
-                imagedata = open(cachedname,'rb').readlines()
-                #trim off newlines...
-                imagedata = map(string.strip, imagedata)
-                
-                #parse line two for width, height
-                words = string.split(imagedata[1])
-                imgwidth = string.atoi(words[1])
-                imgheight = string.atoi(words[3])
-        else:
-            #PIL Image
-            #work out all dimensions
-            myimage = image.convert('RGB')
-            imgwidth, imgheight = myimage.size
-            imagedata = []
-            imagedata.append('BI')   # begin image
-
-            # this describes what is in the image itself
-            imagedata.append('/W %0.4f /H %0.4f /BPC 8 /CS /RGB /F [/A85 /Fl]' % (imgwidth, imgheight))
-            imagedata.append('ID')   
-
-            #use a flate filter and Ascii Base 85 to compress
-            raw = myimage.tostring()
-            assert(len(raw) == imgwidth * imgheight, "Wrong amount of data for image")
-            compressed = zlib.compress(raw)   #this bit is very fast...
-            encoded = pdfutils._AsciiBase85Encode(compressed) #...sadly this isn't
-
-            #write in blocks of (??) 60 characters per line to a list
-            outstream = cStringIO.StringIO(encoded)
-            dataline = outstream.read(60)
-            while dataline <> "":
-                imagedata.append(dataline)
-                dataline = outstream.read(60)
-            imagedata.append('EI')
-
-        #now build the PDF for the image.
-        if not width:
-            width = imgwidth
-        if not height:
-            height = imgheight
-        
-        # this says where and how big to draw it
-        #self._code.append('ET')
-        #self._code.append('q %0.4f 0 0 %0.4f %0.4f %0.4f cm' % (width, height, x, y+height))
-        if self.bottomup:
-            self._code.append('q %0.4f 0 0 %0.4f %0.4f %0.4f cm' % (width, height, x, y))
-        else:
-            # multiply  height by (-1) to overcome flip in coordinate system -cwl
-            self._code.append('q %0.4f 0 0 %0.4f %0.4f %0.4f cm' % (width, -height, x, y+height))
-        self._code.extend(imagedata)
-        self._code.append('Q')
-        #self._code.append('BT')
-
-#########################################################################
-#
-#  JPEG processing code - contributed by Eric Johnson
-#
-#########################################################################
-
-    # Read data from the JPEG file. We should probably be using PIL to
-    # get this information for us -- but this way is more fun!
-    # Returns (width, height, color components) as a triple
-    # This is based on Thomas Merz's code from GhostScript (viewjpeg.ps)
-    def readJPEGInfo(self, image):
-        "Read width, height and number of components from JPEG file"
-    	import struct
-
-	#Acceptable JPEG Markers:
-	#  SROF0=baseline, SOF1=extended sequential or SOF2=progressive
-	validMarkers = [0xC0, 0xC1, 0xC2]
-
-	#JPEG markers without additional parameters
-	noParamMarkers = \
-	    [ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0x01 ]
-
-	#Unsupported JPEG Markers
-	unsupportedMarkers = \
-	    [ 0xC3, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF ]
-
-	#read JPEG marker segments until we find SOFn marker or EOF
-	done = 0
-	while not done:
-	    x = struct.unpack('B', image.read(1))
-	    if x[0] == 0xFF:			#found marker
-	    	x = struct.unpack('B', image.read(1))
-		#print "Marker: ", '%0.2x' % x[0]
-		#check marker type is acceptable and process it
-		if x[0] in validMarkers:
-		    image.seek(2, 1)		#skip segment length
-		    x = struct.unpack('B', image.read(1)) #data precision
-		    if x[0] != 8:
-			raise 'PDFError', ' JPEG must have 8 bits per component'
-		    y = struct.unpack('BB', image.read(2))
-		    height = (y[0] << 8) + y[1] 
-		    y = struct.unpack('BB', image.read(2))
-		    width =  (y[0] << 8) + y[1]
-		    y = struct.unpack('B', image.read(1))
-		    color =  y[0]
-		    return width, height, color
-		    done = 1
-		elif x[0] in unsupportedMarkers:
-		    raise 'PDFError', ' Unsupported JPEG marker: %0.2x' % x[0]
-		elif x[0] not in noParamMarkers:
-		    #skip segments with parameters
-		    #read length and skip the data
-		    x = struct.unpack('BB', image.read(2))
-		    image.seek( (x[0] << 8) + x[1] - 2, 1)	
-
-    def setPageCompression(self, onoff=1):
-        """Possible values 1 or 0 (1 for 'on' is the default).
-        If on, the page data will be compressed, leading to much
-        smaller files, but takes a little longer to create the files.
-        This applies to all subsequent pages, or until setPageCompression()
-        is next called."""
-        self._pageCompression = onoff
-        
-
-    def setPageTransition(self, effectname=None, duration=1, 
-                        direction=0,dimension='H',motion='I'):
-        """PDF allows page transition effects for use when giving
-        presentations.  There are six possible effects.  You can
-        just guive the effect name, or supply more advanced options
-        to refine the way it works.  There are three types of extra
-        argument permitted, and here are the allowed values:
-            direction_arg = [0,90,180,270]
-            dimension_arg = ['H', 'V']
-            motion_arg = ['I','O'] (start at inside or outside)
-            
-        This table says which ones take which arguments:
-
-        PageTransitionEffects = {
-            'Split': [direction_arg, motion_arg],
-            'Blinds': [dimension_arg],
-            'Box': [motion_arg],
-            'Wipe' : [direction_arg],
-            'Dissolve' : [],
-            'Glitter':[direction_arg]
-            }
-        Have fun!
-"""
-        if not effectname:
-            self._pageTransitionString = ''
-            return
-            
-        #first check each optional argument has an allowed value
-        if direction in [0,90,180,270]:
-            direction_arg = '/Di /%d' % direction
-        else:
-            raise 'PDFError', ' directions allowed are 0,90,180,270'
-        
-        if dimension in ['H', 'V']:
-            dimension_arg = '/Dm /%s' % dimension
-        else:
-            raise'PDFError','dimension values allowed are H and V'
-        
-        if motion in ['I','O']:
-            motion_arg = '/M /%s' % motion
-        else:
-            raise'PDFError','motion values allowed are I and O'
-
-
-        # this says which effects require which argument types from above
-        PageTransitionEffects = {
-            'Split': [direction_arg, motion_arg],
-            'Blinds': [dimension_arg],
-            'Box': [motion_arg],
-            'Wipe' : [direction_arg],
-            'Dissolve' : [],
-            'Glitter':[direction_arg]
-            }
-
-        try:
-            args = PageTransitionEffects[effectname]
-        except KeyError:
-            raise 'PDFError', 'Unknown Effect Name "%s"' % effectname
-            self._pageTransitionString = ''
-            return
-        
-
-        self._pageTransitionString = (('/Trans <</D %d /S /%s ' % (duration, effectname)) + 
-            string.join(args, ' ') + ' >>')
-
-
-        
-    
-
-    
-class PDFPathObject:
-    """Represents a graphic path.  There are certain 'modes' to PDF
-    drawing, and making a separate object to expose Path operations
-    ensures they are completed with no run-time overhead.  Ask
-    the Canvas for a PDFPath with getNewPathObject(); moveto/lineto/
-    curveto wherever you want; add whole shapes; and then add it back
-    into the canvas with one of the relevant operators.
-    
-    Path objects are probably not long, so we pack onto one line"""
-    def __init__(self):
-        self._code = []
-        self._code.append('n')   #newpath
-        
-    def getCode(self):
-        "pack onto one line; used internally"
-        return string.join(self._code, ' ')
-    def moveTo(self, x, y):
-        self._code.append('%0.4f %0.4f m' % (x,y))
-    def lineTo(self, x, y):
-        self._code.append('%0.4f %0.4f l' % (x,y))
-    def curveTo(self, x1, y1, x2, y2, x3, y3):
-        self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c' % (x1, y1, x2, y2, x3, y3))
-    
-    def arc(self, x1,y1, x2,y2, startAng=0, extent=90):
-        """Contributed to piddlePDF by Robert Kern, 28/7/99.
-        Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
-        starting at startAng degrees and covering extent degrees.   Angles
-        start with 0 to the right (+x) and increase counter-clockwise.
-        These should have x1<x2 and y1<y2.
-
-        The algorithm is an elliptical generalization of the formulae in
-        Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
-
-        pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
-        #move to first point
-        self._code.append('%0.4f %0.4f m' % pointList[0][:2])
-        for curve in pointList:
-            self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c' % curve[2:])
-
-    def arcTo(self, x1,y1, x2,y2, startAng=0, extent=90):
-        """Like arc, but draws a line from the current point to
-        the start if the start is not the current point."""
-        pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
-        self._code.append('%0.4f %0.4f l' % pointList[0][:2])
-        for curve in pointList:
-            self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c' % curve[2:])
-    
-
-    def rect(self, x, y, width, height):
-        """Adds a rectangle to the path"""
-        self._code.append('%0.4f %0.4f %0.4f %0.4f re' % (x, y, width, height))
-
-    def ellipse(self, x, y, width, height):
-        """adds an ellipse to the path"""
-        pointList = pdfgeom.bezierArc(x, y, x + width,y + height, 0, 360)
-        self._code.append('%0.4f %0.4f m' % pointList[0][:2])
-        for curve in pointList:
-            self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f c' % curve[2:])
-       
-    def circle(self, x_cen, y_cen, r):
-        """adds a circle to the path"""
-        x1 = x_cen - r
-        x2 = x_cen + r
-        y1 = y_cen - r
-        y2 = y_cen + r
-        self.ellipse(x_cen - r, y_cen - r, x_cen + r, y_cen + r)
-        
-    def close(self):
-        "draws a line back to where it started"
-        self._code.append('h')
-
-
-class PDFTextObject:
-    """PDF logically separates text and graphics drawing; you can
-    change the coordinate systems for text and graphics independently.
-    If you do drawings while in text mode, they appear in the right places
-    on the page in Acrobat Reader, bur when you export Postscript to
-    a printer the graphics appear relative to the text coordinate
-    system.  I regard this as a bug in how Acrobat exports to PostScript,
-    but this is the workaround.  It forces the user to separate text
-    and graphics.  To output text, ask te canvas for a text object
-    with beginText(x, y).  Do not construct one directly. It keeps
-    track of x and y coordinates relative to its origin."""
-    def __init__(self, canvas, x=0,y=0):
-        self._code = []
-        self._code.append('BT')
-        self._canvas = canvas  #canvas sets this so it has access to size info
-        self._fontname = self._canvas._fontname
-        self._fontsize = self._canvas._fontsize
-        self._leading = self._canvas._leading
-        
-        self.setTextOrigin(x, y)
-            
-    def getCode(self):
-        "pack onto one line; used internally"
-        self._code.append('ET')
-        return string.join(self._code, ' ')
-
-    def setTextOrigin(self, x, y):    
-        if self._canvas.bottomup:
-            self._code.append('1 0 0 1 %0.4f %0.4f Tm' % (x, y)) #bottom up
-        else:
-            self._code.append('1 0 0 -1 %0.4f %0.4f Tm' % (x, y))  #top down
-        self._x = x
-        self._y = y
-        self._x0 = x #the margin
-
-    def setTextTransform(self, a, b, c, d, e, f):
-        "Like setTextOrigin, but does rotation, scaling etc."
-        # flip "y" coordinate for top down coordinate system -cwl
-        # (1  0)   (a  b)      ( a   b)
-        # (0 -1)   (c  d)   =  (-c  -d) 
-        self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f Tm' % (a, b, -c, -d, e, f))  #top down
-
-        #self._code.append('%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f Tm' % (a, b, c, d, e, f)) #bottom up
-        #we only measure coords relative to present text matrix
-        self._x = e
-        self._y = f
-
-    def moveCursor(self, dx, dy):
-        """Moves to a point dx, dy away from the start of the
-        current line - NOT from the current point! So if
-        you call it in mid-sentence, watch out."""
-        self._code.append('%s %s Td' % (dx, -dy))
-
-    def getCursor(self):
-        """Returns current text position relative to the last origin."""
-        return (self._x, self._y)
-
-    def getX(self):
-        """Returns current x position relative to the last origin."""
-        return self._x
-
-    def getY(self):
-        """Returns current y position relative to the last origin."""
-        return self._y
-
-    def setFont(self, psfontname, size, leading = None):
-        """Sets the font.  If leading not specified, defaults to 1.2 x
-        font size. Raises a readable exception if an illegal font
-        is supplied.  Font names are case-sensitive! Keeps track
-        of font anme and size for metrics."""
-        self._fontname = psfontname
-        self._fontsize = size
-        pdffontname = self._canvas._doc.getInternalFontName(psfontname)
-        if leading is None:
-            leading = size * 1.2
-        self._leading = leading
-        self._code.append('%s %0.1f Tf %0.1f TL' % (pdffontname, size, leading))
-
-
-    def setCharSpace(self, charSpace):
-         """Adjusts inter-character spacing"""
-         self._charSpace = charSpace
-         self._code.append('%0.4f Tc' % charSpace)
-
-    def setWordSpace(self, wordSpace):
-        """Adjust inter-word spacing.  This can be used
-        to flush-justify text - you get the width of the
-        words, and add some space between them."""
-        self._wordSpace = wordSpace
-        self._code.append('%0.4f Tw' % wordSpace)
-
-    def setHorizScale(self, horizScale):
-        "Stretches text out horizontally"
-        self._horizScale = 100 + horizScale
-        self._code.append('%0.4f Tz' % horizScale)
-
-    def setLeading(self, leading):
-        "How far to move down at the end of a line."
-        self._leading = leading
-        self._code.append('%0.4f TL' % leading)
-
-    def setTextRenderMode(self, mode):
-        """Set the text rendering mode.
-
-        0 = Fill text
-        1 = Stroke text
-        2 = Fill then stroke
-        3 = Invisible
-        4 = Fill text and add to clipping path
-        5 = Stroke text and add to clipping path
-        6 = Fill then stroke and add to clipping path
-        7 = Add to clipping path"""
-        
-        assert mode in (0,1,2,3,4,5,6,7), "mode must be in (0,1,2,3,4,5,6,7)"
-        self._textRenderMode = mode
-        self._code.append('%d Tr' % mode)
-
-    def setRise(self, rise):
-        "Move text baseline up or down to allow superscrip/subscripts"
-        self._rise = rise
-        self._y = self._y - rise    # + ?  _textLineMatrix?
-        self._code.append('%0.4f Ts' % rise)
-
-    def setStrokeColorRGB(self, r, g, b):
-        self._strokeColorRGB = (r, g, b)
-        self._code.append('%0.4f %0.4f %0.4f RG' % (r,g,b))
-
-    def setFillColorRGB(self, r, g, b):
-        self._fillColorRGB = (r, g, b)
-        self._code.append('%0.4f %0.4f %0.4f rg' % (r,g,b))
- 
-
-    def textOut(self, text):
-        "prints string at current point, text cursor moves across"
-        text = self._canvas._escape(text)
-        self._x = self._x + self._canvas.stringWidth(
-                    text, self._fontname, self._fontsize)
-        self._code.append('(%s) Tj' % text)
-
-    def textLine(self, text=''):
-        """prints string at current point, text cursor moves down.
-        Can work with no argument to simply move the cursor down."""
-        text = self._canvas._escape(text)
-        self._x = self._x0
-        if self._canvas.bottomup:
-            self._y = self._y - self._leading
-        else:
-            self._y = self._y + self._leading
-        self._code.append('(%s) Tj T*' % text)
-
-    def textLines(self, stuff, trim=1):
-        """prints multi-line or newlined strings, moving down.  One
-        comon use is to quote a multi-line block in your Python code;
-        since this may be indented, by default it trims whitespace
-        off each line and from the beginning; set trim=0 to preserve
-        whitespace."""
-        if type(stuff) == StringType:
-            lines = string.split(string.strip(stuff), '\n')
-            if trim==1:
-                lines = map(string.strip,lines)
-        elif type(stuff) == ListType:
-            lines = stuff
-        elif type(stuff) == TupleType:
-            lines = stuff
-        else:
-            assert 1==0, "argument to textlines must be string,, list or tuple"
-        
-        for line in lines:
-            escaped_text = self._canvas._escape(line)
-            self._code.append('(%s) Tj T*' % escaped_text)
-            if self._canvas.bottomup:
-                self._y = self._y - self._leading
-            else:
-                self._y = self._y + self._leading
-        self._x = self._x0
-        
-        
-
-
-
-if __name__ == '__main__':
-    print 'For test scripts, run testpdfgen.py'
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/pdfgeom.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfgeom.py
--- bkchem-0.12.5/bkchem/plugins/piddle/pdfgeom.py	2005-02-25 07:12:06.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfgeom.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,75 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-# pdfgeom.py
-"""
-This module includes any mathematical methods needed for PIDDLE.
-It should have no dependencies beyond the Python library.
-
-So far, just Robert Kern's bezierArc.
-
-"""
-
-from math import sin, cos, pi, ceil
-
-def bezierArc(x1,y1, x2,y2, startAng=0, extent=90):
-    """bezierArc(x1,y1, x2,y2, startAng=0, extent=90) --> List of Bezier
-curve control points.
-
-(x1, y1) and (x2, y2) are the corners of the enclosing rectangle.  The
-coordinate system has coordinates that increase to the right and down.
-Angles, measured in degress, start with 0 to the right (the positive X
-axis) and increase counter-clockwise.  The arc extends from startAng
-to startAng+extent.  I.e. startAng=0 and extent=180 yields an openside-down
-semi-circle.
-
-The resulting coordinates are of the form (x1,y1, x2,y2, x3,y3, x4,y4)
-such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and
-(x3, y3) as their respective B�zier control points."""
-
-    x1,y1, x2,y2 = min(x1,x2), max(y1,y2), max(x1,x2), min(y1,y2)
-
-    if abs(extent) <= 90:
-        arcList = [startAng]
-        fragAngle = float(extent)
-        Nfrag = 1
-    else:
-        arcList = []
-        Nfrag = int(ceil(abs(extent)/90.))
-        fragAngle = float(extent) / Nfrag
-
-    x_cen = (x1+x2)/2.
-    y_cen = (y1+y2)/2.
-    rx = (x2-x1)/2.
-    ry = (y2-y1)/2.
-    halfAng = fragAngle * pi / 360.
-    kappa = abs(4. / 3. * (1. - cos(halfAng)) / sin(halfAng))
-
-    if fragAngle < 0:
-        sign = -1
-    else:
-        sign = 1
-    
-    pointList = []
-
-    for i in range(Nfrag):
-        theta0 = (startAng + i*fragAngle) * pi / 180.
-        theta1 = (startAng + (i+1)*fragAngle) *pi / 180.
-        if fragAngle > 0:
-            pointList.append((x_cen + rx * cos(theta0),
-                              y_cen - ry * sin(theta0),
-                              x_cen + rx * (cos(theta0) - kappa * sin(theta0)),
-                              y_cen - ry * (sin(theta0) + kappa * cos(theta0)),
-                              x_cen + rx * (cos(theta1) + kappa * sin(theta1)),
-                              y_cen - ry * (sin(theta1) - kappa * cos(theta1)),
-                              x_cen + rx * cos(theta1),
-                              y_cen - ry * sin(theta1)))
-        else:
-            pointList.append((x_cen + rx * cos(theta0),
-                              y_cen - ry * sin(theta0),
-                              x_cen + rx * (cos(theta0) + kappa * sin(theta0)),
-                              y_cen - ry * (sin(theta0) - kappa * cos(theta0)),
-                              x_cen + rx * (cos(theta1) - kappa * sin(theta1)),
-                              y_cen - ry * (sin(theta1) + kappa * cos(theta1)),
-                              x_cen + rx * cos(theta1),
-                              y_cen - ry * sin(theta1)))
-
-    return pointList
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/pdfmetrics.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfmetrics.py
--- bkchem-0.12.5/bkchem/plugins/piddle/pdfmetrics.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfmetrics.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,130 +0,0 @@
-#fontmetrics.py - part of PDFgen - copyright Andy Robinson 1999
-"""This contains pre-canned text metrics for the PDFgen package, and may also
-be used for any other PIDDLE back ends or packages which use the standard
-Type 1 postscript fonts.
-
-Its main function is to let you work out the width of strings; it exposes a 
-single function, stringwidth(text, fontname), which works out the width of a 
-string in the given font. This is an integer defined in em-square units - each
-character is defined in a 1000 x 1000 box called the em-square - for a 1-point high
-character.  So to convert to points, multiply by 1000 and then by point size.
-
-The AFM loading stuff worked for me but is not being heavily tested, as pre-canning
-the widths for the standard 14 fonts in Acrobat Reader is so much more useful. One
-could easily extend it to get the exact bounding box for each characterm useful for 
-kerning.
-
-
-The ascent_descent attribute of the module is a dictionary mapping font names
-(with the proper Postscript capitalisation) to ascents and descents.  I ought
-to sort out the fontname case issue and the resolution of PIDDLE fonts to 
-Postscript font names within this module, but have not yet done so.
-
-
-13th June 1999
-"""
-import string
-
-
-
-StandardEnglishFonts = [
-	'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique',  
-	'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 
-	'Helvetica-BoldOblique',
-	'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic',
-	'Symbol','ZapfDingbats']
-
-##############################################################
-#
-#			PDF Metrics
-# This is a preamble to give us a stringWidth function.
-# loads and caches AFM files, but won't need to as the
-# standard fonts are there already
-##############################################################
-
-widths = {'courier': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],'courier-bold': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],'courier-boldoblique': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],'courier-oblique': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],'helvetica': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,278,355,556,556,889,667,222,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,222,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,556,556,167,556,556,556,556,191,333,556,333,333,500,500,0,556,556,556,278,0,537,350,222,333,333,556,1000,1000,0,611,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,370,0,0,0,0,556,778,1000,365,0,0,0,0,0,889,0,0,0,278,0,0,222,611,944,611,0,0,834],'helvetica-bold': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,333,474,556,556,889,722,278,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,278,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,556,556,167,556,556,556,556,238,500,556,333,333,611,611,0,556,556,556,278,0,556,350,278,500,500,556,1000,1000,0,611,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,370,0,0,0,0,611,778,1000,365,0,0,0,0,0,889,0,0,0,278,0,0,278,611,944,611,0,0,834],'helvetica-boldoblique': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,333,474,556,556,889,722,278,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,278,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,556,556,167,556,556,556,556,238,500,556,333,333,611,611,0,556,556,556,278,0,556,350,278,500,500,556,1000,1000,0,611,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,370,0,0,0,0,611,778,1000,365,0,0,0,0,0,889,0,0,0,278,0,0,278,611,944,611,0,0,834],'helvetica-oblique': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,278,355,556,556,889,667,222,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,222,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,556,556,167,556,556,556,556,191,333,556,333,333,500,500,0,556,556,556,278,0,537,350,222,333,333,556,1000,1000,0,611,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,370,0,0,0,0,556,778,1000,365,0,0,0,0,0,889,0,0,0,278,0,0,222,611,944,611,0,0,834],'symbol': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,713,500,549,833,778,439,333,333,500,549,250,549,250,278,500,500,500,500,500,500,500,500,500,500,278,278,549,549,549,444,549,722,667,722,612,611,763,603,722,333,631,722,686,889,722,722,768,741,556,592,611,690,439,768,645,795,611,333,863,333,658,500,500,631,549,549,494,439,521,411,603,329,603,549,549,576,521,549,549,521,549,603,439,576,713,686,493,686,494,480,200,480,549,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,620,247,549,167,713,500,753,753,753,753,1042,987,603,987,603,400,549,411,549,549,713,494,460,549,549,549,549,1000,603,1000,658,823,686,795,987,768,768,823,768,768,713,713,713,713,713,713,713,768,713,790,790,890,823,549,250,713,603,603,1042,987,603,987,603,494,329,790,790,786,713,384,384,384,384,384,384,494,494,494,494,0,329,274,686,686,686,384,384,384,384,384,384,494,494,790],'times-bold': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,555,500,500,1000,833,333,333,333,500,570,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,930,722,667,722,722,667,611,778,778,389,500,778,667,944,722,778,611,778,722,556,667,722,722,1000,722,722,667,333,278,333,581,500,333,500,556,444,556,444,333,500,556,278,333,556,278,833,556,500,556,556,444,389,333,556,500,722,500,500,444,394,220,394,520,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,500,500,167,500,500,500,500,278,500,500,333,333,556,556,0,500,500,500,250,0,540,350,333,500,500,500,1000,1000,0,500,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,300,0,0,0,0,667,778,1000,330,0,0,0,0,0,722,0,0,0,278,0,0,278,500,722,556,0,0,750],'times-bolditalic': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,389,555,500,500,833,778,333,333,333,500,570,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,832,667,667,667,722,667,667,722,778,389,500,667,611,889,722,722,611,722,667,556,611,722,667,889,667,611,611,333,278,333,570,500,333,500,500,444,500,444,333,500,556,278,278,500,278,778,556,500,500,500,389,389,278,556,444,667,500,444,389,348,220,348,570,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,389,500,500,167,500,500,500,500,278,500,500,333,333,556,556,0,500,500,500,250,0,500,350,333,500,500,500,1000,1000,0,500,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,944,0,266,0,0,0,0,611,722,944,300,0,0,0,0,0,722,0,0,0,278,0,0,278,500,722,500,0,0,750],'times-italic': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,420,500,500,833,778,333,333,333,500,675,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,675,675,675,500,920,611,611,667,722,611,611,722,722,333,444,667,556,833,667,722,611,722,611,500,556,722,611,833,611,556,556,389,278,389,422,500,333,500,500,444,500,444,278,500,500,278,278,444,278,722,500,500,500,500,389,389,278,500,444,667,444,444,389,400,275,400,541,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,389,500,500,167,500,500,500,500,214,556,500,333,333,500,500,0,500,500,500,250,0,523,350,333,556,556,500,889,1000,0,500,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,889,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,889,0,276,0,0,0,0,556,722,944,310,0,0,0,0,0,667,0,0,0,278,0,0,278,500,667,500,0,0,750],'times-roman': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,408,500,500,833,778,333,333,333,500,564,250,333,250,278,500,500,500,500,500,500,500,500,500,500,278,278,564,564,564,444,921,722,667,667,722,611,556,722,722,333,389,722,611,889,722,722,556,722,667,556,611,722,722,944,722,722,611,333,278,333,469,500,333,444,500,444,500,444,333,500,500,278,278,500,278,778,500,500,500,500,333,389,278,500,500,722,500,500,444,480,200,480,541,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,500,500,167,500,500,500,500,180,444,500,333,333,556,556,0,500,500,500,250,0,453,350,333,444,444,500,1000,1000,0,444,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,889,0,276,0,0,0,0,611,722,889,310,0,0,0,0,0,667,0,0,0,278,0,0,278,500,722,500,0,0,750],'zapfdingbats': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,974,961,974,980,719,789,790,791,690,960,939,549,855,911,933,911,945,974,755,846,762,761,571,677,763,760,759,754,494,552,537,577,692,786,788,788,790,793,794,816,823,789,841,823,833,816,831,923,744,723,749,790,792,695,776,768,792,759,707,708,682,701,826,815,789,789,707,687,696,689,786,787,713,791,785,791,873,761,762,762,759,759,892,892,788,784,438,138,277,415,392,392,668,668,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,732,544,544,910,667,760,760,776,595,694,626,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,894,838,1016,458,748,924,748,918,927,928,928,834,873,828,924,924,917,930,931,463,883,836,836,867,867,696,696,874,0,874,760,946,771,865,771,888,967,888,831,873,927,970,234]
-}
-
-ascent_descent = {'Courier': (629, -157), 'Courier-Bold': (626, -142), 'Courier-BoldOblique': (626, -142), 'Courier-Oblique': (629, -157), 'Helvetica': (718, -207), 'Helvetica-Bold': (718, -207), 'Helvetica-BoldOblique': (718, -207), 'Helvetica-Oblique': (718, -207), 'Symbol': (0, 0), 'Times-Bold': (676, -205), 'Times-BoldItalic': (699, -205), 'Times-Italic': (683, -205), 'Times-Roman': (683, -217), 'ZapfDingbats': (0, 0)}
-
-def parseAFMfile(filename):
-    """Returns an array holding the widths of all characters in the font.
-    Ultra-crude parser"""
-    alllines = open(filename, 'r').readlines()
-    # get stuff between StartCharMetrics and EndCharMetrics
-    metriclines = []
-    between = 0
-    for line in alllines:
-        if string.find(string.lower(line), 'endcharmetrics') > -1:
-            between = 0
-            break
-        if between:
-            metriclines.append(line)
-        if string.find(string.lower(line), 'startcharmetrics') > -1:
-            between = 1
-            
-    # break up - very shaky assumption about array size
-    widths = [0] * 255
-    
-    for line in metriclines:
-        chunks = string.split(line, ';')
-        
-        (c, cid) = string.split(chunks[0])
-        (wx, width) = string.split(chunks[1])
-        #(n, name) = string.split(chunks[2])
-        #(b, x1, y1, x2, y2) = string.split(chunks[3])
-        widths[string.atoi(cid)] = string.atoi(width)
-    
-    # by default, any empties should get the width of a space
-    for i in range(len(widths)):
-        if widths[i] == 0:
-            widths[i] == widths[32]
-
-    return widths
-
-
-class FontCache:
-    """Loads and caches font width information on demand.  Font names
-    converted to lower case for indexing.  Public interface is stringwidth"""
-    def __init__(self):
-        global widths
-        self.__widtharrays = widths
-
-        
-    def loadfont(self, fontname):
-        filename = AFMDIR + os.sep + fontname + '.afm'
-        print 'cache loading',filename
-        assert os.path.exists(filename)
-        widths = parseAFMfile(filename)
-        self.__widtharrays[fontname] = widths
-
-    def getfont(self, fontname):
-        try:
-            return self.__widtharrays[fontname]
-        except:
-            try:
-                self.loadfont(fontname)
-                return self.__widtharrays[fontname]
-            except:
-                # font not found, use Courier
-                print 'Font',fontname,'not found - using Courier for widths'
-                return self.getfont('courier')
-    
-
-    def stringwidth(self, text, font):
-        widths = self.getfont(string.lower(font))
-        w = 0
-        for char in text:
-            w = w + widths[ord(char)]
-        return w
-
-    def status(self):
-        #returns loaded fonts
-        return self.__widtharrays.keys()
-        
-TheFontCache = FontCache()
-
-#expose the singleton as a single function
-stringwidth = TheFontCache.stringwidth
-
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/pdfutils.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfutils.py
--- bkchem-0.12.5/bkchem/plugins/piddle/pdfutils.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/pdfutils.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,314 +0,0 @@
-# pdfutils.py - everything to do with images, streams,
-# compression, and some constants
-
-import os
-import string
-import cStringIO
-
-LINEEND = '\015\012'
-
-##########################################################
-#
-#  image compression helpers.  Preprocessing a directory
-#  of images will offer a vast speedup.
-#
-##########################################################
-def cacheImageFile(filename):
-    "Processes the image as if for encoding, saves to a file ending in AHX"
-    import Image
-    import zlib
-    img1 = Image.open(filename)
-    img = img1.convert('RGB')
-    imgwidth, imgheight = img.size
-    code = []
-    code.append('BI')   # begin image
-    # this describes what is in the image itself
-    code.append('/W %s /H %s /BPC 8 /CS /RGB /F [/A85 /Fl]' % (imgwidth, imgheight))
-    code.append('ID')
-    #use a flate filter and Ascii Base 85
-    raw = img.tostring()
-    assert(len(raw) == imgwidth * imgheight, "Wrong amount of data for image")
-    compressed = zlib.compress(raw)   #this bit is very fast...
-    encoded = _AsciiBase85Encode(compressed) #...sadly this isn't
-    
-    #write in blocks of 60 characters per line
-    outstream = cStringIO.StringIO(encoded)
-    dataline = outstream.read(60)
-    while dataline <> "":
-        code.append(dataline)
-        dataline = outstream.read(60)
-    
-    code.append('EI')
-
-    #save it to a file
-    cachedname = os.path.splitext(filename)[0] + '.a85'
-    f = open(cachedname,'wb')
-    f.write(string.join(code, LINEEND)+LINEEND)
-    f.close()
-    print 'cached image as %s' % cachedname
-
-
-def preProcessImages(spec):
-    """accepts either a filespec ('C:\mydir\*.jpg') or a list
-    of image filenames, crunches them all to save time.  Run this
-    to save huge amounts of time when repeatedly building image
-    documents."""
-    import types
-    if type(spec) is types.StringType:
-        filelist = glob.glob(spec)
-    else:  #list or tuple OK
-        filelist = spec
-
-    for filename in filelist:
-        if cachedImageExists(filename):
-            print 'cached version of %s already exists' % filename
-        else:
-            cacheImageFile(filename)
-        
-
-def cachedImageExists(filename):
-    """Determines if a cached image exists which has
-    the same name and equal or newer date to the given
-    file."""
-    cachedname = os.path.splitext(filename)[0] + '.a85'
-    if os.path.isfile(cachedname):
-        #see if it is newer
-        original_date = os.stat(filename)[8]
-        cached_date = os.stat(cachedname)[8]
-        if original_date > cached_date:
-            return 0
-        else:
-            return 1
-    else:
-        return 0
-    
-
-##############################################################
-#
-#            PDF Helper functions
-#
-##############################################################
-def _escape(s):
-    """PDF escapes are almost like Python ones, but brackets
-    need slashes before them too. Use Python's repr function
-    and chop off the quotes first"""
-    s = repr(s)[1:-1]
-    s = string.replace(s, '(','\(')
-    s = string.replace(s, ')','\)')
-    return s
-
-
-def _normalizeLineEnds(text,desired=LINEEND):
-    """ensures all instances of CR, LF and CRLF end up as the specified one"""
-    unlikely = '\000\001\002\003'
-    text = string.replace(text, '\015\012', unlikely)
-    text = string.replace(text, '\015', unlikely)
-    text = string.replace(text, '\012', unlikely)
-    text = string.replace(text, unlikely, desired)
-    return text
-
-def _AsciiHexEncode(input):
-    """This is a verbose encoding used for binary data within
-    a PDF file.  One byte binary becomes two bytes of ASCII."""
-    "Helper function used by images"
-    output = cStringIO.StringIO()
-    for char in input:
-        output.write('%02x' % ord(char))
-    output.write('>')
-    output.reset()
-    return output.read()
-
-def _AsciiHexDecode(input):
-    "Not used except to provide a test of the preceding"
-    #strip out all whitespace
-    stripped = string.join(string.split(input),'')
-    assert stripped[-1] == '>', 'Invalid terminator for Ascii Hex Stream'
-    stripped = stripped[:-1]  #chop off terminator
-    assert len(stripped) % 2 == 0, 'Ascii Hex stream has odd number of bytes'
-    i = 0
-    output = cStringIO.StringIO()
-    while i < len(stripped):
-        twobytes = stripped[i:i+2]
-        output.write(chr(eval('0x'+twobytes)))
-        i = i + 2
-    output.reset()
-    return output.read()
-
-def _AsciiHexTest(text='What is the average velocity of a sparrow?'):
-    "Do the obvious test for whether Ascii Hex encoding works"
-    print 'Plain text:', text
-    encoded = _AsciiHexEncode(text)
-    print 'Encoded:', encoded
-    decoded = _AsciiHexDecode(encoded)
-    print 'Decoded:', decoded
-    if decoded == text:
-        print 'Passed'
-    else:
-        print 'Failed!'
-    
-def _AsciiBase85Encode(input):
-    """This is a compact encoding used for binary data within
-    a PDF file.  Four bytes of binary data become five bytes of
-    ASCII.  This is the default method used for encoding images."""
-    outstream = cStringIO.StringIO()
-    # special rules apply if not a multiple of four bytes.  
-    whole_word_count, remainder_size = divmod(len(input), 4)
-    cut = 4 * whole_word_count
-    body, lastbit = input[0:cut], input[cut:]
-    
-    for i in range(whole_word_count):
-        offset = i*4
-        b1 = ord(body[offset])
-        b2 = ord(body[offset+1])
-        b3 = ord(body[offset+2])
-        b4 = ord(body[offset+3])
-    
-        num = 16777216L * b1 + 65536 * b2 + 256 * b3 + b4
-
-        if num == 0:
-            #special case
-            outstream.write('z')
-        else:
-            #solve for five base-85 numbers                            
-            temp, c5 = divmod(num, 85)
-            temp, c4 = divmod(temp, 85)
-            temp, c3 = divmod(temp, 85)
-            c1, c2 = divmod(temp, 85)
-            assert ((85**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5 == num, 'dodgy code!'
-            outstream.write(chr(c1+33))
-            outstream.write(chr(c2+33))
-            outstream.write(chr(c3+33))
-            outstream.write(chr(c4+33))
-            outstream.write(chr(c5+33))
-
-    # now we do the final bit at the end.  I repeated this separately as
-    # the loop above is the time-critical part of a script, whereas this
-    # happens only once at the end.
-
-    #encode however many bytes we have as usual
-    if remainder_size > 0:
-        while len(lastbit) < 4:
-            lastbit = lastbit + '\000'
-        b1 = ord(lastbit[0])
-        b2 = ord(lastbit[1])
-        b3 = ord(lastbit[2])
-        b4 = ord(lastbit[3])
-
-        num = 16777216L * b1 + 65536 * b2 + 256 * b3 + b4
-
-        #solve for c1..c5
-        temp, c5 = divmod(num, 85)
-        temp, c4 = divmod(temp, 85)
-        temp, c3 = divmod(temp, 85)
-        c1, c2 = divmod(temp, 85)
-
-        #print 'encoding: %d %d %d %d -> %d -> %d %d %d %d %d' % (
-        #    b1,b2,b3,b4,num,c1,c2,c3,c4,c5)
-        lastword = chr(c1+33) + chr(c2+33) + chr(c3+33) + chr(c4+33) + chr(c5+33)
-        #write out most of the bytes.
-        outstream.write(lastword[0:remainder_size + 1])
-
-    #terminator code for ascii 85    
-    outstream.write('~>')
-    outstream.reset()
-    return outstream.read()
-        
-
-def _AsciiBase85Decode(input):
-    """This is not used - Acrobat Reader decodes for you - but a round
-    trip is essential for testing."""
-    outstream = cStringIO.StringIO()
-    #strip all whitespace
-    stripped = string.join(string.split(input),'')
-    #check end
-    assert stripped[-2:] == '~>', 'Invalid terminator for Ascii Base 85 Stream'
-    stripped = stripped[:-2]  #chop off terminator
-
-    #may have 'z' in it which complicates matters - expand them
-    stripped = string.replace(stripped,'z','!!!!!')
-    # special rules apply if not a multiple of five bytes.  
-    whole_word_count, remainder_size = divmod(len(stripped), 5)
-    #print '%d words, %d leftover' % (whole_word_count, remainder_size)
-    assert remainder_size <> 1, 'invalid Ascii 85 stream!'
-    cut = 5 * whole_word_count
-    body, lastbit = stripped[0:cut], stripped[cut:]
-    
-    for i in range(whole_word_count):
-        offset = i*5
-        c1 = ord(body[offset]) - 33
-        c2 = ord(body[offset+1]) - 33
-        c3 = ord(body[offset+2]) - 33
-        c4 = ord(body[offset+3]) - 33
-        c5 = ord(body[offset+4]) - 33
-
-        num = ((85**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5    
-
-        temp, b4 = divmod(num,256)
-        temp, b3 = divmod(temp,256)
-        b1, b2 = divmod(temp, 256)
-
-        assert  num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!'
-        outstream.write(chr(b1))
-        outstream.write(chr(b2))
-        outstream.write(chr(b3))
-        outstream.write(chr(b4))
-        
-    #decode however many bytes we have as usual
-    if remainder_size > 0:
-        while len(lastbit) < 5:
-            lastbit = lastbit + '!'
-        c1 = ord(lastbit[0]) - 33
-        c2 = ord(lastbit[1]) - 33
-        c3 = ord(lastbit[2]) - 33
-        c4 = ord(lastbit[3]) - 33
-        c5 = ord(lastbit[4]) - 33
-        num = ((85**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5    
-        temp, b4 = divmod(num,256)
-        temp, b3 = divmod(temp,256)
-        b1, b2 = divmod(temp, 256)
-        assert  num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!'
-        #print 'decoding: %d %d %d %d %d -> %d -> %d %d %d %d' % (
-        #    c1,c2,c3,c4,c5,num,b1,b2,b3,b4)
-
-        #the last character needs 1 adding; the encoding loses
-        #data by rounding the number to x bytes, and when
-        #divided repeatedly we get one less
-        if remainder_size == 2:
-            lastword = chr(b1+1)
-        elif remainder_size == 3:
-            lastword = chr(b1) + chr(b2+1)
-        elif remainder_size == 4:
-            lastword = chr(b1) + chr(b2) + chr(b3+1)
-        outstream.write(lastword)
-
-    #terminator code for ascii 85    
-    outstream.reset()
-    return outstream.read()
-
-def _wrap(input, columns=60):
-    output = []
-    length = len(input)
-    i = 0
-    pos = columns * i
-    while pos < length:
-        output.append(input[pos:pos+columns])
-        i = i + 1
-        pos = columns * i
-
-    return string.join(output, LINEEND)
-
-    
-
-def _AsciiBase85Test(text='What is the average velocity of a sparrow?'):
-    "Do the obvious test for whether Base 85 encoding works"
-    print 'Plain text:', text
-    encoded = _AsciiBase85Encode(text)
-    print 'Encoded:', encoded
-    decoded = _AsciiBase85Decode(encoded)
-    print 'Decoded:', decoded
-    if decoded == text:
-        print 'Passed'
-    else:
-        print 'Failed!'
-
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleAI.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleAI.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleAI.py	2004-11-16 08:04:28.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleAI.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,585 +0,0 @@
-""" piddleAI.py - a  backend for PIDDLE which produces file in Abode Illustrator 3 format.
-
-These can be read by most vector graphic applications on all common platforms.
-
-Note: This back end assumes that the output is intended as a graphic that
-is to used elsewhere and so the "size" attribute is ignored and the
-artboard size is calculated to slightly bigger than the bounding box.
-
-
-(C) Copyright Bill Bedford 1999
-
-
-BB 28/09/99 Added Rewrote drawEllipse, drawroundRect and drawArc to use drawBezier
-			fixed bug in name in AICanvas.__init__
-			
-			
-"""
-from piddle import *
-import aigen
-import string
-import zlib
-import cStringIO
-
-from math import sin, cos, pi, ceil
-
-author = 'Me'
-pageSize = (595, 842) #A4
-
-class AICanvas(Canvas):
-	"""This works by accumulating a list of strings containing 
-	AI page marking operators, as you call its methods.  We could
-	use a big string but this is more efficient - only concatenate
-	it once, with control over line ends.  When
-	done, it hands off the stream to a AIPage object."""
-
-	def __init__(self, size=(0,0), name='piddle.ai'):
-		Canvas.__init__(self, size, name=name)
-		print name
-		if name[-3:] == '.ai':
-			self.name = name
-		else:
-			self.name = name + '.ai'
-		self.code = []  # list of strings to join later
-		self._colorNames = {}
-		self.currentPageHasImages = 0
-		self.doc = aigen.AIDocument()
-		
-		self.pageNumber = 1   # keep a count
-		
-		# now for any default graphics settings	
-		self.defaultFont=Font(face='Helvetica')
-		self.defaultLineColor = black
-		self.defaultFillColor = transparent
-		c = self._currentLineColor = self.defaultLineColor
-		self._currentFillColor = self.defaultFillColor
-		self._currentTextColor = self.defaultLineColor
-		
-		r,g,b = c.red, c.green, c.blue
-		w = self._currentWidth = self.defaultLineWidth
-		self._currentFont = self.defaultFont
-
-		self._currentWidth = self.defaultLineWidth = 1
-		self.maxline = 1
-		self.maxx, self.minx, self.maxy, self.miny = (0,0,0,0)
-		self.boundbox = self.maxx, self.minx, self.maxy, self.miny, self.maxline
-		
-		self.mitre = '4.5 M'
-		self.linejoin = '0 j'
-		self.linecap = '0 J' # 0=butt, 1=round, 2=square
-		self.winding = '1 XR' # 0=nozerwinding, 1=even/odd fillrule
-		self.winOrder = '0 D' #0=clockwise, 1=antoclockwise
-
-	#info functions - non-standard
-		
-	def setTitle(self):
-		self.doc.setTitle(self.name)
-		
-	def setPageSize(self, pageSize):
-		self.doc.setPageSize(pageSize)
-		
-	def setBoundingBox(self):
-		self.doc.setBoundingBox(self.boundbox)
-		
-		
-		
-	
-	def showPage(self):
-		"""This is where the fun happens"""
-		self.setTitle()
-		self.setAuthor(author)
-		self.setBoundingBox()
-		stream = self.winding + '\n' + self.winOrder
-		stream = [stream] + self.code
- 		self.doc.setPage(stream)
-
-	def save(self, file=None, format=None):
-		"""Saves the file.  If holding data, do
-		a showPage() to save them having to.""" 
-		if len(self.code):  
-			self.showPage()
-		self.doc.SaveToFile(self.name)
-		print 'saved', self.name
-
-
-
-
-	#------------ canvas capabilities -------------
-	def isInteractive(self):
-		return 0
-
-	def canUpdate(self):
-		return 0
-
-	#------------ general management -------------
-	def clear(self):
-		"same as ShowPage"
-		self.showPage()
-	
-	def flush(self):
-                pass
-
-		
-	def setInfoLine(self, s):
-		self.doc.setTitle(s)
-			
-	def setAuthor(self, name):
-		self.doc.setAuthor(name)
-	
-	def _bounds(self, x1, y1, x2, y2):
-#		print self.maxx, x1, x2, y1, y2
-		self.maxx = max(self.maxx, x1, x2)
-		self.maxy = max(self.maxy, y1, y2)
-		self.minx = min(self.minx, x1, x2)
-		self.miny = min(self.miny, y1, y2)
-		self.boundbox = self.minx, self.miny, self.maxx, self.maxy, self.maxline
-#		print self.boundbox
-
-	def _updateLineColor(self, color):
-		color = color or self.defaultLineColor
-		if color != self._currentLineColor:
-			self._currentLineColor = color
-			if  self._currentLineColor != transparent:
-				r,g,b = color.red, color.green, color.blue
-				self.code.append('%s %s %s XA' % (r,g,b))
-	 
-
-
-	def _updateFillColor(self, color):
-		color = color or self.defaultFillColor
-		if color != self._currentFillColor:
-			self._currentFillColor = color
-			if  self._currentLineColor != transparent:
-				r,g,b = color.red, color.green, color.blue
-				self.code.append('%s %s %s Xa' % (r,g,b))
- 
-
-	def _updateLineWidth(self, width):
-		if width == None: 
-			width = self.defaultLineWidth
-		if width != self._currentWidth:
-			self.maxline = max(self.maxline, width)
-			self._currentWidth = width
-			self.code.append('%s w' % width)
-  
-#	def _updateFont(self, font):
-#		font = font or self.defaultFont
-#		if font != self._currentFont:
-#			self._currentFont = font
-#			ps_font_name = self._findExternalFontName(font)
-#			pdf_font_name = self._findInternalFontName(ps_font_name)
-#			s = font.size
-#			self.code.append('BT /%s %s Tf %s TL ET' % (pdf_font_name, s, s))
-#
-
-	#------------ string/font info ------------
-		
-	def stringWidth(self, s, font=None):
-		"Return the logical width of the string if it were drawn \
-		in the current font (defaults to self.font)."
-		if not font:
-			font = self.defaultFont
-		fontname = self._findExternalFontName(font)
-		return pdfmetrics.stringwidth(s, fontname) * font.size * 0.001
-
-
-	def fontHeight(self, font=None):
-		if not font:
-			font = self.defaultFont
-		return font.size
-
-	def fontAscent(self, font=None):
-		if not font:
-			font = self.defaultFont
-		fontname = self._findExternalFontName(font)
-		return pdfmetrics.ascent_descent[fontname][0] * 0.001 * font.size
-		
-	def fontDescent(self, font=None):
-		if not font:
-			font = self.defaultFont
-		fontname = self._findExternalFontName(font)
-		return -pdfmetrics.ascent_descent[fontname][1] * 0.001 * font.size
-	
-	#------------- drawing methods --------------
-
-		
-	def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-		self._updateLineColor(color)
-		self._updateLineWidth(width)
-		w = self._currentWidth/2
-		self._bounds(x1-w, y1-w, x2+w, y2+w)
-		self.code.append(x1, y1, 'm')
-		self.code.append(x2, y2, 'L')
-		self.code.append('S')
-
-
-	def drawLines(self, lineList, color=None, width=None):
-		self._updateLineColor(color)
-		self._updateLineWidth(width)
-		self.code.append('u ')
-		for (x1, y1, x2, y2) in lineList:
-			w = self._currentWidth/2
-			self._bounds(x1-w, y1-w, x2+w, y2+w)
-			self.code.append(x1, y1, 'm')
-			self.code.append(x2, y2, 'L')
-			self.code.append('S')
-		self.code.append('U')
-
-#	def drawString(self, s, x, y, font=None, color=None, angle=0):
-#		# all escaping of text is done in the back end
-#		self._updateLineColor(color)
-#		self._updateFont(font)
-#		text = self._escape(text)
-#		fnt = self._currentFont
-#		if self._currentTextColor != transparent:
-#			c = cos(angle * pi / 180.0)
-#			s = sin(angle * pi / 180.0)
-#			self.code.append('BT')
-#			color = color or self._currentTextColor
-#			r,g,b = color.red, color.green, color.blue
-#			self.code.append('%s %s %s rg' % (r,g,b))
-#			self.code.append('%f %f %f %f %s %s Tm' % (c, s, -s, c, x, -y))
-#			self.code.append(' (%s) Tj ET' % text)
-#			#keep underlining separate - it is slow and unusual anyway
-#			if fnt.underline:
-#				ypos = y + (0.5 * self.fontDescent(fnt))
-#				thickness = 0.08 * fnt.size
-#				width = self.stringWidth(text, fnt)
-#				self.drawLine(x, ypos, x + width, ypos, width=thickness)
-#				
-#	def drawJustifiedString(self, s, x, y, font=None, color=None, angle=0):
-#		self._updateLineColor(color)
-#		self._updateFont(font)
-#		text = self._escape(text)
-#		if self._currentTextColor != transparent:
-#			c = cos(angle * pi / 180.0)
-#			s = sin(angle * pi / 180.0)
-#			self.code.append('BT')
-#			color = color or self._currentTextColor
-#			r,g,b = color.red, color.green, color.blue
-#			self.code.append('%s %s %s rg' % (r,g,b))
-#			self.code.append('%f %f %f %f %s %s Tm' % (c, s, -s, c, x, -y))
-#			self.code.append('36 Tw')
-#			self.code.append(' (%s) Tj ET' % text)
-#
-#
-
-	def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4,
-					edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-		self._updateFillColor(fillColor)
-		self._updateLineWidth(edgeWidth)
-		self._updateLineColor(edgeColor)
-		w = self._currentWidth/2
-		self._bounds(x1-w, y1-w, x4+w, y4+w)
-		if self._currentLineColor != transparent:
-#			if closed:
-#				##XXX Not what we want
-#				if self._currentFillColor != transparent:
-#					op = 'B'	#closepath, eofill, stroke
-#				else:
-#					op = 'S'  # closepath and stroke
-#			else:
-#				if self._currentFillColor != transparent:
-#					op = 'B'	#eofill, stroke
-#				else:
-#					op = 'S'  # stroke
-			op = 'S'  # stroke
-		else:
-			op  = 'f'
-		self.code.append('u')
-		self.code.append(x1, y1, 'm' )
-		self.code.append(x2, y2, x3, y3, x4, y4, 'c')
-		self.code.append(op + ' U')
-		
-	def bezierArc(self, x1,y1, x2,y2, startAng=0, extent=90):
-		"""bezierArc(x1,y1, x2,y2, startAng=0, extent=90) --> List of Bezier
-		curve control points.
-		Contributed by Robert Kern, 28/7/99.
-		(x1, y1) and (x2, y2) are the corners of the enclosing rectangle such that
-		x1<x2 and y1<y2.  The coordinate system has coordinates that increase to the
-		right and down.  Angles, measured in degress, start with 0 to the right (the
-		positive X axis) and increase counter-clockwise.  The arc extends from startAng
-		to startAng+extent.  I.e. startAng=0 and extent=180 yields an openside-down
-		semi-circle.
-
-		The resulting coordinates are of the form (x1,y1, x2,y2, x3,y3, x4,y4) such that
-		the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and (x3, y3) as their
-		respective Bzier control points."""
-
-		if abs(extent) <= 90:
-			arcList = [startAng]
-			fragAngle = float(extent)
-			Nfrag = 1
-		else:
-			arcList = []
-			Nfrag = int(ceil(abs(extent)/90.))
-			fragAngle = float(extent) / Nfrag
-
-		x_cen = (x1+x2)/2.
-		y_cen = (y1+y2)/2.
-		rx = (x2-x1)/2.
-		ry = (y2-y1)/2.
-		halfAng = fragAngle * pi / 360.
-		kappa = 4. / 3. * (1. - cos(halfAng)) / sin(halfAng)
-
-		pointList = []
-
-		for i in range(Nfrag):
-			theta0 = (startAng + i*fragAngle) * pi / 180.
-			theta1 = (startAng + (i+1)*fragAngle) *pi / 180.
-			pointList.append((x_cen + rx * cos(theta0),
-				y_cen - ry * sin(theta0),
-				x_cen + rx * (cos(theta0) - kappa * sin(theta0)),
-				y_cen - ry * (sin(theta0) + kappa * cos(theta0)),
-				x_cen + rx * (cos(theta1) + kappa * sin(theta1)),
-				y_cen - ry * (sin(theta1) - kappa * cos(theta1)),
-				x_cen + rx * cos(theta1),
-				y_cen - ry * sin(theta1)))
-
-		return pointList
-
-
-	
-	def drawArc(self, x1, y1, x2, y2, startAng=0, extent=90, edgeColor=None, 
-			edgeWidth=None, fillColor=None, closed=0):
-		self._updateFillColor(fillColor)
-		self._updateLineWidth(edgeWidth)
-		self._updateLineColor(edgeColor)
-		w = self._currentWidth/2
-		self._bounds(x1-w, y1-w, x2+w, y2+w)
-		if self._currentLineColor != transparent:
-			if self._currentFillColor != transparent:
-				op = 'B'	#eofill, stroke
-			else:
-				op = 'S'  # stroke
-		else:
-			op = 'f'
-		pointlist = self.bezierArc(x1, y1, x2, y2, startAng, extent)
-#		print pointlist, type(pointlist)
-		st = list(pointlist[0][:2])
-		st.append('m')
-		self.code.append('u')
-		self.code.append(tuple(st))
-		for i in range(len(pointlist)):
-			st = list(pointlist[i][2:])
-			st.append('c')
-			self.code.append(tuple(st))
-		self.code.append(op + ' U')
-			
-
-
-	def drawRect(self, x1, y1, x2, y2, edgeColor=None, 
-			edgeWidth=None, fillColor=None, closed=0):
-		self._updateFillColor(fillColor)
-		self._updateLineWidth(edgeWidth)
-		self._updateLineColor(edgeColor)
-		w = self._currentWidth/2
-		self._bounds(x1-w, y1-w, x2+w, y2+w)
-		if self._currentLineColor != transparent:
-			if closed:
-				if self._currentFillColor != transparent:
-					op = 'b'	#closepath, eofill, stroke
-				else:
-					op = 's'  # closepath and stroke
-			else:
-				if self._currentFillColor != transparent:
-					op = 'B'	#eofill, stroke
-				else:
-					op = 'S'  # stroke
-		else:
-			op = 'f'
-		self.code.append('u')
-		self.code.append(x1, y1, 'm' )
-		self.code.append(x1, y2, 'L')
-		self.code.append(x2, y2, 'L')
-		self.code.append(x2, y1, 'L')
-		self.code.append(x1, y1, 'L')
-		self.code.append(op + ' U')
-
-	def drawRoundRect(self, x1, y1, x2, y2, rx=8, ry=8,
-					edgeColor=None, edgeWidth=None, fillColor=None):
-		self._updateLineWidth(edgeWidth)
-		self._updateLineColor(edgeColor)
-		self._updateFillColor(fillColor)
-		w = self._currentWidth
-		self._bounds(x1-w, y1-w, x2+w, y2+w)
-
-		pointlist = self.bezierArc(x1, y1, x1+2*rx, y1+2*ry, 90, 360)
-#		for c in pointlist:
-#			print c
-
-		trans = [(0, 0), (0, y2-y1-2*ry), (x2-x1-2*rx, y2-y1-2*ry), (x2-x1-2*rx, 0)]
-		st = list(pointlist[0][:2])
-		stx = [0, 2, 4, 6]
-		sty = [1, 3, 5, 7]
-		st.append('m')
-#		print st
-		self.code.append('u')
-		self.code.append(tuple(st))
-		for i in range(len(pointlist)):
-			sl = list(pointlist[i])
-			for k in stx:
-				sl[k] = sl[k] + trans[i][0]
-			for k in sty:
-				sl[k] = sl[k] + trans[i][1]
-			sk = sl[2:]
-			sl = sl[:2]
-			sk.append('c')
-			sl.append('l')
-#			print sl
-#			print sk
-			if i <> 0:
-				self.code.append(tuple(sl))
-			self.code.append(tuple(sk))
-
-		if self._currentLineColor != transparent:
-			if self._currentFillColor != transparent:
-				op = 'b'	#closepath, eofill, stroke
-			else:
-				op = 's'  # closepath and stroke
-		else:
-			op = 'f'
-		self.code.append( op + ' U')
-
-
-	def drawEllipse(self, x1, y1, x2, y2,
-					edgeColor=None, edgeWidth=None, fillColor=None):
-		self._updateLineWidth(edgeWidth)
-		self._updateLineColor(edgeColor)
-		self._updateFillColor(fillColor)
-		w = self._currentWidth/2
-		self._bounds(x1-w, y1-w, x2+w, y2+w)
-		pointlist = self.bezierArc(x1, y1, x2, y2, 0, 360)
-#		print pointlist, type(pointlist)
-		st = list(pointlist[0][:2])
-		st.append('m')
-		self.code.append('u')
-		self.code.append(tuple(st))
-		for i in range(len(pointlist)):
-			st = list(pointlist[i][2:])
-			st.append('c')
-			self.code.append(tuple(st))
-
-		if self._currentLineColor != transparent:
-			if self._currentFillColor != transparent:
-				op = 'b'	#closepath, eofill, stroke
-			else:
-				op = 's'  # closepath and stroke
-		else:
-			op = 'f'
-		self.code.append( op + ' U')
-
-
-	def drawPolygon(self, pointlist, edgeColor=None, 
-			edgeWidth=None, fillColor=None, closed=0):
-		start = pointlist[0]
-		pointlist = pointlist[1:]
-		x1 = min(map(lambda (x,y) : x, pointlist))
-		x2 = max(map(lambda (x,y) : x, pointlist))
-		y1 = min(map(lambda (x,y) : y, pointlist))
-		y2 = max(map(lambda (x,y) : y, pointlist))
-		self._updateFillColor(fillColor)
-		self._updateLineWidth(edgeWidth)
-		self._updateLineColor(edgeColor)
-		w = self._currentWidth/2
-		self._bounds(x1-w, y1-w, x2+w, y2+w)
-		self.code.append('u')
-		self.code.append(start[0], start[1], 'm')
-		for point in pointlist:
-			self.code.append(point[0], point[1], 'L')
-		if self._currentLineColor != transparent:
-			if closed:
-				self.code.append(start[0], start[1], 'L')
-				if self._currentFillColor != transparent:
-					op = 'b'	#closepath, eofill, stroke
-				else:
-					op = 's'  # closepath and stroke
-			else:
-				if self._currentFillColor != transparent:
-					op = 'B'	#eofill, stroke
-				else:
-					op = 'S'  # stroke
-		else:
-			op = 'f'
-		self.code.append(op + ' U')
-
-
-	def drawString():
-		print "Sorry Not yet impemented"
-
-
-
-##########################################################
-#
-#  tests
-#
-##########################################################
-
-
-
-
-def test():
-	c = AICanvas('piddletest3')
-	c.drawLine(000, 000, 000, 400, magenta, 20)
-	c.drawLine(000, 400, 400, 400, cyan, 20)
-	c.drawLine(400, 400, 400, 000, yellow, 20)
-	c.drawLine(400, 000, 000, 000, black, 20)
-	
-	Lines = [   (100, 100, 300, 100),
-				(300, 100, 300, 300),
-				(300, 300, 100, 300),
-				(100, 300, 100, 100)  ]
-	c.drawLines(Lines, red, 10)
-##	
-##	f = Font()
-##	f.face = 'Times'
-##	f.bold = 1
-##	f.size = 24
-##
-##	c.drawString('AI PIDDLE RULES!', 
-##			100,450, font=f, angle=0, color=teal)
-##	
-##
-##	for i in range(12):
-##		c.drawString('------AI PIDDLE RULES!', 
-##			300,600, angle=i*15)
-##			
-	#curve
-	c.drawCurve(100,500,300,500,400,600,400,800,
-			edgeColor=purple,
-			edgeWidth=1,
-			fillColor=yellow,
-			closed=1)	
-
-	#rectangle
-	c.drawRect(100,550,200,600,
-			edgeColor=purple,
-			edgeWidth=1,
-			fillColor=yellow,
-			closed=1)	
-
-	#polygon
-	c.drawPolygon([(72,72),(72,150),(80,100), (200,72), (130, 40)],
-			edgeColor=blue,
-			edgeWidth=6,
-			closed=0)
-	#arc
-	c.drawEllipse( 130,30, 200,100,
-		fillColor=green, 
-		edgeWidth=4 )
-#
-	c.drawArc( 130,30, 200,100, 45, 180, 
-		edgeColor=red, 
-		edgeWidth=4 )
-		
-	c.drawRoundRect(0,0, 200,100, 30,30,
-		edgeColor=red, 
-		edgeWidth=2 )
-
-	c.flush()
-
-
-if __name__ == '__main__':
-		test()	
-		import sys
-		sys.exit(1)
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleFIG.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleFIG.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleFIG.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleFIG.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,712 +0,0 @@
-"""piddleFIG - a Fig version 3.2 backend for the PIDDLE drawing module
-
-Note that files are not stored in the .fig file itself, because the .fig format
-does not allow for that; instead, they are placed in a directory named after
-the name argument passed to FIGCanvas.
-
-Also note that the max number of colours is 512, due to a .fig limitation.
-
-2002 John J Lee
-
-"""
-
-# XXX
-# Bugs:
-#   string widths aren't right
-#   transparent lines aren't transparent
-# Could try harder:
-#   latex fonts not supported
-#   depths are all set to 50, which may be a mistake -- perhaps increment for
-#    every object plotted?
-#   could use default colours
-
-from piddle import *
-import piddlePSmetrics
-import math, string, re, os
-
-
-degrees = math.pi / 180
-# XXX should just use bp all the time, even for text?  not clear from piddle
-#   docs
-pt = 1200/72.27  # points
-bp = 1200/72  # big points
-bp_width = 80/72  # xfig measures in 1/80 for linewidths
-
-# lifted from piddlePS
-Roman="Roman"; Bold="Bold"; Italic="Italic"
-PSFontMapStdEnc = { ("helvetica", Roman): "Helvetica-Roman",
-                    ("helvetica", Bold): "Helvetica-Bold",
-                    ("helvetica", Italic): "Helvetica-Oblique",
-                    ("times", Roman) : "Times-Roman",
-                    ("times", Bold) : "Times-Bold",
-                    ("times", Italic) : "Times-Italic",
-                    ("courier", Roman) : "Courier-Roman",
-                    ("courier", Bold) : "Courier-Bold",
-                    ("courier", Italic) : "Courier-Oblique",
-                    ("symbol", Roman) : "Symbol",
-                    ("symbol", Bold) :  "Symbol",
-                    ("symbol", Italic) : "Symbol",
-                    "EncodingName" : 'StandardEncoding' }
-
-
-PSFontMapLatin1Enc = { ("helvetica", Roman): "Helvetica-Roman-ISOLatin1",
-                       ("helvetica", Bold): "Helvetica-Bold-ISOLatin1",
-                       ("helvetica", Italic): "Helvetica-Oblique-ISOLatin1",
-                       ("times", Roman) : "Times-Roman-ISOLatin1",
-                       ("times", Bold) : "Times-Bold-ISOLatin1",
-                       ("times", Italic) : "Times-Italic-ISOLatin1",
-                       ("courier", Roman) : "Courier-Roman-ISOLatin1",
-                       ("courier", Bold) : "Courier-Bold-ISOLatin1",
-                       ("courier", Italic) : "Courier-Oblique-ISOLatin1",
-                       ("symbol", Roman) : "Symbol",
-                       ("symbol", Bold) :  "Symbol",
-                       ("symbol", Italic) : "Symbol",
-                       "EncodingName" : 'Latin1Encoding' }
-
-def color_distance(col1, col2):
-    diff = col2 - col1
-    return math.sqrt(diff.red**2 + diff.green**2 + diff.blue**2)
-
-
-class FIGCanvas(Canvas):
-    """Fig version 3.2 format canvas.
-
-    This canvas is meant for generating Fig 3.2 format files (.fig), used for
-    editing in the free xfig vector graphics program for X windows.
-    """
-    header_fmt = \
-"""#FIG 3.2
-%(orientation)s
-%(justification)s
-%(units)s
-%(papersize)s
-%(magnification)f
-%(multiple-page)s
-%(transparent-color)d
-# Generated by PIDDLE
-1200 2
-"""
-    text_fmt = ("4 %(justification)d %(color)d %(depth)d %(pen_style)d "
-                "%(font)d %(font_size)f %(angle)f %(font_flags)d "
-                "%(height)f %(length)f %(x)d %(y)d %(text)s")
-    poly_fmt = ("2 %(line_type)d %(line_style)d %(thickness)d "
-                "%(pen_color)d %(fill_color)d %(depth)d %(pen_style)d "
-                "%(area_fill)d %(style_val)f %(join_style)d "
-                "%(cap_style)d %(radius)d "
-                "%(forward_arrow)d %(backward_arrow)d %(npoints)d")
-    color_fmt = "0 %d #%.2x%.2x%.2x"  # rgb value
-    coord_fmt = "%d %d"
-    # header options
-    # orientation
-    Landscape = "Landscape"
-    Portrait = "Portrait"
-    # justification
-    CenterJust = "Center"
-    FlushLeftJust = "Flush Left"
-    # units
-    Metric = "Metric"
-    Imperial = "Inches"
-    # papersize
-    Letter = "Letter"
-    Legal = "Legal"
-    Ledger = "Ledger"
-    Tabloid = "Tabloid"
-    A = "A"
-    B = "B"
-    C = "C"
-    D = "D"
-    E = "E"
-    A4 = "A4"
-    A3 = "A3"
-    A2 = "A2"
-    A1 = "A1"
-    A0 = "A0"
-    B5 = "B5"
-    # magnification (percentage)
-    # multiple-page
-    SinglePage = "Single"
-    MultiplePage = "Multiple"
-    # color number for transparent color for GIF export
-    # transparent-color
-    NoTransp = -2
-    BackgroundTransp = -1
-    # 0-31 for standard colors or 32- for user colors
-
-    # object types
-    Color = 0  # Color pseudo-object.
-    Arc = 1
-    Ellipse = 2
-    Polyline = 3  # includes polygon and box
-    Spline = 4  # includes closed/open approximated/interpolated/x-spline spline
-    Text = 5
-    Compound = 6  # Compound object which is composed of one or more objects
-
-    # line_style
-    DefaultLineStyle = -1
-    Solid = 0
-    Dashed = 1
-    Dotted = 2
-    Dash_dotted = 3
-    Dash_double_dotted = 4
-    Dash_triple_dotted = 5
-
-    # polyline style
-    PolyLine = 1
-    Box = 2
-    Polygon = 3
-    ArcBox = 4
-    BoundingBox = 5  # imported-picture bounding-box
-
-    # text justification
-    Left = 0
-    Center = 1
-    Right = 2
-
-    # font style
-    Rigid = 1  # text doesn't scale when scaling compound objects
-    Special = 2  # for LaTeX
-    Postscript = 4  # PostScript font (otherwise LaTeX font is used)
-    Hidden = 8  # Hidden text
-
-    # For font_flags bit 2 = 0 (LaTeX fonts)
-    DefaultFont = 0
-    Roman = 1
-    Bold = 2
-    Italic = 3
-    SansSerif = 4
-    Monospaced = 5
-
-    # For font_flags bit 2 = 1 (PostScript fonts)
-    DefaultFont = -1
-    TimesRoman = 0
-    TimesItalic = 1
-    TimesBold = 2
-    TimesBoldItalic = 3
-    AvantGardeBook = 4
-    AvantGardeBookOblique = 5
-    AvantGardeDemi = 6
-    AvantGardeDemiOblique = 7
-    BookmanLight = 8
-    BookmanLightItalic = 9
-    BookmanDemi = 10
-    BookmanDemiItalic = 11
-    Courier = 12
-    CourierOblique = 13
-    CourierBold = 14
-    CourierBoldOblique = 15
-    Helvetica = 16
-    HelveticaOblique = 17
-    HelveticaBold = 18
-    HelveticaBoldOblique = 19
-    HelveticaNarrow = 20
-    HelveticaNarrowOblique = 21
-    HelveticaNarrowBold = 22
-    HelveticaNarrowBoldOblique = 23
-    NewCenturySchoolbookRoman = 24
-    NewCenturySchoolbookItalic = 25
-    NewCenturySchoolbookBold = 26
-    NewCenturySchoolbookBoldItalic = 27
-    PalatinoRoman = 28
-    PalatinoItalic = 29
-    PalatinoBold = 30
-    PalatinoBoldItalic = 31
-    Symbol = 32
-    ZapfChanceryMediumItalic = 33
-    ZapfDingbats = 34
-
-    # join_style field (for lines)
-    Miter = 0
-    Bevel = 1
-    Round = 2
-
-    # cap_style (for lines, open splines and arcs only)
-    Butt = 0
-    Round = 1
-    Projecting = 2
-
-    # arrow_type (lines, arcs and open splines)
-    Stick = 0
-    Closed_triangle = 1
-    Closed_indented_butt = 2
-    Closed_pointed_butt = 3
-
-    # The arrow_style field is defined for lines, arcs and open splines
-    Hollow = 0
-    Filled = 1
-
-    # Colour values from 32 to 543 (512 total) are user colours and are defined
-    # in colour pseudo-objects (type 0).  At the moment these are always used,
-    # and the default colours never are.
-
-    # XXX Need to check actual hex values in xfig source before inserting these
-    # into the colors dictionary.
-
-    #Default = (-1)
-    from piddle import black, white, red, green ,blue, darkblue, lightblue, \
-         cyan, darkcyan, lightcyan, magenta, darkmagenta, yellow, gold
-    default_colors = [
-        (black, 0), (white, 7),
-        (red, 4),# (darkRed, 18), (lightRed, 19), (lightestRed, 20),
-        (green, 2),# (darkGreen, 12), (lightGreen, 13), (lightestGreen, 14),
-        (blue, 1), #(darkestBlue, 8),
-            (darkblue, 9), (lightblue, 10),
-##             (lightestBlue, 11),
-        (cyan, 3), (darkcyan, 15), (lightcyan, 16),# (lightestCyan, 17),
-        (magenta, 5), (darkmagenta, 21),# (lightmagenta, 22),
-##             (lightestMagenta, 23),
-        (yellow, 6),# (darkBrown, 24), (lightBrown, 25), (lightestBrown, 26),
-##         (darkestPink, 27), (darkPink, 28), (lightPink, 29), (lightestPink, 30),
-        (gold, 31)]
-
-    # We ignore the baroque system of fill styles, and rely on the normal color
-    # system.
-
-    # area fill field for white color
-    NotFilled = -1
-    Filled = 20
-    # -1 = not filled
-    # 0 = black
-    # ...  values from 1 to 19 are shades of grey, from darker to lighter
-    # 20 = white
-    # 21-40 not used
-    # 41-56 see patterns for colors, below
-
-    # area fill field for black or default color
-    # -1 = not filled
-    # 0 = white
-    # ...  values from 1 to 19 are shades of grey, from lighter to darker
-    # 20 = black
-    # 21-40 not used
-    # 41-56 see patterns for colors, below
-
-    # area fill field for all other colors
-    # -1 = not filled
-    # 0 = black
-    # ...  values from 1 to 19 are "shades" of the color, from darker to lighter.
-    # A shade is defined as the color mixed with black
-    # 20 = full saturation of the color
-    # ...  values from 21 to 39 are "tints" of the color from the color to white.
-    # A tint is defined as the color mixed with white
-    # 40 = white
-    # 41 = 30 degree left diagonal pattern
-    # 42 = 30 degree right diagonal pattern
-    # 43 = 30 degree crosshatch
-    # 44 = 45 degree left diagonal pattern
-    # 45 = 45 degree right diagonal pattern
-    # 46 = 45 degree crosshatch
-    # 47 = bricks
-    # 48 = circles
-    # 49 = horizontal lines
-    # 50 = vertical lines
-    # 51 = crosshatch
-    # 52 = fish scales
-    # 53 = small fish scales
-    # 54 = octagons
-    # 55 = horizontal "tire treads"
-    # 56 = vertical "tire treads"
-
-    # depth
-    # 0 ... 999, where larger value means object is deeper than (under) objects
-    # with smaller depth
-
-    # indices into font_map, below
-    Normal = 0
-    Bold = 1
-    Italic = 2
-    BoldItalic = 3
-
-    font_map = {
-        "times": (TimesRoman, TimesBold, TimesItalic, TimesBoldItalic),
-        "serif": (TimesRoman, TimesBold, TimesItalic, TimesBoldItalic),
-        "helvetica": (Helvetica, HelveticaBold, HelveticaOblique,
-                      HelveticaBoldOblique),
-        "sansserif": (Helvetica, HelveticaBold, HelveticaOblique,
-                      HelveticaBoldOblique),
-        "monospaced": (Courier, CourierBold, CourierOblique,
-                       CourierBoldOblique),
-        "courier": (Courier, CourierBold, CourierOblique, CourierBoldOblique),
-        "avantgarde": (AvantGardeBook, AvantGardeDemi, AvantGardeBookOblique,
-                       AvantGardeDemiOblique),
-        "bookman": (BookmanLight, BookmanDemi, BookmanLightItalic, BookmanDemiItalic),
-        "newcenturyschoolbook": (NewCenturySchoolbookRoman,
-                                 NewCenturySchoolbookBold,
-                                 NewCenturySchoolbookItalic,
-                                 NewCenturySchoolbookBoldItalic),
-        "palatino": (PalatinoRoman, PalatinoBold, PalatinoItalic,
-                     PalatinoBoldItalic),
-        "symbol": (Symbol, Symbol, Symbol, Symbol),
-        "zapfchancery": (ZapfChanceryMediumItalic,
-                         ZapfChanceryMediumItalic,
-                         ZapfChanceryMediumItalic,
-                         ZapfChanceryMediumItalic),
-        "zapfdingbats": (ZapfDingbats, ZapfDingbats, ZapfDingbats,
-                         ZapfDingbats)
-        }
-    defaultFace = "times"
-
-    text_sub = re.compile(r"([\177-\377])")
-
-    def __init__(self, size=(300,300), name='piddleFIG',
-                 fontMapEncoding=PSFontMapLatin1Enc):
-        Canvas.__init__(self, size, name)
-        self.fontMapEncoding = fontMapEncoding
-        self.size = size
-        self.name = name
-        self.color_code = []
-        self.code = []
-        # indexed by PIDDLE color instances, containing FIG color nr.
-        self.colors = {}
-        # indexed by PIL image instances, containing file names
-        self.images = {}
-        self.color_nr = 32  # first user-defined FIG color nr.
-        self.fileNameCounter = -1  # for saving images
-
-    def clear(self):
-        """Reset canvas to its default state."""
-        raise NotImplementedError, "clear"
-
-    def save(self, file=None, format=None):
-        """Write the current document to a file or stream and close the file.
-
-        The format argument is not used.
-
-        """
-        if file is None:
-            file = self.name
-            if not file.endswith(".fig"):
-                file = file+".fig"
-        f = open(file, "w")
-        header = self.header_fmt % {
-            "orientation": self.Portrait,
-            "justification": self.CenterJust,
-            # units only affects the rules displayed in xfig, not the internal
-            # units used in the file format
-            "units": self.Metric,
-            "papersize": self.A4,
-            "magnification": 100,
-            "multiple-page": self.SinglePage,
-            "transparent-color": self.BackgroundTransp
-            }
-        f.write(header)
-        for line in self.color_code+self.code:
-            f.write(line)
-            f.write("\n")
-
-    def drawLine(self, x1, y1, x2, y2, color=None, width=None):
-        """
-
-        x1: horizontal (right) coordinate of the starting point
-        y1: vertical (down) coordinate of the starting point
-        x2: horizontal (right) coordinate of the ending point
-        y2: vertical (down) coordinate of the ending point
-        color: Color of the line to draw; defaults to the Canvas's
-         defaultLineColor
-        width: width of the line to draw; defaults to the Canvas's
-         defaultLineWidth
-
-        """
-        if color is None:
-            color = self.defaultLineColor
-        if width is None:
-            width = self.defaultLineWidth
-        pointlist = [(x1, y1), (x2, y2)]
-        self._drawPolygon(pointlist, color, width, self.defaultFillColor, 0,
-                          self.NotFilled)
-
-    def drawPolygon(self, pointlist, 
-                    edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-        """Draw a set of joined-up line segments.
-
-        edgeColor: color of the polygon edges; defaults to the Canvas's
-         defaultLineColor
-        edgeWidth: width of the polygon edges; defaults to the Canvas's
-         defaultLineWidth
-        fillColor: color of the polygon interior; defaults to the Canvas's
-         defaultFillColor
-        closed: if 1, adds an extra segment smoothly connecting the first
-         vertex to the last; defaults to 0
-
-        """
-        if edgeColor is None:
-            edgeColor = self.defaultLineColor
-        if edgeWidth is None:
-            edgeWidth = self.defaultLineWidth
-        if fillColor is None:
-            fillColor = self.defaultFillColor
-        self._drawPolygon(pointlist, edgeColor, edgeWidth, fillColor, closed,
-                          self.Filled)
-
-    def _drawPolygon(self, pointlist, 
-                     edgeColor, edgeWidth, fillColor, closed,
-                     filled):
-        if closed:
-            line_type = self.Polygon
-            nr_points = len(pointlist) + 1
-        else:
-            line_type = self.PolyLine
-            nr_points = len(pointlist)
-        fig_edgeColor = self._figColor(edgeColor)
-        fig_fillColor = self._figColor(fillColor)
-        if fillColor == transparent:
-            fig_fillColor = 0
-            filled = self.NotFilled
-        if edgeColor == transparent:
-            # XXX should use background color, which is what, in general?
-            fig_edgeColor = 7  # white
-        code = self.poly_fmt % {
-            "line_type": line_type,
-            "line_style": self.Solid,
-            "thickness": edgeWidth*bp_width,  # 1/80 inch
-            "pen_color": fig_edgeColor,
-            "fill_color": fig_fillColor,
-            "depth": 50,  # XXX
-            "pen_style": 0,  # ignored
-            "area_fill": filled,
-            "style_val": 0,  # no meaning for solid lines
-            "join_style": self.Miter,
-            "cap_style": self.Projecting,
-            "radius": 1,  # only meaningful for ArcBox
-            "forward_arrow": 0,
-            "backward_arrow": 0,
-            "npoints": nr_points
-            }
-        self.code.append(code)
-        line = []
-        pointlist = map(lambda p, f=bp: (p[0]*bp, p[1]*bp), pointlist)
-        for coords in pointlist:
-            code = self.coord_fmt % coords
-            line.append(code)
-        if closed:
-            coords = pointlist[0]
-            line.append(self.coord_fmt % coords)
-        self.code.append(" ".join(line))
-
-    def _imageFileName(self):
-        self.fileNameCounter = self.fileNameCounter + 1
-        dir = os.path.splitext(self.name)[0]
-        try:
-            os.mkdir(dir)
-        except OSError:
-            # complain if directory already exists when saving first image
-            if self.fileNameCounter == 0: raise
-        return os.path.join(dir, "image%d.gif" % (self.fileNameCounter,))
-
-    def drawImage(self, image, x1,y1, x2=None,y2=None):
-        """Draw a bitmap image at specified coordinates.
-
-        If x2 and y2 are omitted, they are calculated from image size.
-        (x1,y1) is upper left of image, (x2,y2) is lower right of image in
-        PIDDLE coordinates.
-
-        image: a Python Imaging Library Image object
-        x1: leftmost edge of destination rectangle
-        y1: topmost edge of destination rectangle
-        x2: rightmost edge of destination rectangle (defaults to x1 plus the
-         image width)
-        y2: bottom of destination rectangle (defaults to y1 plus the image
-         height)
-
-        """
-        code = self.poly_fmt % {
-            "line_type": self.BoundingBox,
-            "line_style": self.DefaultLineStyle,  # no meaning
-            "thickness": 0,  # no meaning
-            "pen_color": 0,  # no meaning
-            "fill_color": 0,  # no meaning
-            "depth": 50,  # XXX
-            "pen_style": 0,  # ignored
-            "area_fill": self.NotFilled,
-            "style_val": 0,  # no meaning
-            "join_style": self.Miter,  # no meaning
-            "cap_style": self.Butt,  # no meaning
-            "radius": 1,  # no meaning
-            "forward_arrow": 0,  # no meaning
-            "backward_arrow": 0,  # no meaning
-            "npoints": 5
-            }
-        self.code.append(code)
-        # add the filename and bounding box in the reverse order to that
-        # specified in the fig 3.2 standard, because xfig wants it to be that
-        # way
-        fileName = self.images.get(image)
-        if fileName is None:
-            fileName = self._imageFileName()
-            image.save(fileName)
-            self.images[image] = fileName
-        code = "0 %s" % (fileName,)
-        self.code.append(code)
-        code = []
-        pointlist = [(x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1)]
-        pointlist = map(lambda p, f=bp: (p[0]*bp, p[1]*bp), pointlist)
-        for coords in pointlist:
-            code.append(self.coord_fmt % coords)
-        code = " ".join(code)
-        self.code.append(code)
-
-    def _figColor(self, color):
-        if color == transparent:
-            return None
-        fig_color = self.colors.get(color)
-        if fig_color is None:
-            fig_color = self._makeCustomColor(color)
-            self.colors[color] = fig_color
-        return fig_color
-
-    def _makeCustomColor(self, color):
-        code = self.color_fmt % (
-            self.color_nr,
-            255*color.red, 255*color.green, 255*color.blue)
-        self.color_nr = self.color_nr + 1
-        self.color_code.append(code)
-        return self.color_nr-1
-
-    def _findExternalFontName(self, font):
-        """Attempts to return proper font name.
-        PDF uses a standard 14 fonts referred to
-        by name. Default to self.defaultFont('Helvetica').
-        The dictionary allows a layer of indirection to
-        support a standard set of PIDDLE font names."""
-        # lifted from piddlePDF via piddlePS
-        piddle_font_map = {
-            'Times':'Times',
-            'times':'Times',
-            'Courier':'Courier',
-            'courier':'Courier',
-            'helvetica':'Helvetica',
-            'Helvetica':'Helvetica',
-            'symbol':'Symbol',
-            'Symbol':'Symbol',
-            'monospaced':'Courier',
-            'serif':'Times',
-            'sansserif':'Helvetica',
-            'ZapfDingbats':'ZapfDingbats',
-            'zapfdingbats':'ZapfDingbats',
-            'arial':'Helvetica'
-            }
-
-        try:
-            face = piddle_font_map[string.lower(font.face)]
-        except:
-            return 'Helvetica'
-
-        name = face + '-'
-        if font.bold and face in ['Courier','Helvetica','Times']:
-            name = name + 'Bold'
-        if font.italic and face in ['Courier', 'Helvetica']:
-            name = name + 'Oblique'
-        elif font.italic and face == 'Times':
-            name = name + 'Italic'
-
-        if name == 'Times-':
-            name = name + 'Roman'
-        # symbol and ZapfDingbats cannot be modified!
-
-        #trim and return
-        if name[-1] == '-':
-            name = name[0:-1]
-        return name
-
-    def stringWidth(self, s, font=None):
-        "Return the logical width of the string if it were drawn \
-        in the current font (defaults to self.font)."
-        # lifted from piddlePS
-        if not font:
-            font = self.defaultFont
-        fontname = self._findExternalFontName(font)
-        return 0.001 * font.size * piddlePSmetrics.psStringWidth(
-            s, fontname, self.fontMapEncoding["EncodingName"])
-
-    def fontAscent(self, font=None):
-        "Find the ascent (height above base) of the given font."
-        # lifted from piddlePS.py
-        if not font:
-            font = self.defaultFont
-        fontname = self._findExternalFontName(font)
-        return piddlePSmetrics.ascent_descent[fontname][0] * 0.001 * font.size
-
-    def fontDescent(self, font=None):
-        "Find the descent (extent below base) of the given font."
-        # lifted from piddlePS.py
-        if not font:
-            font = self.defaultFont
-        fontname = self._findExternalFontName(font)
-        return -piddlePSmetrics.ascent_descent[fontname][1] * 0.001 * font.size
-
-    def drawString(self, s, x, y, font=None, color=None, angle=0):
-        """Draw a string s at position x,y.
-
-        The color argument is ignored.
-        angle is in degrees?
-
-        s: the string to draw
-        x: horizontal (right) coordinate of the starting position for the text
-        y: vertical (down) coordinate of the starting position for the text
-        font: font face and style for drawing; defaults to the Canvas's
-         defaultFont
-        color: Color of the drawn text; defaults to the Canvas's
-         defaultLineColor
-        angle: angle (degrees counter-clockwise from +X) at which the text
-         should be drawn; defaults to 0
-
-        """
-        if font is None:
-            font = self.defaultFont
-        if color is None:
-            color = self.defaultLineColor
-        ss = s.split("\n")
-        for i in range(len(ss)):
-            s = ss[i]
-            self._drawString(s, x, y, font, color, angle, i)
-
-    def _drawString(self, s, x, y, font, color, angle, line_nr):
-        # units, where given below in comments, are what fig expects
-        fig_color = self._figColor(color)
-        if font.face is None:
-            face = self.defaultFace
-        else:
-            face = font.face
-        fig_font = self.font_map[face]
-        if not (font.bold or font.italic):
-            fig_font = fig_font[self.Normal]
-        elif font.bold and not font.italic:
-            fig_font = fig_font[self.Bold]
-        elif font.italic and not font.bold:
-            fig_font = fig_font[self.Italic]
-        elif font.italic and font.bold:
-            fig_font = fig_font[self.BoldItalic]
-
-        def escape(x): return "\\%.3o" % ord(x.group(1))
-        s = self.text_sub.sub(escape, s)
-
-        width = self.stringWidth(s, font)
-        height = self.fontHeight(font)
-        offset = line_nr*height
-        dx = offset*math.sin(angle*degrees)
-        dy = offset*math.cos(angle*degrees)
-        x, y = x+dx, y+dy
-        code = self.text_fmt % {
-            "justification": self.Left,
-            "color": fig_color,
-            "depth": 50,
-            "pen_style": 0,  # ignored
-            "font": fig_font,
-            "font_size": font.size,  # points
-            "angle": angle*degrees,  # radians
-            "font_flags": self.Postscript,
-            "height": height*pt,  # 1/1200 inch
-            "length": width*pt,  # 1/1200 inch
-            "x": x*bp,  # 1/1200 inch
-            "y": y*bp,  # 1/1200 inch
-            "text": s+"\\001"}  # XXX what is the \001 for??
-        self.code.append(code)
-        if font.underline:
-            dy = self.fontDescent(font)
-            st = math.sin(angle*degrees)
-            ct = math.cos(angle*degrees)
-            xoff = dy*st
-            yoff = dy*ct
-            x1 = x+xoff
-            y1 = y+yoff
-            x2 = (x+width)-width*(1.0-ct)+xoff
-            y2 = y-width*st+yoff
-            self.drawLine(x1,y1, x2,y2, color)
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleGL.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleGL.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleGL.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleGL.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,567 +0,0 @@
-#####
-#
-# glPiddle.py
-#
-#####
-# 
-# Copyright 1999 by David Ascher
-# 
-#                         All Rights Reserved
-# 
-# Permission to use, copy, modify, and distribute this software and its
-# documentation for any purpose and without fee is hereby granted,
-# provided that the above copyright notice appear in all copies and that
-# both that copyright notice and this permission notice appear in
-# supporting documentation, and that the name of David Ascher not be
-# used in advertising or publicity pertaining to distribution of the
-# software without specific, written prior permission.
-# 
-# DAVID ASCHER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
-# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
-# EVENT SHALL DAVID ASCHER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
-# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
-# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-#                      
-#####
-
-__version__ = 0.1 # public release -- Sep 28, 1999
-
-# TODO: fix GLU Tesselation code for polygon handling
-# TODO: fix font size information (ascent/descent), fix symbol font behavior
-# TODO: document the ToglCanvas vs. GlutCanvas issues, as well as
-#       GLTT vs. GLUT font handling
-# TODO: document apparent GLTT bug which causes the glitch in the
-#       basics test (missing character)
-# TODO: document fact that OpenGL doesn't specify pixel-specific behavior,
-#       which explains the spectrum issue.
-# $Log: piddleGL.py,v $
-# Revision 1.4  2000/08/03 23:03:24  clee
-# revert  "import PIL as package" fix because it breaks under win32
-#   - will try again for piddle 1.1 (possibly w/ PIL 1.1)
-#
-
-
-
-from piddle import *
-from OpenGL.GL import *
-from OpenGL.GLU import *
-try:
-    # the tesselator is currently broken.  As the latest spec says
-    # that polygon filling for non-simple polygons is undefined, it's
-    # 'more or less' ok.  I will fix it.
-    gluTessProperty
-    _have_tesselator = 0
-    class Combo:
-        def beginCB(self, i):
-            glBegin(i)
-        def endCB(self):
-            glEnd()
-        def vertexCB(self, O):
-            glVertex2d(O[0], O[1])
-        def combineCB(self, p1, p2, p3):
-            print len(p3)
-            return p3[0][-1]
-        def edgeFlagCB(self, *args):
-            pass
-        combo = Combo()
-
-except NameError:
-    _have_tesselator = 0
-
-from math import sin, cos, atan, pi
-import os
-_debug = 0
-_tryGLTTFirst = 0
-
-class _GLCanvas(Canvas):
-    defaultFont = Font()
-    def __init__(self, size=(300,300), name='piddleGL'):
-        self._inList = 0
-        self._lists = []
-        if _have_tesselator:
-            self.tesselator = gluNewTess()
-        self.defaultTransparency = 1.0
-        Canvas.__init__(self, size=size, name=name)
-        
-    def __setattr__(self, attribute, value):
-        self.__dict__[attribute] = value
-        if attribute == "defaultTransparency":
-            if hasattr(self, 'defaultLineColor'):
-                glColor4f(self.defaultLineColor.red,
-                          self.defaultLineColor.green,
-                          self.defaultLineColor.blue,
-                          self.defaultTransparency)
-        elif attribute == "defaultLineColor":
-            if not self._inList: self._startList()
-            glColor4f(self.defaultLineColor.red,
-                      self.defaultLineColor.green,
-                      self.defaultLineColor.blue,
-                      self.defaultTransparency)
-        elif attribute == "defaultLineWidth":
-            if not self._inList: self._startList()
-            glLineWidth(value/5.0)
-        
-    def __repr__(self):
-        return '<GLCanvas at ' + str(id(self)) + '>'
-    
-    __str__=__repr__
-    
-    def clear(self):
-        if self._inList: self._saveList()
-        self._lists = []
-        glClearColor(1,1,1,1)
-        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
-
-    def flush(self):
-        glFlush()
-
-    def _saveList(self):
-        glEndList()
-        self._inList = 0
-        
-    def _startList(self):
-        list = glGenLists(1)
-        self._lists.append(list)
-        glNewList(list, GL_COMPILE)
-        self._inList = 1
-
-    def _glSetup(self):
-        glMatrixMode(GL_MODELVIEW)
-        glLoadIdentity()
-        glMatrixMode(GL_PROJECTION)
-        glLoadIdentity()
-        glOrtho(0, float(self._width), float(self._height), 0, 0, 1)
-        glEnable(GL_POINT_SMOOTH)
-        glEnable(GL_LINE_SMOOTH)
-        glLineWidth(1.)
-        glEnable(GL_BLEND)
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
-        glClearColor(1,1,1,1)
-        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
-
-    def _drawList(self):
-        if self._inList: self._saveList()
-        IntType = type(1)
-        for list in self._lists:
-            if type(list) is IntType:
-                glCallList(list)
-            else:
-                func, args, kw = list
-                apply(func, args, kw)
-        glFlush()
-        
-    def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-        "Draw a straight line between x1,y1 and x2,y2."
-        if not self._inList: self._startList()
-        if color is None:
-            color = self.defaultLineColor
-        glColor4f(color.red, color.green, color.blue, self.defaultTransparency)
-        if width is None:
-            width = self.defaultLineWidth
-        glLineWidth(width)
-
-        x1, x2 = min(x1, x2), max(x1, x2)
-        y1, y2 = min(y1, y2), max(y1, y2)
-        dx = x2-x1
-        dy = y2-y1
-        if abs(dx) < 0.001:
-            a = pi/2.0
-        else:
-            a = atan(dy/dx)
-        delta_x = width/2.0*cos(a)
-        delta_y = width/2.0*sin(a)
-        x1 = x1 - delta_x
-        x2 = x2 + delta_x
-        y1 = y1 - delta_y
-        y2 = y2 + delta_y
-
-        glBegin(GL_LINES)
-        glVertex2f(x1, y1)
-        glVertex2f(x2, y2)
-        glVertex2f(0,0)
-        glEnd()
-        
-    def drawPolygon(self, pointlist, 
-                    edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-        """drawPolygon(pointlist) -- draws a polygon
-        pointlist: a list of (x,y) tuples defining vertices
-        closed: if 1, adds an extra segment connecting the last point
-                    to the first 
-        """
-        if not self._inList: self._startList()
-
-        if closed: pointlist.append(pointlist[0])
-
-        if fillColor is None:
-            fillColor = self.defaultFillColor
-        if fillColor is not transparent:
-            glColor4f(fillColor.red, fillColor.green,
-                      fillColor.blue, self.defaultTransparency)
-            if _have_tesselator:
-                self._drawTess(pointlist)
-            else:
-                glBegin(GL_POLYGON)
-                for x,y in pointlist:
-                    glVertex2f(x, y)
-                glEnd()
-	    #if self._inList: self._saveList()
-	    #self._lists.append(self._drawTess, (pointlist,), {})
-	    #self._startList()
-        if edgeWidth is None:
-            edgeWidth = self.defaultLineWidth
-        if edgeColor is not transparent:
-            glLineWidth(edgeWidth)
-            if edgeColor is None:
-                edgeColor = self.defaultLineColor
-            glColor4f(edgeColor.red, edgeColor.green, edgeColor.blue,
-                      self.defaultTransparency)
-            glBegin(GL_LINE_STRIP)
-            for x,y in pointlist:
-                glVertex2f(x, y)
-            glEnd()
-    
-
-    def _drawTess(self, pointlist):
-        if _have_tesselator:
-            gluTessNormal(self.tesselator, 0,0,1)
-            gluTessProperty(self.tesselator,
-			    GLU_TESS_WINDING_RULE,
-			    GLU_TESS_WINDING_ODD)
-            gluTessBeginPolygon(self.tesselator, combo)
-            gluTessBeginContour(self.tesselator)
-            for (x,y) in pointlist:
-                gluTessVertex(self.tesselator, (x,y,0.0), (x,y,combo))
-            gluTessEndContour(self.tesselator)
-            gluTessEndPolygon(self.tesselator)
-        else:
-            glBegin(GL_POLYGON)
-            for x,y in pointlist:
-                glVertex2f(x, y)
-            glEnd()
-
-    # no colors apply to drawImage; the image is drawn as-is
-
-    def drawImage(self, image, x1,y1, x2=None,y2=None):
-        """Draw a PIL Image into the specified rectangle.  If x2 and y2 are
-        omitted, they are calculated from the image size."""
-        w, h = image.size
-        if x2 is None: x2 = x1 + w
-        if y2 is None: y2 = y1 + h
-        glPixelZoom((x2-x1)/float(w), -(y2-y1)/float(h))
-        glRasterPos2(x1,y1)
-        # IMPORTANT -- otherwise can crash!
-        glBitmap(0, 0, 0, 0, 0, 0)
-        # in case we have files which aren't 4-byte aligned
-        glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
-        # could be LUMINANCE, RGB, RGBA  (could add more, but why?)
-        if image.mode == 'L':
-            mode = GL_LUMINANCE
-        elif image.mode == 'RGB':
-            mode = GL_RGB
-        elif image.mode == 'RGBA':
-            mode = GL_RGBA
-        else:
-            image = image.convert('RGBA')
-            mode = GL_RGBA
-        glDrawPixels(w, h, mode, GL_UNSIGNED_BYTE, image.tostring())
-
-    def save(self, fname):
-        glFlush()
-        base, ext = os.path.splitext(fname)
-        if ext != '.ppm':
-            try:
-                import Image
-            except ImportError:
-                raise ImportError, 'Saving to a non-PPM format is not available because PIL is not installed'
-            savefname = base+'.ppm'
-            glSavePPM(savefname, self._width, self._height)
-            i = Image.open(savefname)
-            i.save(fname)
-            os.unlink(savefname)
-        else:
-            glSavePPM(fname, self._width, self._height)
-
-    def stringWidth(self, s, font=None):
-        if font is None: font = self.defaultFont
-        return FontWrapper(font).stringWidth(s)
-
-    def fontHeight(self, font=None):
-        if font is None: font = self.defaultFont
-        return FontWrapper(font).fontHeight(s)
-
-    def fontAscent(self, s='l', font=None):
-        if font is None: font = self.defaultFont
-        # XXX hack
-        return FontWrapper(font).fontHeight(s)*.8
-
-    def fontHeight(self, s='l', font=None):
-        if font is None: font = self.defaultFont
-        # XXX hack
-        return FontWrapper(font).fontHeight(s)
-
-    def fontDescent(self, s='g', font=None):
-        if font is None: font = self.defaultFont
-        # XXX hack
-        return FontWrapper(font).fontHeight(s)*.2
-
-    def drawString(self, s, x,y, font=None, color=None, angle=0):
-        if '\n' in s or '\r' in s:
-            self.drawMultiLineString(s, x,y, font, color, angle)
-            return
-        if color is None: color = self.defaultLineColor
-        if font is None: font = self.defaultFont
-        glColor4f(color.red, color.green,color.blue, 1)
-        glTranslate(float(x),float(y),0)
-        glScale(1,-1,1)
-        glRotate(angle,0,0,1)
-        # for some reason, GLTTFont calls can't be in CallLists
-        self._saveList()
-        self._lists.append(FontWrapper(font).render, (s, angle), {})
-        self._startList()
-        # end of workaround
-        glRotate(-angle,0,0,1)
-        glScale(1,-1,1)
-        glTranslate(-float(x),-float(y),0)
-
-from GLTT.GLTTFont import MyGLTTFont
-def getGLTTFontWrapper():
-    class FontWrapper(MyGLTTFont):
-        maps = {'newyork': 'times',
-                'times':'times',
-                'serif': 'times',
-                'sansserif': 'arial',
-                'symbol': 'symbol',
-                'monospaced': 'cour',
-                'courier': 'cour',
-                'helvetica': 'arial'}
-
-        psfaces = {'arial': 'Helvetica',
-		   'arialbd': 'Helvetica-Bold',
-		   'ariali': 'Helvetica-Oblique',
-		   'cour': 'Courier',
-		   'courb': 'Courier-Bold',
-		   'times': 'Times-Roman',
-		   'timesbd': 'Times-Bold',
-		   'timesi': 'Times-Italic',
-		   'timesbi': 'Times-BoldItalic',
-		   'symbol': 'Symbol'}
-
-        def __init__(self, font):
-            bold=font.bold
-            italic=font.italic
-            underline=font.underline
-            face=font.face
-            self.fsize=size=font.size
-            if face is None: face = 'arial'
-            face = string.lower(face)
-            self.face = face
-            if self.maps.has_key(face):
-                face = self.maps[face]
-            if bold:
-                if italic:
-                    boldflag = 'b'
-                else:
-                    boldflag = 'bd'
-            else: boldflag = ''
-            if italic: italflag = 'i'
-            else: italflag = ''
-            glttface = face + boldflag + italflag
-            self.psface = self.psfaces.get(glttface, 'Courier')
-            MyGLTTFont.__init__(self, glttface, size)
-
-        def render(self, s, angle):
-            if angle == 0:
-                self.pfont.output(0,0, s)
-            else:
-                self.font.output(s)
-
-        def fontHeight(self,s):
-            return self.size(s)[1]
-
-        def stringWidth(self,s):
-            return self.pfont.getWidth(s)
-
-    return FontWrapper
-
-try:
-    from OpenGL.GLUT import *
-    class GlutCanvas(_GLCanvas):
-        def __init__(self, size=(300,300), name='GlutCanvas'):
-            width, height = size
-            self._width = width
-            self._height = height
-            glutInit(name)
-            glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
-            glutInitWindowSize(width, height)
-            glutCreateWindow(name)
-            glutSetReshapeFuncCallback(self.reshape)
-            glutReshapeFunc()
-            glutSetDisplayFuncCallback(self.display)
-            glutDisplayFunc()
-            glutSetMouseFuncCallback(self.mouse)
-            glutMouseFunc()
-            glutSetKeyboardFuncCallback(self.keyboard)
-            glutKeyboardFunc()
-            _GLCanvas.__init__(self, size=size, name=name)
-
-        def reshape(self, x, y):
-            #glViewport(0, 0, x, y)
-            self._winHeight = y
-
-        def display(self):
-            if self._inList: self._saveList()
-            self._glSetup()
-            glTranslate(0,-self._winHeight+self._height,0)
-            self._drawList()
-            glutSwapBuffers()
-
-        def mouse(self, button, state, x, y):
-            pass
-
-        def keyboard(*args):
-            print args
-
-        def mainloop(self):
-            glutMainLoop()
-    if _debug: print "# GlutCanvas available"
-except NameError:
-    pass
-
-try:
-    import Tkinter
-    from OpenGL.Tk import RawOpengl
-    class TkInteractive:
-        def __init__(self):
-            self.bind('<Button-1>', self.mouseDown)
-            self.bind('<B1-Motion>', self.mouseMove)
-            self.bind('<Motion>', self.mouseMove)
-            self.bind('<Key>', self.keyDown)
-
-        def mouseDown(self, e):
-            if hasattr(self, 'onClick'):
-                self.onClick(self, e.x, e.y)
-
-        def mouseMove(self, e):
-            if hasattr(self, 'onOver'):
-                self.onOver(self, e.x, e.y)
-
-        def keyDown(self, e):
-            if hasattr(self, 'onKey'):
-                self.onKey(self, e.char, e.keysym)
-
-    class ToglCanvas(_GLCanvas, RawOpengl, TkInteractive):
-        def __init__(self, size=(300,300), name='piddleGL', 
-                     master=None, double=1, depth=1,
-                     **kw):
-            width, height = size
-            kw.update({'master':master,
-                   'double':1,
-                   'depth':depth,
-                   'width':width,
-                   'height':height})
-            self._width = width
-            self._height = height
-            apply(RawOpengl.__init__, (self,), kw)
-            _GLCanvas.__init__(self, size=size, name=name)
-            TkInteractive.__init__(self)
-            self.bind('<Configure>', self.resize)
-            self._texts = []
-
-        def resize(self, e):
-            w , h = e.width, e.height
-            self.configure(width=w, height=h)
-            self._width = w
-            self._height= h 
-            Tkinter.Frame.configure(self)
-
-        def redraw(self):
-            if self._inList: self._saveList()
-            self._glSetup()
-            self._drawList()
-
-        def isInteractive(self):
-            return 1
-
-        def canUpdate(self):
-            return 1
-
-        def setInfoLine(self, s):
-            pass
-    if _debug: print "# ToglCanvas available"
-except ImportError:
-    pass
-
-try:
-    GLCanvas = ToglCanvas
-except NameError:
-    GLCanvas = GlutCanvas
-except NameError:
-    raise ImportError, "Couldn't get either GLUT or Togl loaded"
-
-def getGLUTFontWrapper():
-    class GLUTFontWrapper:
-        maps = {'newyork': 'glutStrokeRoman',
-                'times':'glutStrokeRoman',
-                'serif': 'glutStrokeRoman',
-                'sansserif': 'glutStrokeRomanFixed',
-                'monospaced': 'glutStrokeRomanFixed',
-                'courier': 'glutStrokeRomanFixed',
-                'helvetica': 'glutStrokeRomanFixed'}
-
-        def __init__(self, font):
-            self.bold=font.bold
-            face=font.face
-            self.size=font.size
-            if face is None: face = 'glutStrokeRomanFixed'
-            face = string.lower(face)
-            if self.maps.has_key(face):
-                face = self.maps[face]
-            self.glutface = face
-        def stringWidth(self, s):
-            x = 0
-            for c in s:
-                x = x + glutStrokeWidth(self.glutface, ord(c))
-            return x * self.size / 100.0
-
-        def fontHeight(self, s):
-            return self.size
-
-        def render(self, s, angle):
-            glPushMatrix()
-            if self.bold:
-                glLineWidth(1.9)
-            else:
-                glLineWidth(1.0)
-            glScale(1/100.0, 1/100.0, 1.0)
-            glScale(self.size, self.size, 1.0)
-            for c in s:
-                glutStrokeCharacter(self.glutface, ord(c))
-            glPopMatrix()
-
-    return GLUTFontWrapper
-
-
-if _tryGLTTFirst:
-    firstTry = getGLTTFontWrapper
-    secondTry = getGLUTFontWrapper
-else:
-    firstTry = getGLUTFontWrapper
-    secondTry = getGLTTFontWrapper
-
-try:
-    FontWrapper = firstTry()
-    FontSupport = 1 
-except ImportError:
-    try:
-        FontWrapper = secondTry()
-        FontSupport = 1 
-    except ImportError:
-        FontSupport = 0
-
-if _debug:
-    if FontSupport == 0:	
-        print "# Can't find font support"
-    else:
-        print "# Using fonts from:", FontWrapper.__name__
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddlePDF.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddlePDF.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddlePDF.py	2005-12-15 06:58:21.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddlePDF.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,671 +0,0 @@
-# pdfpiddle.py - a PDF backend for PIDDLE
-"""This is the PIDDLE back end for PDF.  It acts as a wrapper
-over the pdfgen.Canvas class, and translates between the
-PIDDLE graphics state and the PDF/PostScript one. It only
-exposes PIDDLE methods; however, it has an attribute
-self.pdf which offers numerous lower-level drawing routines.
-"""
-
-# Copyright Robinson Analytics 1998-9
-# 6/10/99 - near total rewrite.  This is just a wrapper
-# on top of a lower-level graphics canvas which speaks the
-# Postscript metaphor.  Offers native methods for everything
-# except drawFigure, which doesn't behave like PostScript
-# paths so I left it unchanged.
-
-
-#standard python library modules
-import string
-import cStringIO
-import pdfmetrics
-import glob
-import os
-import types
-from math import sin, cos, pi, ceil
-
-# app specific
-import pagesizes
-from piddle import *
-import pdfgen
-import pdfgeom
-
-#edit this is the setting offends you, or set it in the constructor
-DEFAULT_PAGE_SIZE = pagesizes.A4
-#DEFAULT_PAGE_SIZE = pagesizes.letter
-
-
-
-##########################################################################
-#
-#      Font Mappings
-# The brute force approach to finding the correct postscript font name;
-# much safer than the rule-based ones we tried.
-# preprocessor to reduce font face names to the shortest list
-# possible.  Add any aliases you wish; it keeps looking up
-# until it finds no more translations to do.  Any input
-# will be lowercased before checking.
-font_face_map = {
-            'serif':'times',
-            'sansserif':'helvetica',
-            'monospaced':'courier',
-            'arial':'helvetica',
-            'times new roman': 'times'
-            }
-
-#maps a piddle font to a postscript one.
-ps_font_map = {
-            #face, bold, italic -> ps name
-            ('times', 0, 0) :'Times-Roman', 
-            ('times', 1, 0) :'Times-Bold', 
-            ('times', 0, 1) :'Times-Italic', 
-            ('times', 1, 1) :'Times-BoldItalic', 
-
-            ('courier', 0, 0) :'Courier', 
-            ('courier', 1, 0) :'Courier-Bold', 
-            ('courier', 0, 1) :'Courier-Oblique', 
-            ('courier', 1, 1) :'Courier-BoldOblique', 
-            
-            ('helvetica', 0, 0) :'Helvetica', 
-            ('helvetica', 1, 0) :'Helvetica-Bold', 
-            ('helvetica', 0, 1) :'Helvetica-Oblique', 
-            ('helvetica', 1, 1) :'Helvetica-BoldOblique', 
-
-            # there is only one Symbol font            
-            ('symbol', 0, 0) :'Symbol', 
-            ('symbol', 1, 0) :'Symbol', 
-            ('symbol', 0, 1) :'Symbol', 
-            ('symbol', 1, 1) :'Symbol', 
-
-            # ditto for dingbats
-            ('zapfdingbats', 0, 0) :'ZapfDingbats', 
-            ('zapfdingbats', 1, 0) :'ZapfDingbats', 
-            ('zapfdingbats', 0, 1) :'ZapfDingbats', 
-            ('zapfdingbats', 1, 1) :'ZapfDingbats', 
-      }
-            
-######################################################################
-#
-#     Canvas class
-#
-######################################################################
-
-class PDFCanvas(Canvas):
-    """This works by accumulating a list of strings containing 
-    PDF page marking operators, as you call its methods.  We could
-    use a big string but this is more efficient - only concatenate
-    it once, with control over line ends.  When
-    done, it hands off the stream to a PDFPage object."""
-
-    def __init__(self,
-            size=None,
-            name="piddlePDF.pdf",
-            pagesize=DEFAULT_PAGE_SIZE
-            ):
-        #if no extension, add .PDF
-        root, ext = os.path.splitext(name)
-        if ext == '':
-            name = root + '.pdf'
-
-        #create the underlying pdfgen canvas and set some attributes
-        self.pdf = pdfgen.Canvas(name, pagesize=pagesize, bottomup=0)  # add pagesize to constructor
-        # by default do not use comrpression (mod by cwl, may not be necessary w/ newer pdfgen)
-        self.pdf.setPageCompression(0)
-        
-        self.pdf.setLineCap(2)
-        #now call super init, which will trigger
-        #calls into self.pdf
-        
-        Canvas.__init__(self, size=size,name=name)
-
-        #memorize stuff
-        self.pagesize = pagesize
-        self.filename = name
-               
-        # self.pdf.setPageSize(pagesize) # This doesn't seem to work correctly -cwl 
-        if size == None:
-            #take the page size, which might not be default
-            self.drawingsize = self.pagesize
-        else:
-            #convenience for other platformslike GUI views
-            #we let them centre a smaller drawing in a page            
-            self.drawingsize = size  
-
-        self.pageTransitionString = ''
-        self.pageNumber = 1   # keep a count
-               
-        #if they specified a size smaller than page,
-        # be helpful and centre their diagram
-        if self.pagesize <> self.drawingsize:
-            dx = 0.5 * (self.pagesize[0] - self.drawingsize[0])
-            dy = 0.5 * (self.pagesize[1] - self.drawingsize[1])
-            self.pdf.translate(dx, dy)
-
-        
-    def _resetDefaults(self):
-        """Only used in setup - persist from page to page"""
-        self.defaultLineColor = black
-        self.defaultFillColor = transparent
-        self.defaultLineWidth = 1
-        self.defaultFont = Font()
-        self.pdf.setLineCap(2)
-        
-        
-    def showPage(self):
-        """ensure basic settings are the same after a page break"""
-        self.pdf.showPage()
-        self.defaultFont = self.defaultFont
-        self.defaultLineColor = self.defaultLineColor
-        self.defaultFillColor = self.defaultFillColor
-        self.defaultLineWidth = self.defaultLineWidth
-        self.pdf.setLineCap(2)
-
-        
-    #------------ canvas capabilities -------------
-    def isInteractive(self):
-        return 0
-
-    def canUpdate(self):
-        return 0
-
-    #------------ general management -------------
-    def clear(self):
-        "Not wll defined for file formats, use same as ShowPage"
-        self.showPage()
-
-    def flush(self):
-        pass
-
-    def save(self, file=None, format=None):
-        """Saves the file.  If holding data, do
-        a showPage() to save them having to.""" 
-
-        if self.pdf.pageHasData():
-            self.pdf.showPage()
-            
-        if hasattr(file, 'write'):
-            self.pdf.save(fileobj=file)
-        elif isinstance(file, types.StringType):
-            self.pdf.save(filename=file)
-        else:
-            self.pdf.save()
-
-
-
-    def setInfoLine(self, s):
-        self.pdf.setTitle(s)
-
-
-    #-------------handle assignment to defaultXXX-------
-
-    def __setattr__(self, key, value):
-        #we let it happen...
-        self.__dict__[key] = value
-        #...but take action if needed
-        if key == "defaultLineColor":
-            self._updateLineColor(value)
-        elif key == "defaultLineWidth":
-            self._updateLineWidth(value)
-        elif key == "defaultFillColor":
-            self._updateFillColor(value)
-        elif key == "defaultFont":
-            self._updateFont(value)
-            
-        
-        
-    def _updateLineColor(self, color):
-        """Triggered when someone assigns to defaultLineColor"""
-        self.pdf.setStrokeColorRGB(color.red, color.green, color.blue)
-        
-    def _updateFillColor(self, color):
-        """Triggered when someone assigns to defaultFillColor"""
-        self.pdf.setFillColorRGB(color.red, color.green, color.blue)
-        
-    def _updateLineWidth(self, width):
-        """Triggered when someone assigns to defaultLineWidth"""
-        self.pdf.setLineWidth(width)
-        
-    def _updateFont(self, font):
-        """Triggered when someone assigns to defaultFont"""
-        psfont = self._findPostScriptFontName(font)
-        self.pdf.setFont(psfont, font.size)
-        
-    def _findPostScriptFontName(self, font):
-        """Attempts to return proper font name."""
-
-        #step 1 - no face ends up serif, others are lowercased
-        if not font.face:
-            face = 'serif'
-        else:
-            face = string.lower(font.face)
-        while font_face_map.has_key(face):
-            face = font_face_map[face]
-        #step 2, - resolve bold/italic to get the right PS font name
-        psname = ps_font_map[(face, font.bold, font.italic)]
-        return psname
-    
-
-
-    
-    def _escape(self, s):
-        """PDF escapes are like Python ones, but brackets need slashes before them too.
-        Use Python's repr function and chop off the quotes first"""
-        s = repr(s)[1:-1]
-        s = string.replace(s, '(','\(')
-        s = string.replace(s, ')','\)')
-        return s
-
-    def resetDefaults(self):
-        """If you drop down to a lower level, PIDDLE can lose
-        track of the current graphics state.  Calling this after
-        wards ensures that the canvas is updated to the same
-        defaults as PIDDLE thinks they should be."""
-        self.defaultFont = self.defaultFont
-        self.defaultLineColor = self.defaultLineColor
-        self.defaultFillColor = self.defaultFillColor
-        self.defaultLineWidth = self.defaultLineWidth
-    #------------ string/font info ------------
-        
-    def stringWidth(self, s, font=None):
-        "Return the logical width of the string if it were drawn \
-        in the current font (defaults to self.font)."
-        if not font:
-            font = self.defaultFont
-        fontname = self._findPostScriptFontName(font)
-        return pdfmetrics.stringwidth(s, fontname) * font.size * 0.001
-
-
-    def fontHeight(self, font=None):
-        if not font:
-            font = self.defaultFont
-        return font.size
-
-    def fontAscent(self, font=None):
-        if not font:
-            font = self.defaultFont
-        fontname = self._findPostScriptFontName(font)
-        return pdfmetrics.ascent_descent[fontname][0] * 0.001 * font.size
-        
-    def fontDescent(self, font=None):
-        if not font:
-            font = self.defaultFont
-        fontname = self._findPostScriptFontName(font)
-        return -pdfmetrics.ascent_descent[fontname][1] * 0.001 * font.size
-
-    #------------- drawing helpers --------------
-    def _endPath(self, path, edgeColor, fillColor):
-        """in PIDDLE, the edge and fil colors might be transparent,
-        and might also be None, in which case they should be taken
-        from the defaults.  This leads to a standard 10 lines of code
-        when closing each shape, which are wrapped up here.  Use
-        these if you implement new PIDDLE shapes."""
-        #allow for transparent fills and lines
-        fill = fillColor or self.defaultFillColor
-        edge = edgeColor or self.defaultLineColor
-        if (fill == transparent and edge == transparent):
-            pass
-        else:
-            self.pdf.drawPath(
-                        path,
-                        (edge <> transparent),  #whether to stroke
-                        (fill <> transparent)   #whether to fill
-                        )
-        
-    #------------- drawing methods --------------
-
-   
-    def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-        """Calls the underlying methods in pdfgen.canvas.  For the
-        highest performance, use canvas.setDefaultFont and
-        canvas.setLineWidth, and draw batches of similar
-        lines together."""
-        #set the state if needed
-        if color:
-            self._updateLineColor(color)
-        if width:
-            self._updateLineWidth(width)
-
-        # now do the work
-        self.pdf.line(x1, y1, x2, y2)
-
-        #now reset state if needed
-        if color:
-            self._updateLineColor(self.defaultLineColor)
-        if width:
-            self._updateLineWidth(self.defaultLineWidth)
-        
-
-    def drawLines(self, lineList, color=None, width=None):
-        """Draws several distinct lines, all with same color
-        and width, efficiently"""
-        if color:
-            self._updateLineColor(color)
-        if width:
-            self._updateLineWidth(width)
-
-        self.pdf.lines(lineList)
-        
-        if color:
-            self._updateLineColor(self.defaultLineColor)
-        if width:
-            self._updateLineWidth(self.defaultLineWidth)
-        
-    
-    def drawString(self, s, x, y, font=None, color=None, angle=0):
-        """As it says, but many options to process.  It translates
-        user space rather than text space, in case underlining is
-        needed on rotated text.  It cheats and does literals
-        for efficiency, avoiding changing the python graphics state."""
-        self.pdf.addLiteral('%begin drawString')
-        col = color or self.defaultLineColor
-        if col != transparent:
-            if '\n' in s or '\r' in s:
-                #normalize line ends
-                s = string.replace(s, '\r\n','\n')
-                s = string.replace(s, '\n\r','\n')
-                lines = string.split(s, '\n')
-            else:
-                lines = [s]
-            fnt = font or self.defaultFont
-            self._updateFont(fnt)
-            text = self.pdf._escape(s)   
-
-            # start of Chris's hacking
-            # inserting basic commands here  to see if can get working
-            textobj = self.pdf.beginText()
-
-            if col <> self.defaultFillColor:
-                textobj.setFillColorRGB(col.red,col.green, col.blue)
-
-            if angle != 0 :
-                co = cos(angle * pi / 180.0) 
-                si = sin(angle * pi / 180.0)
-                textobj.setTextTransform(co, -si, si, co, x, y)  #top down coords so reverse angle
-            else :
-                textobj.setTextOrigin(x,y)
-
-            for line in lines:
-                #keep underlining separate - it is slow and unusual anyway
-                if fnt.underline:
-                    #breaks on angled text - FIXME
-                    ycursor = textobj.getY() # returns offset from last set origin 
-                    dy = 0.5 * self.fontDescent(fnt)
-                    width = self.stringWidth(line, fnt)
-                    linewidth = fnt.size * 0.1
-
-                    self.pdf.saveState()
-                    self.pdf.setLineWidth(linewidth)
-                    self.pdf.translate(x,y) # need to translate first before rotate
-                    if angle != 0 :
-                        self.pdf.rotate(-angle)
-                    self.pdf.translate(0, ycursor-y) #move down to start of current text line 
-                    self.pdf.line(0,dy, width, dy) 
-                    self.pdf.restoreState()
-                    lasty = ycursor
-                textobj.textLine(line) # adds text to textobj, advances getY's cursor
-            # finally actually send text object to the page
-            self.pdf.drawText(textobj)  # draw all the text afterwards? Doesn't seem right
-        self.pdf.addLiteral('%end drawString')
-        # done wth drawString()
-        
-    def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4,
-                  edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-        """This could do two totally different things.  If not closed,
-        just does a bezier curve so fill is irrelevant.  If closed,
-        it is actually a filled shape."""
-        if closed:
-            if edgeColor:
-                self._updateLineColor(edgeColor)
-            if edgeWidth:
-                self._updateLineWidth(edgeWidth)
-            if fillColor:
-                self._updateFillColor(fillColor)
-
-            p = self.pdf.beginPath()
-            p.moveTo(x1, y1)
-            p.curveTo(x2, y2, x3, y3, x4, y4)
-            p.close()
-            self._endPath(p, edgeColor, fillColor) #handles case of transparency
-            
-            if edgeColor:
-                self._updateLineColor(self.defaultLineColor)
-            if edgeWidth:
-                self._updateLineWidth(self.defaultLineWidth)
-            if fillColor:
-                self._updateFillColor(self.defaultFillColor)
-        else:
-            #just a plain old line segment
-            if edgeColor:
-                self._updateLineColor(edgeColor)
-            if edgeWidth:
-                self._updateLineWidth(edgeWidth)
-            
-            self.pdf.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
-
-            if edgeColor:
-                self._updateLineColor(self.defaultLineColor)
-            if edgeWidth:
-                self._updateLineWidth(self.defaultLineWidth)
-            
-    def drawRect(self, x1, y1, x2, y2, edgeColor=None, 
-            edgeWidth=None, fillColor=None):
-        if edgeColor:
-            self._updateLineColor(edgeColor)
-        if edgeWidth:
-            self._updateLineWidth(edgeWidth)
-        if fillColor:
-            self._updateFillColor(fillColor)
-
-        p = self.pdf.beginPath()
-        p.rect(x1, y1, x2-x1, y2-y1)
-        self._endPath(p, edgeColor, fillColor) #handles case of transparency
-        
-        if edgeColor:
-            self._updateLineColor(self.defaultLineColor)
-        if edgeWidth:
-            self._updateLineWidth(self.defaultLineWidth)
-        if fillColor:
-            self._updateFillColor(self.defaultFillColor)
-    
-    #drawRoundRect is inherited - cannot really improve on that one,
-    #and figures are quite efficient now.
-    def drawEllipse(self, x1,y1, x2,y2, edgeColor=None, edgeWidth=None, fillColor=None):
-        if edgeColor:
-            self._updateLineColor(edgeColor)
-        if edgeWidth:
-            self._updateLineWidth(edgeWidth)
-        if fillColor:
-            self._updateFillColor(fillColor)
-
-        p = self.pdf.beginPath()
-        p.ellipse(x1, y1, x2-x1, y2-y1)
-        self._endPath(p, edgeColor, fillColor) #handles case of transparency
-        
-        if edgeColor:
-            self._updateLineColor(self.defaultLineColor)
-        if edgeWidth:
-            self._updateLineWidth(self.defaultLineWidth)
-        if fillColor:
-            self._updateFillColor(self.defaultFillColor)
-    
-    def drawArc(self, x1,y1, x2,y2, startAng=0, extent=90, edgeColor=None,
-                edgeWidth=None, fillColor=None):
-        """This draws a PacMan-type shape connected to the centre.  One
-        idiosyncracy - if you specify an edge color, it apples to the
-        outer curved rim but not the radial edges."""
-        if edgeColor:
-            self._updateLineColor(edgeColor)
-        if edgeWidth:
-            self._updateLineWidth(edgeWidth)
-        if fillColor:
-            self._updateFillColor(fillColor)
-        # I need to do some more work on flipping the coordinate system -
-        # in pdfgen - note the angle reversal needed when drawing top-down.
-        pointList = pdfgeom.bezierArc(x1,y1, x2,y2, -startAng, -extent)
-        start = pointList[0]
-        end = pointList[-1]
-        x_cen = 0.5 * (x1 + x2)
-        y_cen = 0.5 * (y1 + y2)
-
-        #first the fill                
-        p = self.pdf.beginPath()
-        p.moveTo(x_cen, y_cen)
-        p.lineTo(start[0], start[1])
-        for curve in pointList:
-            p.curveTo(curve[2], curve[3], curve[4], curve[5], curve[6], curve[7])
-        p.close()  #back to centre
-        self._endPath(p, transparent, fillColor) #handles case of transparency
-        #now the outer rim
-        p2 = self.pdf.beginPath()
-        p2.moveTo(start[0], start[1])
-        for curve in pointList:
-            p2.curveTo(curve[2], curve[3], curve[4], curve[5], curve[6], curve[7])
-        self._endPath(p2, edgeColor, transparent) #handles case of transparency
-        
-        
-        if edgeColor:
-            self._updateLineColor(self.defaultLineColor)
-        if edgeWidth:
-            self._updateLineWidth(self.defaultLineWidth)
-        if fillColor:
-            self._updateFillColor(self.defaultFillColor)
-    
-
-    def drawPolygon(self, pointlist, edgeColor=None, 
-            edgeWidth=None, fillColor=None, closed=0):
-        """As it says.  Easy with paths!"""
-        if edgeColor:
-            self._updateLineColor(edgeColor)
-        if edgeWidth:
-            self._updateLineWidth(edgeWidth)
-        if fillColor:
-            self._updateFillColor(fillColor)
-
-        p = self.pdf.beginPath()
-        p.moveTo(pointlist[0][0], pointlist[0][1])
-        for point in pointlist[1:]:
-            p.lineTo(point[0], point[1])
-        if closed:
-            p.close()
-        
-        self._endPath(p, edgeColor, fillColor) #handles case of transparency
-        
-        if edgeColor:
-            self._updateLineColor(self.defaultLineColor)
-        if edgeWidth:
-            self._updateLineWidth(self.defaultLineWidth)
-        if fillColor:
-            self._updateFillColor(self.defaultFillColor)
-
-##    def drawFigure(self, partList,
-##            edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-##        """This is PIDDLE's attempt at Postscript paths.  Due to
-##        necessary limitations in the algorithm, if the start and end
-##        points are not connected but closed=1, then you get the full shape
-##        filled but the final line segment does not join up.  I have to
-##        do extra work to simulate this."""
-##        
-##        if edgeColor:
-##            self._updateLineColor(edgeColor)
-##        if edgeWidth:
-##            self._updateLineWidth(edgeWidth)
-##        if fillColor:
-##            self._updateFillColor(fillColor)
-##
-##        p1 = self.pdf.beginPath()  #use for the fill (i.e. closed)
-##        p2 = self.pdf.beginPath()  #use for the edge (may not be closed)
-##
-##        #move to first point
-##        start = (partList[0][1:3])
-##        end = None
-##        p1.moveTo(start[0], start[1])
-##        p2.moveTo(start[0], start[1])
-##        
-##        for tuple in partList:
-##            op = tuple[0]
-##            args = list(tuple[1:])
-##            start = args[0:2]
-##            # lineTo the start if not coincident with end of last segment
-##            if start <> end:
-##                p1.lineTo(start[0], start[1])
-##                p2.lineTo(start[0], start[1])
-##                
-##            #now draw appropriate segment           
-##            if op == figureLine:
-##                p1.lineTo(args[2], args[3])
-##                p2.lineTo(args[2], args[3])
-##                end = args[2:4]
-##            elif op == figureArc:
-##                #p1.arcTo(args[0], args[1], args[2], args[3], args[4], args[5])
-##                #p2.arcTo(args[0], args[1], args[2], args[3], args[4], args[5])
-##                p1.arc(args[0], args[1], args[2], args[3], args[4], args[5])
-##                p2.arc(args[0], args[1], args[2], args[3], args[4], args[5])
-##                end = args[2:4]
-##            elif op == figureCurve:
-##                p1.curveTo(args[2], args[3], args[4], args[5], args[6], args[7])
-##                p2.curveTo(args[2], args[3], args[4], args[5], args[6], args[7])
-##                end = args[6:8]
-##            else:
-##                raise TypeError, "unknown figure operator: " + op
-##
-##        #now for the weirdness
-##        p1.close()
-##        if closed:
-##            p2.close()
-##            print 'closed edge path'
-##        print 'inner path p1:' + p1.getCode()
-##        print 'outer path p2:' + p2.getCode()
-##        
-##        self._endPath(p1, transparent, fillColor)
-##        self._endPath(p2, edgeColor, transparent)
-##        
-##        if edgeColor:
-##            self._updateLineColor(self.defaultLineColor)
-##        if edgeWidth:
-##            self._updateLineWidth(self.defaultLineWidth)
-##        if fillColor:
-##            self._updateFillColor(self.defaultFillColor)
-
-        
-    def drawImage(self, image, x1,y1, x2=None,y2=None):
-        """Draw a PIL Image or image filename into the specified rectangle.
-        If x2 and y2 are omitted, they are calculated from the image size.
-        """
-        # chris starts meddling here -cwl
-        # piddle only takes PIL images 
-        im_width, im_height = image.size
-        if not x2 :
-            x2 = x1 + im_width
-        if not y2 :
-            y2 = y1 + im_height
-            
-        self.pdf.saveState()  # I'm changing coordinates to isolate the problem -cwl
-
-        self.pdf.translate(x1,y1)
-        self.pdf.drawInlineImage(image, 0, 0, abs(x1-x2), abs(y1-y2))
-
-        self.pdf.restoreState()
-        
-        ### original code below -cwl
-        #the underlying canvas uses a bott-up coord system, so flips things        
-        #if x2:
-            #width = abs(x2 - x1)
-            #x = min(x1, x2)
-        #if y2:
-            #height = abs(y2 - y1)
-            #y = min(y1, y2)
-        #self.pdf.drawInlineImage(image, x, y, width, height)        
-
-
-##########################################################
-#
-#   non-standard extensions outside Piddle API
-#
-##########################################################
-
-
-    def drawLiteral(self, literal):
-        #adds a chunk of raw stuff to the PDF contents stream
-        self.code.append(literal)
-
-
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddlePIL.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddlePIL.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddlePIL.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddlePIL.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,460 +0,0 @@
-# piddlePIL.py -- a Python Imaging Library backend for PIDDLE
-# Copyright (C) 1999  Joseph J. Strout
-# 
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-# 
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-# 
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-"""piddlePIL
-
-This module implements a Python Imaging Library PIDDLE canvas.
-In other words, this is a PIDDLE backend that renders into a
-PIL Image object.  From there, you can save as GIF, plot into
-another PIDDLE canvas, etc.
-        
-Joe Strout (joe@strout.net), 10/26/99
-"""
-
-###  6/22/99: updated drawString to handle non-integer x and y
-
-from piddle import *
-import Image
-import string
-
-import os, sys
-Log =  sys.stderr
-
-if __name__ == '__main__':
-    _fontprefix = os.path.join(os.curdir,'pilfonts')
-else:
-    _fontprefix = os.path.join(os.path.split(__file__)[0],'pilfonts')
-
-# load font metrics
-try:
-    f = open(os.path.join(_fontprefix,'metrics.dat'), 'rb')
-    import cPickle
-    _widthmaps = cPickle.load(f)
-    _ascents = cPickle.load(f)
-    _descents = cPickle.load(f)
-    f.close()
-except:
-    Log.write("Warning: unable to load font metrics!\n")
-    _widthmaps = {}
-    _ascents = {}
-    _descents = {}
-#finally:
-#   pass    # (just here so we can comment out the except clause for debugging)
-
-def _closestSize(size):
-    supported = [8,10,12,14,18,24]      # list of supported sizes
-    if size in supported: return size
-    best = supported[0]
-    bestdist = abs(size-best)
-    for trial in supported[1:]:
-        dist = abs(size - trial)
-        if dist < bestdist:
-            best = trial
-            bestdist = dist
-    return best
-
-def _pilFontPath(face,size,bold=0):
-    if face == 'monospaced': face = 'courier'
-    elif face == 'serif': face = 'times'
-    elif face == 'sansserif' or face == 'system': face = 'helvetica'
-
-    if bold and face != 'symbol': fname = "%s-bold-%d.pil" % (face,size)
-    else: fname = "%s-%d.pil" % (face,size)
-    path = os.path.join(_fontprefix,fname)
-    return path
-
-def _matchingFontPath(font):
-    # returns a font path which matches info in our font metrics
-    if font.face: face = font.face
-    else: face = 'times'
-
-    size = _closestSize(font.size)
-    if type(face) == StringType:
-        path = _pilFontPath(face,size,font.bold)
-        path = string.split(path,os.sep)[-1]
-        if path in _widthmaps.keys(): return path
-    else:
-        for item in font.face:
-            path = _pilFontPath(item,size,font.bold)
-            path = string.split(path,os.sep)[-1]
-            if path in _widthmaps.keys(): return path
-    # not found?  Try it with courier, which should always be there
-    path = _pilFontPath('courier',size,font.bold)
-    return string.split(path,os.sep)[-1]
-
-def _pilFont(font):
-    import ImageFont
-    if font.face: face = font.face
-    else: face = 'times'
-
-    size = _closestSize(font.size)
-    if type(face) == StringType:
-        try: 
-            pilfont = ImageFont.load_path(_pilFontPath(face,size,font.bold))
-        except:
-            return 0        # font not found!
-    else:
-        for item in font.face:
-            pilfont = None
-            try:
-                pilfont = ImageFont.load_path(_pilFontPath(item,size,font.bold))
-                break
-            except: pass
-        if pilfont == None: return 0    # font not found!
-    return pilfont
-
-
-class PILCanvas( Canvas ):
-    
-    def __init__(self, size=(300,300), name='piddlePIL'):
-        self._image = Image.new('RGB',size, (255,255,255))
-        import ImageDraw
-        self._pen = ImageDraw.ImageDraw(self._image)
-        self._pen.setink(0)
-        self._setFont( Font() )
-        self._pilversion = map(string.atoi, string.split(Image.VERSION, "."))
-        Canvas.__init__(self, size, name)
-        
-    def __setattr__(self, attribute, value):
-        self.__dict__[attribute] = value
-        if attribute == "defaultLineColor":
-            self._setColor(self.defaultLineColor)
-
-    # utility functions
-    def _setColor(self,c):
-        "Set the pen color from a piddle color."
-        self._pen.setink( (c.red*255, c.green*255, c.blue*255) )
-
-    def _setFont(self,font):
-        self._pen.setfont( _pilFont(font) )
-
-    # public functions
-
-    def getImage(self):
-        return self._image
-    
-    def save(self, file=None, format=None):
-        """format may be a string specifying a file extension corresponding to
-                an image file format. Ex: 'png', 'jpg', 'gif', 'tif' etc."""
-        file = file or self.name
-        if hasattr(file, 'write'):
-            self._image.save(file, format)
-            return
-                # below here, file is guaranteed to be a string
-        if format == None:
-            if '.' not in file:
-                raise TypeError, 'no file type given to save()'
-            filename = file
-        else:
-            filename = file + '.' + format
-        self._image.save(filename)
-
-
-        def clear(self) :
-                # why is edgeColor yellow ???
-                self.drawRect( 0,0,self.size[0],self.size[1], edgeColor=yellow,fillColor=white )
-                ### FIXME: need to reset canvas as well to defaults ???
-
-
-    #------------ string/font info ------------
-    def stringWidth(self, s, font=None):
-        "Return the logical width of the string if it were drawn \
-        in the current font (defaults to self.defaultFont)."
-
-        if not font: font = self.defaultFont
-        if not _widthmaps:
-            Log.write("warning no _widthmaps available\n")
-            return font.size * len(s)
-
-        path = _matchingFontPath(font)
-        map = _widthmaps[path]
-        out = 0
-        for c in s:
-            out = out + map[c]
-        return out          
-    
-    def fontAscent(self, font=None):
-        "Find the ascent (height above base) of the given font."
-
-        if not font: font = self.defaultFont
-        if not _ascents: return font.size
-        
-        path = _matchingFontPath(font)
-        return _ascents[path]
-    
-    def fontDescent(self, font=None):
-        "Find the descent (extent below base) of the given font."
-
-        if not font: font = self.defaultFont
-        if not _descents: return font.size/2
-        
-        path = _matchingFontPath(font)
-        return _descents[path]
-        
-    #------------- drawing methods --------------
-    def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-        "Draw a straight line between x1,y1 and x2,y2."
-        # set color...
-        if color:
-            if color == transparent: return
-            self._setColor(color)
-        elif self.defaultLineColor == transparent: return
-        
-        if width: w = width
-        else: w = self.defaultLineWidth
-        if w > 1:
-            # thick lines are not supported by PIL,
-            # so we'll have to implement them as polygons
-            self._pen.setfill(1)
-            hw = int((w-1)/2)
-            pts = []
-            if (x1<=x2 and y1<=y2):     # line down and to the right
-                pts.append( (x1-hw+w,y1-hw) )
-                pts.append( (x1-hw,  y1-hw) )
-                pts.append( (x1-hw,  y1-hw+w) )
-
-                pts.append( (x2-hw,  y2-hw+w) )
-                pts.append( (x2-hw+w,y2-hw+w) )
-                pts.append( (x2-hw+w,y2-hw) )
-
-            elif (x1<=x2):              # line up and to the right
-                pts.append( (x1-hw,  y1-hw) )
-                pts.append( (x1-hw,  y1-hw+w) )
-                pts.append( (x1-hw+w,y1-hw+w) )
-
-                pts.append( (x2-hw+w,y2-hw+w) )
-                pts.append( (x2-hw+w,y2-hw) )
-                pts.append( (x2-hw,  y2-hw) )
-                
-            elif (y1<=y2):              # line down and to the left
-                pts.append( (x1-hw+w,y1-hw+w) )
-                pts.append( (x1-hw+w,y1-hw) )
-                pts.append( (x1-hw,  y1-hw) )
-
-                pts.append( (x2-hw,  y2-hw) )
-                pts.append( (x2-hw,  y2-hw+w) )
-                pts.append( (x2-hw+w,y2-hw+w) )
-                
-            else:                       # line up and to the left
-                pts.append( (x1-hw,  y1-hw+w) )
-                pts.append( (x1-hw+w,y1-hw+w) )
-                pts.append( (x1-hw+w,y1-hw) )
-
-                pts.append( (x2-hw+w,y2-hw) )
-                pts.append( (x2-hw,  y2-hw) )
-                pts.append( (x2-hw,  y2-hw+w) )
-                
-            self._pen.polygon(pts)
-            
-        else:
-            # for width <= 1, just use fast line method
-            # This switch is not good.Must be updated when PIL
-            # changes it version. Argh.
-            if self._pilversion[0] < 1.0:
-                self._pen.line(x1,y1,x2,y2)  # pre 1.0 no tuple
-            else:
-                self._pen.line( (x1,y1,x2,y2) )
-
-
-    def drawPolygon(self, pointlist, 
-                edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-        """drawPolygon(pointlist) -- draws a polygon
-        pointlist: a list of (x,y) tuples defining vertices
-        """
-        # PIL's routine requires a sequence of tuples...
-        # the input is not so restricted, so fix it
-        pts = list(pointlist)
-        for i in range(len(pts)):
-            pts[i] = tuple(pts[i])
-
-        # set color for fill...
-        filling = 0
-        if fillColor:
-            if fillColor != transparent:
-                self._setColor(fillColor)
-                filling = 1
-        elif self.defaultFillColor != transparent:
-            self._setColor(self.defaultFillColor)
-            filling = 1
-
-        # do the fill
-        if filling:
-            self._pen.setfill(1)
-            self._pen.polygon(pts)
-        self._pen.setfill(0)
-            
-        # set color for edge...
-        if edgeColor:
-            self._setColor(edgeColor)
-        else:
-            self._setColor(self.defaultLineColor)
-
-        if edgeColor != transparent:        
-            # set edge width...
-            if edgeWidth == None: edgeWidth = self.defaultLineWidth
-
-            # draw the outline
-
-            if (closed or (pts[0][0]==pts[-1][0] and pts[0][1]==pts[-1][1])) \
-                     and edgeWidth <= 1:
-                self._pen.polygon(pts)
-            else:
-                # ...since PIL's polygon routine insists on closing,
-                # and does not support thick edges, we'll use our drawLine instead
-                # OFI: use default color/width to speed this up!
-                oldp = pts[0]
-                if closed: pts.append(oldp)
-                for p in pts[1:]:
-                    self.drawLine(oldp[0],oldp[1], p[0],p[1], edgeColor, edgeWidth)
-                    oldp = p
-
-    def drawString(self, s, x,y, font=None, color=None, angle=0):
-        "Draw a string starting at location x,y."
-        if '\n' in s or '\r' in s:
-            self.drawMultiLineString(s, x,y, font, color, angle) 
-            return
-        if not font: font = self.defaultFont
-        
-        if not color:
-            color = self.defaultLineColor
-        if color == transparent: return
-
-        # draw into an offscreen Image
-        # tmpsize was originally 1.2* stringWidth, added code to give enough room for single character strings (piddle bug#121995)
-        sHeight = (self.fontAscent(font) + self.fontDescent(font)) 
-        sWidth = self.stringWidth(s, font)
-        tempsize = max(sWidth*1.2, sHeight*2.0)
-
-        tempimg = Image.new('RGB',(tempsize,tempsize), (0,0,0))
-        import ImageDraw
-        temppen = ImageDraw.ImageDraw(tempimg)
-        temppen.setink( (255,255,255) )
-        pilfont = _pilFont(font)
-        if not pilfont: raise "bad font!", font
-        temppen.setfont( pilfont )
-        pos = [4, int(tempsize/2 - self.fontAscent(font)) - self.fontDescent(font)]
-        temppen.text( pos, s )
-        pos[1] = int(tempsize/2)
-        
-        # underline
-        if font.underline:
-            ydown = (0.5 * self.fontDescent(font) )
-            # thickness = 0.08 * font.size # may need to ceil this
-            temppen.line([(pos[0], pos[1]+ydown), (pos[0]+sWidth,pos[1]+ydown)])
-            
-        # rotate
-        if angle:
-            from math import pi, sin, cos
-            tempimg = tempimg.rotate( angle, Image.BILINEAR )
-            temppen = ImageDraw.ImageDraw(tempimg)
-            radians = -angle * pi/180.0
-            r = tempsize/2 - pos[0]
-            pos[0] = int(tempsize/2 - r * cos(radians))
-            pos[1] = int(pos[1] - r * sin(radians))
-            
-        ### temppen.rectangle( (pos[0],pos[1],pos[0]+2,pos[1]+2) ) # PATCH for debugging
-        # colorize, and copy it in
-        mask = tempimg.convert('L').point(lambda c:c)
-        temppen.setink( (color.red*255, color.green*255, color.blue*255) )
-        temppen.setfill(1)
-        temppen.rectangle( (0,0,tempsize,tempsize) )
-        self._image.paste( tempimg, (int(x)-pos[0],int(y)-pos[1]), mask )       
-
-
-        
-    def drawImage(self, image, x1,y1, x2=None,y2=None):
-        """Draw a PIL Image into the specified rectangle.  If x2 and y2 are
-        omitted, they are calculated from the image size."""
-
-        if x2 and y2:
-            bbox = image.getbbox()
-            if x2-x1 != bbox[2]-bbox[0] or y2-y1 != bbox[3]-bbox[1]:
-                image = image.resize( (x2-x1,y2-y1) )
-        self._image.paste( image, (x1,y1) )
-
-def test():
-#... for testing...
-    canvas = PILCanvas()
-
-    canvas.defaultLineColor = Color(0.7,0.7,1.0)    # light blue
-    canvas.drawLines( map(lambda i:(i*10,0,i*10,300), range(30)) )
-    canvas.drawLines( map(lambda i:(0,i*10,300,i*10), range(30)) )
-    canvas.defaultLineColor = black     
-    
-    canvas.drawLine(10,200, 20,190, color=red)
-
-    canvas.drawEllipse( 130,30, 200,100, fillColor=yellow, edgeWidth=4 )
-    
-    canvas.drawArc( 130,30, 200,100, 45,50, fillColor=blue, edgeColor=navy, edgeWidth=4 )
-    
-    canvas.defaultLineWidth = 4
-    canvas.drawRoundRect( 30,30, 100,100, fillColor=blue, edgeColor=maroon )
-    canvas.drawCurve( 20,20, 100,50, 50,100, 160,160 )
-    
-    canvas.drawString("This is a test!", 30,130, Font(face="times",size=16,bold=1), 
-            color=green, angle=-45)
-
-    canvas.drawString("This is a test!", 30,130, color=red, angle=-45)
-    
-    polypoints = [ (160,120), (130,190), (210,145), (110,145), (190,190) ]
-    canvas.drawPolygon(polypoints, fillColor=lime, edgeColor=red, edgeWidth=3, closed=1)
-    
-    canvas.drawRect( 200,200,260,260, edgeColor=yellow, edgeWidth=5 )
-    canvas.drawLine( 200,260,260,260, color=green, width=5 )
-    canvas.drawLine( 260,200,260,260, color=red, width=5 )
-
-    # now, for testing, save the image as a PNG file
-    canvas.flush()
-    canvas.getImage().save("test.png")
-
-    
-    return canvas
-
-def testit(canvas, s, x,y, font=None):
-    canvas.defaultLineColor = black
-    canvas.drawString(s, x,y, font=font)
-    canvas.defaultLineColor = blue
-    w = canvas.stringWidth(s, font=font)
-    canvas.drawLine(x,y, x+w,y)
-    canvas.drawLine(x,y-canvas.fontAscent(font=font), x+w,y-canvas.fontAscent(font=font))
-    canvas.drawLine(x,y+canvas.fontDescent(font=font), x+w,y+canvas.fontDescent(font=font)) 
-    
-def test2():
-    
-    canvas = PILCanvas()
-    testit( canvas, "Foogar", 20, 30 )
-    
-    testit( canvas, "Foogar", 20, 90, font=Font(size=24) )
-    global dammit
-    dammit = _pilFont(Font(size=24))
-    
-    testit( canvas, "Foogar", 20, 150, font=Font(face='courier',size=24) )
-    
-    testit( canvas, "Foogar", 20, 240, font=Font(face='courier') )
-    
-    
-    import piddleQD
-    global qdcanvas
-    try:
-        qdcanvas.close()
-    except: pass
-    qdcanvas = piddleQD.QDCanvas()
-    qdcanvas.drawImage( canvas.getImage(), 0, 0 );
-    
-
-if __name__ == '__main__': test()
-    
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddlePSmetrics.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddlePSmetrics.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddlePSmetrics.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddlePSmetrics.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,176 +0,0 @@
-#fontmetrics.py - part of PDFgen - copyright Andy Robinson 1999
-"""This contains pre-canned text metrics for the PDFgen package, and may also
-be used for any other PIDDLE back ends or packages which use the standard
-Type 1 postscript fonts.
-
-Its main function is to let you work out the width of strings; it exposes a 
-single function, stringwidth(text, fontname), which works out the width of a 
-string in the given font. This is an integer defined in em-square units - each
-character is defined in a 1000 x 1000 box called the em-square - for a 1-point high
-character.  So to convert to points, multiply by 1000 and then by point size.
-
-The AFM loading stuff worked for me but is not being heavily tested, as pre-canning
-the widths for the standard 14 fonts in Acrobat Reader is so much more useful. One
-could easily extend it to get the exact bounding box for each characterm useful for 
-kerning.
-
-
-The ascent_descent attribute of the module is a dictionary mapping font names
-(with the proper Postscript capitalisation) to ascents and descents.  I ought
-to sort out the fontname case issue and the resolution of PIDDLE fonts to 
-Postscript font names within this module, but have not yet done so.
-
-
-13th June 1999
-"""
-import string
-
-
-
-StandardEnglishFonts = [
-	'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique',  
-	'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 
-	'Helvetica-BoldOblique',
-	'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic',
-	'Symbol','ZapfDingbats']
-
-StandardRomanFonts = [
-	'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique',  
-	'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 
-	'Helvetica-BoldOblique',
-	'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic' ]
-
-
-NonRomanFonts = ["Symbol", "ZapfDingbats"]
-
-
-##############################################################
-#
-#			PDF Metrics
-# This is a preamble to give us a stringWidth function.
-# loads and caches AFM files, but won't need to as the
-# standard fonts are there already
-##############################################################
-
-widths = {'courier': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],'courier-bold': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],'courier-boldoblique': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],'courier-oblique': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,0,600,600,600,600,0,600,600,600,600,600,600,600,600,0,600,0,600,600,600,600,600,600,600,600,0,600,600,0,600,600,600,600,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,600,0,600,0,0,0,0,600,600,600,600,0,0,0,0,0,600,0,0,0,600,0,0,600,600,600,600,0,0,600],'helvetica': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,278,355,556,556,889,667,222,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,222,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,556,556,167,556,556,556,556,191,333,556,333,333,500,500,0,556,556,556,278,0,537,350,222,333,333,556,1000,1000,0,611,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,370,0,0,0,0,556,778,1000,365,0,0,0,0,0,889,0,0,0,278,0,0,222,611,944,611,0,0,834],'helvetica-bold': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,333,474,556,556,889,722,278,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,278,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,556,556,167,556,556,556,556,238,500,556,333,333,611,611,0,556,556,556,278,0,556,350,278,500,500,556,1000,1000,0,611,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,370,0,0,0,0,611,778,1000,365,0,0,0,0,0,889,0,0,0,278,0,0,278,611,944,611,0,0,834],'helvetica-boldoblique': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,333,474,556,556,889,722,278,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,278,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,556,556,167,556,556,556,556,238,500,556,333,333,611,611,0,556,556,556,278,0,556,350,278,500,500,556,1000,1000,0,611,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,370,0,0,0,0,611,778,1000,365,0,0,0,0,0,889,0,0,0,278,0,0,278,611,944,611,0,0,834],'helvetica-oblique': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,278,355,556,556,889,667,222,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,222,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,556,556,167,556,556,556,556,191,333,556,333,333,500,500,0,556,556,556,278,0,537,350,222,333,333,556,1000,1000,0,611,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,370,0,0,0,0,556,778,1000,365,0,0,0,0,0,889,0,0,0,278,0,0,222,611,944,611,0,0,834],
-          'symbol': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,713,500,549,833,778,439,333,333,500,549,250,549,250,278,500,500,500,500,500,500,500,500,500,500,278,278,549,549,549,444,549,722,667,722,612,611,763,603,722,333,631,722,686,889,722,722,768,741,556,592,611,690,439,768,645,795,611,333,863,333,658,500,500,631,549,549,494,439,521,411,603,329,603,549,549,576,521,549,549,521,549,603,439,576,713,686,493,686,494,480,200,480,549,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,620,247,549,167,713,500,753,753,753,753,1042,987,603,987,603,400,549,411,549,549,713,494,460,549,549,549,549,1000,603,1000,658,823,686,795,987,768,768,823,768,768,713,713,713,713,713,713,713,768,713,790,790,890,823,549,250,713,603,603,1042,987,603,987,603,494,329,790,790,786,713,384,384,384,384,384,384,494,494,494,494,0,329,274,686,686,686,384,384,384,384,384,384,494,494,790],'times-bold': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,555,500,500,1000,833,333,333,333,500,570,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,930,722,667,722,722,667,611,778,778,389,500,778,667,944,722,778,611,778,722,556,667,722,722,1000,722,722,667,333,278,333,581,500,333,500,556,444,556,444,333,500,556,278,333,556,278,833,556,500,556,556,444,389,333,556,500,722,500,500,444,394,220,394,520,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,500,500,167,500,500,500,500,278,500,500,333,333,556,556,0,500,500,500,250,0,540,350,333,500,500,500,1000,1000,0,500,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1000,0,300,0,0,0,0,667,778,1000,330,0,0,0,0,0,722,0,0,0,278,0,0,278,500,722,556,0,0,750],'times-bolditalic': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,389,555,500,500,833,778,333,333,333,500,570,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,832,667,667,667,722,667,667,722,778,389,500,667,611,889,722,722,611,722,667,556,611,722,667,889,667,611,611,333,278,333,570,500,333,500,500,444,500,444,333,500,556,278,278,500,278,778,556,500,500,500,389,389,278,556,444,667,500,444,389,348,220,348,570,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,389,500,500,167,500,500,500,500,278,500,500,333,333,556,556,0,500,500,500,250,0,500,350,333,500,500,500,1000,1000,0,500,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,944,0,266,0,0,0,0,611,722,944,300,0,0,0,0,0,722,0,0,0,278,0,0,278,500,722,500,0,0,750],'times-italic': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,420,500,500,833,778,333,333,333,500,675,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,675,675,675,500,920,611,611,667,722,611,611,722,722,333,444,667,556,833,667,722,611,722,611,500,556,722,611,833,611,556,556,389,278,389,422,500,333,500,500,444,500,444,278,500,500,278,278,444,278,722,500,500,500,500,389,389,278,500,444,667,444,444,389,400,275,400,541,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,389,500,500,167,500,500,500,500,214,556,500,333,333,500,500,0,500,500,500,250,0,523,350,333,556,556,500,889,1000,0,500,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,889,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,889,0,276,0,0,0,0,556,722,944,310,0,0,0,0,0,667,0,0,0,278,0,0,278,500,667,500,0,0,750],'times-roman': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,408,500,500,833,778,333,333,333,500,564,250,333,250,278,500,500,500,500,500,500,500,500,500,500,278,278,564,564,564,444,921,722,667,667,722,611,556,722,722,333,389,722,611,889,722,722,556,722,667,556,611,722,722,944,722,722,611,333,278,333,469,500,333,444,500,444,500,444,333,500,500,278,278,500,278,778,500,500,500,500,333,389,278,500,500,722,500,500,444,480,200,480,541,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,500,500,167,500,500,500,500,180,444,500,333,333,556,556,0,500,500,500,250,0,453,350,333,444,444,500,1000,1000,0,444,0,333,333,333,333,333,333,333,333,0,333,333,0,333,333,333,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,889,0,276,0,0,0,0,611,722,889,310,0,0,0,0,0,667,0,0,0,278,0,0,278,500,722,500,0,0,750],
-          'zapfdingbats': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,974,961,974,980,719,789,790,791,690,960,939,549,855,911,933,911,945,974,755,846,762,761,571,677,763,760,759,754,494,552,537,577,692,786,788,788,790,793,794,816,823,789,841,823,833,816,831,923,744,723,749,790,792,695,776,768,792,759,707,708,682,701,826,815,789,789,707,687,696,689,786,787,713,791,785,791,873,761,762,762,759,759,892,892,788,784,438,138,277,415,392,392,668,668,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,732,544,544,910,667,760,760,776,595,694,626,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,894,838,1016,458,748,924,748,918,927,928,928,834,873,828,924,924,917,930,931,463,883,836,836,867,867,696,696,874,0,874,760,946,771,865,771,888,967,888,831,873,927,970,234]
-}
-
-# The same fonts but now with the metrics indexed by the ISO-Latin-1
-# encoding (except symbol and zapfdingbats which use the stdencoding
-
-Latin1FontWidths = {  
- 'courier': [  600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600 ],
- 'courier-bold': [  600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600 ],
- 'courier-oblique': [  600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600 ],
- 'courier-boldoblique': [  600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600 ],
- 'helvetica': [  278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500 ],
- 'helvetica-bold': [  833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556 ],
- 'helvetica-oblique': [  833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500 ],
- 'helvetica-boldoblique': [  833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556 ],
- 'times-roman': [  889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 250, 333, 408, 500, 500, 833, 778, 180, 333, 333, 500, 564, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 564, 564, 564, 444, 921, 722, 667, 667, 722, 611, 556, 722, 722, 333, 389, 722, 611, 889, 722, 722, 556, 722, 667, 556, 611, 722, 722, 944, 722, 722, 611, 333, 278, 333, 469, 500, 333, 444, 500, 444, 500, 444, 333, 500, 500, 278, 278, 500, 278, 778, 500, 500, 500, 500, 333, 389, 278, 500, 500, 722, 500, 500, 444, 480, 200, 480, 541, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 250, 333, 500, 500, 500, 500, 200, 500, 333, 760, 276, 500, 564, 333, 760, 333, 400, 564, 300, 300, 333, 500, 453, 250, 333, 300, 310, 500, 750, 750, 750, 444, 722, 722, 722, 722, 722, 722, 889, 667, 611, 611, 611, 611, 333, 333, 333, 333, 722, 722, 722, 722, 722, 722, 722, 564, 722, 722, 722, 722, 722, 722, 556, 500, 444, 444, 444, 444, 444, 444, 667, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 564, 500, 500, 500, 500, 500, 500, 500, 500 ],
- 'times-bold': [  944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 250, 333, 555, 500, 500, 1000, 833, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 930, 722, 667, 722, 722, 667, 611, 778, 778, 389, 500, 778, 667, 944, 722, 778, 611, 778, 722, 556, 667, 722, 722, 1000, 722, 722, 667, 333, 278, 333, 581, 500, 333, 500, 556, 444, 556, 444, 333, 500, 556, 278, 333, 556, 278, 833, 556, 500, 556, 556, 444, 389, 333, 556, 500, 722, 500, 500, 444, 394, 220, 394, 520, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 944, 250, 333, 500, 500, 500, 500, 220, 500, 333, 747, 300, 500, 570, 333, 747, 333, 400, 570, 300, 300, 333, 556, 540, 250, 333, 300, 330, 500, 750, 750, 750, 500, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 389, 389, 389, 389, 722, 722, 778, 778, 778, 778, 778, 570, 778, 722, 722, 722, 722, 722, 611, 556, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556, 500, 556, 500 ],
- 'times-italic': [  833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 250, 333, 420, 500, 500, 833, 778, 214, 333, 333, 500, 675, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 675, 675, 675, 500, 920, 611, 611, 667, 722, 611, 611, 722, 722, 333, 444, 667, 556, 833, 667, 722, 611, 722, 611, 500, 556, 722, 611, 833, 611, 556, 556, 389, 278, 389, 422, 500, 333, 500, 500, 444, 500, 444, 278, 500, 500, 278, 278, 444, 278, 722, 500, 500, 500, 500, 389, 389, 278, 500, 444, 667, 444, 444, 389, 400, 275, 400, 541, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 833, 250, 389, 500, 500, 500, 500, 275, 500, 333, 760, 276, 500, 675, 333, 760, 333, 400, 675, 300, 300, 333, 500, 523, 250, 333, 300, 310, 500, 750, 750, 750, 500, 611, 611, 611, 611, 611, 611, 889, 667, 611, 611, 611, 611, 333, 333, 333, 333, 722, 667, 722, 722, 722, 722, 722, 675, 722, 722, 722, 722, 722, 556, 611, 500, 500, 500, 500, 500, 500, 500, 667, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 675, 500, 500, 500, 500, 500, 444, 500, 444 ],
- 'times-bolditalic': [  889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 250, 389, 555, 500, 500, 833, 778, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 832, 667, 667, 667, 722, 667, 667, 722, 778, 389, 500, 667, 611, 889, 722, 722, 611, 722, 667, 556, 611, 722, 667, 889, 667, 611, 611, 333, 278, 333, 570, 500, 333, 500, 500, 444, 500, 444, 333, 500, 556, 278, 278, 500, 278, 778, 556, 500, 500, 500, 389, 389, 278, 556, 444, 667, 500, 444, 389, 348, 220, 348, 570, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 250, 389, 500, 500, 500, 500, 220, 500, 333, 747, 266, 500, 606, 333, 747, 333, 400, 570, 300, 300, 333, 576, 500, 250, 333, 300, 300, 500, 750, 750, 750, 500, 667, 667, 667, 667, 667, 667, 944, 667, 667, 667, 667, 667, 389, 389, 389, 389, 722, 722, 722, 722, 722, 722, 722, 570, 722, 722, 722, 722, 722, 611, 611, 500, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556, 444, 500, 444 ],
-'symbol': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250,333,713,500,549,833,778,439,333,333,500,549,250,549,250,278,500,500,500,500,500,500,500,500,500,500,278,278,549,549,549,444,549,722,667,722,612,611,763,603,722,333,631,722,686,889,722,722,768,741,556,592,611,690,439,768,645,795,611,333,863,333,658,500,500,631,549,549,494,439,521,411,603,329,603,549,549,576,521,549,549,521,549,603,439,576,713,686,493,686,494,480,200,480,549,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,620,247,549,167,713,500,753,753,753,753,1042,987,603,987,603,400,549,411,549,549,713,494,460,549,549,549,549,1000,603,1000,658,823,686,795,987,768,768,823,768,768,713,713,713,713,713,713,713,768,713,790,790,890,823,549,250,713,603,603,1042,987,603,987,603,494,329,790,790,786,713,384,384,384,384,384,384,494,494,494,494,0,329,274,686,686,686,384,384,384,384,384,384,494,494,790, 250],
-'zapfdingbats': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,974,961,974,980,719,789,790,791,690,960,939,549,855,911,933,911,945,974,755,846,762,761,571,677,763,760,759,754,494,552,537,577,692,786,788,788,790,793,794,816,823,789,841,823,833,816,831,923,744,723,749,790,792,695,776,768,792,759,707,708,682,701,826,815,789,789,707,687,696,689,786,787,713,791,785,791,873,761,762,762,759,759,892,892,788,784,438,138,277,415,392,392,668,668,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,732,544,544,910,667,760,760,776,595,694,626,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,894,838,1016,458,748,924,748,918,927,928,928,834,873,828,924,924,917,930,931,463,883,836,836,867,867,696,696,874,0,874,760,946,771,865,771,888,967,888,831,873,927,970,234,250]}
-
-
-ascent_descent = {'Courier': (629, -157), 'Courier-Bold': (626, -142), 'Courier-BoldOblique': (626, -142), 'Courier-Oblique': (629, -157), 'Helvetica': (718, -207), 'Helvetica-Bold': (718, -207), 'Helvetica-BoldOblique': (718, -207), 'Helvetica-Oblique': (718, -207), 'Symbol': (0, 0), 'Times-Bold': (676, -205), 'Times-BoldItalic': (699, -205), 'Times-Italic': (683, -205), 'Times-Roman': (683, -217), 'ZapfDingbats': (0, 0)}
-
-def parseAFMfile(filename):
-    """Returns an array holding the widths of all characters in the font.
-    Ultra-crude parser"""
-    alllines = open(filename, 'r').readlines()
-    # get stuff between StartCharMetrics and EndCharMetrics
-    metriclines = []
-    between = 0
-    for line in alllines:
-        if string.find(string.lower(line), 'endcharmetrics') > -1:
-            between = 0
-            break
-        if between:
-            metriclines.append(line)
-        if string.find(string.lower(line), 'startcharmetrics') > -1:
-            between = 1
-            
-    # break up - very shaky assumption about array size
-    widths = [0] * 255
-    
-    for line in metriclines:
-        chunks = string.split(line, ';')
-        
-        (c, cid) = string.split(chunks[0])
-        (wx, width) = string.split(chunks[1])
-        #(n, name) = string.split(chunks[2])
-        #(b, x1, y1, x2, y2) = string.split(chunks[3])
-        widths[string.atoi(cid)] = string.atoi(width)
-    
-    # by default, any empties should get the width of a space
-    for i in range(len(widths)):
-        if widths[i] == 0:
-            widths[i] == widths[32]
-
-    return widths
-
-
-class FontCache:
-    """Loads and caches font width information on demand.  Font names
-    converted to lower case for indexing.  Public interface is stringwidth"""
-    def __init__(self):
-        global widths
-        self.__widtharrays = widths
-
-        
-    def loadfont(self, fontname):
-        filename = AFMDIR + os.sep + fontname + '.afm'
-        print 'cache loading',filename
-        assert os.path.exists(filename)
-        widths = parseAFMfile(filename)
-        self.__widtharrays[fontname] = widths
-
-    def getfont(self, fontname):
-        try:
-            return self.__widtharrays[fontname]
-        except:
-            try:
-                self.loadfont(fontname)
-                return self.__widtharrays[fontname]
-            except:
-                # font not found, use Courier
-                print 'Font',fontname,'not found - using Courier for widths'
-                return self.getfont('courier')
-    
-
-    def stringwidth(self, text, font):
-        widths = self.getfont(string.lower(font))
-        w = 0
-        for char in text:
-            w = w + widths[ord(char)]
-        return w
-
-    def status(self):
-        #returns loaded fonts
-        return self.__widtharrays.keys()
-        
-TheFontCache = FontCache()
-
-#expose the singleton as a single function
-stringwidth = TheFontCache.stringwidth
-
-_psWidths = { 'StandardEncoding' : widths,
-              'Latin1Encoding' :  Latin1FontWidths }
-
-
-
-def psStringWidth(text, font, encoding):
-    try:
-        widths = _psWidths[encoding][string.lower(font) ]
-    except:
-        raise KeyError, "Improper encoding %s or font name %s" % (encoding, font)
-    w = 0
-    for char in text:
-        w = w + widths[ord(char)]
-    return w
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddlePS.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddlePS.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddlePS.py	2005-07-07 05:46:01.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddlePS.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,978 +0,0 @@
-"""
-piddlePS - a PostScript backend for the PIDDLE drawing module
-
-   Magnus Lie Hetland
-   
-   1999
-"""
-# some fixups by Chris Lee (clee@users.sourceforge.net)
-
-# $Id: piddlePS.py,v 1.26 2002/01/08 01:28:40 clee Exp $
-#
-# For each page, the coordinate system is intialized with "0 canvasHeight translate" so
-# that coordiante (0,0) is at the top left of the page
-# Therefore all y coordinates must be entered in opposite sign to go down
-# Also, remember that angles are reversed relative to postscript standard -cwl
-
-# TODO for version 1.0:
-#     X add greyscale image support for smaller images (added to level 2)
-#     X add Level2 postscript options
-#     X add underline implementation from piddlePDF
-#     X ** fix \n breaking in drawString
-#     ? Bezier curve thing (Robert Kern's fix is in piddlePDF)
-#          Hmm..drawCurve(..) already uses postscript's bezier curveto-- is there anything else
-#          to be done? 
-#     X drawArc stuff from Eric
-
-# In the Future:
-#    _ Base85 ecooding just use hex encoding involves 1:2 expansion of image data vs 4:5
-#    _ Don't see a flate/deflate filter for Postscript, jpeg DCTEncode could be added.
-#         PIL may have a LZW encoder 
-#    _ check Adobe Document struturing conventions (half done)
-#    _ look at symbol's font metrics--they appear to be a little off
-#    X postscript native implementation of drawRoundRect 
-#    _ improve underlining placement...doesn't look good for courier and symbol
-
-#  DSC: plan uses flags for keeping track of BeginX/EndX pairs.
-#            convention: use flag _inXFlag
-
-
-from piddle import *
-import string, cStringIO
-import piddlePSmetrics # for font info
-import math
-
-linesep = '\n'
-### constants for fonts ###
-
-# This is actually a mapping between legal font names and PSFontMapXXX keys
-# note: all piddle font names are lower-cased before running against this mapping)
-PiddleLegalFonts = {"helvetica": "helvetica",  # note: keys are lowercased 
-                    "times": "times",
-                    "courier": "courier",
-                    "serif": "times",
-                    "sansserif": "helvetica",
-                    "monospaced": "courier",
-                    "symbol" : "symbol"} # Could add more...
-
-
-Roman="Roman"; Bold="Bold"; Italic="Italic"
-
-
-
-PSFontMapStdEnc = { ("helvetica", Roman): "Helvetica-Roman",
-                    ("helvetica", Bold): "Helvetica-Bold",
-                    ("helvetica", Italic): "Helvetica-Oblique",
-                    ("times", Roman) : "Times-Roman",
-                    ("times", Bold) : "Times-Bold",
-                    ("times", Italic) : "Times-Italic",
-                    ("courier", Roman) : "Courier-Roman",
-                    ("courier", Bold) : "Courier-Bold",
-                    ("courier", Italic) : "Courier-Oblique",
-                    ("symbol", Roman) : "Symbol",
-                    ("symbol", Bold) :  "Symbol",
-                    ("symbol", Italic) : "Symbol",
-                    "EncodingName" : 'StandardEncoding' }
-
-
-PSFontMapLatin1Enc = { ("helvetica", Roman): "Helvetica-Roman-ISOLatin1",
-                       ("helvetica", Bold): "Helvetica-Bold-ISOLatin1",
-                       ("helvetica", Italic): "Helvetica-Oblique-ISOLatin1",
-                       ("times", Roman) : "Times-Roman-ISOLatin1",
-                       ("times", Bold) : "Times-Bold-ISOLatin1",
-                       ("times", Italic) : "Times-Italic-ISOLatin1",
-                       ("courier", Roman) : "Courier-Roman-ISOLatin1",
-                       ("courier", Bold) : "Courier-Bold-ISOLatin1",
-                       ("courier", Italic) : "Courier-Oblique-ISOLatin1",
-                       ("symbol", Roman) : "Symbol",
-                       ("symbol", Bold) :  "Symbol",
-                       ("symbol", Italic) : "Symbol",
-                       "EncodingName" : 'Latin1Encoding' }
-
-
-###############################################################
-
-
-
-def latin1FontEncoding(fontname):
-
-    """use this to generating PS code for re-encoding a font as ISOLatin1
-    from font with name 'fontname' defines reencoded font, 'fontname-ISOLatin1'"""
-    
-    latin1FontTemplate =  """/%s findfont
-dup length dict begin
-  {1 index /FID ne
-        {def}
-        {pop pop}
-      ifelse
-   } forall
-   /Encoding ISOLatin1Encoding  def
-   currentdict
-end
-/%s-ISOLatin1 exch definefont pop
-"""
-    #
-    return latin1FontTemplate % (fontname, fontname)
-
-
-class EpsDSC:
-
-    # remeber %% will be reduced to % when using string substitution
-    # returned strings do not end with \n
-    
-    def __init__(self):
-        pass 
-        
-    ## Genral DSC conventions
-    def documentHeader(self):
-        return  "%!PS-Adobe-3.0 EPSF-3.0"
-
-
-    def boundingBoxStr(self, x0, y0, x1,y1):
-        "coordinates of bbox in default PS coordinates"
-        return "%%BoundingBox: " + "%s %s %s %s" % (x0, y0, x1,y1)
-
-    def BeginPageStr(self, pageSetupStr, pageName=None):
-
-        """Use this at the beginning of each page, feed it your setup code
-        in the form of a string of postscript.  pageName is the "number" of the
-        page.  By default it will be 0."""
-
-        self.inPageFlag = 1  # keep track
-        
-        if not pageName:
-            pageDeclaration = r"%%Page: " +  "%d" % 0  # default 0
-        else:
-            pageDeclaration = "%%Page: " + pageName
-            
-        ret = pageDeclaration + "\n" + \
-"""%%BeginPageSetup
-/pgsave save def
-"""
-        # print pageSetupStr ???
-        return ret  + pageSetupStr  + "\n%%EndPageSetup"
-
-    def EndPageStr(self):
-        self.inPageFlag = 0
-        return ""
-
-
-##########################################################################
-
-class PSCanvas(Canvas):
-
-    """This canvas is meant for generating encapsulated PostScript files
-    (EPS) used for inclusion in other documents; thus really only
-    single-page documents are supported.  For historical reasons and
-    because they can be printed (a showpage is included), the files are
-    given a .ps extension by default, and a primitive sort of multipage
-    document can be generated using nextPage() or clear().  Use at your own
-    risk!  Future versions of piddlePS will include an EPSCanvas and a
-    PSCanvas which will clearly delineate between single and multipage
-    documents.
-
-    Note: All font encodings must be taken care in __init__, you can't add
-          more after this"""
-
-    def __init__(self,size=(300,300),name='piddlePS',
-                 PostScriptLevel=2,
-                 fontMapEncoding=PSFontMapLatin1Enc):
-        
-       Canvas.__init__(self,size,name)
-       width, height = self.size = size
-       self.filename = name
-       if len(name) < 3 or string.lower(name[-3:]) != '.ps':
-           self.filename = name + ".ps"
-
-       # select between postscript level 1 or level 2
-       if PostScriptLevel == 1 :
-           self.drawImage = self._drawImageLevel1
-       elif PostScriptLevel == 2 :
-           self.drawImage = self._drawImageLevel2
-       else :
-           raise 'PostScriptLevelException'
-
-
-       self.code = []
-       self.dsc = EpsDSC()  # handle document structing conventions
-       
-       c = self._currentColor = self.defaultLineColor
-       r,g,b = c.red, c.green, c.blue
-       
-       w = self._currentWidth = self.defaultLineWidth
-       self.setLineCap( 0) # sets butt as default cap style
-
-       self.defaultFont = Font(face='serif') 
-       self.fontMapEncoding = fontMapEncoding
-       
-       self._currentFont = self.defaultFont
-       f = self._findFont(self._currentFont)
-       s = self._currentFont.size
-
-       # Page Structure State
-       #----------------------
-       self._inDocumentFlag = 0  # this is set in psBeginDocument
-       self._inPageFlag = 0   # we have't started a page
-
-       self.pageNum = 1   # User is free to reset this or even make this a string
-
-
-       self.psBeginDocument()
-
-       ## finally begin the first page ##
-       self.psBeginPage() # each psBeginPage() needs to be closed w/ a psEndPage()
-
-
-    def psBeginDocument(self):
-        # General DSC Prolog := <header> [<defaults>] <procedures>
-        self.code.append(self.dsc.documentHeader())
-        self.code.append(self.dsc.boundingBoxStr(0,0,self.size[0],self.size[1]))
-        self.code.append("%%Pages: (atend)")
-        self._inDocumentFlag = 1  # we need a Trailer to fix this up
-
-        ######  Defaults Procedures Prolog Setup  ??? ######
-        # are these procedures??? check this chris
-       
-        # Now create Latin1ISO font encodings for non-symbol fonts (need to add pdf fonts too)
-        shapes = {"Helvetica": ["Roman", "Bold", "Oblique"],
-                  "Times": ["Roman", "Bold", "Italic"],
-                  "Courier": ["Roman", "Bold", "Oblique"] }
-        fntnames = []
-        
-        for basename in ['Helvetica', 'Times', 'Courier']:
-            for mys in shapes[basename]:
-                fntnames.append(basename + '-' + mys)
-
-        # assign the default FontMapping (which also determines the encoding)
-
-        for fontname in fntnames:
-            self.code.append(latin1FontEncoding(fontname))
-
-
-
-    def psEndDocument(self):
-        
-        if self._inDocumentFlag:
-            # Take care of Trailer
-            self.code.append("%%Trailer")
-            self.code.append("%%%%Pages: %d" % self.pageNum)
-
-            # signal end of file
-            # check on need for %%EOF
-            self.code.append("%%EOF\n")
-            
-
-
-    def psBeginPage(self, pageName=None):
-        # call this function when beginning a new page but before any piddle drawing commands
-        # pagesetup contains code to insert into DSC page 'header'
-        if not pageName:
-            pageName = "%s" % self.pageNum
-        pagesetup = self._psPageSetupStr(self.size[1], self.defaultLineColor,
-                                       self._findFont(self.defaultFont),
-                                       self.defaultFont.size,
-                                       self.defaultLineWidth)
-        self.code.append(self.dsc.BeginPageStr(pageSetupStr= pagesetup, pageName=pageName ))
-        self._inPageFlag = 1
-        
-
-    def _psPageSetupStr(self,pageheight, initialColor, font_family, font_size, line_width):
-        "ps code for settin up coordinate system for page in accords w/ piddle standards"
-        r,g,b = initialColor.red, initialColor.green, initialColor.blue
-        return '''
-%% initialize
-
-2 setlinecap
-
-0 %d
-translate
-
-%s %s %s setrgbcolor
-(%s) findfont %s scalefont setfont
-%s setlinewidth''' % (pageheight, r, g, b, font_family, font_size, line_width)
-
-
-
-    def psEndPage(self):
-        self.code.append("pgsave restore")
-        self.code.append("showpage")
-        self._inPageFlag = 0
-
-       
-       
-
-    def _findFont(self,font):
-
-        requested = font.face or "Serif"  # Serif is the default
-        if type(requested) == StringType:
-            requested = [requested]
-
-        # once again, fall back to default, redundant, no?
-        face = string.lower(PiddleLegalFonts["serif"])  
-        for reqFace in requested:
-            if PiddleLegalFonts.has_key(string.lower(reqFace)):
-                face = string.lower(PiddleLegalFonts[string.lower(reqFace)])
-                break
-
-        if font.bold:
-            shape = Bold
-        elif font.italic:
-            shape = Italic
-        else:
-            shape = Roman
-
-        return self.fontMapEncoding[(face, shape)]
-
-
-
-
-
-    def _findExternalFontName(self, font):       #copied from piddlePDF by cwl- hack away!
-        """Attempts to return proper font name.
-        PDF uses a standard 14 fonts referred to
-        by name. Default to self.defaultFont('Helvetica').
-        The dictionary allows a layer of indirection to
-        support a standard set of PIDDLE font names."""
-
-        piddle_font_map = {
-            'Times':'Times',
-            'times':'Times',
-            'Courier':'Courier',
-            'courier':'Courier',
-            'helvetica':'Helvetica',
-            'Helvetica':'Helvetica',
-            'symbol':'Symbol',
-            'Symbol':'Symbol',
-            'monospaced':'Courier',
-            'serif':'Times',
-            'sansserif':'Helvetica',
-            'ZapfDingbats':'ZapfDingbats',
-            'zapfdingbats':'ZapfDingbats',
-            'arial':'Helvetica'
-            }
-
-        try:
-            face = piddle_font_map[string.lower(font.face)]
-        except:
-            return 'Helvetica'
-
-        name = face + '-'
-        if font.bold and face in ['Courier','Helvetica','Times']:
-            name = name + 'Bold'
-        if font.italic and face in ['Courier', 'Helvetica']:
-            name = name + 'Oblique'
-        elif font.italic and face == 'Times':
-            name = name + 'Italic'
-
-        if name == 'Times-':
-            name = name + 'Roman'
-        # symbol and ZapfDingbats cannot be modified!
-
-        #trim and return
-        if name[-1] == '-':
-            name = name[0:-1]
-        return name
-
-
-
-    def _psNextPage(self):
-        "advance to next page of document.  "
-        self.psEndPage()
-        self.pageNum = self.pageNum + 1
-        self.psBeginPage()
-
-    def nextPage(self):
-        self.clear()
-
-    def clear(self):
-
-        """clear resets the canvas to it's default state.  Though this
-        canvas is really only meant to be an EPS canvas, i.e., single page,
-        for historical reasons we will allow multipage documents.  Thus
-        clear will end the page, clear the canvas state back to default,
-        and start a new page.  In the future, this PSCanvas will become
-        EPSCanvas and will not support multipage documents.  In that case,
-        the canvas will be reset to its default state and the file will be
-        emptied of all previous drawing commands"""
-
-        self.resetToDefaults()
-        self._psNextPage()
-
-    def resetToDefaults(self):
-        self._currentColor = self.defaultLineColor
-        self._currentWidth = self.defaultLineWidth
-        self._currentFont  = self.defaultFont
-
-        
-    def flush(self):
-        pass
-
-        # Comment the following out to make flush() a null function
-        # file = open(self.filename,'w')
-        # from string import join
-        # file.write(join(self.code,linesep))
-        # file.write('\nshowpage\n')
-        # file.close()
-
-
-    
-    def save(self, file=None, format=None):
-        """Write the current document to a file or stream and close the file
-        Computes any final trailers, etc. that need to be done in order to
-        produce a well formed postscript file.  At least for now though,
-        it still allows you to add to the file after a save by not actually
-        inserting the finalization code into self.code
-
-        the format argument is not used"""
-        
-        # save() will now become part of the spec.
-        file = file or self.filename
-        fileobj = getFileObject(file)
-        fileobj.write(string.join(self.code, linesep))
-        # here's a hack. we might want to be able to add more after saving so
-        # preserve the current code ???
-        preserveCode = self.code        
-        self.code = finalizationCode = [""]
-
-        # might be able to move these things into DSC class & use a save state call
-        preserve_inPageFlag = self._inPageFlag
-        preserve_inDocumentFlag = self._inDocumentFlag
-        
-        # now do finalization code in via self.code
-        # first question: are we in the middle of a page?
-
-        if self._inPageFlag:
-            self.psEndPage()
-
-        self.psEndDocument()  # depends on _inDocumentFlag :(
-
-        fileobj.write(string.join(finalizationCode, linesep))
-        #  fileobj.close()  ### avoid this for now
-        ## clean up my mess: This is not a good way to do things FIXME!!! ???
-        self.code = preserveCode
-        self._inPageFlag = preserve_inPageFlag
-        self._inDocumentFlag = preserve_inDocumentFlag 
-
-
-       
-    def stringWidth(self, s, font=None):
-        "Return the logical width of the string if it were drawn \
-        in the current font (defaults to self.font)."
-        if not font:
-            font = self.defaultFont
-        fontname = self._findExternalFontName(font)
-        return piddlePSmetrics.psStringWidth(s, fontname, self.fontMapEncoding["EncodingName"]) * font.size * 0.001
-
-    
-    ### def fontHeight(self, font=None) ### use default piddle method
-    # distance between baseline of two lines of text 1.2 * font.size for piddle.py
-    # Thus is 1.2 times larger than postscript minimum baseline to baseline separation
-    # for single-spaced text.  fontHeight() used internally for drawString() multi-line text
-
-    def fontAscent(self, font=None):
-        if not font:
-            font = self.defaultFont
-        fontname = self._findExternalFontName(font)
-        return piddlePSmetrics.ascent_descent[fontname][0] * 0.001 * font.size
-
-
-    def fontDescent(self, font=None):
-        if not font:
-            font = self.defaultFont
-        fontname = self._findExternalFontName(font)
-        return -piddlePSmetrics.ascent_descent[fontname][1] * 0.001 * font.size
-
-
-
-    def _updateLineColor(self, color):
-       color = color or self.defaultLineColor
-       if color != self._currentColor:
-          self._currentColor = color
-          r,g,b = color.red, color.green, color.blue
-          self.code.append('%s %s %s setrgbcolor' % (r,g,b))
-
-
-    def _updateFillColor(self, color):
-       color = color or self.defaultFillColor
-       if color != self._currentColor:
-          self._currentColor = color
-          r,g,b = color.red, color.green, color.blue
-          self.code.append('%s %s %s setrgbcolor' % (r,g,b))
-
-
-    def _updateLineWidth(self, width):
-       if width == None: width = self.defaultLineWidth
-       if width != self._currentWidth:
-          self._currentWidth = width
-          self.code.append('%s setlinewidth' % width)
-
-
-##     def _updateLineCap(self, cap):
-##        """0 is butt, 1 is round, 2 is projecting"""
-##        assert cap in (0,1,2), "The lineCap is allowed to be only one of 0,1,2"  
-##        if cap == None: cap = self.defaultLineCap
-##        if cap != self._currentCap:
-##           self._currentCap = cap
-##           self.code.append('%s setlinecap' % cap)
-          
-
-    def _updateFont(self, font):
-       font = font or self.defaultFont
-       if font != self._currentFont:
-          self._currentFont = font
-          f = self._findFont(font)
-          s = font.size
-          self.code.append('(%s) findfont %s scalefont setfont' % (f, s))
-
-
-    def setLineCap( self, cap):
-       assert cap in (0,1,2), "The lineCap is allowed to be only one of 0,1,2"          
-       self._currentCap = cap
-
-
-    def drawLine(self, x1, y1, x2, y2, color=None, width=None):
-       self._updateLineColor(color)
-       self._updateLineWidth(width)
-       if self._currentColor != transparent:
-          self.code.append('%d setlinecap %s %s neg moveto %s %s neg lineto stroke' %
-                           (self._currentCap, x1, y1, x2, y2))
-
-
-    def drawLines(self, lineList, color=None, width=None):
-       self._updateLineColor(color)
-       self._updateLineWidth(width)
-       codeline = ('%d setlinecap' % self._currentCap) + '%s %s neg moveto %s %s neg lineto stroke'
-       if self._currentColor != transparent:
-          for line in lineList:
-             self.code.append(codeline % line)
-
-
-    def _escape(self, s):
-        # return a copy of string s with special characters in postscript strings
-        # escaped" with backslashes."""
-        # Have not handled characters that are converted normally in python strings
-        # i.e. \n -> newline 
-        str = string.replace(s, chr(0x5C), r'\\' )
-        str = string.replace(str, '(', '\(' )
-        str = string.replace(str, ')', '\)')
-        return str 
-
-    # ??? check to see if \n response is handled correctly (should move cursor down)
-       
-    def _drawStringOneLineNoRot(self, s, x, y, font=None) :
-       # PRE: x and y and position at which to place text
-       # PRE: helper function, only called from drawString(..)
-       text = self._escape(s)
-       self.code.append('%s %s neg moveto (%s) show' % (x,y,text))
-       if self._currentFont.underline:
-           swidth = self.stringWidth(s, self._currentFont)
-           ypos =  (0.5 * self.fontDescent(self._currentFont))
-           thickness = 0.08 * self._currentFont.size  # relate to font.descent?
-           self.code.extend(['%s setlinewidth' % thickness,
-                             '0 %s neg rmoveto' % (ypos),
-                             '%s 0 rlineto stroke' % -swidth])
-       
-
-
-    def _drawStringOneLine(self, s, x, y, font=None, color=None, angle=0):
-       # PRE: assumes that coordinate system has already been set for rotated strings
-       # PRE: only meant to be called by drawString(..)
-       text = self._escape(s)
-       self.code.extend(['%f %f neg moveto (%s) show' % (x, y, text)])
-
-       if self._currentFont.underline:
-          swidth = self.stringWidth(s, self._currentFont)
-          dy =  (0.5 * self.fontDescent(self._currentFont))
-          thickness = 0.08 * self._currentFont.size  # relate to font.descent?
-          self.code.extend(['%s setlinewidth' % thickness,
-                            '%f %f neg moveto' % (x, dy+y),
-                            '%f 0 rlineto stroke' % swidth])
-
-
-    def drawString(self, s, x, y, font=None, color=None, angle=0):
-        """drawString(self, s, x, y, font=None, color=None, angle=0)
-        draw a string s at position x,y"""
-        self._updateLineColor(color)
-        self._updateFont(font)
-        if self._currentColor != transparent:
-
-            lines = string.split(s, '\n')   
-            lineHeight = self.fontHeight(font)
-
-            if angle == 0 :   # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops
-               for line in lines:
-                  self._drawStringOneLineNoRot(line, x, y, font=font)
-            else : # general case, rotated text
-               self.code.extend([
-                  'gsave',
-                  '%s %s neg translate' % (x,y),
-                  `angle`+' rotate'])
-               down = 0
-               for line in lines :
-                  self._drawStringOneLine(line, 0, 0+down, font, color, angle)
-                  down = down + lineHeight
-               self.code.extend(['grestore'])
-
-    def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4,
-                  edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-       codeline = '%s %s neg moveto %s %s neg %s %s neg %s %s neg curveto'
-       data = (x1, y1, x2, y2, x3, y3, x4, y4)
-       self._updateFillColor(fillColor)
-       if self._currentColor != transparent:
-          self.code.append((codeline % data) + ' eofill')
-       self._updateLineWidth(edgeWidth)
-       self._updateLineColor(edgeColor)
-       if self._currentColor != transparent:
-          self.code.append((codeline % data)
-                           + ((closed and ' closepath') or '')
-                           + ' stroke')
-
-    ########################################################################################
-
-    def drawRoundRect(self, x1,y1, x2,y2, rx=8, ry=8,
-                      edgeColor=None, edgeWidth=None, fillColor=None):
-        "Draw a rounded rectangle between x1,y1, and x2,y2, \
-        with corners inset as ellipses with x radius rx and y radius ry. \
-        These should have x1<x2, y1<y2, rx>0, and ry>0."
-        # Path is drawn in counter-clockwise direction"
-        
-        x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py
-        y1, y2 = min(y1,y2), max(y1, y2)
-
-        # Note: arcto command draws a line from current point to beginning of arc
-        # save current matrix, translate to center of ellipse, scale by rx ry, and draw
-        # a circle of unit radius in counterclockwise dir, return to original matrix
-        # arguments are (cx, cy, rx, ry, startAngle, endAngle)
-        ellipsePath = 'matrix currentmatrix %s %s neg translate %s %s scale 0 0 1 %s %s arc setmatrix' 
-
-        # choice between newpath and moveto beginning of arc
-        # go with newpath for precision, does this violate any assumptions in code???
-        # rrcode = ['%s %s neg moveto' % (x1+rx, y1)]  # this also works
-        rrcode = ['newpath'] # Round Rect code path
-        # upper left corner ellipse is first
-        rrcode.append(ellipsePath % (x1+rx, y1+ry, rx, ry, 90, 180))
-        rrcode.append(ellipsePath % (x1+rx, y2-ry, rx, ry, 180, 270))
-        rrcode.append(ellipsePath % (x2-rx, y2-ry, rx, ry, 270, 360))
-        rrcode.append(ellipsePath % (x2-rx, y1+ry, rx, ry, 0,  90) )
-        rrcode.append('closepath')
-        
-        # This is what you are required to do to take care of all color cases
-        # should fix this so it doesn't define path twice, just use gsave if need
-        # to fill and stroke path-need to figure out this system
-        self._updateFillColor(fillColor)
-        if self._currentColor != transparent:
-            self.code.extend(rrcode)
-            self.code.append("eofill")
-        self._updateLineWidth(edgeWidth)
-        self._updateLineColor(edgeColor)
-        if self._currentColor != transparent:
-            self.code.extend(rrcode)
-            self.code.append("stroke")
-
-
-
-        
-    def drawEllipse(self, x1,y1, x2,y2, edgeColor=None, edgeWidth=None, 
-                    fillColor=None):
-        "Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2. \
-        These should have x1<x2 and y1<y2."
-        #Just invoke drawArc to actually draw the ellipse
-        self.drawArc(x1,y1, x2,y2, edgeColor=edgeColor, edgeWidth=edgeWidth,
-                fillColor=fillColor)
-
-    def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360, edgeColor=None,
-                edgeWidth=None, fillColor=None):
-        "Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2, \
-        starting at startAng degrees and covering extent degrees.   Angles \
-        start with 0 to the right (+x) and increase counter-clockwise. \
-        These should have x1<x2 and y1<y2."
-        #calculate centre of ellipse
-        cx, cy = (x1+x2)/2.0, (y1+y2)/2.0
-        rx, ry = (x2-x1)/2.0, (y2-y1)/2.0
-        
-        codeline = self._genArcCode(x1, y1, x2, y2, startAng, extent) 
-
-        # fill portion
-        self._updateFillColor(fillColor)
-
-        if self._currentColor != transparent:
-            self.code.append('%s %s neg moveto' % (cx,cy)) # move to center of circle
-            self.code.append(codeline + ' eofill')
-
-        # stroke portion
-        self._updateLineWidth(edgeWidth)
-        self._updateLineColor(edgeColor)
-
-        if self._currentColor != transparent:
-            # move current point to start of arc, note negative angle because y increases down
-            self.code.append('%s %s neg moveto' % (cx+rx*math.cos(-startAng),
-                                                   cy+ry*math.sin(-startAng))) 
-            self.code.append(codeline + ' stroke')
-
-    def _genArcCode(self, x1, y1, x2, y2, startAng, extent):
-        "Calculate the path for an arc inscribed in rectangle defined by (x1,y1),(x2,y2)"
-        #calculate semi-minor and semi-major axes of ellipse
-        xScale = abs((x2-x1)/2.0)
-        yScale = abs((y2-y1)/2.0)
-        #calculate centre of ellipse
-        x, y = (x1+x2)/2.0, (y1+y2)/2.0
-
-        codeline = 'matrix currentmatrix '+\
-                '%s %s neg translate %s %s scale 0 0 1 %s %s %s '+\
-                'setmatrix'
-
-        if extent >= 0:
-            arc='arc'
-        else:
-            arc='arcn'
-        data = (x,y, xScale, yScale, startAng, startAng+extent, arc)
-
-        return codeline % data
-
-    
-    def drawPolygon(self, pointlist, 
-                    edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-
-       start = pointlist[0]
-       pointlist = pointlist[1:]
-       
-       polyCode = []
-       polyCode.append("%s %s neg moveto" % start)
-       for point in pointlist:
-          polyCode.append("%s %s neg lineto" % point)
-       if closed:
-          polyCode.append("closepath")
-
-       self._updateFillColor(fillColor)
-       if self._currentColor != transparent:
-          self.code.extend(polyCode)
-          self.code.append("eofill")
-       self._updateLineWidth(edgeWidth)
-       self._updateLineColor(edgeColor)
-       if self._currentColor != transparent:
-          self.code.extend(polyCode)
-          self.code.append("stroke")
-
-
-    def drawFigure(self, partList,
-                   edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-       
-       figureCode = []
-       first = 1
-       
-       for part in partList:
-           op = part[0]
-           args = list(part[1:])
-
-           if op == figureLine:
-               if first:
-                   first = 0
-                   figureCode.append("%s %s neg moveto" % tuple(args[:2]))
-               else:
-                   figureCode.append("%s %s neg lineto" % tuple(args[:2]))
-               figureCode.append("%s %s neg lineto" % tuple(args[2:]))
-
-           elif op == figureArc:
-               first = 0
-               x1,y1,x2,y2,startAngle,extent = args[:6]
-               figureCode.append(self._genArcCode(x1,y1,x2,y2,startAngle,extent))
-
-           elif op == figureCurve:
-               if first:
-                   first = 0
-                   figureCode.append("%s %s neg moveto" % tuple(args[:2]))
-               else:
-                   figureCode.append("%s %s neg lineto" % tuple(args[:2]))
-               figureCode.append("%s %s neg %s %s neg %s %s neg curveto" % tuple(args[2:]))
-           else:
-               raise TypeError, "unknown figure operator: "+op
-
-       if closed:
-           figureCode.append("closepath")
-
-       self._updateFillColor(fillColor)
-       if self._currentColor != transparent:
-          self.code.extend(figureCode)
-          self.code.append("eofill")
-       self._updateLineWidth(edgeWidth)
-       self._updateLineColor(edgeColor)
-       if self._currentColor != transparent:
-          self.code.extend(figureCode)
-          self.code.append("stroke")
-
-    ############################################################################################
-    # drawImage(self. image, x1, y1, x2=None, y2=None) is now defined by either _drawImageLevel1
-    #    ._drawImageLevel2, the choice is made in .__init__ depending on option 
-
-
-    def _drawImageLevel1(self, image, x1, y1, x2=None,y2=None):
-        # Postscript Level1 version available for fallback mode when Level2 doesn't work
-       """drawImage(self,image,x1,y1,x2=None,y2=None) : If x2 and y2 are ommitted, they are
-       calculated from image size.  (x1,y1) is upper left of image, (x2,y2) is lower right of
-       image in piddle coordinates."""
-       try:
-           import Image
-       except ImportError:
-           print 'Python Imaging Library not available'
-           return
-       # For now let's start with 24 bit RGB images (following piddlePDF again)
-       print "Trying to drawImage in piddlePS"
-       component_depth = 8
-       myimage = image.convert('RGB')
-       imgwidth, imgheight = myimage.size
-       if not x2:
-            x2 = imgwidth + x1
-       if not y2:
-            y2 = y1 + imgheight
-       drawwidth = x2 - x1
-       drawheight = y2 - y1
-       print 'Image size (%d, %d); Draw size (%d, %d)' % (imgwidth, imgheight, drawwidth, drawheight)
-       # now I need to tell postscript how big image is
-
-       # "image operators assume that they receive sample data from
-       # their data source in x-axis major index order.  The coordinate
-       # of the lower-left corner of the first sample is (0,0), of the
-       # second (1,0) and so on" -PS2 ref manual p. 215
-       #
-       # The ImageMatrix maps unit squre of user space to boundary of the source image
-       #
-
-       # The CurrentTransformationMatrix (CTM) maps the unit square of
-       # user space to the rect...on the page that is to receive the
-       # image. A common ImageMatrix is [width 0 0 -height 0 height]
-       # (for a left to right, top to bottom image )
-       
-       # first let's map the user coordinates start at offset x1,y1 on page
-
-       self.code.extend([
-           'gsave',
-           '%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image
-           '%s %s scale' % (drawwidth,drawheight),
-           '/scanline %d 3 mul string def' % imgwidth  # scanline by multiples of image width
-           ])
-
-       # now push the dimensions and depth info onto the stack
-       # and push the ImageMatrix to map the source to the target rectangle (see above)
-       # finally specify source (PS2 pp. 225 ) and by exmample
-       self.code.extend([
-           '%s %s %s' % (imgwidth, imgheight, component_depth),
-           '[%s %s %s %s %s %s]' % (imgwidth, 0, 0, -imgheight, 0, imgheight),
-           '{ currentfile scanline readhexstring pop } false 3',
-           'colorimage '
-           ])
-
-       # data source output--now we just need to deliver a hex encode
-       # series of lines of the right overall size can follow
-       # piddlePDF again
-
-       rawimage = myimage.tostring()
-       assert(len(rawimage) == imgwidth*imgheight, 'Wrong amount of data for image') 
-       #compressed = zlib.compress(rawimage) # no zlib at moment
-       hex_encoded = self._AsciiHexEncode(rawimage)
-       
-       # write in blocks of 78 chars per line
-       outstream = cStringIO.StringIO(hex_encoded)
-
-       dataline = outstream.read(78)
-       while dataline <> "":
-           self.code.append(dataline)
-           dataline= outstream.read(78)
-       self.code.append('% end of image data') # for clarity
-       self.code.append('grestore') # return coordinates to normal
-
-    # end of drawImage
-
-       
-    def _AsciiHexEncode(self, input):  # also based on piddlePDF
-        "Helper function used by images"
-        output = cStringIO.StringIO()
-        for char in input:
-            output.write('%02x' % ord(char))
-        output.reset()
-        return output.read()
-
-    def _drawImageLevel2(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version
-        try:
-            import Image
-        except ImportError:
-            print 'Python Imaging Library not available'
-            return
-               # I don't have zlib -cwl
-#         try:
-#             import zlib
-#         except ImportError:
-#             print 'zlib not available'
-#             return
-       
-
-        ### what sort of image are we to draw
-        if image.mode=='L' :
-            print 'found image.mode= L'
-            imBitsPerComponent = 8
-            imNumComponents = 1
-            myimage = image 
-        elif image.mode == '1':
-            print 'found image.mode= 1'
-            myimage = image.convert('L')
-            imNumComponents = 1
-            myimage = image 
-        else :
-            myimage = image.convert('RGB')
-            imNumComponents = 3
-            imBitsPerComponent = 8
-
-        imwidth, imheight = myimage.size
-        # print 'imwidth = %s, imheight = %s' % myimage.size
-        if not x2:
-            x2 = imwidth + x1
-        if not y2:
-            y2 = y1 + imheight
-        drawwidth = x2 - x1
-        drawheight = y2 - y1
-        self.code.extend([
-            'gsave',
-            '%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image
-            '%s %s scale' % (drawwidth,drawheight)])
-
-        if imNumComponents == 3 :
-            self.code.append('/DeviceRGB setcolorspace')
-        elif imNumComponents == 1 :
-            self.code.append('/DeviceGray setcolorspace')
-            print 'setting colorspace gray'
-        # create the image dictionary
-        self.code.append("""
-<<
-/ImageType 1
-/Width %d /Height %d  %% dimensions of source image
-/BitsPerComponent %d""" % (imwidth, imheight, imBitsPerComponent) )
-
-        if imNumComponents == 1:
-            self.code.append('/Decode [0 1]')
-        if imNumComponents == 3:
-            self.code.append('/Decode [0 1 0 1 0 1]  %% decode color values normally')
-
-        self.code.extend(['/ImageMatrix [%s 0 0 %s 0 %s]' % (imwidth, -imheight, imheight),
-                          '/DataSource currentfile /ASCIIHexDecode filter',
-                          '>> % End image dictionary',
-                          'image'])
-        # after image operator just need to dump image dat to file as hexstring
-        rawimage = myimage.tostring()
-        assert(len(rawimage) == imwidth*imheight, 'Wrong amount of data for image') 
-        #compressed = zlib.compress(rawimage) # no zlib at moment
-        hex_encoded = self._AsciiHexEncode(rawimage)
-       
-        # write in blocks of 78 chars per line
-        outstream = cStringIO.StringIO(hex_encoded)
-
-        dataline = outstream.read(78)
-        while dataline <> "":
-            self.code.append(dataline)
-            dataline= outstream.read(78)
-        self.code.append('> % end of image data') # > is EOD for hex encoded filterfor clarity
-        self.code.append('grestore') # return coordinates to normal
-
-        
-        
-                         
-                       
-            
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddle.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddle.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddle.py	2004-11-02 14:19:20.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddle.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,806 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-# piddle.py -- Plug In Drawing, Does Little Else
-# Copyright (C) 1999  Joseph J. Strout
-# 
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-# 
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-# 
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-"""
-PIDDLE (Plug-In Drawing, Does Little Else)
-2D Plug-In Drawing System
-
-Magnus Lie Hetland
-Andy Robinson
-Joseph J. Strout
-and others
-
-February-March 1999
-
-On coordinates: units are Big Points, approximately 1/72 inch.
-The origin is at the top-left, and coordinates increase down (y)
-and to the right (x).
-
-Progress Reports...
-JJS, 2/10/99: as discussed, I've removed the Shape classes and moved
-	the drawing methods into the Canvas class.  Numerous other changes
-	as discussed by email as well.
-
-JJS, 2/11/99: removed Canvas default access functions; added fontHeight
-	etc. functions; fixed numerous typos; added drawRect and drawRoundRect
-	(how could I forget those?).  Added StateSaver utility class.
-	
-	2/11/99 (later): minor fixes.
-
-JJS, 2/12/99: removed scaling/sizing references.  Changed event handler
-	mechanism per Magnus's idea.  Changed drawCurve into a fillable
-	drawing function (needs default implementation).  Removed edgeList
-	from drawPolygon.  Added drawFigure.  Changed drawLines to draw
-	a set of disconnected lines (of uniform color and width).
-	
-	2/12/99 (later): added HexColor function and WWW color constants.
-	Fixed bug in StateSaver.  Changed params to drawArc.
-
-JJS, 2/17/99: added operator methods to Color; added default implementation
-	of drawRoundRect in terms of Line, Rect, and Arc.
-
-JJS, 2/18/99: added isInteractive and canUpdate methods to Canvas.
-
-JJS, 2/19/99: added drawImage method; added angle parameter to drawString.
-
-JJS, 3/01/99: nailed down drawFigure interface (and added needed constants).
-
-JJS, 3/08/99: added arcPoints and curvePoints methods; added default 
-	implementations for drawRect, drawRoundRect, drawArc, drawCurve, 
-	drawEllipse, and drawFigure (!), mostly thanks to Magnus.
-
-JJS, 3/09/99: added 'closed' parameter to drawPolygon, drawCurve, and
-	drawFigure.  Made use of this in several default implementations.
-
-JJS, 3/11/99: added 'onKey' callback and associated constants; also added
-	Canvas.setInfoLine(s) method.
-
-JJS, 3/12/99: typo in drawFigure.__doc__ corrected (thanks to Magnus).
-
-JJS, 3/19/99: fixed bug in drawArc (also thanks to Magnus).
-
-JJS, 5/30/99: fixed bug in arcPoints.
-
-JJS, 6/10/99: added __repr__ method to Font.
-
-JJS, 6/22/99: added additional WWW colors thanks to Rafal Smotrzyk
-
-JJS, 6/29/99: added inch and cm units
-
-JJS, 6/30/99: added size & name parameters to Canvas.__init__
-
-JJS, 9/21/99: fixed bug in arcPoints
-
-JJS, 9/29/99: added drawMultiLineStrings, updated fontHeight with new definition
-
-JJS, 10/21/99: made Color immutable; fixed bugs in default fontHeight,
-		drawMultiLineString
-"""
-
-__version_maj_number__ = 1.0 
-__version_min_number__ = 15  
-__version__ = "%s.%s" % ( __version_maj_number__,  __version_min_number__) # c.f. "1.0.15"
-
-from types import StringType, IntType, InstanceType
-import string
-
-inch = 72		# 1 PIDDLE drawing unit == 1/72 imperial inch
-cm = inch/2.54	# more sensible measurement unit
-
-#-------------------------------------------------------------------------
-# StateSaver
-#-------------------------------------------------------------------------
-class StateSaver:
-	"""This is a little utility class for saving and restoring the
-	default drawing parameters of a canvas.  To use it, add a line
-	like this before changing any of the parameters:
-	
-		saver = StateSaver(myCanvas)
-	
-	then, when "saver" goes out of scope, it will automagically
-	restore the drawing parameters of myCanvas."""
-
-	def __init__(self, canvas):
-		self.canvas = canvas
-		self.defaultLineColor = canvas.defaultLineColor
-		self.defaultFillColor = canvas.defaultFillColor
-		self.defaultLineWidth = canvas.defaultLineWidth
-		self.defaultFont = canvas.defaultFont
-	
-	def __del__(self):
-		self.canvas.defaultLineColor = self.defaultLineColor
-		self.canvas.defaultFillColor = self.defaultFillColor
-		self.canvas.defaultLineWidth = self.defaultLineWidth
-		self.canvas.defaultFont = self.defaultFont
-
-#-------------------------------------------------------------------------
-# Color
-#-------------------------------------------------------------------------
-class Color:
-	"""This class is used to represent color.  Components red, green, blue 
-	are in the range 0 (dark) to 1 (full intensity)."""
-
-	def __init__(self, red=0, green=0, blue=0):
-		"Initialize with red, green, blue in range [0-1]."
-		_float = float
-		d = self.__dict__
-		d["red"] = _float(red)
-		d["green"] = _float(green)
-		d["blue"] = _float(blue)
-
-	def __setattr__(self, name, value):
-		raise TypeError, "piddle.Color has read-only attributes"
-
-	def __mul__(self,x):
-		return Color(self.red*x, self.green*x, self.blue*x)
-	
-	def __rmul__(self,x):
-		return Color(self.red*x, self.green*x, self.blue*x)
-	
-	def __div__(self,x):
-		return Color(self.red/x, self.green/x, self.blue/x)
-	
-	def __rdiv__(self,x):
-		return Color(self.red/x, self.green/x, self.blue/x)
-	
-	def __add__(self,x):
-		return Color(self.red+x.red, self.green+x.green, self.blue+x.blue)
-		
-	def __sub__(self,x):
-		return Color(self.red-x.red, self.green-x.green, self.blue-x.blue)
-		
-	def __repr__(self):
-		return "Color(%1.2f,%1.2f,%1.2f)" % (self.red, self.green, self.blue)
-
-	def __hash__(self):
-		return hash( (self.red, self.green, self.blue) )
-		
-	def __cmp__(self,other):
-		try:
-			dsum = 4*self.red-4*other.red + 2*self.green-2*other.green + self.blue-other.blue
-		except:
-			return -1
-		if dsum > 0: return 1
-		if dsum < 0: return -1
-		return 0
-		
-def HexColor(val):
-	"""This class converts a hex string, or an actual integer number,
-	into the corresponding color.  E.g., in "AABBCC" or 0xAABBCC,
-	AA is the red, BB is the green, and CC is the blue (00-FF)."""
-	if type(val) == StringType:
-		val = string.atoi(val,16)
-	factor = 1.0 / 255
-	return Color(factor * ((val >> 16) & 0xFF), 
-			  factor * ((val >> 8) & 0xFF),
-			  factor * (val & 0xFF))
-		
-# color constants -- mostly from HTML standard
-aliceblue = 	HexColor(0xF0F8FF)
-antiquewhite = 	HexColor(0xFAEBD7)
-aqua = 	HexColor(0x00FFFF)
-aquamarine = 	HexColor(0x7FFFD4)
-azure = 	HexColor(0xF0FFFF)
-beige = 	HexColor(0xF5F5DC)
-bisque = 	HexColor(0xFFE4C4)
-black = 	HexColor(0x000000)
-blanchedalmond = 	HexColor(0xFFEBCD)
-blue = 	HexColor(0x0000FF)
-blueviolet = 	HexColor(0x8A2BE2)
-brown = 	HexColor(0xA52A2A)
-burlywood = 	HexColor(0xDEB887)
-cadetblue = 	HexColor(0x5F9EA0)
-chartreuse = 	HexColor(0x7FFF00)
-chocolate = 	HexColor(0xD2691E)
-coral = 	HexColor(0xFF7F50)
-cornflower = 	HexColor(0x6495ED)
-cornsilk = 	HexColor(0xFFF8DC)
-crimson = 	HexColor(0xDC143C)
-cyan = 	HexColor(0x00FFFF)
-darkblue = 	HexColor(0x00008B)
-darkcyan = 	HexColor(0x008B8B)
-darkgoldenrod = 	HexColor(0xB8860B)
-darkgray = 	HexColor(0xA9A9A9)
-darkgreen = 	HexColor(0x006400)
-darkkhaki = 	HexColor(0xBDB76B)
-darkmagenta = 	HexColor(0x8B008B)
-darkolivegreen = 	HexColor(0x556B2F)
-darkorange = 	HexColor(0xFF8C00)
-darkorchid = 	HexColor(0x9932CC)
-darkred = 	HexColor(0x8B0000)
-darksalmon = 	HexColor(0xE9967A)
-darkseagreen = 	HexColor(0x8FBC8B)
-darkslateblue = 	HexColor(0x483D8B)
-darkslategray = 	HexColor(0x2F4F4F)
-darkturquoise = 	HexColor(0x00CED1)
-darkviolet = 	HexColor(0x9400D3)
-deeppink = 	HexColor(0xFF1493)
-deepskyblue = 	HexColor(0x00BFFF)
-dimgray = 	HexColor(0x696969)
-dodgerblue = 	HexColor(0x1E90FF)
-firebrick = 	HexColor(0xB22222)
-floralwhite = 	HexColor(0xFFFAF0)
-forestgreen = 	HexColor(0x228B22)
-fuchsia = 	HexColor(0xFF00FF)
-gainsboro = 	HexColor(0xDCDCDC)
-ghostwhite = 	HexColor(0xF8F8FF)
-gold = 	HexColor(0xFFD700)
-goldenrod = 	HexColor(0xDAA520)
-gray = 	HexColor(0x808080)
-grey = gray
-green = 	HexColor(0x008000)
-greenyellow = 	HexColor(0xADFF2F)
-honeydew = 	HexColor(0xF0FFF0)
-hotpink = 	HexColor(0xFF69B4)
-indianred = 	HexColor(0xCD5C5C)
-indigo = 	HexColor(0x4B0082)
-ivory = 	HexColor(0xFFFFF0)
-khaki = 	HexColor(0xF0E68C)
-lavender = 	HexColor(0xE6E6FA)
-lavenderblush = 	HexColor(0xFFF0F5)
-lawngreen = 	HexColor(0x7CFC00)
-lemonchiffon = 	HexColor(0xFFFACD)
-lightblue = 	HexColor(0xADD8E6)
-lightcoral = 	HexColor(0xF08080)
-lightcyan = 	HexColor(0xE0FFFF)
-lightgoldenrodyellow = 	HexColor(0xFAFAD2)
-lightgreen = 	HexColor(0x90EE90)
-lightgrey = 	HexColor(0xD3D3D3)
-lightpink = 	HexColor(0xFFB6C1)
-lightsalmon = 	HexColor(0xFFA07A)
-lightseagreen = 	HexColor(0x20B2AA)
-lightskyblue = 	HexColor(0x87CEFA)
-lightslategray = 	HexColor(0x778899)
-lightsteelblue = 	HexColor(0xB0C4DE)
-lightyellow = 	HexColor(0xFFFFE0)
-lime = 	HexColor(0x00FF00)
-limegreen = 	HexColor(0x32CD32)
-linen = 	HexColor(0xFAF0E6)
-magenta = 	HexColor(0xFF00FF)
-maroon = 	HexColor(0x800000)
-mediumaquamarine = 	HexColor(0x66CDAA)
-mediumblue = 	HexColor(0x0000CD)
-mediumorchid = 	HexColor(0xBA55D3)
-mediumpurple = 	HexColor(0x9370DB)
-mediumseagreen = 	HexColor(0x3CB371)
-mediumslateblue = 	HexColor(0x7B68EE)
-mediumspringgreen = 	HexColor(0x00FA9A)
-mediumturquoise = 	HexColor(0x48D1CC)
-mediumvioletred = 	HexColor(0xC71585)
-midnightblue = 	HexColor(0x191970)
-mintcream = 	HexColor(0xF5FFFA)
-mistyrose = 	HexColor(0xFFE4E1)
-moccasin = 	HexColor(0xFFE4B5)
-navajowhite = 	HexColor(0xFFDEAD)
-navy = 	HexColor(0x000080)
-oldlace = 	HexColor(0xFDF5E6)
-olive = 	HexColor(0x808000)
-olivedrab = 	HexColor(0x6B8E23)
-orange = 	HexColor(0xFFA500)
-orangered = 	HexColor(0xFF4500)
-orchid = 	HexColor(0xDA70D6)
-palegoldenrod = 	HexColor(0xEEE8AA)
-palegreen = 	HexColor(0x98FB98)
-paleturquoise = 	HexColor(0xAFEEEE)
-palevioletred = 	HexColor(0xDB7093)
-papayawhip = 	HexColor(0xFFEFD5)
-peachpuff = 	HexColor(0xFFDAB9)
-peru = 	HexColor(0xCD853F)
-pink = 	HexColor(0xFFC0CB)
-plum = 	HexColor(0xDDA0DD)
-powderblue = 	HexColor(0xB0E0E6)
-purple = 	HexColor(0x800080)
-red = 	HexColor(0xFF0000)
-rosybrown = 	HexColor(0xBC8F8F)
-royalblue = 	HexColor(0x4169E1)
-saddlebrown = 	HexColor(0x8B4513)
-salmon = 	HexColor(0xFA8072)
-sandybrown = 	HexColor(0xF4A460)
-seagreen = 	HexColor(0x2E8B57)
-seashell = 	HexColor(0xFFF5EE)
-sienna = 	HexColor(0xA0522D)
-silver = 	HexColor(0xC0C0C0)
-skyblue = 	HexColor(0x87CEEB)
-slateblue = 	HexColor(0x6A5ACD)
-slategray = 	HexColor(0x708090)
-snow = 	HexColor(0xFFFAFA)
-springgreen = 	HexColor(0x00FF7F)
-steelblue = 	HexColor(0x4682B4)
-tan = 	HexColor(0xD2B48C)
-teal = 	HexColor(0x008080)
-thistle = 	HexColor(0xD8BFD8)
-tomato = 	HexColor(0xFF6347)
-turquoise = 	HexColor(0x40E0D0)
-violet = 	HexColor(0xEE82EE)
-wheat = 	HexColor(0xF5DEB3)
-white = 	HexColor(0xFFFFFF)
-whitesmoke = 	HexColor(0xF5F5F5)
-yellow = 	HexColor(0xFFFF00)
-yellowgreen = 	HexColor(0x9ACD32)
-
-# special case -- indicates no drawing should be done
-transparent = Color(-1, -1, -1)
-
-#-------------------------------------------------------------------------
-# Font
-#-------------------------------------------------------------------------
-class Font:
-    "This class represents font typeface, size, and style."
-	
-    def __init__(self, size=12, bold=0, italic=0, underline=0, face=None):
-        # public mode variables
-        d = self.__dict__
-        d["bold"] = bold
-        d["italic"] = italic
-        d["underline"] = underline
-
-        # public font size (points)
-        d["size"] = size
-
-        # typeface -- a name or set of names, interpreted by the Canvas,
-        # or "None" to indicate the Canvas-specific default typeface
-        d["face"] = face
-	
-    def __cmp__(self, other):
-        """Compare two fonts to see if they're the same."""
-        if self.face == other.face and self.size == other.size and \
-           self.bold == other.bold and self.italic == other.italic \
-           and self.underline == other.underline:
-            return 0
-        else:
-            return 1
-
-    def __repr__(self):
-        return "Font(%d,%d,%d,%d,%s)" % (self.size, self.bold, self.italic, \
-                                         self.underline, repr(self.face))
-
-    def __setattr__(self, name, value):
-        raise TypeError, "piddle.Font has read-only attributes"
-
-
-#-------------------------------------------------------------------------
-# constants needed for Canvas.drawFigure
-#-------------------------------------------------------------------------
-figureLine = 1
-figureArc = 2
-figureCurve = 3
-
-#-------------------------------------------------------------------------
-# key constants used for special keys in the onKey callback
-#-------------------------------------------------------------------------
-keyBksp = '\010'		# (erases char to left of cursor)
-keyDel = '\177'			# (erases char to right of cursor)
-keyLeft = '\034'
-keyRight = '\035'
-keyUp = '\036'
-keyDown = '\037'
-keyPgUp = '\013'
-keyPgDn = '\014'
-keyHome = '\001'
-keyEnd = '\004'
-keyClear = '\033'
-keyTab = '\t'
-
-modShift = 1		# shift key was also held
-modControl = 2		# control key was also held
-
-#-------------------------------------------------------------------------
-# Canvas
-#-------------------------------------------------------------------------
-class Canvas:
-	"""This is the base class for a drawing canvas.  The 'plug-in renderers'
-	we speak of are really just classes derived from this one, which implement
-	the various drawing methods."""
-	
-	def __init__(self, size=(300,300), name="PIDDLE"):
-		"""Initialize the canvas, and set default drawing parameters. 
-		Derived classes should be sure to call this method."""
-		# defaults used when drawing
-		self.defaultLineColor = black
-		self.defaultFillColor = transparent
-		self.defaultLineWidth = 1
-		self.defaultFont = Font()
-
-		# set up null event handlers
-		
-		# onClick: x,y is Canvas coordinates of mouseclick
-		def ignoreClick(canvas,x,y): pass
-		self.onClick = ignoreClick
-
-		# onOver: x,y is Canvas location of mouse
-		def ignoreOver(canvas,x,y): pass
-		self.onOver = ignoreOver
-
-		# onKey: key is printable character or one of the constants above;
-		#	modifiers is a tuple containing any of (modShift, modControl)
-		def ignoreKey(canvas,key,modifiers): pass
-		self.onKey = ignoreKey
-
-		# size and name, for user's reference
-		self.size, self.name = size,name
-
-	#------------ canvas capabilities -------------
-	def isInteractive(self):
-		"Returns 1 if onClick, onOver, and onKey events are possible, 0 otherwise."
-		return 0
-	
-	def canUpdate(self):
-		"Returns 1 if the drawing can be meaningfully updated over time \
-		(e.g., screen graphics), 0 otherwise (e.g., drawing to a file)."
-		return 0
-
-	#------------ general management -------------
-	def clear(self):
-		"Call this to clear and reset the graphics context."
-		pass
-		
-	def flush(self):
-		"Call this to indicate that any comamnds that have been issued \
-                but which might be buffered should be flushed to the screen"
-		pass
-
-        def save(self, file=None, format=None):
-
-                """For backends that can be save to a file or sent to a
-                stream, create a valid file out of what's currently been
-                drawn on the canvas.  Trigger any finalization here.
-                Though some backends may allow further drawing after this call,
-                presume that this is not possible for maximum portability
-
-                file may be either a string or a file object with a write method
-                     if left as the default, the canvas's current name will be used
-
-                format may be used to specify the type of file format to use as
-                     well as any corresponding extension to use for the filename
-                     This is an optional argument and backends may ignore it if
-                     they only produce one file format."""
-                pass 
-
-                                
-	
-	def setInfoLine(self, s):
-		"For interactive Canvases, displays the given string in the \
-		'info line' somewhere where the user can probably see it."
-		pass 
-		
-	#------------ string/font info ------------
-	def stringWidth(self, s, font=None):
-		"Return the logical width of the string if it were drawn \
-		in the current font (defaults to self.font)."
-		raise NotImplementedError, 'stringWidth'
-	
-	def fontHeight(self, font=None):
-		"Find the height of one line of text (baseline to baseline) of the given font."
-		# the following approxmation is correct for PostScript fonts,
-		# and should be close for most others:
-		if not font: font = self.defaultFont
-		return 1.2 * font.size
-		
-	def fontAscent(self, font=None):
-		"Find the ascent (height above base) of the given font."
-		raise NotImplementedError, 'fontAscent'
-	
-	def fontDescent(self, font=None):
-		"Find the descent (extent below base) of the given font."
-		raise NotImplementedError, 'fontDescent'		
-		
-	#------------- drawing helpers --------------
-
-	def arcPoints(self, x1,y1, x2,y2, startAng=0, extent=360):
-		"Return a list of points approximating the given arc."		
-		# Note: this implementation is simple and not particularly efficient.
-		xScale = abs((x2-x1)/2.0)
-		yScale = abs((y2-y1)/2.0)
-	
-		x = min(x1,x2)+xScale
-		y = min(y1,y2)+yScale
-	
-		# "Guesstimate" a proper number of points for the arc:
-		steps = min(max(xScale,yScale)*(extent/10.0)/10,200)
-		if steps < 5: steps = 5
-		
-		from math import sin, cos, pi
-	
-		pointlist = []
-		step = float(extent)/steps
-		angle = startAng
-		for i in range(int(steps+1)):
-			point = (x+xScale*cos((angle/180.0)*pi),
-					 y-yScale*sin((angle/180.0)*pi))
-			pointlist.append(point)
-			angle = angle+step
-	
-		return pointlist
-			
-	def curvePoints(self, x1, y1, x2, y2, x3, y3, x4, y4):
-		"Return a list of points approximating the given Bezier curve."
-	
-		# Adapted from BEZGEN3.HTML, one of the many
-		# Bezier utilities found on Don Lancaster's Guru's Lair at
-		# <URL: http://www.tinaja.com/cubic01.html>	
-		bezierSteps = min(max(max(x1,x2,x3,x4)-min(x1,x2,x3,x3),
-		                      max(y1,y2,y3,y4)-min(y1,y2,y3,y4)),
-		                  200)
-	
-		dt1 = 1. / bezierSteps
-		dt2 = dt1 * dt1
-		dt3 = dt2 * dt1
-	
-		xx = x1
-		yy = y1
-		ux = uy = vx = vy = 0
-	
-		ax = x4 - 3*x3 + 3*x2 - x1
-		ay = y4 - 3*y3 + 3*y2 - y1
-		bx = 3*x3 - 6*x2 + 3*x1
-		by = 3*y3 - 6*y2 + 3*y1
-		cx = 3*x2 - 3*x1
-		cy = 3*y2 - 3*y1
-	
-		mx1 = ax * dt3
-		my1 = ay * dt3
-	
-		lx1 = bx * dt2
-		ly1 = by * dt2
-	
-		kx = mx1 + lx1 + cx*dt1
-		ky = my1 + ly1 + cy*dt1
-	
-		mx = 6*mx1 
-		my = 6*my1
-	
-		lx = mx + 2*lx1
-		ly = my + 2*ly1
-	
-		pointList = [(xx, yy)]
-	
-		for i in range(bezierSteps):
-			xx = xx + ux + kx
-			yy = yy + uy + ky
-			ux = ux + vx + lx
-			uy = uy + vy + ly
-			vx = vx + mx
-			vy = vy + my
-			pointList.append((xx, yy)) 
-	
-		return pointList
-
-	def drawMultiLineString(self, s, x,y, font=None, color=None, angle=0):
-		"Breaks string into lines (on \n, \r, \n\r, or \r\n), and calls drawString on each."
-		import math
-		import string
-		h = self.fontHeight(font)
-		dy = h * math.cos(angle*math.pi/180.0)
-		dx = h * math.sin(angle*math.pi/180.0)
-		s = string.replace(s, '\r\n', '\n')
-		s = string.replace(s, '\n\r', '\n')
-		s = string.replace(s, '\r', '\n')
-		lines = string.split(s, '\n')
-		for line in lines:
-			self.drawString(line, x, y, font, color, angle)
-			x = x + dx
-			y = y + dy
-
-	#------------- drawing methods --------------
-
-	# Note default parameters "=None" means use the defaults
-	# set in the Canvas method: defaultLineColor, etc.
-
-	def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-		"Draw a straight line between x1,y1 and x2,y2."
-		raise NotImplementedError, 'drawLine'
-	
-	def drawLines(self, lineList, color=None, width=None):
-		"Draw a set of lines of uniform color and width.  \
-		lineList: a list of (x1,y1,x2,y2) line coordinates."
-		# default implementation:
-		for x1, y1, x2, y2 in lineList:
-			self.drawLine(x1, y1, x2, y2 ,color,width)
-		
-
-	#	For text, color defaults to self.lineColor.
-	
-	def drawString(self, s, x,y, font=None, color=None, angle=0):
-		"Draw a string starting at location x,y."
-		# NOTE: the baseline goes on y; drawing covers (y-ascent,y+descent)
-		raise NotImplementedError, 'drawString'
-
-
-	#	For fillable shapes, edgeColor defaults to self.defaultLineColor,
-	#	edgeWidth defaults to self.defaultLineWidth, and
-	#	fillColor defaults to self.defaultFillColor.
-	#	Specify "don't fill" by passing fillColor=transparent.
-
-	def drawCurve(self, x1,y1, x2,y2, x3,y3, x4,y4, 
-				edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-		"Draw a B�zier curve with control points x1,y1 to x4,y4."
-
-		pointlist = self.curvePoints(x1, y1, x2, y2, x3, y3, x4, y4)
-		self.drawPolygon(pointlist,
-						 edgeColor=edgeColor,
-						 edgeWidth=edgeWidth,
-						 fillColor=fillColor, closed=closed)
-
-	def drawRect(self, x1,y1, x2,y2, edgeColor=None, edgeWidth=None, fillColor=None):
-		"Draw the rectangle between x1,y1, and x2,y2. \
-		These should have x1<x2 and y1<y2."
-		
-		pointList = [ (x1,y1), (x2,y1), (x2,y2), (x1,y2) ]
-		self.drawPolygon(pointList, edgeColor, edgeWidth, fillColor, closed=1)
-
-	def drawRoundRect(self, x1,y1, x2,y2, rx=8, ry=8,
-					edgeColor=None, edgeWidth=None, fillColor=None):
-		"Draw a rounded rectangle between x1,y1, and x2,y2, \
-		with corners inset as ellipses with x radius rx and y radius ry. \
-		These should have x1<x2, y1<y2, rx>0, and ry>0."
-
-		x1, x2 = min(x1,x2), max(x1, x2)
-		y1, y2 = min(y1,y2), max(y1, y2)
-		
-		dx = rx*2
-		dy = ry*2
-	
-		partList = [
-			(figureArc, x1, y1, x1+dx, y1+dy, 180, -90),
-			(figureLine, x1+rx, y1, x2-rx, y1),
-			(figureArc, x2-dx, y1, x2, y1+dy, 90, -90),
-			(figureLine, x2, y1+ry, x2, y2-ry),
-			(figureArc, x2-dx, y2, x2, y2-dy, 0, -90),
-			(figureLine, x2-rx, y2, x1+rx, y2),
-			(figureArc, x1, y2, x1+dx, y2-dy, -90, -90),
-			(figureLine, x1, y2-ry, x1, y1+rx)
-			]
-
-		self.drawFigure(partList, edgeColor, edgeWidth, fillColor, closed=1)
-
-	def drawEllipse(self, x1,y1, x2,y2, edgeColor=None, edgeWidth=None, fillColor=None):
-		"Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2. \
-		These should have x1<x2 and y1<y2."
-
-		pointlist = self.arcPoints(x1, y1, x2, y2, 0, 360)
-		self.drawPolygon(pointlist,edgeColor, edgeWidth, fillColor, closed=1)
-
-	def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360,
-				edgeColor=None, edgeWidth=None, fillColor=None):
-		"Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2, \
-		starting at startAng degrees and covering extent degrees.   Angles \
-		start with 0 to the right (+x) and increase counter-clockwise. \
-		These should have x1<x2 and y1<y2."
-		
-		center = (x1+x2)/2, (y1+y2)/2
-		pointlist = self.arcPoints(x1, y1, x2, y2, startAng, extent)
-		
-		# Fill...
-		self.drawPolygon(pointlist+[center]+[pointlist[0]],
-		         transparent, 0, fillColor)
-
-		# Outline...
-		self.drawPolygon(pointlist, edgeColor, edgeWidth, transparent)
-
-	def drawPolygon(self, pointlist, 
-				edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-		"""drawPolygon(pointlist) -- draws a polygon
-		pointlist: a list of (x,y) tuples defining vertices
-		closed: if 1, adds an extra segment connecting the last point to the first
-		"""
-		raise NotImplementedError, 'drawPolygon'
-	
-	def drawFigure(self, partList,
-				edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-		"""drawFigure(partList) -- draws a complex figure
-		partlist: a set of lines, curves, and arcs defined by a tuple whose
-				  first element is one of figureLine, figureArc, figureCurve
-				  and whose remaining 4, 6, or 8 elements are parameters."""
-	
-		pointList = []
-	
-		for tuple in partList:
-			op = tuple[0]
-			args = list(tuple[1:])
-	
-			if op == figureLine:
-				pointList.extend( [args[:2], args[2:]] )
-			elif op == figureArc:
-				pointList.extend(apply(self.arcPoints,args))
-			elif op == figureCurve:
-				pointList.extend(apply(self.curvePoints,args))
-			else:
-				raise TypeError, "unknown figure operator: "+op
-	
-		self.drawPolygon(pointList, edgeColor, edgeWidth, fillColor, closed=closed)
-
-
-	# no colors apply to drawImage; the image is drawn as-is
-
-	def drawImage(self, image, x1,y1, x2=None,y2=None):
-		"""Draw a PIL Image into the specified rectangle.  If x2 and y2 are
-		omitted, they are calculated from the image size."""
-		raise NotImplementedError, 'drawImage'
-
-
-
-#-------------------------------------------------------------------------
-
-# utility functions #
-
-def getFileObject(file):
-        """Common code for every Canvas.save() operation takes a string
-        or a potential file object and assures that a valid fileobj is returned"""
-
-        if file:
-                if isinstance(file, StringType):
-                        fileobj = open(file, "wb")
-                else:
-                        if hasattr(file, "write"):
-                                fileobj = file
-                        else:
-                                raise 'Invalid file argument to save'
-        else:
-                raise 'Invalid file argument to save'
-        
-        return fileobj
-
-
-class AffineMatrix:
-    # A = [ a c e]
-    #     [ b d f]
-    #     [ 0 0 1]
-    # self.A = [a b c d e f] = " [ A[0] A[1] A[2] A[3] A[4] A[5] ]"
-    def __init__(self, init=None):
-        if init:
-            if len(init) == 6 :
-                self.A = init
-            if type(init) == type(self): # erpht!!! this seems so wrong
-                self.A = init.A
-        else:
-            self.A = [1.0, 0, 0, 1.0, 0.0, 0.0] # set to identity
-
-    def scale(self, sx, sy):
-        self.A = [sx*self.A[0], sx*self.A[1], sy*self.A[2], sy * self.A[3], self.A[4], self.A[5] ]
-
-    def rotate(self, theta):
-        "counter clockwise rotation in standard SVG/libart coordinate system"
-        # clockwise in postscript "y-upward" coordinate system
-        # R = [ c  -s  0 ]
-        #     [ s   c  0 ]
-        #     [ 0   0  1 ]
-
-        co = math.cos(PI*theta/180.0)
-        si = math.sin(PI*theta/180.0)
-        self.A = [self.A[0] * co + self.A[2] * si,
-                  self.A[1] * co + self.A[3] * si,
-                  -self.A[0]* si + self.A[2] * co,
-                  -self.A[1]* si + self.A[3] * co,
-                  self.A[4],
-                  self.A[5] ]
-
-    def translate(self, tx, ty):
-        self.A  = [ self.A[0], self.A[1], self.A[2], self.A[3],
-                    self.A[0]*tx + self.A[2]*ty + self.A[4],
-                    self.A[1]*tx + self.A[3]*ty + self.A[5] ]
-
-
-
-
-
-
-
-
-
-
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleQD.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleQD.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleQD.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleQD.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,611 +0,0 @@
-# piddleQD.py -- a QuickDraw backend for PIDDLE
-# Copyright (C) 1999  Joseph J. Strout
-# 
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-# 
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-# 
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-"""QDCanvas
-
-This class implements a PIDDLE Canvas object that draws using QuickDraw
-(the MacOS drawing API) into an IDE window.
-
-Joe Strout (joe@strout.net), September 1999
-"""
-
-# Revisions:
-#
-#	9/20/99 JJS: added interactive methods and info line
-#
-#   6/14/99 JJS: added support for Copy command
-
-# Implementation notes:
-#	Each QDCanvas maintains a Picture, and uses this for refreshing.
-#	This means that drawing is not visible until it is flushed, but
-#	flushing is expensive, as the entire picture must be redrawn (to
-#	reopen it for further drawing).
-#
-#	The line color is stored in the QD fore color, and the fill color
-#	is stored in the back color -- fills are actually done by erasing.
-
-from piddle import *
-import Qd
-import QuickDraw
-import Scrap
-import W
-import Fonts
-import Events
-import Evt
-import string
-from types import *
-
-# utility functions
-def _setForeColor(c):
-	"Set the QD fore color from a piddle color."
-	Qd.RGBForeColor( (c.red*65535, c.green*65535, c.blue*65535) )
-
-def _setBackColor(c):
-	"Set the QD background color from a piddle color."
-	Qd.RGBBackColor( (c.red*65535, c.green*65535, c.blue*65535) )
-
-# global -- which QDCanvas has the current port
-_curCanvas = None
-
-# global dictionary mapping font names to QD font IDs
-_fontMap = {}
-for item in filter(lambda x:x[0]!='_',dir(Fonts)):
-	_fontMap[string.lower(item)] = Fonts.__dict__[item]
-_fontMap['system'] = Fonts.kFontIDGeneva
-_fontMap['monospaced'] = Fonts.kFontIDMonaco
-_fontMap['serif'] = Fonts.kFontIDNewYork
-_fontMap['sansserif'] = Fonts.kFontIDGeneva
-
-# utility classes
-class _PortSaver:
-
-	def __init__(self, qdcanvas):
-		self.port = Qd.GetPort()
-		Qd.SetPort(qdcanvas._window.wid)
-	
-	def __del__(self):
-		Qd.SetPort(self.port)
-
-class _QDCanvasWindow(W.Window):
-	"This internally-used class implements the window in which QDCanvas draws."
-	
-	def __init__(self, owner, size=(300,300), title="Graphics"):
-		self.owner = owner
-		size = (size[0], size[1]+16)			# leave room for info line!
-		W.Window.__init__(self, size, title)
-		self.infoline = ''
-		self.open()
-		self.lastMouse = (-1,-1)
-		
-	def close(self):
-		try: self.owner._noteWinClosed(self)
-		except: pass
-		W.Window.close(self)
-	
-	def domenu_copy(self, *args):
-		r = self._bounds
-		pict = Qd.OpenPicture(r)
-		self.owner._drawWindow()
-		Qd.ClosePicture()
-		Scrap.ZeroScrap()
-		Scrap.PutScrap('PICT',pict.data)
-
-	def do_update(self, window, event):
-		# draw the content
-		try: self.owner._drawWindow()
-		except: pass
-		
-		# draw the info line
-		self.drawInfoLine()
-	
-	def drawInfoLine(self):
-		Qd.ForeColor(QuickDraw.blackColor)
-		Qd.BackColor(QuickDraw.whiteColor)
-		bounds = self.getbounds()
-		width = bounds[2] - bounds[0]
-		height = bounds[3] - bounds[1]
-		Qd.MoveTo( 0, height-16 )
-		Qd.LineTo( width, height-16 )
-		r = (0,height-15,width,height)
-		Qd.EraseRect(r)
-		Qd.TextFont(Fonts.kFontIDGeneva)
-		Qd.TextSize(9)
-		Qd.TextFace(0)
-		Qd.MoveTo( 8, height-6 )
-		Qd.DrawString(self.infoline)
-	
-	def do_activate(self, activate, event):
-		global _curCanvas
-		if _curCanvas == self.owner and not activate:
-			#print self.owner, "is no longer current"
-			_curCanvas = None
-		
-	def do_contentclick(self, point, modifiers, event):
-		if self.owner: self.owner.onClick( self.owner, point[0], point[1] )
-		
-	def do_char(self, char, event):
-		import Wkeys
-		(what, message, when, where, modifiers) = event
-		mods = []
-		if modifiers & Events.shiftKey: mods.append( modShift )
-		if modifiers & Events.controlKey: mods.append( modControl )
-		if self.owner: self.owner.onKey( self.owner, char, mods )
-		W.Window.do_char(self, char, event)
-
-	def idle(self, *args):
-		if self.owner:
-			bounds = self.getbounds()
-			x,y = mouse = Evt.GetMouse()
-			maxx, maxy = self.owner.size
-			if mouse != self.lastMouse and x >= 0 and x < maxx \
-					and y >= 0 and y < maxy:
-				self.owner.onOver( self.owner, x,y )
-				self.lastMouse = mouse
-		W.Window.idle(self,args)
-
-class QDCanvas( Canvas ):
-	
-	def __init__(self, size=(300,300), name="Graphics"):
-		"Initialize QuickDraw canvas with window size and title."
-		self._window = _QDCanvasWindow(self, size, name)
-		self._port = Qd.GetPort()
-		Canvas.__init__(self, size, name)
-		self._penstate = Qd.GetPenState()
-		self.picture = Qd.OpenPicture(self._window._bounds)
-		self.picopen = 1
-	
-		self.patch = 0		# PATCH just for testing!
-	
-	#----------- custom QDCanvas methods -----------
-	
-	def __setattr__(self, attribute, value):
-		self.__dict__[attribute] = value
-		if attribute == "defaultLineColor":
-			self._window.SetPort()
-			_setForeColor(self.defaultLineColor)
-		elif attribute == "defaultLineWidth":
-			self._window.SetPort()
-			Qd.PenSize(value,value)
-			self._penstate = Qd.GetPenState()
-		elif attribute == "defaultFont":
-			self._window.SetPort()
-			self._setFont(value)
-	
-	def __del__(self):
-		#print "Deleting", self
-		try: self._window.close()
-		except: pass
-		self._window = None
-		Qd.KillPicture(self.picture)
-
-	def _noteWinClosed(self, win):
-		"Note that our window has been closed."
-		self._window = None
-
-	def _drawWindow(self):
-		"Update the drawing in the window."
-		if not hasattr(self,'picture'): return
-		if self.picopen:
-			self.flush()
-		else:
-			portsaver = _PortSaver(self)
-			Qd.DrawPicture(self.picture, self._window._bounds)		
-
-	def _prepareToDraw(self):
-		global _curCanvas
-		
-		# open the picture, if not already open
-		if not self.picopen:
-			portsaver = _PortSaver(self)
-			temp = Qd.OpenPicture(self._window._bounds)
-			Qd.DrawPicture( self.picture, self._window._bounds)
-			self.picture = temp
-			self.picopen = 1
-		
-		# and set the default drawing parameters, if we weren't the default before
-		if Qd.GetPort() != self._port: # _curCanvas != self:
-			#print "setting port to", self
-			self._window.SetPort()
-			_setForeColor(self.defaultLineColor)
-			_setBackColor(self.defaultFillColor)
-			Qd.SetPenState(self._penstate)
-			self.patch = self.patch + 1
-			_curCanvas = self
-
-	def _setFont(self, font):
-		global _fontMap
-		if not font.face:
-			Qd.TextFont(Fonts.applFont)
-		elif hasattr(font,'_QDfontID'):
-			Qd.TextFont(font._QDfontID)
-		else:
-			if type(font.face) == StringType:
-				try: fontID = _fontMap[string.lower(font.face)]
-				except:
-					return 0	# font not found!
-			else:
-				for item in font.face:
-					fontID = None
-					try:
-						fontID = _fontMap[string.lower(item)]
-						break
-					except: pass
-				if fontID == None:
-					return 0	# font not found!
-
-			# cache the fontID for quicker reference next time!
-                        font.__dict__['_QDfontID'] = fontID
-			# font._QDfontID = fontID
-			Qd.TextFont(fontID)
-		
-		# now, set the size and style as well!
-		Qd.TextSize(font.size)
-		stylecode = QuickDraw.bold * font.bold + \
-					QuickDraw.italic * font.italic + \
-					QuickDraw.underline * font.underline
-		Qd.TextFace( stylecode)
-		return 1
-		
-	def close(self):
-		self._window.close()
-		self._window = None
-	
-	#------------ canvas capabilities -------------
-	def isInteractive(self):
-		"Returns 1 if onClick, onOver, and onKey events are possible, 0 otherwise."
-		return 1
-	
-	def canUpdate(self):
-		"Returns 1 if the drawing can be meaningfully updated over time \
-		(e.g., screen graphics), 0 otherwise (e.g., drawing to a file)."
-		# since we can update, but it gets progressively more expensive
-		# until you call clear(), we'll return 0.5 for canUpdate
-		return 0.5
-
-	#------------ general management -------------
-
-	def clear(self, andFlush=1):
-		"Call this to clear and reset the graphics context."
-		# in addition to clearing the screen, also reset our picture
-		portsaver = _PortSaver(self)
-		if self.picopen:
-			Qd.ClosePicture()
-		self.picture = Qd.OpenPicture(self._window._bounds)
-		self.picopen = 1
-		self._prepareToDraw()
-		_setBackColor(white)
-		Qd.EraseRect( self._window._bounds )
-		_setBackColor(self.defaultFillColor)
-		if andFlush: self.flush()		# by default, we flush upon clear
-		
-	def flush(self):
-		"Call this when done with drawing, to indicate that the drawing \
-		should be printed/saved/blasted to screen etc."
-		if not self.picopen: return
-		portsaver = _PortSaver(self)
-		Qd.ClosePicture()
-		Qd.DrawPicture(self.picture, self._window._bounds)
-		self.picopen = 0
-
-	def setInfoLine(self, s):
-		"For interactive Canvases, displays the given string in the \
-		'info line' somewhere where the user can probably see it."
-		if self._window:
-			portsaver = _PortSaver(self)
-			self._window.infoline = str(s)
-			self._window.drawInfoLine()
-		
-	#------------ string/font info ------------
-	def stringWidth(self, s, font=None):
-		"Return the logical width of the string if it were drawn \
-		in the current font (defaults to self.font)."
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		if font: self._setFont(font)
-		return Qd.StringWidth(s)
-	
-	def fontHeight(self, font=None):
-		"Find the line height of the given font."
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		if font: self._setFont(font)
-		fontinfo = Qd.GetFontInfo()
-		return fontinfo[0] + fontinfo[1] + fontinfo[3]
-	
-	def fontAscent(self, font=None):
-		"Find the ascent (height above base) of the given font."
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		if font: self._setFont(font)
-		return Qd.GetFontInfo()[0]
-	
-	def fontDescent(self, font=None):
-		"Find the descent (extent below base) of the given font."
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		if font: self._setFont(font)
-		return Qd.GetFontInfo()[1]
-		
-	#------------- drawing methods --------------
-
-	# Note default parameters "=None" means use the defaults
-	# set in the Canvas method: defaultLineColor, etc.
-
-	def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-		"Draw a straight line between x1,y1 and x2,y2."
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		if color:
-			if color == transparent: return
-			_setForeColor(color)
-		elif self.defaultLineColor == transparent: return
-		if width!=None:
-			Qd.PenSize(width, width)
-			hw = (width-1)/2
-		else: hw = (self.defaultLineWidth-1)/2
-		if hw:
-			# adjust so that thick lines are centered on the given coordinates!
-			x1 = x1-hw
-			x2 = x2-hw
-			y1 = y1-hw
-			y2 = y2-hw
-		Qd.MoveTo( x1,y1 )	# later: handle scaling!
-		Qd.LineTo( x2,y2 )
-		if color:
-			_setForeColor(self.defaultLineColor)
-		if width: Qd.SetPenState(self._penstate)
-
-	def drawRect(self, x1,y1, x2,y2, edgeColor=None, edgeWidth=None, fillColor=None):
-		"Draw the rectangle between x1,y1, and x2,y2. \
-		These should have x1<x2 and y1<y2."
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		# first, draw the fill (if any)
-		if fillColor:
-			if fillColor != transparent:
-				_setBackColor(fillColor)
-				Qd.EraseRect( (x1,y1, x2,y2) )
-		else:
-			if self.defaultFillColor != transparent:
-				Qd.EraseRect( (x1,y1, x2,y2) )
-		# then, draw the frame
-		if edgeColor:
-			if edgeColor == transparent: return
-			_setForeColor(edgeColor)
-		elif self.defaultLineColor == transparent: return
-		if edgeWidth:
-			Qd.PenSize(edgeWidth, edgeWidth)
-			hw = (edgeWidth-1)/2
-		else: hw = (self.defaultLineWidth-1)/2
-		if hw:
-			# adjust so that thick lines are centered on the given coordinates!
-			x1 = x1-hw
-			x2 = x2+hw
-			y1 = y1-hw
-			y2 = y2+hw
-		Qd.FrameRect( (x1,y1, x2+1,y2+1) )
-		if edgeColor:
-			_setForeColor(self.defaultLineColor)
-		if edgeWidth: Qd.SetPenState(self._penstate)
-
-	def drawEllipse(self, x1,y1, x2,y2, edgeColor=None, edgeWidth=None, fillColor=None):
-		"Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2. \
-		These should have x1<x2 and y1<y2."
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		# first, draw the fill (if any)
-		if fillColor:
-			if fillColor != transparent:
-				_setBackColor(fillColor)
-				Qd.EraseOval( (x1,y1, x2,y2) )
-		elif self.defaultFillColor != transparent:
-			Qd.EraseOval( (x1,y1, x2,y2) )
-		# then, draw the frame
-		if edgeColor:
-			if edgeColor == transparent: return
-			_setForeColor(edgeColor)
-		elif self.defaultLineColor == transparent: return
-		if edgeWidth:
-			Qd.PenSize(edgeWidth, edgeWidth)
-			hw = (edgeWidth-1)/2
-		else: hw = (self.defaultLineWidth-1)/2
-		if hw:
-			# adjust so that thick lines are centered on the given coordinates!
-			x1 = x1-hw
-			x2 = x2+hw
-			y1 = y1-hw
-			y2 = y2+hw
-		Qd.FrameOval( (x1,y1, x2+1,y2+1) )
-		if edgeColor:
-			_setForeColor(self.defaultLineColor)
-		if edgeWidth: Qd.SetPenState(self._penstate)
-
-	def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360,
-			    edgeColor=None, edgeWidth=None, fillColor=None):
-		"Draw a partial oval inscribed within the rectangle x1,y1,x2,y2, \
-		starting at startAng degrees and covering extent degrees (counterclockwise). \
-		These should have x1<x2, y1<y2, and angle1 < angle2."
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		# first, draw the fill (if any)
-		if fillColor:
-			if fillColor != transparent:
-				_setBackColor(fillColor)
-				Qd.EraseArc( (x1,y1, x2,y2), 90-startAng, -extent )
-		elif self.defaultFillColor != transparent:
-			Qd.EraseOval( (x1,y1, x2,y2), 90-startAng, -extent )
-		# then, draw the frame
-		if edgeColor:
-			if edgeColor == transparent: return
-			_setForeColor(edgeColor)
-		elif self.defaultLineColor == transparent: return
-		if edgeWidth:
-			Qd.PenSize(edgeWidth, edgeWidth)
-			hw = (edgeWidth-1)/2
-		else: hw = (self.defaultLineWidth-1)/2
-		if hw:
-			# adjust so that thick lines are centered on the given coordinates!
-			x1 = x1-hw
-			x2 = x2+hw
-			y1 = y1-hw
-			y2 = y2+hw
-		Qd.FrameArc( (x1,y1, x2+1,y2+1), 90-startAng, -extent )
-		if edgeColor:
-			_setForeColor(self.defaultLineColor)
-		if edgeWidth: Qd.SetPenState(self._penstate)
-
-
-	def drawPolygon(self, pointlist, 
-				edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-		"""drawPolygon(pointlist) -- draws a polygon
-		pointlist: a list of (x,y) tuples defining vertices
-		"""
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		polygon = Qd.OpenPoly()
-		filling = 0
-		if fillColor:
-			if fillColor != transparent:
-				_setBackColor(fillColor)
-				filling = 1
-		elif self.defaultFillColor != transparent:
-			filling = 1
-
-		Qd.MoveTo(pointlist[0][0], pointlist[0][1])
-		for p in pointlist[1:]:
-			Qd.LineTo(p[0],p[1])
-		
-		Qd.ClosePoly()
-		if filling:
-			Qd.ErasePoly(polygon)
-			if fillColor:
-				_setBackColor(self.defaultFillColor)
-		
-		if edgeColor:
-			if edgeColor == transparent: return
-			_setForeColor(edgeColor)
-		elif self.defaultLineColor == transparent: return
-		if edgeWidth: Qd.PenSize(edgeWidth, edgeWidth)
-		Qd.FramePoly(polygon)
-		if closed:
-			Qd.MoveTo( pointlist[0][0], pointlist[0][1] )
-			Qd.LineTo( pointlist[-1][0], pointlist[-1][1] )
-		if edgeColor:
-			_setForeColor(self.defaultLineColor)
-		if edgeWidth: Qd.SetPenState(self._penstate)
-		
-
-
-	def drawString(self, s, x,y, font=None, color=None, angle=0):
-		"Draw a string starting at location x,y."
-		if '\n' in s or '\r' in s:
-			self.drawMultiLineString(s, x,y, font, color, angle)
-			return
-		portsaver = _PortSaver(self)
-		self._prepareToDraw()
-		if font: self._setFont(font)
-		if color:
-			if color == transparent: return
-			_setForeColor(color)
-		elif self.defaultLineColor == transparent: return
-		Qd.MoveTo( x,y )
-		if angle:
-			import QDRotate
-			QDRotate.DrawRotatedString(s,angle)
-		else:
-			Qd.DrawString(s)
-		if font: self._setFont(self.defaultFont)
-		if color:
-			_setForeColor(self.defaultLineColor)
-
-
-	def drawImage(self, image, x1,y1, x2=None,y2=None):
-		"""Draw a PIL Image into the specified rectangle.  If x2 and y2 are
-		omitted, they are calculated from the image size."""
-	
-		from PixMapWrapper import PixMapWrapper
-		pm = PixMapWrapper()	# make a QD pixel map
-		pm.fromImage(image)		# load the image into it
-		if x2==None: x2 = x1 + pm.bounds[2]-pm.bounds[0]
-		if y2==None: y2 = y1 + pm.bounds[3]-pm.bounds[1]
-		self._prepareToDraw()
-		Qd.ForeColor(QuickDraw.blackColor)
-		Qd.BackColor(QuickDraw.whiteColor)
-		pm.blit(x1,y1,x2,y2, self._port)
-		_setForeColor(self.defaultLineColor)
-		
-
-#-------------------------------------------------------------------------
-
-def test():
-	global canvas
-	
-	# testing...
-	try:
-		canvas.close()
-	except: pass
-
-	canvas = QDCanvas()
-	canvas.defaultLineColor = Color(0.7,0.7,1.0)	# light blue
-
-	#import macfs
-	#fsspec, ok = macfs.PromptGetFile("Image File:")
-	#if not ok: return
-	#path = fsspec.as_pathname()
-	#import Image
-	#canvas.drawImage( Image.open(path), 0,0,300,300 );
-
-	def myOnClick(canvas,x,y): print "clicked %s,%s" % (x,y)
-	canvas.onClick = myOnClick
-
-	def myOnOver(canvas,x,y): canvas.setInfoLine( "mouse is over %s,%s" % (x,y) )
-
-	canvas.onOver = myOnOver
-
-	def myOnKey(canvas,key,mods): print "pressed %s with modifiers %s" % (key,mods)
-	canvas.onKey = myOnKey
-	
-
-	canvas.drawLines( map(lambda i:(i*10,0,i*10,300), range(30)) )
-	canvas.drawLines( map(lambda i:(0,i*10,300,i*10), range(30)) )
-	canvas.defaultLineColor = black		
-	
-	canvas.drawLine(10,200, 20,190, color=red)
-	canvas.drawEllipse( 130,30, 200,100, fillColor=yellow, edgeWidth=4 )
-	
-	canvas.drawArc( 130,30, 200,100, 45,50, fillColor=blue, edgeColor=navy, edgeWidth=4 )
-	
-	canvas.defaultLineWidth = 4
-	canvas.drawRoundRect( 30,30, 100,100, fillColor=blue, edgeColor=maroon )
-	canvas.drawCurve( 20,20, 100,50, 50,100, 160,160 )
-	
-	canvas.drawString("This is a test!", 30,130, Font(face="newyork",size=16,bold=1), 
-			color=green, angle=-45)
-	
-	polypoints = [ (160,120), (130,190), (210,145), (110,145), (190,190) ]
-	canvas.drawPolygon(polypoints, fillColor=lime, edgeColor=red, edgeWidth=3, closed=1)
-	
-	canvas.drawRect( 200,200,260,260, edgeColor=yellow, edgeWidth=5 )
-	canvas.drawLine( 200,260,260,260, color=green, width=5 )
-	canvas.drawLine( 260,200,260,260, color=red, width=5 )
-
-	
-	canvas.flush()
-
-
-#test()
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleSVG.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleSVG.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleSVG.py	2004-12-10 13:18:20.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleSVG.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,628 +0,0 @@
-# Copyright (C) 2000  Greg Landrum
-# 
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-# 
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-# 
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-"""piddleSVG
-
-This module implements an SVG PIDDLE canvas.
-In other words, this is a PIDDLE backend that renders into a
-SVG file.
-
-Bits have been shamelessly cobbled from piddlePDF.py and/or
-piddlePS.py
-		
-Greg Landrum (greglandrum@earthlink.net) 3/10/2000
-"""
-
-"""
-  Functionality implemented:
-  -drawLine
-  -drawPolygon
-  -drawEllipse
-  -drawArc
-  -drawCurve
-  -drawString (rotated text is, mostly, fine... see below)
-  -drawFigure
-  -drawImage
-
-  Known problems:
-   -Rotated text is right in either IBM's SVGView or Adobe's plugin.  This
-    problem is explained in drawString()
-   -The font/string handling is not perfect.  There are definite problems
-    with getting the widths of strings.  Thus far heights seem to work okay
-    in the tests that I've done, but those could well be broken as well.
-
-"""
-
-from piddle import *
-import pdfmetrics # for font info
-import string, os, types
-
-from math import *
-
-SVG_HEADER = """<?xml version="1.0" encoding="iso-8859-1"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 03December 1999//EN"
-"SVG-19991203.dtd">
-"""
-
-def _ColorToSVG(color):
-  """ convenience function for converting a PIDDLE color to an SVG color
-
-  """
-  if color == transparent:
-    return 'none'
-  else:
-    return 'rgb(%d,%d,%d)'%(int(color.red*255),int(color.green*255),
-                            int(color.blue*255))
-def _PointListToSVG(points,dupFirst=0):
-  """ convenience function for converting a list of points to a string
-      suitable for passing to SVG path operations
-
-  """
-  outStr = ''
-  for i in xrange(len(points)):
-    outStr = outStr + '%.2f,%.2f '%(points[i][0],points[i][1])
-  # add back on the first point.  This is not required in the spec,
-  #  but Adobe's beta-quality viewer seems to not like it being skipped
-  if dupFirst == 1:
-    outStr = outStr + '%.2f,%.2f'%(points[0][0],points[0][1])
-  return outStr
-
-class SVGCanvas( Canvas ):
-  def __init__(self, size=(300,300), name='SVGCanvas'):
-    self._nImages=1
-    # I'd rather do this as PNG, but IBM's SVGView doesn't support those
-    #  yet. Adobe's plugin works just fine with them, however.
-    self._imageFormat='GIF'
-    self.size = size
-    self._initOutput()
-    Canvas.__init__(self, size, name)
-
-  def _initOutput(self):
-    self._txt = SVG_HEADER +\
-                '<svg xml:space="preserve" width="%dpx" height="%dpx">\n'%self.size
-    
-  def _findExternalFontName(self, font):       #copied from piddlePDF by cwl- hack away!
-        """Attempts to return proper font name.
-        PDF uses a standard 14 fonts referred to
-        by name. Default to self.defaultFont('Helvetica').
-        The dictionary allows a layer of indirection to
-        support a standard set of PIDDLE font names."""
-
-        piddle_font_map = {
-            'Times':'Times',
-            'times':'Times',
-            'Courier':'Courier',
-            'courier':'Courier',
-            'helvetica':'Helvetica',
-            'Helvetica':'Helvetica',
-            'symbol':'Symbol',
-            'Symbol':'Symbol',
-            'monospaced':'Courier',
-            'serif':'Times',
-            'sansserif':'Helvetica',
-            'ZapfDingbats':'ZapfDingbats',
-            'zapfdingbats':'ZapfDingbats',
-            'arial':'Helvetica'
-            }
-
-        try:
-            face = piddle_font_map[string.lower(font.face)]
-        except:
-            return piddle_font_map[string.lower('sansserif')]
-
-        name = face + '-'
-        if font.bold and face in ['Courier','Helvetica','Times']:
-            name = name + 'Bold'
-        if font.italic and face in ['Courier', 'Helvetica']:
-            name = name + 'Oblique'
-        elif font.italic and face == 'Times':
-            name = name + 'Italic'
-
-        if name == 'Times-':
-            name = name + 'Roman'
-        # symbol and ZapfDingbats cannot be modified!
-
-        #trim and return
-        if name[-1] == '-':
-            name = name[0:-1]
-        return name
-
-  def _FormFontStr(self,font):
-    """ form what we hope is a valid SVG font string.
-      Defaults to 'sansserif'
-      This should work when an array of font faces are passed in.
-    """
-    fontStr = ''
-    if font.face is None:
-      font.face = 'sansserif'
-    if type(font.face) == StringType:
-      if len(string.split(font.face)) > 1:
-        familyStr = '\'%s\''%font.face
-      else:
-        familyStr = font.face
-    else:
-      face = font.face[0]
-      if len(string.split(face)) > 1:
-        familyStr = '\'%s\''%(face)
-      else:
-        familyStr = face
-      for i in xrange(1,len(font.face)):
-        face = font.face[i]
-        if len(string.split(face)) > 1:
-          familyStr = ', \'%s\''%(face)
-        else:
-          familyStr = familyStr + ', %s'%face
-    if font.italic:
-      styleStr = 'font-style:italic;'
-    else:
-      styleStr = ''
-    if font.bold:
-      weightStr = 'font-weight:bold;'
-    else:
-      weightStr = ''
-    if font.size:
-      sizeStr = 'font-size:%.2f;'%font.size
-    else:
-      sizeStr = ''
-
-    fontStr = 'font-family: %s; %s %s %s'%(familyStr,styleStr,weightStr,sizeStr)
-    return fontStr
-
-  def _FormArcStr(self,x1,y1,x2,y2,theta1,extent):
-    """ Forms an arc specification for SVG
-    
-    """
-    if abs(extent) > 360:
-      if extent < 0:
-        extent = -abs(extent)%360
-      else:
-        extent = extent%360
-    
-    # deal with figuring out the various arc flags
-    #  required by SVG.
-    if extent > 180:   # this one is easy
-      arcFlag = 1
-    else:
-      arcFlag = 0
-
-    if extent >=0: 
-      sweepFlag = 0
-    else:
-      sweepFlag = 1
-
-    # convert angles to radians (grn)
-    theta1 = pi * theta1 / 180.
-    extent = pi * extent / 180.
-
-    # center of the arc
-    cx = (x1+x2)/2.
-    cy = (y1+y2)/2.
-    # its radius
-    rx = abs(x2 - x1)/2.
-    ry = abs(y2 - y1)/2.    
-
-    # final angle
-    theta2 = theta1 + extent
-
-    # SVG takes arcs as paths running from one point to another.
-    #  figure out what those start and end points are now.
-    #  the -thetas are required because of a difference in the handedness
-    #  of angles in Piddle and SVG
-    startx = cx + rx*cos(-theta1)
-    starty = cy + ry*sin(-theta1)
-    endx = cx + rx*cos(-theta2)
-    endy = cy + ry*sin(-theta2)
-
-    arcStr = '%.2f %.2f A%.2f %.2f 0 %d %d %.2f %.2f'%(startx,starty,rx,ry,
-                                                       arcFlag,sweepFlag,
-                                                       endx,endy)
-    return arcStr
-
-  # public functions
-  def clear(self):
-    self._initOutput()
-
-  def flush(self):
-    pass
-    
-  def save(self, file=None, type='svg'):
-    if file:
-      self.name = file
-    if type == '':
-      if '.' not in self.name:
-        raise TypeError, 'no file type given to save()'
-      filename = self.name
-    else:
-      if '.' not in self.name:
-        filename = self.name + '.' + type
-      else:
-        filename = self.name
-    outFile = open(filename,'w+')
-    outFile.write(self._txt+'</svg>')
-    outFile.close()
-    #print filename, "saved"
-
-
-  #------------- drawing methods --------------
-  def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-    "Draw a straight line between x1,y1 and x2,y2."
-    # set color...
-    if color:
-      if color == transparent: return
-    elif self.defaultLineColor == transparent:
-      return
-    else:
-      color = self.defaultLineColor
-      
-    svgColor = _ColorToSVG(color)
-
-    if width:
-      w = width
-    else:
-      w = self.defaultLineWidth
-    styleStr = '"stroke:%s; stroke-width:%d"'%(svgColor,w)
-    outStr = '<line x1="%.2f" y1="%.2f" x2="%.2f" y2="%.2f" style=%s />\n'%(x1,y1,x2,y2,styleStr)
-    self._txt = self._txt + outStr
-    
-
-  def drawPolygon(self, pointlist, 
-                  edgeColor=None, edgeWidth=None, fillColor=transparent, closed=0):
-    """drawPolygon(pointlist) -- draws a polygon
-    pointlist: a list of (x,y) tuples defining vertices
-    """
-
-    # get the points into SVG format
-    pointStr = _PointListToSVG(pointlist,dupFirst=closed)
-
-    # set color for fill...
-    filling = 0
-    if fillColor:
-      if fillColor != transparent:
-        filling = 1
-          
-    # do the fill
-    if filling:
-      fillStr = 'fill:%s;'%_ColorToSVG(fillColor)
-    else:
-      fillStr = 'fill:none;'
-
-    # set color for edge...
-    if not edgeColor:
-      edgeColor = self.defaultLineColor
-    # set edge width...
-    if edgeWidth == None: edgeWidth = self.defaultLineWidth
-
-    # SVG markers
-    edgeStr = 'stroke:%s; stroke-width:%d;'%(_ColorToSVG(edgeColor),int(edgeWidth))
-
-    # draw it
-    outStr = '<polygon style="%s %s" points="%s"/>\n'%(fillStr,edgeStr,pointStr)
-    self._txt = self._txt + outStr
-
-  def drawEllipse(self, x1,y1,x2,y2,
-                  edgeColor=None, edgeWidth=None, fillColor=transparent):
-
-    # get the points into SVG format
-    cx = (x1+x2)/2.
-    cy = (y1+y2)/2.
-    rx = abs(x2 - x1)/2.
-    ry = abs(y2 - y1)/2.    
-    ellipseStr = 'cx="%.2f" cy="%.2f" rx="%.2f" ry="%.2f"'%(cx,cy,rx,ry)
-
-    # set color for fill...
-    filling = 0
-    if fillColor:
-      if fillColor != transparent:
-        filling = 1
-          
-    # do the fill
-    if filling:
-      fillStr = 'fill:%s;'%_ColorToSVG(fillColor)
-    else:
-      fillStr = 'fill:none;'
-
-    # set color for edge...
-    if not edgeColor:
-      edgeColor = self.defaultLineColor
-    # set edge width...
-    if edgeWidth == None: edgeWidth = self.defaultLineWidth
-
-    edgeStr = 'stroke:%s; stroke-width:%d;'%(_ColorToSVG(edgeColor),int(edgeWidth))
-
-    # draw it
-    outStr = '<ellipse style="%s %s" %s/>\n'%(fillStr,edgeStr,ellipseStr)
-    self._txt = self._txt + outStr
-
-
-  def drawArc(self, x1,y1,x2,y2,theta1=0,extent=360,
-              edgeColor=None, edgeWidth=None, fillColor=None):
-
-    # set color for fill...
-    filling = 0
-    if not fillColor:
-      fillColor = self.defaultFillColor
-
-    if fillColor != transparent:
-      filling = 1
-
-          
-    # do the fill
-    if filling:
-      fillStr = 'fill:%s;'%_ColorToSVG(fillColor)
-    else:
-      fillStr = 'fill:none;'
-    arcStr = self._FormArcStr(x1,y1,x2,y2,theta1,extent)
-
-    if not filling:
-      pathStr = 'M' + arcStr
-    else:
-      # this is a bit trickier.  Piddle requires filled arcs to stroke the
-      #  arc bit and fill into the middle (like a piece of pie) without
-      #  stroking the lines to the middle.  So we need *two* paths here.
-      strokePathStr = 'M' + arcStr
-      cx = (x1 + x2)/2.
-      cy = (y1 + y2)/2.
-      fillPathStr = 'M%.2f %.2f L%sZ'%(cx,cy,arcStr)
-
-    # set color for edge...
-    if not edgeColor:
-      edgeColor = self.defaultLineColor
-    # set edge width...
-    if edgeWidth == None: edgeWidth = self.defaultLineWidth
-
-    # SVG markers
-    edgeStr = 'stroke:%s; stroke-width:%d;'%(_ColorToSVG(edgeColor),int(edgeWidth))
-
-    # draw it
-    if not filling:
-      outStr = '<path style="%s %s" d="%s"/>\n'%(fillStr,edgeStr,pathStr)
-    else:
-      outStr = '<path style="%s" d="%s"/>\n'%(fillStr,fillPathStr)
-      outStr = outStr+'<path style="fill:none; %s" d="%s"/>\n'%(edgeStr,strokePathStr)      
-    self._txt = self._txt + outStr
-
-  def drawCurve(self, x1,y1,x2,y2,x3,y3,x4,y4,
-                  edgeColor=None, edgeWidth=None, fillColor=transparent, closed=0):
-
-    # get the points into SVG format
-    curveStr = 'M%.2f %.2f C%.2f %.2f %.2f %.2f %.2f %.2f'%(x1,y1,x2,y2,x3,y3,x4,y4)
-    if closed:
-      curveStr = curveStr + 'Z'
-
-    # set color for fill...
-    filling = 0
-    if fillColor:
-      if fillColor != transparent:
-        filling = 1
-          
-    # do the fill
-    if filling:
-      fillStr = 'fill:%s;'%_ColorToSVG(fillColor)
-    else:
-      fillStr = 'fill:none;'
-
-    # set color for edge...
-    if not edgeColor:
-      edgeColor = self.defaultLineColor
-
-    # set edge width...
-    if edgeWidth == None: edgeWidth = self.defaultLineWidth
-
-    # SVG markers
-    edgeStr = 'stroke:%s; stroke-width:%d;'%(_ColorToSVG(edgeColor),int(edgeWidth))
-
-    # draw it
-    outStr = '<path style="%s %s" d="%s"/>\n'%(fillStr,edgeStr,curveStr)
-    self._txt = self._txt + outStr
-
-  
-  def drawString(self, s, x,y, font=None, color=None, angle=0):
-    # set color...
-    if color:
-      if color == transparent: return
-    elif self.defaultLineColor == transparent:
-      return
-    else:
-      color = self.defaultLineColor
-    if font is None:
-      font = self.defaultFont
-    if font:
-      fontStr = self._FormFontStr(font)
-    else:
-      fontStr = ''
-      
-    svgColor = _ColorToSVG(color)
-
-    outStr = ''
-    if angle != 0:
-      # note: this is the correct order of the transforms according to my reading of
-      #  the SVG spec and the behavior of Adobe's SVG plugin.  If you want it to work
-      #  in IBM's SVGView, you'll have to use the second (commented out) form.
-      # Ah, the joys of using mature technologies. ;-)
-      outStr = outStr + '<g transform="translate(%.2f,%.2f) rotate(%.2f)">\n'%(x,y,360-angle)
-      #outStr = outStr + '<g transform="rotate(%.2f) translate(%.2f,%.2f)">\n'%(360-angle,x,y)
-      xLoc = 0
-      yLoc = 0
-    else:
-      xLoc = x
-      yLoc = y
-    lines = string.split(s,'\n')
-    lineHeight = self.fontHeight(font)
-    yP = yLoc
-    for line in lines:
-      outStr = outStr + self._drawStringOneLine(line,xLoc,yP,fontStr,svgColor)
-      yP = yP + lineHeight
-
-    if angle != 0:
-      outStr = outStr + '</g>'
-
-    self._txt = self._txt + outStr
-		
-  def _drawStringOneLine(self,line,x,y,fontStr,svgColor):
-    styleStr = 'style="%s stroke:%s"'%(fontStr,svgColor)
-    return '  <text %s x="%.2f" y="%.2f">%s</text>\n'%(styleStr,x,y,line)
-
-  def drawFigure(self, partList,
-                 edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-    """drawFigure(partList) -- draws a complex figure
-    partlist: a set of lines, curves, and arcs defined by a tuple whose
-    first element is one of figureLine, figureArc, figureCurve
-    and whose remaining 4, 6, or 8 elements are parameters."""
-
-    filling = 0
-    if fillColor:
-      if fillColor != transparent:
-        filling = 1
-          
-    # do the fill
-    if filling:
-      fillStr = 'fill:%s;'%_ColorToSVG(fillColor)
-    else:
-      fillStr = 'fill:none;'
-
-    # set color for edge...
-    if not edgeColor:
-      edgeColor = self.defaultLineColor
-    # set edge width...
-    if edgeWidth == None: edgeWidth = self.defaultLineWidth
-
-    # SVG markers
-    edgeStr = 'stroke:%s; stroke-width:%d;'%(_ColorToSVG(edgeColor),int(edgeWidth))
-
-    pathStr = ''
-    for item in partList:
-      op = item[0]
-      args = list(item[1:])
-
-      if pathStr == '':
-        pathStr = pathStr + 'M'
-      else:
-        pathStr = pathStr + 'L'          
-      if op == figureLine:
-        pathStr = pathStr + '%.2f %.2f L%.2f %.2f'%(tuple(args))
-      elif op == figureCurve:
-        pathStr = pathStr + '%.2f %.2f C%.2f %.2f %.2f %.2f %.2f %.2f'%(tuple(args))
-      elif op == figureArc:
-        x1,y1,x2,y2,theta1,extent=tuple(args)
-        pathStr = pathStr + self._FormArcStr(x1,y1,x2,y2,theta1,extent)
-
-      else:
-        raise TypeError, "unknown figure operator: "+op
-
-    if closed == 1:
-      pathStr = pathStr + 'Z'
-    outStr = '<path style="%s %s" d="%s"/>\n'%(edgeStr,fillStr,pathStr)
-    self._txt = self._txt + outStr
-
-  def drawImage(self, image, x1,y1, x2=None,y2=None):
-    """
-      to the best of my knowledge, the only real way to get an image
-      into SVG is to read it from a file.  So we'll save out to a PNG
-      file, then set a link to that in the SVG.
-    """
-    imageFileName= '%s-%d.%s'%(self.name,self._nImages,string.lower(self._imageFormat))
-    self._nImages = self._nImages + 1
-    image.save(imageFileName,format=self._imageFormat)
-
-    im_width,im_height=image.size
-    if x2 is not None:
-      im_width = abs(x2-x1)
-    if y2 is not None:
-      im_height = abs(y2-y1)
-    outStr = '<image x="%.2f" y="%.2f" width="%.2f" height="%.2f" xlink:href="%s"/>\n'%\
-             (x1,y1,im_width,im_height,imageFileName)
-    self._txt = self._txt + outStr
-
-  def stringWidth(self, s, font=None):
-    "Return the logical width of the string if it were drawn \
-    in the current font (defaults to self.font)."
-    if not font:
-      font = self.defaultFont
-    fontname = self._findExternalFontName(font)
-    return pdfmetrics.stringwidth(s, fontname) * font.size * 0.001
-
-  def fontAscent(self, font=None):
-    if not font:
-      font = self.defaultFont
-    #return -font.size
-    fontname = self._findExternalFontName(font)
-    return pdfmetrics.ascent_descent[fontname][0] * 0.001 * font.size
-
-  def fontDescent(self, font=None):
-    if not font:
-      font = self.defaultFont
-    fontname = self._findExternalFontName(font)
-    return -pdfmetrics.ascent_descent[fontname][1] * 0.001 * font.size
-
-
-def test():
-  #... for testing...
-  canvas = SVGCanvas(name="test")
-
-  canvas.defaultLineColor = Color(0.7,0.7,1.0)	# light blue
-  canvas.drawLines( map(lambda i:(i*10,0,i*10,300), range(30)) )
-  canvas.drawLines( map(lambda i:(0,i*10,300,i*10), range(30)) )
-  canvas.defaultLineColor = black		
-
-  canvas.drawLine(10,200, 20,190, color=red)
-
-  canvas.drawEllipse( 130,30, 200,100, fillColor=yellow, edgeWidth=4 )
-
-  canvas.drawArc( 130,30, 200,100, 45,50, fillColor=blue, edgeColor=navy, edgeWidth=4 )
-
-  canvas.defaultLineWidth = 4
-  canvas.drawRoundRect( 30,30, 100,100, fillColor=blue, edgeColor=maroon )
-  canvas.drawCurve( 20,20, 100,50, 50,100, 160,160 )
-
-  canvas.drawString("This is a test!", 30,130, Font(face="times",size=16,bold=1), 
-                  color=green, angle=-45)
-
-  canvas.drawString("This is a test!", 30,130, color=red, angle=-45)
-
-  polypoints = [ (160,120), (130,190), (210,145), (110,145), (190,190) ]
-  canvas.drawPolygon(polypoints, fillColor=lime, edgeColor=red, edgeWidth=3, closed=1)
-
-  canvas.drawRect( 200,200,260,260, edgeColor=yellow, edgeWidth=5 )
-  canvas.drawLine( 200,260,260,260, color=green, width=5 )
-  canvas.drawLine( 260,200,260,260, color=red, width=5 )
-
-  canvas.flush()
-
-def testit(canvas, s, x,y, font=None):
-  canvas.defaultLineColor = black
-  canvas.drawString(s, x,y, font=font)
-  canvas.defaultLineColor = blue
-  w = canvas.stringWidth(s, font=font)
-  canvas.drawLine(x,y, x+w,y)
-  canvas.drawLine(x,y-canvas.fontAscent(font=font), x+w,y-canvas.fontAscent(font=font))
-  canvas.drawLine(x,y+canvas.fontDescent(font=font), x+w,y+canvas.fontDescent(font=font))	
-
-def test2():
-
-  canvas = SVGCanvas(name="Foogar")
-  testit( canvas, "Foogar", 20, 30 )
-
-  testit( canvas, "Foogar", 20, 90, font=Font(size=24) )
-  global dammit
-
-  testit( canvas, "Foogar", 20, 150, font=Font(face='courier',size=24) )
-
-  testit( canvas, "Foogar", 20, 240, font=Font(face='courier') )
-  canvas.flush()
-
-if __name__ == '__main__':
-  test()
-  test2()
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddletest.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddletest.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddletest.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddletest.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,398 +0,0 @@
-"""piddletest.py
-
-This module puts the various PIDDLE backends through their paces.
-"""
-
-import pagesizes
-from piddle import *
-import string
-import math
-
-backends = ['piddlePDF','piddlePIL','piddleVCR','piddleTK',
-		    'piddlePS','piddleAI','piddleQD','piddleGL', 'piddleWX']
-backends.sort()
-
-#----------------------------------------------------------------------
-# note, these tests do not flush() the canvas
-#----------------------------------------------------------------------
-def minimal(canvasClass):
-	"""Just a very basic test of line drawing and canvas size."""
-	canvas = canvasClass(pagesizes.A6, "testA")		# A6 is a quarter page
-	drawMinimal(canvas)
-	return canvas
-
-def drawMinimal(canvas):
-	saver = StateSaver(canvas) # leave canvas state as you found it, restores state when leaves scope
-	size = canvas.size	# (actual size *may* differ from requested size)
-	canvas.defaultLineColor = green
-	canvas.drawLine(1,1,size[0]-1,size[1]-1)
-	canvas.drawLine(1,size[1]-1,size[0]-1,1)
-	canvas.drawRect(1,1,size[0]-1,size[1]-1, edgeWidth=5)
-
-	return canvas
-
-#----------------------------------------------------------------------
-def basics(canvasClass):
-	"""A general test of most of the drawing primitives except images and strings."""
-	canvas = canvasClass((300,300), "test-basics")
-	return drawBasics(canvas)
-
-def drawBasics(canvas):
-	saver = StateSaver(canvas) # leave canvas state as you found it, restores state when leaves scope
-	canvas.defaultLineColor = Color(0.7,0.7,1.0)	# light blue
-	canvas.drawLines( map(lambda i:(i*10,0,i*10,300), range(30)) )
-	canvas.drawLines( map(lambda i:(0,i*10,300,i*10), range(30)) )
-	canvas.defaultLineColor = black
-	
-	canvas.drawLine(10,200, 20,190, color=red)
-	canvas.drawEllipse( 130,30, 200,100, fillColor=yellow, edgeWidth=4 )
-	
-	canvas.drawArc( 130,30, 200,100, 45,50, fillColor=blue, edgeColor=navy, edgeWidth=4 )
-	
-	canvas.defaultLineWidth = 4
-	canvas.drawRoundRect( 30,30, 100,100, fillColor=blue, edgeColor=maroon )
-	canvas.drawCurve( 20,20, 100,50, 50,100, 160,160 )
-	
-	#canvas.drawString("This is a test!", 30,130, Font(face="times",size=16,bold=1),
-	#				color=green, angle=-45)
-	
-	polypoints = [ (160,120), (130,190), (210,145), (110,145), (190,190) ]
-	canvas.drawPolygon(polypoints, fillColor=lime, edgeColor=red, edgeWidth=3, closed=1)
-	
-	canvas.drawRect( 200,200,260,260, edgeColor=yellow, edgeWidth=5 )
-	canvas.drawLine( 200,260,260,260, color=green, width=5 )
-	canvas.drawLine( 260,200,260,260, color=red, width=5 )
-	
-	return canvas
-
-#----------------------------------------------------------------------
-def advanced(canvasClass):
-	"""A test of figures and images."""
-	canvas = canvasClass((300,300), "test-advanced")
-	return drawAdvanced(canvas)
-
-def drawAdvanced(canvas):
-	saver = StateSaver(canvas) # leave canvas state as you found it, restores state when leaves scope
-	figure = [
-			( figureCurve, 20,20, 100,50, 50,100, 160,160 ),
-			( figureLine, 200,200, 250,150 ),
-			( figureArc, 50,10, 250,150, 10,90 ) ]
-
-	canvas.drawFigure(figure, fillColor=yellow, edgeWidth=4)
-		
-	try:
-		import Image
-	except:
-		canvas.drawString("PIL not available!", 20,200)
-		Image = None
-	
-	if Image:
-		img = Image.open("python.gif")
-		canvas.drawImage( img, 120,50,120+32,50+64 );
-		canvas.drawImage( img, 0,210,300,210+32 );
-
-	return canvas
-
-
-#----------------------------------------------------------------------
-def bluefunc(x):  return 1.0 / (1.0 + math.exp(-10*(x-0.6)))
-def redfunc(x): return 1.0 / (1.0 + math.exp(10*(x-0.5)))
-def greenfunc(x): return 1 - pow(redfunc(x+0.2),2) - bluefunc(x-0.3)
-
-def spectrum(canvasClass):
-	canvas = canvasClass((300,300), "test-spectrum")
-	return drawSpectrum(canvas)
-
-def drawSpectrum(canvas):
-	"""Generates a spectrum plot; illustrates colors and useful application."""
-	saver = StateSaver(canvas) # leave canvas state as you found it, restores state when leaves scope
-	def plot(f,canvas,offset=0):
-		for i in range(0,100):
-			x = float(i)/100
-			canvas.drawLine(i*3+offset,250, i*3+offset,250-100*f(x))
-	
-	def genColors(n=100):
-		out = [None]*n;
-		for i in range(n):
-			x = float(i)/n
-			out[i] = Color(redfunc(x), greenfunc(x), bluefunc(x));
-		return out
-	
-
-	colors = genColors(300)
-
-	# draw a black background for the spectrum
-	canvas.drawRect( 0,0,300,100, edgeColor=black, fillColor=black )
-
-	# draw the spectrum
-	for i in range(len(colors)):
-		canvas.drawLine(i,20,i,80, colors[i])
-
-	# plot the components of the spectrum
-	canvas.defaultLineColor = red
-	plot(redfunc, canvas)
-
-	canvas.defaultLineColor = blue
-	plot(bluefunc, canvas, 1)
-
-	canvas.defaultLineColor = green
-	plot(greenfunc, canvas, 2)
-	
-	return canvas
-
-#----------------------------------------------------------------------
-def strings(canvasClass):
-	canvas = canvasClass( size=(400,400), name="test-strings" )
-	return drawStrings(canvas)
-
-
-def CenterAndBox(canvas, s, cx=200, y=40):
-	"tests string positioning, stringWidth, fontAscent, and fontDescent"
-	canvas.drawLine(cx,y-30, cx,y+30, color=yellow)
-	w = canvas
-	w = canvas.stringWidth(s)
-
-	canvas.drawLine(cx-w/2, y, cx+w/2, y, color=red)
-	canvas.drawString(s, cx-w/2, y )
-	canvas.defaultLineColor = Color(0.7,0.7,1.0)	# light blue
-	canvas.drawLine(cx-w/2, y-20, cx-w/2, y+20)	# left
-	canvas.drawLine(cx+w/2, y-20, cx+w/2, y+20)	# right
-	asc, desc = canvas.fontAscent(), canvas.fontDescent()
-	canvas.drawLine(cx-w/2-20, y-asc, cx+w/2+20, y-asc)		# top
-	canvas.drawLine(cx-w/2-20, y+desc, cx+w/2+20, y+desc)	# bottom
-
-
-
-def drawStrings(canvas):
-	"""Checks font metrics, and also illustrates the standard fonts."""
-
-	saver = StateSaver(canvas) # leave canvas state as you found it, restores state when leaves scope
-	def Write(canvas, s, font, curs):
-		if font: canvas.defaultFont = font
-		text = s
-		while text and text[-1] == '\n': text = text[:-1]
-		canvas.drawString(text, x=curs[0], y=curs[1])
-		if s[-1] == '\n':
-			curs[0] = 10
-			curs[1] = curs[1] + canvas.fontHeight() + canvas.fontDescent()
-		else:
-			curs[0] = curs[0] + canvas.stringWidth(s)
-	
-	
-	def StandardFonts(canvas, Write):
-		canvas.defaultLineColor = black
-		curs = [10,70]
-		for size in (12, 18):
-			for fontname in ("times", "courier", "helvetica", "symbol",
-							"monospaced", "serif", "sansserif"):			
-				curs[0] = 10
-				curs[1] = curs[1] + size*1.5
-				Write(canvas, "%s %d " % (fontname,size), Font(face=fontname, size=size), curs)
-				Write(canvas, "bold ", Font(face=fontname, size=size, bold=1), curs)
-				Write(canvas, "italic ", Font(face=fontname, size=size, italic=1), curs)
-				Write(canvas, "underline", Font(face=fontname, size=size, underline=1), curs)
-	
-	CenterAndBox(canvas, "spam, spam, spam, baked beans, and spam!")
-	StandardFonts(canvas, Write)
-	return canvas
-
-#----------------------------------------------------------------------
-def rotstring(canvasClass):
-	canvas = canvasClass( (450,300), name='test-rotstring' )
-	return drawRotstring(canvas)
-
-def drawRotstring(canvas):
-	"""Draws rotated strings."""
-	saver = StateSaver(canvas) # leave canvas state as you found it, restores state when leaves scope
-	canvas.defaultFont = Font(bold=1)
-	
-	canvas.defaultLineColor = (blue + white)/2
-	canvas.drawLine(0,150, 300,150)
-	canvas.drawLine(150,0, 150,300)
-	
-	s = " __albatros at "
-	w = canvas.stringWidth(s)
-	canvas.drawEllipse(150-w,150-w, 150+w, 150+w, fillColor=transparent)
-	
-	colors = [red,orange,yellow,green,blue,purple]
-	cnum = 0
-	for ang in range(0, 359, 30):
-		canvas.defaultLineColor = colors[cnum]
-		s2 = s + str(ang)
-		canvas.drawString(s2, 150, 150, angle=ang)
-		cnum = (cnum+1) % len(colors)
-
-      	canvas.drawString( "This is  a\nrotated\nmulti-line string!!!", 350, 100, angle= -90, font=Font(underline=1) )
-        #canvas.drawString( "This is  a\nrotated\nmulti-line string!!!", 400, 175, angle= -45, font=Font(underline=1) )
-	return canvas
-
-#----------------------------------------------------------------------
-#----------------------------------------------------------------------
-def tkTest(testfunc):
-	# piddleTK tests are called from here because need TK's event loop
-	try :
-		import piddleTK
-		import Tkinter
-	except:
-		print "A module needed for piddleTK is not available, select another backend"
-		return
-
-	root = Tkinter.Tk()
-	frame = Tkinter.Frame(root)  # label='piddletestTK'
-
-	#tkcanvas = piddleTK.TKCanvas(size=(400,400), name='piddletestTK', master = frame)
-        # try new Tk canvas
-        tkcanvas = piddleTK.TKCanvas(size=(400,400), name='piddletestTK', master = frame)
-	bframe = Tkinter.Frame(root)
-
-	minimalB=Tkinter.Button(bframe, text='minimal test',
-				command= lambda c=tkcanvas : (c.clear(),drawMinimal(c), c.flush())).pack(side=Tkinter.LEFT)
-	basicB = Tkinter.Button(bframe, text='basic test',
-				command= lambda c=tkcanvas: (c.clear(),drawBasics(c),c.flush()) ).pack(side=Tkinter.LEFT)
-	spectB =Tkinter.Button(bframe, text='spectrum test',
-			       command= lambda c=tkcanvas: (c.clear(),drawSpectrum(c),c.flush()) ).pack(side=Tkinter.LEFT)
-	stringsB = Tkinter.Button(bframe, text='strings test',
-				  command= lambda c=tkcanvas:(c.clear(),drawStrings(c),c.flush()) ).pack(side=Tkinter.LEFT)
-	rotstrB = Tkinter.Button(bframe, text='rotated strings test',
-				 command= lambda c=tkcanvas:(c.clear(), drawRotstring(c),c.flush()) ).pack(side=Tkinter.LEFT)
-	advancedB = Tkinter.Button(bframe, text='advanced test',
-				   command= lambda c=tkcanvas:(c.clear(), drawAdvanced(c),c.flush() ) ).pack(side=Tkinter.LEFT)
-        bframe.pack(side=Tkinter.TOP)
-	frame.pack()
-	# try to draw before running mainloop
-	if testfunc== minimal:
-		drawMinimal(tkcanvas)
-	elif testfunc == basics:
-		drawBasics(tkcanvas)
-	elif testfunc == advanced :
-		drawAdvanced(tkcanvas)
-	elif testfunc == spectrum :
-		drawSpectrum(tkcanvas)
-	elif testfunc == strings :
-		drawStrings(tkcanvas)
-	elif testfunc == rotstring :
-		drawRotstring(tkcanvas)
-        else :
-                print "Illegal testfunc handed to tkTest"
-                raise "Unsupported testfunc"
-
-        tkcanvas.flush() 
-	
-	root.mainloop()
-	root.destroy()
-
-#----------------------------------------------------------------------
-def wxTest(testfunc):
-	try :
-		import piddleWX
-		from wxPython.wx import wxApp
-	except:
-		print "A module needed for piddleWX is not available, select another backend"
-		return
-
-        global wx_app
-        if not globals().has_key("wx_app"):
-                class CanvasApp(wxApp):
-                        "The wxApp that runs canvas.  Initializes windows, and handles redrawing"
-                        def OnInit(self):
-                                return 1
-
-                wx_app = CanvasApp(0)
-
-	# run the test, passing the canvas class and returning the canvas
-	canvas = testfunc(piddleWX.WXCanvas)
-	
-	canvas.flush()
-
-        # Run the main loop
-        wx_app.MainLoop()
-
-
-def runtest(backend, testfunc):
-
-	# special cases:
-	if backend=='piddleTK':
-		tkTest(testfunc) # takes care of import, etc.
-		return
-
-	if backend=='piddleWX':
-		wxTest(testfunc) # takes care of import, etc.
-		return
-
-	# import the relevant module		
-	module = __import__(backend)
-
-	# figure out the canvas class name (e.g., "PILCanvas") and get that
-	canvasClass = getattr(module, backend[6:]+"Canvas")
-
-	# run the test, passing the canvas class and returning the canvas
-	canvas = testfunc(canvasClass)
-	
-	# do post-test cleanup
-	canvas.flush()
-        # handle save's here
-	if backend == 'piddlePIL':
-		canvas.save(format='png')		# save as a PNG file
-        elif backend == 'piddleVCR':
-		filename = canvas.name + ".vcr"
-		canvas.save(filename)
-		print filename, "saved"
-        else:     # if backend == 'piddlePS' or backend== 'piddlePDF':
-                canvas.save()  # should be "pass'ed" by Canvas's that don't use save
-
-
-def mainLoop():
-	global tests, backends
-	backend = None
-	test = None
-
-	while 1:
-		# print backends on left, tests on right, indicate chosen one of each
-		i = 0
-		while i < len(backends) or i < len(tests):
-			try: bstr = str(i+1) + '. ' + backends[i]
-			except: bstr = ''
-			try: tstr = chr(65+i) + '. ' + tests[i].__name__
-			except: tstr = ''
-			if i == backend: bflag = '==>'
-			else: bflag = ''
-			if i == test: tflag = '==>'
-			else: tflag = ''
-			print "%10s %-20s %10s %-20s" % (bflag, bstr, tflag, tstr)
-			i = i+1		
-		print
-		
-		inp = raw_input("Selection (0 to exit): ")
-		print
-
-		if inp == '0': return
-		if inp:
-			testinp = ''
-			if inp[-1] in string.letters: testinp = inp[-1]
-			elif inp[0] in string.letters: testinp = inp[0]
-			backinp = string.join(filter(lambda x:x in '0123456789',inp))
-			if backinp:
-				backend = int(backinp)-1
-				if backend < len(backends):
-					docstr = __import__(backends[backend]).__doc__
-					if docstr: print docstr
-					else: print "<no doc string>"
-				else: backend = None
-			if testinp:
-				test = ord(string.upper(testinp[0])) - ord('A')
-				if test >= 0 and test < len(tests):
-					docstr = tests[test].__doc__
-					if docstr:
-                                          print docstr
-				else: test = None
-		print
-		
-		# now, if we have a valid backend and test, run it
-		if backend != None and test != None:
-			runtest(backends[backend], tests[test])
-		
-
-tests = (minimal, basics, advanced, spectrum, strings, rotstring)
-
-if __name__=='__main__':
-	mainLoop()
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleTK.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleTK.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleTK.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleTK.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,396 +0,0 @@
-""" 
-A Tkinter based backend for piddle. 
-  
-Perry A. Stoll 
-  
-Created: February 15, 1999 
-  
-Requires PIL for rotated string support.
-
-Known Problems: 
-     - Doesn't handle the interactive commands yet.
-     - PIL based canvas inherits lack of underlining strings from piddlePIL
-     
-You can find the latest version of this file:
-    via http://piddle.sourceforge.net
-""" 
-
-import Tkinter, tkFont
-import piddle 
-import string
-  
-__version__ = "0.3" 
-__date__    = "April 8, 1999" 
-__author__  = "Perry Stoll,  perry.stoll@mail.com " 
-
-# fixups by chris lee, cwlee@artsci.wustl.edu
-# $Id: piddleTK.py,v 1.5 2000/11/11 18:31:21 clee Exp $
-# - added drawImage scaling support
-# - shifted baseline y parameter in drawString to work around font metric
-#         shift due to Tkinter's Canvas text_item object
-# - fixed argument names so that argument keywords agreed with piddle.py (passes discipline.py)
-#
-#
-# ToDo: for BaseTKCanvas
-#         make sure that fontHeight() is returnng appropriate measure.  Where is this info?
-#     
-# $Log: piddleTK.py,v $
-# Revision 1.5  2000/11/11 18:31:21  clee
-# add method getTkinter(..) to retrieve underlying tkinter canvas
-#
-# Revision 1.4  2000/02/26 23:12:42  clee
-# turn off compression by default on piddlePDF
-# add doc string to new pil-based piddleTK
-#
-# Revision 1.3  2000/02/26 21:23:19  clee
-# update that makes PIL based TKCanvas the default Canvas for TK.
-# Updated piddletest.py.  Also, added clear() methdo to piddlePIL's
-# canvas it clears to "white" is this correct behavior?  Not well
-# specified in current documents.
-#
-# ToDo:
-#   - document special access methods
-
-class FontManager:
-    __alt_faces = { "serif": "Times",
-                    "sansserif": "Helvetica",
-                    "monospaced": "Courier"}
-    def __init__(self):
-        self.font_cache = {}
-
-    # the main interface
-    def stringWidth(self, s, font):
-        tkfont = self.piddleToTkFont(font)
-        return tkfont.measure(s)
-
-    def fontHeight(self, font):
-        tkfont = self.piddleToTkFont(font)
-        return self._tkfontHeight(tkfont) 
-    
-    def fontAscent(self, font):
-        tkfont = self.piddleToTkFont(font)
-        return self._tkfontAscent(tkfont)
-
-    def fontDescent(self, font):
-        tkfont = self.piddleToTkFont(font)
-        return self._tkfontDescent(tkfont)
-
-    def getTkFontString(self, font):
-        """Return a string suitable to pass as the -font option to
-        to a Tk widget based on the piddle-style FONT"""
-        tkfont = self.piddleToTkFont(font)
-        # XXX: should just return the internal tk font name?
-        # return str(tkfont)
-        return ('-family %(family)s -size %(size)s '
-                '-weight %(weight)s -slant %(slant)s '
-                '-underline %(underline)s' % tkfont.config())
-    
-
-    def getTkFontName(self, font):
-        """Return a the name associated with the piddle-style FONT"""
-        tkfont = self.piddleToTkFont(font)
-        return str(tkfont)
-
-    def piddleToTkFont(self, font): 
-        """Return a tkFont instance based on the piddle-style FONT"""
-        if font is None: 
-            return ''
-
-        if font.face:
-            # check if the user specified a generic face type
-            # like serif or monospaced. check is case-insenstive.
-            f = string.lower(font.face)
-            if self.__alt_faces.has_key(f):
-                family = self.__alt_faces[f]
-            else:
-                family = font.face
-        else:
-            # if no face is specified
-            family = "Times"
-            
-        size = font.size or 12
-
-        if font.bold:
-            weight = 'bold'
-        else:
-            weight = 'normal'
-
-        if font.italic:
-            slant = 'italic'
-        else:
-            slant = 'roman'
-
-        if font.underline:
-            underline = 'true'
-        else:
-            underline = 'false'
-
-        # ugh... is there a better way to do this?
-        key = (family,size,weight,slant,underline)
-
-        # check if we've already seen this font.
-        if self.font_cache.has_key(key):
-            # yep, don't bother creating a new one. just fetch it.
-            font = self.font_cache[key]
-        else:
-            # nope, let's create a new tk font.
-            # this way we will return info about the actual font
-            # selected by Tk, which may be different than what we ask
-            # for if it's not availible.
-            font = tkFont.Font(family=family, size=size, weight=weight,
-                               slant=slant,underline=underline)
-            self.font_cache[(family,size,weight,slant,underline)] = font
-        
-        return font
-
-    def _tkfontAscent(self, tkfont):
-        return tkfont.metrics("ascent")
-
-    def _tkfontDescent(self, tkfont):
-        return tkfont.metrics("descent")
-
-
-class BaseTKCanvas(piddle.Canvas): 
-
-    __TRANSPARENT = ''                  # transparent for Tk color
-    def __init__(self, size=(0,0), name="piddleTK", master = None): 
-        piddle.Canvas.__init__(self, size, name)
-        self.draw_width, self.draw_height = size 
-        self._create_widgets(master) 
-
-        self._font_manager = FontManager()
-        # XXX: do we need this?
-        # keep track of the ids of all objects created so we 
-        # can delete them later 
-        self._item_ids = [] 
-        self._images   = []
-        
-    def _create_widgets(self, master): 
-  
-        draw_area = Tkinter.Canvas(master = master, 
-                                   height=self.draw_height, 
-                                   width=self.draw_width, 
-                                   relief=Tkinter.SUNKEN, 
-                                   background="white", 
-                                   borderwidth=2) 
-        #draw_area.tk.call('package','require','Tkimaging')
-        self.draw_area = draw_area 
-        self.draw_area.pack() 
-  
-        self.quit_btn = Tkinter.Button(master, 
-                                       text='QUIT', 
-                                       foreground='red', 
-                                       command = self._quit) 
-        self.quit_btn.pack(side=Tkinter.LEFT) 
-  
-        self.clear_btn = Tkinter.Button(master, 
-                                        text='CLEAR', 
-                                        foreground='red', 
-                                        command = self.clear) 
-        self.clear_btn.pack(side=Tkinter.LEFT) 
-  
-    # special access method for underlying Tkinter.Canvas
-    def getTkinterCanvas(self):
-        return self.draw_area()
-          
-    def _display(self): 
-        self.flush()
-        self.draw_area.mainloop() 
-  
-    def _quit(self): 
-        self.draw_area.quit() 
-
-    # Hmmm...the postscript generated by this causes my Ghostscript to barf... 
-    def _to_ps_file(self, filename): 
-        self.draw_area.postscript(file=filename) 
-          
-  
-    def flush(self): 
-        self.draw_area.update() 
-  
-    def clear(self): 
-        map(self.draw_area.delete,self._item_ids) 
-        self._item_ids = [] 
-        self._images   = []
-        
-    def _colorToTkColor(self, c): 
-        return "#%02X%02X%02X" %( int(c.red*255), 
-                                  int(c.green*255), 
-                                  int(c.blue*255)) 
-  
-    def _getTkColor(self, color, defaultColor): 
-        if color is None: 
-            color = defaultColor 
-        if color is piddle.transparent: 
-            color = self.__TRANSPARENT
-        else: 
-            color = self._colorToTkColor(color) 
-        return color 
-  
-    def drawLine(self, x1, y1, x2, y2, color = None, width = None): 
-        color = self._getTkColor(color, self.defaultLineColor) 
-        if width is None: 
-            width  = self.defaultLineWidth 
-        new_item = self.draw_area.create_line(x1,y1,x2,y2,
-                                              fill=color, width=width) 
-        self._item_ids.append(new_item) 
-  
-    # NYI: curve with fill 
-    #def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, 
-    #              edgeColor=None, edgeWidth=None, fillColor=None, closed=0): 
-    # 
-
-    def stringWidth(self, s, font=None):
-        return self._font_manager.stringWidth(s, font or self.defaultFont)
-    
-    def fontAscent(self, font=None):
-        return self._font_manager.fontAscent(font or self.defaultFont)
-
-    def fontDescent(self, font=None):
-        return self._font_manager.fontDescent(font or self.defaultFont)
-
-    def drawString(self, s, x, y, font=None, color=None, angle=None): 
-        # fudge factor for TK on linux (at least)
-        # strings are being drawn using create_text in canvas
-        y = y - self.fontHeight(font) * .28 # empirical
-        #y = y - self.fontDescent(font)
-    
-        color = self._getTkColor(color, self.defaultLineColor) 
-        font  = self._font_manager.getTkFontString(font or self.defaultFont) 
-        new_item = self.draw_area.create_text(x, y, text=s, 
-                                              font=font, fill=color, 
-                                              anchor=Tkinter.W 
-                                              ) 
-        self._item_ids.append(new_item) 
-  
-    def drawRect(self, x1, y1, x2, y2, edgeColor=None, 
-                 edgeWidth=None, fillColor=None): 
-        fillColor = self._getTkColor(fillColor, self.defaultFillColor) 
-        edgeColor = self._getTkColor(edgeColor, self.defaultLineColor) 
-        if edgeWidth is None: 
-            edgeWidth  = self.defaultLineWidth 
-        new_item = self.draw_area.create_rectangle(x1,y1,x2,y2, 
-                                                   fill=fillColor, 
-                                                   width=edgeWidth, 
-                                                   outline=edgeColor) 
-        self._item_ids.append(new_item) 
-          
-    # NYI: 
-    #def drawRoundRect(self, x1,y1, x2,y2, rx=5, ry=5, 
-    #                  edgeColor=None, edgeWidth=None, fillColor=None): 
-  
-  
-    def drawEllipse(self, x1,y1, x2,y2, 
-                    edgeColor=None, edgeWidth=None, fillColor=None): 
-        fillColor = self._getTkColor(fillColor, self.defaultFillColor) 
-        edgeColor = self._getTkColor(edgeColor, self.defaultLineColor) 
-        if edgeWidth is None: 
-            edgeWidth  = self.defaultLineWidth 
-        new_item = self.draw_area.create_oval(x1, y1, x2, y2, 
-                                              fill=fillColor, 
-                                              outline=edgeColor,
-                                              width=edgeWidth) 
-        self._item_ids.append(new_item) 
-  
-    def drawArc(self, x1, y1, x2, y2, startAng=0, extent=360, 
-                edgeColor=None, edgeWidth=None, fillColor=None): 
-        fillColor = self._getTkColor(fillColor, self.defaultFillColor) 
-        edgeColor = self._getTkColor(edgeColor, self.defaultLineColor) 
-        if edgeWidth is None: 
-            edgeWidth  = self.defaultLineWidth 
-        new_item = self.draw_area.create_arc(x1, y1, x2, y2, 
-                                             start=startAng, 
-                                             extent=extent, 
-                                             fill=fillColor, 
-                                             width=edgeWidth,
-                                             outline=edgeColor) 
-        self._item_ids.append(new_item) 
-  
-    def drawPolygon(self, pointlist, 
-                    edgeColor=None, edgeWidth=None, fillColor=None, closed=0): 
-        fillColor = self._getTkColor(fillColor, self.defaultFillColor) 
-        edgeColor = self._getTkColor(edgeColor, self.defaultLineColor) 
-        if edgeWidth is None: 
-            edgeWidth  = self.defaultLineWidth 
-        if closed:
-            # draw a closed shape
-            new_item = self.draw_area.create_polygon(pointlist, 
-                                                     fill=fillColor, 
-                                                     width=edgeWidth,
-                                                     outline=edgeColor)
-        else:
-            if fillColor == self.__TRANSPARENT:
-                # draw open-ended set of lines
-                d = { 'fill':edgeColor, 'width': edgeWidth}
-                new_item = apply(self.draw_area.create_line, pointlist, d)
-            else:
-                # open filled shape.
-                # draw it twice:
-                #    once as a polygon with no edge outline with the fill color
-                #    and once as an open set of lines of the appropriate color
-                new_item = self.draw_area.create_polygon(pointlist, 
-                                                         fill=fillColor, 
-                                                         outline=self.__TRANSPARENT)
-                
-                self._item_ids.append(new_item)
-                
-                d = { 'fill':edgeColor, 'width': edgeWidth}
-                new_item = apply(self.draw_area.create_line, pointlist, d)
-                                                   
-        self._item_ids.append(new_item) 
-  
-    
-    #def drawFigure(self, partList, 
-    #               edgeColor=None, edgeWidth=None, fillColor=None):
-    # use default implementation
-          
-    def drawImage(self, image, x1, y1, x2=None,y2=None):
-
-        try:
-            import ImageTk
-        except ImportError:
-            raise NotImplementedError('drawImage - require the ImageTk module')
-
-        w,h = image.size
-        if not x2 :
-            x2 = w + x1
-        if not y2 :
-            y2 = h + y1
-
-        if (w != x2-x1) or (h != y2-y1) :  # need to scale image
-            myimage = image.resize((x2-x1,y2-y1))
-        else:
-            myimage = image
-            
-        # unless I keep a copy of this PhotoImage, it seems to be garbage collected
-        # and the image is removed from the display after this function. weird
-        itk = ImageTk.PhotoImage(myimage)
-        new_item = self.draw_area.create_image(x1, y1, image=itk, anchor=Tkinter.NW)
-        self._item_ids.append(new_item) 
-        self._images.append(itk)
-
-
-try :
-    import piddlePIL
-
-    class TKCanvas(piddlePIL.PILCanvas):
-
-        """This canvas maintains a PILCanvas as its backbuffer.  Drawing calls
-        are made to the backbuffer and flush() sends the image to the screen
-        using BaseTKCanvas.  
-           You can also save what is displayed to a file in any of the formats
-        supported by PIL"""
-        
-        def  __init__(self, size=(300,300), name='TKCanvas',  master = None) :
-            piddlePIL.PILCanvas.__init__(self, size=size, name=name)
-            self._tkcanvas = BaseTKCanvas(size=size, name=name, master=master)
-
-        def flush(self) :
-            piddlePIL.PILCanvas.flush(self) # call inherited one first
-            self._tkcanvas.drawImage(self._image, 0,0)  # self._image should be a PIL image
-            self._tkcanvas.flush()
-
-
-except:
-    # couldn't import piddlePIL so the PIL-based TkCanvas is not available, fall back to BaseTKCanvas w/o rotated strings
-    TKCanvas = BaseTKCanvas
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleVCR.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleVCR.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleVCR.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleVCR.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,271 +0,0 @@
-# piddleVCR.py -- a record/playback canvas for PIDDLE
-# Copyright (C) 1999  Joseph J. Strout
-# 
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-# 
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-# 
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-# $Log: piddleVCR.py,v $
-# Revision 1.4  2000/08/29 04:21:28  clee
-# sweep through w/ changes to save()/flush()
-# just before release
-#
-# Revision 1.3  2000/08/28 17:08:55  clee
-# check of CanvasXX.save() signature
-#
-# Revision 1.2  2000/07/06 02:58:19  clee
-# fixed reference to .recordfunc to .recordfunc, now gives warning when asked to drawImage
-#
-
-
-"""piddleVCR
-
-This module implements a PIDDLE canvas that simply records all
-drawing commands given to it.  It can save this recording to
-a file, load it again, and play it back into another canvas.
-
-Usage: to record, it must be attached to a canvas so that it
-can ask the attached canvas for font metrics.  To play back,
-or if no font metrics are needed, then attaching it to a
-canvas is not necessary.  Just call myPiddleVCR.playBack(canvas)
-to play back recorded drawing commands.
-
-STATUS: Working, but still contains some debugging code.
-        Will NOT work with images :(  -cwl
-Joe Strout (joe@strout.net), 10/21/99.
-"""
-
-VERSION = 0.1
-
-from piddle import *
-from types import *
-import string
-
-# internal utility functions
-def _repr(x):
-    "Convert x into a representation we can use to play back."
-    return repr(x)
-
-def _reprs(*args):
-    "Convert a whole set of items into a tuple of their representations."
-    return tuple(map(lambda x:_repr(x),args))
-
-
-class VCRCanvas( Canvas ):
-    
-    def __init__(self, size=(0,0), name="piddleVCR", playthru=None):
-        # canvas we send drawing commands to while recording;
-        # also used for font metrics:
-        self.__dict__['playthru'] = playthru    
-        self.__dict__['recording'] = []
-        Canvas.__init__(self, size, name)
-        self.__dict__['recording'] = []        # wipe out any extra piddle initialization
-        
-    def __setattr__(self, attribute, value):
-        if self.playthru: setattr(self.playthru, attribute, value)
-        self.__dict__[attribute] = value
-        if attribute[0] == '_': return
-        self._record("%s = %s" % (attribute, _repr(value)) )
-                
-    # private functions
-    def _record(self,s):
-        self.recording.append(s)
-        # print s  # debugging function
-
-    def _recordfunc(self,func,*args):
-        prototype = func + "(" + ("%s," * len(args))[:-1] + ")"
-        s = prototype % apply(_reprs, args)
-        if self.playthru:
-            exec('self.playthru.' + s)
-        self._record(s)
-        
-    # public functions
-
-    def flush(self):
-        if self.playthru:
-            self.playthru.flush()
-        
-
-    def playBack(self, canvas):
-        for item in self.recording:
-            exec("canvas." + item)
-        canvas.flush()
-    
-    def save(self, file=None, format=None):
-        "file may be either a filename or a file object. The format argument is not used"
-        if type(file) == StringType: file = open(file,'w')
-        file.write("piddleVCR %s\n" % VERSION)
-        for item in self.recording:
-            file.write(item + '\n')
-        file.write("END")
-    
-    def load(self, file):
-        if type(file) == StringType: file = open(file,'r')
-        line = ''
-        # skip header
-        while line[:10] != 'piddleVCR ':
-            line = file.readline()
-        self.__dict__['recording'] = []
-        # read data lines, and append to recording
-        line = file.readline()
-        while line != "END":
-            self.recording.append(string.strip(line))
-            line = file.readline()
-        return len(self.recording)        
-
-    #------------ string/font info ------------
-    def stringWidth(self, s, font=None):
-        "Return the logical width of the string if it were drawn \
-        in the current font (defaults to self.defaultFont)."
-
-        if self.playthru:
-                        return self.playthru.stringWidth(s,font)
-        else:
-                        return Canvas.stringWidth(self,s,font)
-    
-    def fontAscent(self, font=None):
-        "Find the ascent (height above base) of the given font."
-
-        if self.playthru:
-                        return self.playthru.fontAscent(font)
-        else:
-                        return Canvas.fontAscent(self,font)
-    
-    def fontDescent(self, font=None):
-        "Find the descent (extent below base) of the given font."
-
-        if self.playthru: return self.playthru.fontDescent(font)
-        else: return Canvas.fontDescent(self,font)
-    
-    #------------- drawing methods --------------
-    def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-        "Draw a straight line between x1,y1 and x2,y2."
-
-        self._recordfunc('drawLine', x1,y1,x2,y2,color,width)
-
-    def drawLines(self, lineList, color=None, width=None):
-        "Draw a set of lines of uniform color and width.  \
-        lineList: a list of (x1,y1,x2,y2) line coordinates."
-
-        self._recordfunc("drawLines", lineList,color,width)
-
-
-    def drawString(self, s, x,y, font=None, color=None, angle=0):
-        "Draw a string starting at location x,y."
-
-        self._recordfunc("drawString", s,x,y,font,color,angle)
-        
-
-    def drawCurve(self, x1,y1, x2,y2, x3,y3, x4,y4, 
-                edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-        "Draw a B�zier curve with control points x1,y1 to x4,y4."
-
-        self._recordfunc("drawCurve", x1,y1,x2,y2,x3,y3,x4,y4,edgeColor,edgeWidth,fillColor,closed)
-
-
-    def drawRect(self, x1,y1, x2,y2, edgeColor=None, edgeWidth=None, fillColor=None):
-        "Draw the rectangle between x1,y1, and x2,y2. \
-        These should have x1<x2 and y1<y2."
-        
-        self._recordfunc("drawRect", x1,y1,x2,y2,edgeColor,edgeWidth,fillColor)
-
-
-    def drawRoundRect(self, x1,y1, x2,y2, rx=8, ry=8,
-                    edgeColor=None, edgeWidth=None, fillColor=None):
-        "Draw a rounded rectangle between x1,y1, and x2,y2, \
-        with corners inset as ellipses with x radius rx and y radius ry. \
-        These should have x1<x2, y1<y2, rx>0, and ry>0."
-
-        self._recordfunc("drawRoundRect", x1,y1, x2,y2, rx, ry, edgeColor,edgeWidth,fillColor)
-
-
-    def drawEllipse(self, x1,y1, x2,y2, edgeColor=None, edgeWidth=None, fillColor=None):
-        "Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2. \
-        These should have x1<x2 and y1<y2."
-
-        self._recordfunc("drawEllipse", x1,y1,x2,y2,edgeColor,edgeWidth,fillColor)
-
-
-    def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360,
-                edgeColor=None, edgeWidth=None, fillColor=None):
-        "Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2, \
-        starting at startAng degrees and covering extent degrees.   Angles \
-        start with 0 to the right (+x) and increase counter-clockwise. \
-        These should have x1<x2 and y1<y2."
-
-        self._recordfunc("drawArc", x1,y1,x2,y2,startAng,extent,edgeColor,edgeWidth,fillColor)
-
-    def drawPolygon(self, pointlist, 
-                edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-        """drawPolygon(pointlist) -- draws a polygon
-        pointlist: a list of (x,y) tuples defining vertices
-        """
-
-        self._recordfunc("drawPolygon", pointlist,edgeColor,edgeWidth,fillColor,closed)
-
-
-    def drawFigure(self, partList,
-                edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-        """drawFigure(partList) -- draws a complex figure
-        partlist: a set of lines, curves, and arcs defined by a tuple whose
-                  first element is one of figureLine, figureArc, figureCurve
-                  and whose remaining 4, 6, or 8 elements are parameters."""
-
-        self._recordfunc("drawFigure", partList, edgeColor,edgeWidth,fillColor,closed)
-
-    def drawImage(self, image, x1,y1, x2=None,y2=None):
-        print "Warning!!! piddleVCR does not implent drawImage"
-        # These are thoughts on how to implement this using a shelf to store image
-        # it kept everyting contained in one file
-#          import shelve
-#          imageKeyName = `image`
-#          print imageKeyName
-
-#          if hasattr(self, 'shelf'):
-#              print "has shelf"
-#          else:
-#              print "creating shelf"
-#              self.shelfName = self.name + ".vcr.shelf"
-#              self.shelf = shelve.open(self.shelfName)
-#              # now let the vcr file know we have this shelf
-#              self._record("import shelve")
-#              self._record("
-#          self.shelf[imageKeyName] = image
-
-#          self._recordfunc("drawImage, 
-        
-        
-def test1():
-    import piddlePS
-    canvas = piddlePS.PSCanvas()
-    vcr = VCRCanvas(canvas)
-    vcr.defaultLineWidth = 3
-    vcr.drawLine(10,10,100,30, color=red)
-    vcr.drawString("This is a test!", 25,50, Font(size=24))
-    vcr.flush()
-
-    f = open('test1.vcr','w')
-    vcr.save(f)
-
-def test2():
-    vcr = VCRCanvas()
-    f = open('test1.vcr','r')
-    vcr.load(f)
-    f.close()
-    
-    import piddlePS
-    canvas2 = piddlePS.PSCanvas()
-    vcr.playBack(canvas2)
-    canvas2.flush()
-
-test1()
-test2()
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleWxDc.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleWxDc.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleWxDc.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleWxDc.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,279 +0,0 @@
-'''\
-a piddle wrapper for wxPython DeviceContexts
-piddleWxDc.py
-
-By Paul and Kevin Jacobs
-
-History -
-
-   1.0  Many fixes and the rest of required piddle functionality added.
-   0.5  Much work done by Jeffrey Kunce on image support and code factoring.
-
-PiddleWxDc adds Piddle-compatible methods to any wxPython DeviceContext.
-It can be used any where a wxDC is used (onPaint, onDraw, etc).
-
-Code factoring and pil image support by Jeffrey Kunce
-
-see also piddleWxDcDemo.py
-'''
-
-from wxPython.wx import *
-import piddle
-
-
-class PiddleWxDc(piddle.Canvas):
-
-    def __init__(self, aWxDc, size=(300,300), name="piddleWX"): 
-        piddle.Canvas.__init__(self, size, name)
-        self.dc = aWxDc
-        self.dc.BeginDrawing()
-
-    def __del__(self): 
-        self.dc.EndDrawing()
-
-    def _getWXcolor(self, color, default = None):
-        '''Converts PIDDLE colors to wx colors'''
-        if color is not None:
-           if color == piddle.transparent:
-             return None
-           elif color.red >= 0 and color.green >= 0 and color.blue >= 0:
-             return wxColour(color.red * 255, color.green * 255, color.blue * 255)
-
-        if default is not None:
-          return self._getWXcolor(default)
-        else:
-          return None   # End of the line
-
-    def _getWXbrush(self, color, default_color = None):
-        '''Converts PIDDLE colors to a wx brush'''
-
-        if color == piddle.transparent:
-          return wxTRANSPARENT_BRUSH
-
-        wxcolor = self._getWXcolor(color)
-
-        if wxcolor is None:
-          if default_color is not None:
-            return self._getWXbrush(default_color)
-          else:
-            raise "WXcanvas error:  Cannot create brush."
-
-        return wxBrush(wxcolor)
-
-    def _getWXpen(self, width, color, default_color = None):
-        '''Converts PIDDLE colors to a wx pen'''
-
-        if width is None or width < 0:
-            width = self.defaultLineWidth
-
-        if color == piddle.transparent:
-          return wxTRANSPARENT_PEN
-
-        wxcolor = self._getWXcolor(color)
-
-        if wxcolor is None:
-          if default_color is not None:
-            return self._getWXpen(width, default_color)
-          else:
-            raise "WXcanvas error:  Cannot create pen."
-
-        return wxPen(wxcolor, width)
-
-    def _getWXfont(self, font):
-        '''Returns a wxFont roughly equivalent to the requested PIDDLE font'''
-        if font is None:
-            font = self.defaultFont
-        #  PIDDLE fonts are matched to wxFont families.  While it is possible to
-        #  match them to individual fonts, this is difficult to do in a platform
-        #  independant way
-        if font.face is None or font.face == 'times':
-            family = wxDEFAULT 
-        elif font.face == 'courier' or font.face == 'monospaced':
-            family = wxMODERN 
-        elif font.face == 'helvetica' or font.face == 'sansserif':
-            family = wxSWISS 
-        elif font.face == 'serif' or font.face == 'symbol':
-            family = wxDEFAULT 
-        else:
-            family = wxDEFAULT
-        weight = wxNORMAL 
-        style = wxNORMAL 
-        underline = 0
-        if font.bold == 1:
-            weight = wxBOLD
-        if font.underline == 1:
-            underline = 1
-        if font.italic == 1:
-            style = wxITALIC
-        return wxFont(font.size, family, style, weight, underline)
-
-    def _setWXfont(self, font=None):
-        '''set/return the current font for the dc
-        jjk  10/28/99'''
-        wx_font = self._getWXfont(font)
-        self.dc.SetFont(wx_font)
-        return(wx_font)
-
-    def isInteractive(self):
-        return(0)
-
-    def canUpdate(self):
-        return 1
-
-    def clear(self):
-        self.dc.Clear()
-    
-  #------------ string/font info ------------
-
-    def stringWidth(self, s, font=None):
-        '''Return the logical width of the string if it were drawn \
-        in the current font (defaults to self.font).'''
-        wx_font = self._setWXfont(font)
-        return self.dc.GetTextExtent(s)[0]
-
-    def fontHeight(self, font=None):
-        '''Find the total height (ascent + descent) of the given font.'''
-        return self.fontAscent(font) + self.fontDescent(font)
-
-    def fontAscent(self, font=None):
-        '''Find the ascent (height above base) of the given font.'''
-        wx_font = self._setWXfont(font)
-        return self.dc.GetCharHeight() - self.fontDescent(font)
-
-    def fontDescent(self, font=None):
-        '''Find the descent (extent below base) of the given font.'''
-        wx_font = self._setWXfont(font)
-        extents = self.dc.GetFullTextExtent(' ', wx_font)
-        return extents[2]
-
-  #------------- drawing methods --------------
-  # Note default parameters "=None" means use the defaults set in the
-  # Canvas method: defaultLineColor, etc.
-
-    def drawLine(self, x1,y1, x2,y2, color=None, width=None):
-        '''Draw a straight line between x1,y1 and x2,y2.'''
-
-        if width is None or width < 0:
-            width = self.defaultLineWidth
-        self.dc.SetPen(self._getWXpen(width, color, self.defaultLineColor))
-        self.dc.DrawLine(x1,y1,x2,y2)
-
-    def drawString(self, s, x,y, font=None, color=None, angle=None):
-        '''Draw a string starting at location x,y.
-        NOTE: the baseline goes on y; drawing covers (y-ascent,y+descent)
-        Text rotation (angle%360 != 0) is not supported.'''
-        
-        self._setWXfont(font)
-
-        if color == piddle.transparent:
-            return
-
-        # No defaultFontColor?
-        wx_color = self._getWXcolor(color, self.defaultLineColor)
-
-        if wx_color is None:
-            wx_color = wxBLACK;
-
-        self.dc.SetTextForeground(wx_color)
-            
-        if '\n' in s or '\r' in s:
-          #normalize line ends
-          s = string.replace(s, '\r\n','\n')
-          s = string.replace(s, '\n\r','\n')
-          lines = string.split(s, '\n')
-        else:
-          lines = [s]
-
-        if angle is not None:
-          self._drawRotatedString(lines, x, y, font, wx_color, angle)
-        else:
-          line_height = self.fontHeight(font)
-          for l in range(0,len(lines)):
-            self.dc.DrawText(lines[l], x, y - self.fontAscent(font) + l*line_height)
-
-    def _drawRotatedString(self, lines, x, y, font = None, color = None, angle=0):
-
-        import math
-        
-        # [kbj] Hack since the default system font may not be able to rotate.
-        if font is None:
-          font = piddle.Font(face='helvetica')            
-          self._setWXfont(font)
-
-        ascent = self.fontAscent(font)
-        height = self.fontHeight(font)
-
-        rad = angle * math.pi / 180.
-        s = math.sin(rad)
-        c = math.cos(rad)
-        dx = s*height
-        dy = c*height
-        lx = x - dx
-        ly = y - c*ascent
-
-        for i in range(0, len(lines)):
-          self.dc.DrawRotatedText(lines[i], lx + i*dx, ly + i*dy, angle) 
-        
-    # drawPolygon: For fillable shapes, edgeColor defaults to
-    # self.defaultLineColor, edgeWidth defaults to self.defaultLineWidth, and
-    # fillColor defaults to self.defaultFillColor.  Specify "don't fill" by
-    # passing fillColor=transparent.
-
-    def drawPolygon(self, pointlist, edgeColor=None, edgeWidth=None, fillColor=None, closed=0):
-        """drawPolygon(pointlist) -- draws a polygon 
-        pointlist: a list of (x,y) tuples defining vertices
-        closed:    if 1, adds an extra segment connecting the last point 
-            to the first
-        """
-
-
-        # Because wxPython automatically closes polygons, the polygon fill and the border
-        # are drawn seperately, so open polygons will display correctly
-        self.dc.SetPen(wxTRANSPARENT_PEN)
-        self.dc.SetBrush(self._getWXbrush(fillColor, self.defaultFillColor))
-
-        #  Workaround : PIDDLE will pass mixed lists of lists and 2-tuples
-        #  instead of just 2-tuples.  Therefore, pointlist must be re-created as
-        #  only 2-tuples
-
-        pointlist = map(lambda i: tuple(i), pointlist)
-        if closed == 1:
-            pointlist.append(pointlist[0])
-
-        self.dc.DrawPolygon(pointlist)
-
-        # Create a list of lines (4-tuples) to pass to drawLines
-        linelist = []
-        if len(pointlist) > 1:
-            for i in range(1, len(pointlist)):
-                linelist.append( (pointlist[i-1][0], pointlist[i-1][1], pointlist[i][0], pointlist[i][1]) )
-            else:
-                linelist.append( (pointlist[0][0], pointlist[0][1], pointlist[0][0], pointlist[0][1]))
-        self.drawLines(linelist, edgeColor, edgeWidth)
-
-    # no colors apply to drawImage; the image is drawn as-is
-    def drawImage(self, image, x1,y1, x2=None,y2=None):
-        """Draw a PIL Image into the specified rectangle.  If x2 and y2 are
-        omitted, they are calculated from the image size.
-        jjk  11/03/99"""
-
-        try:
-            from PIL import Image
-        except ImportError:
-            print 'PIL not installed as package'
-            try:
-                import Image
-            except ImportError:
-                raise "PIL not available!"
-
-        if (x2 and y2 and x2>x1 and y2>y1):
-            imgPil = image.resize((x2-x1,y2-y1))
-        else:
-            imgPil = image
-        if (imgPil.mode!='RGB'):
-            imgPil = imgPil.convert('RGB')
-        imgData = imgPil.tostring('raw','RGB')
-        imgWx = wxEmptyImage(imgPil.size[0],imgPil.size[1])
-        imgWx.SetData(imgData)
-        self.dc.DrawBitmap(imgWx.ConvertToBitmap(), x1, y1)
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/piddleWX.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleWX.py
--- bkchem-0.12.5/bkchem/plugins/piddle/piddleWX.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/piddleWX.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,402 +0,0 @@
-# piddleWX.py -- a wxPython backend for PIDDLE
-# Copyright (c) 2000  Paul & Kevin Jacobs
-#
-#   This program is free software; you can redistribute it and/or modify
-#   it under the terms of the GNU General Public License as published by
-#   the Free Software Foundation; either version 2, or (at your option)
-#   any later version.
-#
-#   This program is distributed in the hope that it will be useful,
-#   but WITHOUT ANY WARRANTY; without even the implied warranty of
-#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#   GNU General Public License for more details.
-#
-#   You should have received a copy of the GNU General Public License
-#   along with this program; if not, write to the Free Software
-#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-"""WXCanvas
-
-This class implements a PIDDLE Canvas object that draws using wxPython, the
-wxWindows Python language bindings into a GUI window.
-"""
-# Status as of version 1.0:
-#
-# -  All test cases work correctly!  (except for the symbol font)
-#
-# -  The full interactive canvas API is supported, but not extensively
-#    tested.  However, the default status bar object displays interactive
-#    events nicely.
-#
-# -  The drawing code has been separated into a PiddleWxDc class that
-#    enables any wxPython application to use Piddle methods as a
-#    DeviceContext.
-#
-# History:
-#
-#    0.1     First release.  Most PIDDLE functionality is there.
-#    0.5     PIL support and code factoring added by Jeffrey Kunce.
-#    1.0     All PIDDLE functionality supported.  Rotated test, multiline text,
-#            and pil image support added.
-#
-# Known Bugs:
-#
-# -  Due to limitations in wxWindows, some fonts will not display when rotated;
-#    As the default GUI font is not true-type, it cannot be rotated, and
-#    PiddleWX will automatically re-assign the offending font to a type face
-#    that can.
-#
-#
-# Extensions:
-#
-# -  The canvas init function can take up to two additional arguments, both
-#    of which are on by default.  The first is a boolean which enables the
-#    interactive mode when true.  The second is a boolean that will add a
-#    status bar to the canvas when true.
-#
-# -  The canvas can handle a left button released mouse event.  It's
-#    handler is called onClickUp and is similar to the other event handlers.
-#    handlers.  The standard onClick event registers left-click button down
-#    events.
-#
-# -  The canvas can switch between interactive and non-interactive modes using
-#    the SetInteractive function
-#
-# -  The canvas also supports an onLeaveWindow event, to trap when the mouse
-#    leaves the canvas window, but due to a wxWindows bug it may not work.
-#
-# Limitations:
-#
-# -  There is no symbol font support at the moment.  It should be possible
-#    under MS Windows.  Not sure about UNIX (wxGTK).
-#
-# -  The default status bar cursor co-ordinates display may not behave
-#    properly due to several bugs.
-#
-# TODO:
-#
-# -  Make the status bar more user-configurable.
-#
-# -  Support printing & generation of Postscript output (just because it is
-#    easy to do).
-
-
-from wxPython.wx import *
-
-from piddleWxDc import PiddleWxDc
-
-__version__ = "1.0"
-__date__    = "February 6, 2000"
-__author__  = "Paul & Kevin Jacobs"
-class _WXCanvasDefaultStatusBar(wxStatusBar):
-  """This status bar displays clear and quit buttons, as well as the
-   location of the mouse and if the left button is down."""
-
-  def __init__(self, canvas, pos, size):
-
-    # These are terribly wrong here in the init under wxGTK but they firm up after a while
-    # I think they need to be set in OnSize event
-    
-    #wxStatusBar.__init__(self, canvas.window, -1) # known to work w/ wxGTK 
-
-    wxStatusBar.__init__(self, canvas.window, -1, pos, size)  # original
-    self.parentwindow = canvas.window
-    self.parentwindow.SetStatusBar(self) # added because it seems to be necessary
-
-    
-    self.SetFieldsCount(3)
-
-    self.sizeChanged = false    
-    self.text = ""
-    self.old_text = ""
-
-    self.canvas = canvas
-
-    qID = NewId()
-    cID = NewId()
-    sID = NewId()
-    bID = NewId()
-
-    # under linux this field value is wrong at this point in constructor so use OnSize event as well
-    # Note: under windows the field sizes are correct at this point
-
-    self.quitButton= wxButton(self, qID, "Quit")
-    self.clearButton = wxButton(self, cID, "Clear")
-    self.click = wxCheckBox(self, bID, "Click")  # This checkbox is down when the left button is pressed 
-
-    self.Reposition()  # set up sizes for layout
-    
-    EVT_BUTTON(self, qID, canvas._OnQuit)
-    EVT_BUTTON(self, cID, canvas._OnClear)
-    EVT_PAINT(self, self.repaint)
-    EVT_SIZE(self, self.OnSize)
-    EVT_IDLE(self, self.OnIdle)
-
-    
-  def repaint(self, event):
-    dc = wxPaintDC(self)
-    self.draw(dc)
-
-  def redraw(self):
-    dc = wxClientDC(self)
-    self.draw(dc)
-
-  def draw(self, dc):
-    # 
-    field = self.GetFieldRect(1)
-    extents = dc.GetTextExtent(self.old_text)
-    dc.SetPen(wxTRANSPARENT_PEN)
-    dc.SetBrush(dc.GetBackground())
-    field = self.GetFieldRect(2)
-    dc.DrawRectangle(field.x, field.y, field.width, field.height)
-    dc.SetFont(wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL))
-    extents = dc.GetTextExtent(self.text)
-    dc.DrawText(self.text, field.x + field.width/2 - extents[0]/2, field.y)
-    self.old_text = self.text
-
-  def SetStatusText(self, s):
-    self.text  =  s
-    self.redraw()
-
-  def OnOver(self, x, y):
-    self.text = `x` + "," + `y`
-    self.redraw()
-
-  def OnClick(self, x, y):
-    self.text = `x` + "," + `y`
-    self.click.SetValue(true)
-    self.redraw()
-
-  def OnLeaveWindow(self):
-    self.click.SetValue(false)
-    self.text = ""
-    self.redraw()
-
-  def OnClickUp(self, x, y):
-    self.click.SetValue(false)
-
-  def OnSize(self, evt):
-    self.Reposition()
-
-    # Set a flag so the idle time handler will also do the repositioning.
-    # It is done this way to get around a buglet where GetFieldRect is not
-    # accurate during the EVT_SIZE resulting from a frame maximize.
-    self.sizeChanged = true
-
-
-  def OnIdle(self, evt):
-    if self.sizeChanged:
-      self.Reposition()
-
-  def Reposition(self):
-    #print "Field Rects:"
-    #print self.GetFieldRect(0)
-    #print self.GetFieldRect(1)
-    #print self.GetFieldRect(2)
-
-    # layout field 0 with Buttons for quit and clear
-
-    field = self.GetFieldRect(0)
-    self.quitButton.SetPosition(wxPoint(field.x, field.y))
-    self.quitButton.SetSize(wxSize(field.width/2, field.height) )
-    
-    self.clearButton.SetPosition(wxPoint(field.x + field.width/2, field.y))
-    self.clearButton.SetSize(wxSize(field.width/2, field.height) )
-    
-    # layout sizing of field 1 w/ check box
-
-    field = self.GetFieldRect(1)
-    #self.click.SetPosition(wxPoint(field.x + field.width/2 -20, field.y)) # original
-    self.click.SetPosition(wxPoint(field.x+15, field.y))
-    #self.click.SetSize(wxSize(100, field.height)) # original but try using defaults instead
-
-    self.sizeChanged = false
-
-
-
-############################################################################
-
-class WXCanvas(PiddleWxDc):
-
-  def __init__(self, size=(300,300), name="piddleWX", status_bar = None, interactive = 1, show_status = 1):
-    """Works like all other PIDDLE canvases, except with extra interactive controls.
-       interactive is set if the canvas is to use the interactive parts of
-       the PIDDLE API, and show_status controls if the default status bar is
-       shown"""
-
-    window = wxFrame(NULL, -1, "WXCanvas", wxPyDefaultPosition, wxSize(size[0], size[1]))
-    window.Show(true)
-
-    self.window = window
-
-    # Resize the window so the client area is equal to the canvas size
-    CSize = window.GetClientSizeTuple()
-
-    # Only leave space for a status bar if it is specified
-    if show_status:
-      # (should eventually be a call to getStatusBarHeight())
-      status_area = 20
-    else:
-      status_area = 0
-
-    window.SetSize(wxSize(size[0] + (size[0] - CSize[0]), size[1] + (size[1] - CSize[1]+ status_area)))
-
-    # This bitmap is used to buffer drawing commands.  It is set to the same
-    # depth as the screen - explicitly changing it can cause errors when it
-    # is blitted to the screen
-
-    bmp = wxEmptyBitmap(size[0], size[1])
-    MemDc = wxMemoryDC()
-    MemDc.SelectObject(bmp)
-
-    MemDc.Clear()
-
-    self.MemDc = MemDc
-    PiddleWxDc.__init__(self, MemDc, size, name)
-
-    self.window = window
-    self.size = size
-
-    # Different status bars can be substituted in by overriding self.sb
-    if status_bar is None:
-      self.sb = _WXCanvasDefaultStatusBar(self, wxPoint(0, size[1]), wxSize(size[0], 20))
-    else:
-      self.sb = status_bar
-
-    if show_status == 0:
-      self.sb.Show(false)
-
-    self.sb.redraw()
-
-    # The default behavior for ignoring events is to pass it only to the
-    # status bar.
-
-    # onClick: x,y is Canvas coordinates of mouseclick def
-    def ignoreClick(canvas,x,y):
-      canvas.sb.OnClick(x,y)
-    self.onClick = ignoreClick
-
-    # onOver: x,y is Canvas location of mouse
-    def ignoreOver(canvas,x,y):
-      canvas.sb.OnOver(x,y)
-    self.onOver = ignoreOver
-
-    # onKey: key is printable character or one of the constants above;
-    #    modifiers is a tuple containing any of (modshift, modControl)
-    def ignoreKey(canvas,key,modifiers):
-      pass
-    self.onKey = ignoreKey
-
-    # onClickUp: This is an extension:  It registers mouse left-button up
-    # events
-    def ignoreClickUp(canvas,x,y):
-      canvas.sb.OnClickUp(x,y)
-    self.onClickUp = ignoreClickUp
-
-    self.interactive = interactive
-
-    EVT_PAINT(window, self._OnPaint)
-    EVT_LEFT_DOWN(window, self._OnClick)
-    EVT_LEFT_UP(window, self._OnClickUp)
-    EVT_MOTION(window, self._OnOver)
-    EVT_CHAR(window, self._OnKey)
-    # Does not seem to be generated as of wxPython 2.1.13
-    EVT_LEAVE_WINDOW(window, self._OnLeaveWindow)
-
-    # onLeaveWindow: This is an extension; it is called when the mouse
-    # leaves the canvas window
-    def leaveWindow(canvas):
-      canvas.sb.OnLeaveWindow()
-    self.onLeaveWindow = leaveWindow
-
-  ################################################################
-  #  Event Managers for wxPython.  To override event handling, alter the
-  #  PIDDLE event handlers, not these
-
-  def _OnClick(self, event):
-    if self.interactive == false:
-      return None
-    if event.GetY() <= self.size[1]:
-      self.onClick(self, event.GetX(), event.GetY())
-
-  def _OnClickUp(self, event):
-    if self.interactive == false:
-      return None
-    self.onClickUp(self, event.GetX(), event.GetY())
-
-  def _OnOver(self, event):
-    if self.interactive == false:
-      return None
-    if event.GetY() <= self.size[1]:
-      self.onOver(self, event.GetX(), event.GetY())
-
-  def _OnLeaveWindow(self, event):
-    if self.interactive == false:
-      return None
-    self.onLeaveWindow(self)
-
-  def _OnKey(self, event):
-    # The following logic is OK for ASCII characters.  Others will need work.
-    code = event.KeyCode()
-    key = None
-    if code >= 0 and code < 256:
-      key = chr(event.KeyCode())
-
-    modifier = []
-
-    if event.ControlDown():
-      modifier.append('modControl')
-
-    if event.ShiftDown():
-      modifier.append('modshift')
-
-    self.onKey(self, key, tuple(modifier) )
-
-  def _OnPaint(self, event):
-    dc = wxPaintDC(self.window)
-    dc.Blit(0,0,self.size[0],self.size[1],self.MemDc,0,0,wxCOPY)
-    del dc
-
-  def _OnQuit(self, event):
-    """Closes the canvas.  Call to return control your application"""
-    self.window.Close()
-
-  def _OnClear(self, event):
-    """Clears the canvas by emptying the memory buffer, and redrawing"""
-    self.MemDc.Clear()
-    dc = wxClientDC(self.window)
-    dc.Blit(0,0,self.size[0],self.size[1],self.MemDc,0,0,wxCOPY)
-
-  #############################################################
-
-  #------------ canvas capabilities -------------
-  def isInteractive(self):
-    """Returns 1 if onClick and onOver events are possible, 0 otherwise."""
-    return self.interactive
-
-  def canUpdate(self):
-    """Returns 1 if the drawing can be meaningfully updated over time (e.g.,
-       screen graphics), 0 otherwise (e.g., drawing to a file)."""
-    return 1
-
-  #------------ general management -------------
-  def clear(self):
-    self.Clear()
-    dc  = wxClientDC(self.window)
-    dc.Blit(0,0,self.size[0], self.size[1], self.MemDc,0,0,wxCOPY)
-
-  def flush(self):
-    """Copies the contents of the memory buffer to the screen and enters the
-       application main loop"""
-
-    dc = wxClientDC(self.window)
-    dc.Blit(0,0,self.size[0],self.size[1],self.MemDc,0,0,wxCOPY)
-    del dc
-
-  def setInfoLine(self, s):
-    """For interactive Canvases, displays the given string in the 'info
-       line' somewhere where the user can probably see it."""
-    if self.sb != None:
-      self.sb.SetStatusText(s)
-
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/PixMapWrapper.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/PixMapWrapper.py
--- bkchem-0.12.5/bkchem/plugins/piddle/PixMapWrapper.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/PixMapWrapper.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,241 +0,0 @@
-"""PixMapWrapper - defines the PixMapWrapper class, which wraps an opaque
-QuickDraw PixMap data structure in a handy Python class.  Also provides 
-methods to convert to/from pixel data (from, e.g., the img module) or a
-Python Imaging Library Image object.
-
-J. Strout <joe@strout.net>  February 1999"""
-
-import Qd
-import QuickDraw
-import struct
-import MacOS
-import img
-import imgformat
-
-# PixMap data structure element format (as used with struct)
-_pmElemFormat = {
-	'baseAddr':'l',		# address of pixel data
-	'rowBytes':'h',		# bytes per row, plus 0x8000
-	'bounds':'hhhh',	# coordinates imposed over pixel data
-		'top':'h',
-		'left':'h',
-		'bottom':'h',
-		'right':'h',
-	'pmVersion':'h',	# flags for Color QuickDraw
-	'packType':'h',		# format of compression algorithm
-	'packSize':'l',		# size after compression
-	'hRes':'l',			# horizontal pixels per inch
-	'vRes':'l',			# vertical pixels per inch
-	'pixelType':'h',	# pixel format
-	'pixelSize':'h',	# bits per pixel
-	'cmpCount':'h',		# color components per pixel
-	'cmpSize':'h',		# bits per component
-	'planeBytes':'l',	# offset in bytes to next plane
-	'pmTable':'l',		# handle to color table
-	'pmReserved':'l'	# reserved for future use
-}
-
-# PixMap data structure element offset
-_pmElemOffset = {
-	'baseAddr':0,
-	'rowBytes':4,
-	'bounds':6,
-		'top':6,
-		'left':8,
-		'bottom':10,
-		'right':12,
-	'pmVersion':14,
-	'packType':16,
-	'packSize':18,
-	'hRes':22,
-	'vRes':26,
-	'pixelType':30,
-	'pixelSize':32,
-	'cmpCount':34,
-	'cmpSize':36,
-	'planeBytes':38,
-	'pmTable':42,
-	'pmReserved':46
-}
-
-class PixMapWrapper:
-	"""PixMapWrapper -- wraps the QD PixMap object in a Python class,
-	with methods to easily get/set various pixmap fields.  Note: Use the
-	PixMap() method when passing to QD calls."""
-
-	def __init__(self):
-		self.__dict__['data'] = ''
-		self._header = struct.pack("lhhhhhhhlllhhhhlll",
-			id(self.data)+MacOS.string_id_to_buffer,
-			0,						# rowBytes
-			0, 0, 0, 0,				# bounds
-			0,						# pmVersion
-			0, 0,					# packType, packSize
-			72<<16, 72<<16,			# hRes, vRes
-			QuickDraw.RGBDirect,	# pixelType
-			16,						# pixelSize
-			2, 5,					# cmpCount, cmpSize,
-			0, 0, 0)				# planeBytes, pmTable, pmReserved
-		self.__dict__['_pm'] = Qd.RawBitMap(self._header)
-	
-	def _stuff(self, element, bytes):
-		offset = _pmElemOffset[element]
-		fmt = _pmElemFormat[element]
-		self._header = self._header[:offset] \
-			+ struct.pack(fmt, bytes) \
-			+ self._header[offset + struct.calcsize(fmt):]
-		self.__dict__['_pm'] = None
-	
-	def _unstuff(self, element):
-		offset = _pmElemOffset[element]
-		fmt = _pmElemFormat[element]
-		return struct.unpack(fmt, self._header[offset:offset+struct.calcsize(fmt)])[0]
-
-	def __setattr__(self, attr, val):
-		if attr == 'baseAddr':
-			raise 'UseErr', "don't assign to .baseAddr -- assign to .data instead"
-		elif attr == 'data':
-			self.__dict__['data'] = val
-			self._stuff('baseAddr', id(self.data) + MacOS.string_id_to_buffer)
-		elif attr == 'rowBytes':
-			# high bit is always set for some odd reason
-			self._stuff('rowBytes', val | 0x8000)
-		elif attr == 'bounds':
-			# assume val is in official Left, Top, Right, Bottom order!
-			self._stuff('left',val[0])
-			self._stuff('top',val[1])
-			self._stuff('right',val[2])
-			self._stuff('bottom',val[3])
-		elif attr == 'hRes' or attr == 'vRes':
-			# 16.16 fixed format, so just shift 16 bits
-			self._stuff(attr, int(val) << 16)
-		elif attr in _pmElemFormat.keys():
-			# any other pm attribute -- just stuff
-			self._stuff(attr, val)
-		else:
-			self.__dict__[attr] = val	
-
-	def __getattr__(self, attr):
-		if attr == 'rowBytes':
-			# high bit is always set for some odd reason
-			return self._unstuff('rowBytes') & 0x7FFF
-		elif attr == 'bounds':
-			# return bounds in official Left, Top, Right, Bottom order!
-			return ( \
-				self._unstuff('left'),
-				self._unstuff('top'),
-				self._unstuff('right'),
-				self._unstuff('bottom') )
-		elif attr == 'hRes' or attr == 'vRes':
-			# 16.16 fixed format, so just shift 16 bits
-			return self._unstuff(attr) >> 16
-		elif attr in _pmElemFormat.keys():
-			# any other pm attribute -- just unstuff
-			return self._unstuff(attr)
-		else:
-			return self.__dict__[attr]	
-
-		
-	def PixMap(self):
-		"Return a QuickDraw PixMap corresponding to this data."
-		if not self.__dict__['_pm']:
-			self.__dict__['_pm'] = Qd.RawBitMap(self._header)
-		return self.__dict__['_pm']
-
-	def blit(self, x1=0,y1=0,x2=None,y2=None, port=None):
-		"""Draw this pixmap into the given (default current) grafport.""" 
-		src = self.bounds
-		dest = [x1,y1,x2,y2]
-		if x2 == None:
-			dest[2] = x1 + src[2]-src[0]
-		if y2 == None:
-			dest[3] = y1 + src[3]-src[1]
-		if not port: port = Qd.GetPort()
-		print "blit port:", port
-		Qd.CopyBits(self.PixMap(), port.portBits, src, tuple(dest),
-				QuickDraw.srcCopy, None)
-
-	def grab(self, x1=0,y1=0,x2=None,y2=None, port=None):
-		"Grab data for this pixmap from the given (default current) grafport."
-		# NOTE: may not work in 8-bit mode!
-		dest = self.bounds
-		src = [x1,y1,x2,y2]
-		if x2 == None:
-			src[2] = x1 + dest[2]-dest[0]
-		if y2 == None:
-			src[3] = y1 + dest[3]-dest[1]
-		if not port: port = Qd.GetPort()
-		if not self.data:
-			width = dest[2]-dest[0]
-			height = dest[3]-dest[1]
-			self.bounds = (0,0,width,height)
-			self.cmpCount = 3
-			self.pixelType = QuickDraw.RGBDirect
-			self.pixelSize = 32
-			self.cmpSize = 8
-			self.rowBytes = width*self.pixelSize/8
-			self.data = '\255' * (self.rowBytes*height)
-
-		Qd.CopyBits(port.portBits, self.PixMap(), tuple(src), dest,
-				QuickDraw.srcCopy, None)
-		
-	def fromstring(self,s,width,height,format=imgformat.macrgb):
-		"""Stuff this pixmap with raw pixel data from a string.
-		Supply width, height, and one of the imgformat specifiers."""
-		# we only support 16- and 32-bit mac rgb...
-		# so convert if necessary
-		if format != imgformat.macrgb and format != imgformat.macrgb16:
-			# (LATER!)
-			raise "NotImplementedError", "conversion to macrgb or macrgb16"
-		self.data = s
-		self.bounds = (0,0,width,height)
-		self.cmpCount = 3
-		self.pixelType = QuickDraw.RGBDirect
-		if format == imgformat.macrgb:
-			self.pixelSize = 32
-			self.cmpSize = 8
-		else:
-			self.pixelSize = 16
-			self.cmpSize = 5
-		self.rowBytes = width*self.pixelSize/8
-
-	def tostring(self, format=imgformat.macrgb):
-		"""Return raw data as a string in the specified format."""
-		# is the native format requested?  if so, just return data
-		if (format == imgformat.macrgb and self.pixelSize == 32) or \
-		   (format == imgformat.macrgb16 and self.pixelsize == 16):
-			return self.data
-		# otherwise, convert to the requested format
-		# (LATER!)
-			raise "NotImplementedError", "data format conversion"
-
-	def fromImage(self,im):
-		"""Initialize this PixMap from a PIL Image object."""
-		# We need data in ARGB format; PIL can't currently do that,
-		# but it can do RGBA, which we can use by inserting one null
-		# up frontpm = 
-		if im.mode != 'RGBA': im = im.convert('RGBA')
-		data = chr(0) + im.tostring()
-		self.fromstring(data, im.size[0], im.size[1])
-
-	def toImage(self):
-		"""Return the contents of this PixMap as a PIL Image object."""
-		import Image
-		# our tostring() method returns data in ARGB format,
-		# whereas Image uses RGBA; a bit of slicing fixes this...
-		data = self.tostring()[1:] + chr(0)
-		bounds = self.bounds
-		return Image.fromstring('RGBA',(bounds[2]-bounds[0],bounds[3]-bounds[1]),data)
-
-def test():
-	import MacOS
-	import macfs
-	import Image
-	fsspec, ok = macfs.PromptGetFile("Image File:")
-	if not ok: return
-	path = fsspec.as_pathname()
-	pm = PixMapWrapper()
-	pm.fromImage( Image.open(path) )
-	pm.blit(20,20)
-	return pm
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/polyfunc.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/polyfunc.py
--- bkchem-0.12.5/bkchem/plugins/piddle/polyfunc.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/polyfunc.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,322 +0,0 @@
-# Christopher Lee (c) 2000
-# code commisioned by Greg Wilson 
-# draw polynomial functions using bezier exact representations in piddle
-
-# Primary Interface Functions:
-#    drawQuad - draws to a canvas
-#    drawCubicPolynomial
-#    class CoordFrame  - defines a coordinate frame in which to draw
-    
-# See the function runtest() for basic use
-
-# Testing:
-#   works w/ piddlePS and piddlePIL
-
-    
-
-def quadratic(x,A,B,C):
-    # y(x) = A x^2 + B x + C
-    return A*x*x + B*x + C
-
-def cubic(x, A,B,C,D):
-    # y(x) = A x^3 + B x^2 + C x + D
-    xsq = x * x
-    return A * x * xsq + B * xsq + C * x + D
-
-
-def quadPts(xinterval,  A=0., B=0., C=0.):
-    """return the control points for a bezier curve that fits the quadratic function
-    y = A x^2 + B x + C.  for the interval along the x-axis given by xinterval=(x0,x1)"""
-    # y(x) = A x^2  + B x + C
-    # for x \in [ xinterval[0], xinterval[1] ]
-    x0 = float(xinterval[0])
-    x3 = float(xinterval[1])
-    y0 = quadratic(x0, A,B,C)
-    ### print 'y0=', y0 DEBUG
-
-    xlen = x3-x0
-    # print 'xlen = ', xlen
-    cx = xlen  # set x scaling, check if makes sense for xlen <0
-    
-    # convert to cubic paramaters, adjust for xscaling
-    # lower case used for cubic: w(t) = a t^3 + b*t^2 + c* t + w0
-    # t: [0,1]
-    # parameters are adjusted to accout for x(t) = cx*t+x0 tranformation
-    #by <-> A
-    #cy <-> B
-    a = (0,0)
-    b = (0, A*cx*cx )
-    c = (cx, B*cx+2*b[1]*x0/cx)
-    v0 = (x0,C-(b[1]/(cx*cx)) *x0*x0) ;
-    ### print a,b,c,v0
-    x = [x0, 0, 0, 0]; y = [y0, 0, 0, 0]
-    v = [x, y]
-    for ii in (0, 1): # iterate over vector components 
-        v[ii][1] = v[ii][0] + c[ii]/3.0
-        v[ii][2] = v[ii][1] + (c[ii]+b[ii])/3.0
-        v[ii][3] = v[ii][0] + c[ii] + b[ii] + a[ii]
-
-    return (v[0][0], v[1][0],
-            v[0][1], v[1][1],
-            v[0][2], v[1][2],
-            v[0][3], v[1][3])
-    
-def cubicPts(xinterval,  A=0., B=0., C=0., D=0):
-    """return the control points for a bezier curve that fits the cubic polynomial
-    y = A x^3 + B x^2 + C x + D.  for the interval along the x-axis given by
-    xinterval=(xStart,xEnd)"""
-    # for x \in [ xinterval[0], xinterval[1] ]
-    x0 = float(xinterval[0])
-    x3 = float(xinterval[1])
-    y0 = cubic(x0, A,B,C,D)  # y0 = y(x0)
-    # print 'y0=', y0 ##DEBUG
-
-    xlen = x3-x0
-    # print 'xlen = ', xlen
-    cx = xlen  # set x scaling, check if makes sense for xlen <0
-    
-    # convert to cubic paramaters, adjust for xscaling
-    # lower case used for cubic: w(t) = a t^3 + b*t^2 + c* t + w0
-    # t: [0,1]
-    # parameters are adjusted to accout for x(t) = cx*t+x0 tranformation
-    #by <-> A
-    #cy <-> B
-    a = (0,A*cx*cx*cx) 
-    b = (0, B*cx*cx+ 3*A*cx*cx*x0 )
-    c = (cx, C*cx + 2*B*cx*x0+3*A*cx*x0*x0)
-    v0 = (x0,y0) ;
-    ### print a,b,c,v0
-    x = [x0, 0, 0, 0]; y = [y0, 0, 0, 0]
-    v = [x, y]
-    for ii in (0, 1): # iterate over vector components 
-        v[ii][1] = v[ii][0] + c[ii]/3.0
-        v[ii][2] = v[ii][1] + (c[ii]+b[ii])/3.0
-        v[ii][3] = v[ii][0] + c[ii] + b[ii] + a[ii]
-
-    return (v[0][0], v[1][0],
-            v[0][1], v[1][1],
-            v[0][2], v[1][2],
-            v[0][3], v[1][3])
-
-
-class AffineMatrix:
-    # taken from PyArt
-    # use this for transforming vpaths and bpaths, can also be used to set gstate.CTM
-    # A = [ a c e]
-    #     [ b d f]
-    #     [ 0 0 1]
-    # self.A = [a b c d e f] = " [ A[0] A[1] A[2] A[3] A[4] A[5] ]"
-    def __init__(self, init=None):
-        if init:
-            if len(init) == 6 :
-                self.A = init
-            if type(init) == type(self): # erpht!!! this seems so wrong
-                self.A = init.A
-        else:
-            self.A = [1.0, 0, 0, 1.0, 0.0, 0.0] # set to identity
-
-    def scale(self, sx, sy):
-        self.A = [sx*self.A[0], sx*self.A[1], sy*self.A[2], sy * self.A[3], self.A[4], self.A[5] ]
-
-    def rotate(self, theta):
-        "counter clockwise rotation in standard SVG/libart coordinate system"
-        # clockwise in postscript "y-upward" coordinate system
-        # R = [ c  -s  0 ]
-        #     [ s   c  0 ]
-        #     [ 0   0  1 ]
-
-        co = math.cos(PI*theta/180.0)
-        si = math.sin(PI*theta/180.0)
-        self.A = [self.A[0] * co + self.A[2] * si,
-                  self.A[1] * co + self.A[3] * si,
-                  -self.A[0]* si + self.A[2] * co,
-                  -self.A[1]* si + self.A[3] * co,
-                  self.A[4],
-                  self.A[5] ]
-
-    def translate(self, tx, ty):
-        self.A  = [ self.A[0], self.A[1], self.A[2], self.A[3],
-                    self.A[0]*tx + self.A[2]*ty + self.A[4],
-                    self.A[1]*tx + self.A[3]*ty + self.A[5] ]
-
-    def rightMultiply(self, a, b, c, d, e, f):
-        "multiply self.A by matrix M defined by coefficients a,b,c,d,e,f"
-        # 
-
-        #             [    m0*a+m2*b,    m0*c+m2*d, m0*e+m2*f+m4]
-        #  ctm * M =  [    m1*a+m3*b,    m1*c+m3*d, m1*e+m3*f+m5]
-        #             [            0,            0,            1]
-        m= self.A
-        self.A = [ m[0]*a+m[2]*b,
-                   m[1]*a+m[3]*b,
-                   m[0]*c+m[2]*d,
-                   m[1]*c+m[3]*d,
-                   m[0]*e+m[2]*f+m[4],
-                   m[1]*e+m[3]*f+m[5] ]
-
-    def transformPt(self, pt):
-        #                   [
-        #  pt = A * [x, y, 1]^T  =  [a*x + c*y+e, b*x+d*y+f, 1]^T
-        #
-        x,y = pt
-        a,b,c,d,e,f = self.A
-        return [ a*x + c*y+e, b*x+d*y+f]
-        
-    def transformFlatList(self, seq):
-        # transform a (flattened) sequence of points in form [x0,y0, x1,y1,..., x(N-1), y(N-1)]
-        N = len(seq) # assert N even
-
-        # would like to reshape the sequence, do w/ a loop for now
-        res = []
-        for ii in xrange(0,N, 2):
-            pt = self.transformPt( (seq[ii], seq[ii+1]) )
-            res.extend(pt)
-
-        return res
-
-#    def transformPath(self, path):  # overload multiply for this
-#        # experiment w/ skipping intermediate call
-#        Part2Libart.PartVpath_transform(path._Ppath, self.A)
-
-
-
-class CoordFrame:
-    # Essentially this identifies a target rectangle on the canvas
-    # in which to draw and allows me to rescale the coordinates (u is for user coordinates)
-    # within the box for my convenience when plotting
-    # Note: no clipping is done
-    def __init__(self,x0,y0, w, h, uX0, uY0, uWidth=1.0, uHeight=1.0, theta=0.0):
-        self.x0 = x0
-        self.y0 = y0
-        self.w = w
-        self.h = h
-        # might want to save other parameters later
-
-        Sx = float(w)/uWidth
-        Sy = float(h)/uHeight
-        
-        self.u2dTM = AffineMatrix() # user to device transformation matrix
-
-        self.u2dTM.translate(x0, y0)
-
-        # if YaxisGoesUp
-        self.u2dTM.translate(0,h) # go down this far
-        self.u2dTM.scale(Sx, -Sy)  
-        self.u2dTM.translate(-uX0, -uY0)  
-
-    def drawFrameBox(self,canvas):
-        'Draw a rectangle that represents the "target area" for the coord frame'
-        # helps with debugging
-        canvas.drawRect(self.x0, self.y0, self.x0+self.w, self.y0 + self.h)
-        
-
-
-def drawCircleAt(canvas, x,y,r, **kw):
-    # useful for marking a particular location w/ a glyph
-    df = canvas.__class__
-    apply(df.drawEllipse, (canvas, x-r, y-r, x+r,y+r), kw)
-
-
-def drawCubicPolynomial(canvas, frame, xinterval, A=0.0, B=0.0, C=0.0, D=0.0,
-             edgeColor=None, edgeWidth=None,fillColor=None):
-    ctlpts = cubicPts(xinterval=xinterval, A=A,B=B,C=C, D=D)
-    Tctlpts = frame.u2dTM.transformFlatList(ctlpts)
-    canvas.drawCurve(Tctlpts[0],Tctlpts[1],Tctlpts[2],Tctlpts[3],
-                     Tctlpts[4],Tctlpts[5],Tctlpts[6],Tctlpts[7],
-                     edgeColor=edgeColor,edgeWidth=edgeWidth,
-                     fillColor=fillColor)
-
-
-
-
-
-def drawQuad(canvas, frame, xinterval, A=0.0, B=0.0, C=0.0,
-             edgeColor=None, edgeWidth=None,fillColor=None):
-    qpts = quadPts(xinterval=xinterval, A=A,B=B,C=C)
-    Tqpts = frame.u2dTM.transformFlatList(qpts)
-    canvas.drawCurve(Tqpts[0],Tqpts[1],Tqpts[2],Tqpts[3],
-                     Tqpts[4],Tqpts[5],Tqpts[6],Tqpts[7], edgeColor=edgeColor,edgeWidth=edgeWidth,
-                     fillColor=fillColor)
-
-
-def drawQuadWithCtrlPts(canvas, frame, xinterval, A=0.0, B=0.0, C=0.0,
-                        edgeColor=None, fillColor=None):
-    # This is useful for debugging--shows where the bezier control points really are
-    qpts = quadPts(xinterval=xinterval, A=A,B=B,C=C)
-    Tqpts = frame.u2dTM.transformFlatList(qpts)
-    canvas.drawCurve(Tqpts[0],Tqpts[1],Tqpts[2],Tqpts[3],
-                     Tqpts[4],Tqpts[5],Tqpts[6],Tqpts[7])
-    # useful code for marking ctrl pts
-    drawCircleAt(canvas, Tqpts[0], Tqpts[1], 5)
-    drawCircleAt(canvas, Tqpts[2], Tqpts[3], 5)
-    drawCircleAt(canvas, Tqpts[4], Tqpts[5], 5)
-    drawCircleAt(canvas, Tqpts[6], Tqpts[7], 5)
-    
-
-def runtest():
-    # These are my parameters for the coodinate frame
-    # Essentially this identifies a target rectangle on the canvas
-    # in which to draw and allows me to rescale the coordinates
-    # within the box for my convenience when plotting
-    # Note: no clipping is done
-    import sys,os, os.path
-    sys.path.append(os.path.join(os.getcwd(), "examples"))
-    import textClasses
-    
-    my_x0 = 50
-    my_y0 = 50
-    my_w  = 300
-    my_h  = 300
-
-    my_uX0 = -10.0
-    my_uY0 = -100.0
-    my_uWidth = 20.0
-    my_uHeight = 200.0
-    
-    frame = CoordFrame(x0= my_x0, y0= my_y0, w=my_w, h=my_h,
-                       uX0= my_uX0, uY0= my_uY0,
-                       uWidth= my_uWidth,
-                       uHeight= my_uHeight)
-
-    import piddlePS
-
-    canvas = piddlePS.PSCanvas((400,400), 'qtest.ps')
-
-
-    def ToCanvas(canvas, frame, textClasses=textClasses):
-        # Given a canvas and a coordinate frame, do some tests on it
-        frame.drawFrameBox(canvas)
-        # draw a family of quadratics w/in the box w/ x-intercept x=0
-        NA = 10.0
-        for AA in xrange(1,NA,2):
-            drawQuad(canvas, frame, xinterval=(-10,10), A= AA/NA, B=0.0)
-            
-        # now some other assorted quadratics
-        
-        drawQuad(canvas, frame, xinterval=(-10,10), A=-1.0, B=0.0, C=0, edgeColor=piddle.red)
-        # shift down and add B portion
-        drawQuad(canvas, frame, xinterval=(-10,10), A=-.4, B=-3.0, C=-30.0, edgeColor=piddle.blue)
-
-        drawCubicPolynomial(canvas, frame, xinterval=(-10,10), A=.14, B=-1.4, C=-10,D=85,
-                                                      edgeColor=piddle.purple)
-
-
-        textbox = textClasses.TextBox(canvas, 100,350, 200, 50, input_text="a family of quadratic curves with a cubic curve in purple", align=textClasses.TA_CENTERED)
-        textbox.draw()
-        canvas.flush()
-        canvas.save()
-
-    ToCanvas(canvas,frame)
-
-    # now send it to a PIL canvas (this works on my machine but commented out because pil
-    #                              may not be working on your machine-cwl )
-    #import piddlePIL
-    #canvas = piddlePIL.PILCanvas(size=(400,400), name='qtest.png')
-    #ToCanvas(canvas,frame)
-    
-        
-if __name__== '__main__':
-    print 'Running test drawing assorted quadratics to qtest.ps'
-    import piddle
-    runtest()
-
diff -uNr bkchem-0.12.5/bkchem/plugins/piddle/stringformat.py bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/stringformat.py
--- bkchem-0.12.5/bkchem/plugins/piddle/stringformat.py	2004-10-30 16:37:30.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/plugins/piddle/stringformat.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,543 +0,0 @@
-"""
-Module StringFormat
-The StringFormat module allows for character-by-character formatting of
-strings. It imitates the PIDDLE string drawing and string metrics
-interface. The string formatting is done with specialized XML syntax
-within the string. Therefore, the interface for the StringFormat module
-consists of wrapper functions for the PIDDLE string interface and
-various XML tags and characters.
-
-StringFormat functions 
-
-       drawString(canvas, s, x, y, [font], [color], [angle]) 
-       stringWidth(canvas, s, [font]) 
-       fontHeight(canvas, [font]) 
-       fontAscent(canvas, [font]) 
-       fontDescent(canvas, [font]) 
-StringFormat XML tags 
-
-       <b> </b> - bold 
-       <i> </i> - italics 
-       <u> </u> - underline 
-       <super> </super> - superscript 
-       <sub> </sub> - subscript 
-
-StringFormat XML characters 
-
-       Greek Letter Symbols as specified in MathML 
-"""
-
-#	How it works: Each tag grouping <b></b> sets a flag upon entry and 
-#	clears the flag upon exit.  Each call to handle_data creates a 
-#	StringSegment which takes on all of the characteristics specified
-#	by flags currently set.  The greek letters can be specified as either
-#	&alpha; or <alpha/>.  The are essentially transformed into <alpha/>
-#	no matter what and then there is a handler for each greek letter.  
-#	To add or change greek letter to symbol font mappings only
-#	the greekchars map needs to change.
-
-from piddle import *
-import xmllib
-import math
-
-#------------------------------------------------------------------------
-# constants
-sizedelta = 2		# amount to reduce font size by for super and sub script
-subFraction = 0.5	# fraction of font size that a sub script should be lowered
-superFraction = 0.5	# fraction of font size that a super script should be raised
-
-#------------------------------------------------------------------------
-# greek mapping dictionary
-
-# characters not supported: epsi, Gammad, gammad, kappav, rhov
-#    Upsi, upsi
-
-greekchars = {
-	'alpha':'a',
-	'beta':'b',
-	'chi':'c',
-	'Delta':'D',
-	'delta':'d',
-	'epsiv':'e',
-	'eta':'h',
-	'Gamma':'G',
-	'gamma':'g',
-	'iota':'i',
-	'kappa':'k',
-	'Lambda':'L',
-	'lambda':'l',
-	'mu':'m',
-	'nu':'n',
-	'Omega':'W',
-	'omega':'w',
-	'omicron':'x',
-	'Phi':'F',
-	'phi':'f',
-	'phiv':'j',
-	'Pi':'P',
-	'pi':'p',
-	'piv':'v',
-	'Psi':'Y',
-	'psi':'y',
-	'rho':'r',
-	'Sigma':'S',
-	'sigma':'s',
-	'sigmav':'V',
-	'tau':'t',
-	'Theta':'Q',
-	'theta':'q',
-	'thetav':'j',
-	'Xi':'X',
-	'xi':'x',
-	'zeta':'z'	
-}
-
-#------------------------------------------------------------------------
-class StringSegment:
-	"""class StringSegment contains the intermediate representation of string
-	segments as they are being parsed by the XMLParser. 
-	"""
-	
-	def __init__(self):
-		self.super = 0
-		self.sub = 0
-		self.bold = 0
-		self.italic = 0
-		self.underline = 0
-		self.s = ""
-		self.width = 0
-		self.greek = 0
-	
-	def calcNewFont(self,font):
-		"Given a font (does not accept font==None), creates a \
-		new font based on the format of this text segment."
-		
-		# if we are a greek character we need to pick a different fontface
-		if self.greek:
-			face = "symbol"
-		else:
-			face = font.face
-		
-		# want to make sure that we don't lose any of the base
-		# font formatting
-		return Font(face=face, 
-				size=font.size - (self.super*sizedelta) - (self.sub*sizedelta), 
-				underline = self.underline or font.underline, 
-				bold = self.bold or font.bold, 
-				italic = self.italic or font.italic)
-	
-	def calcNewY(self,font,y):
-		"Returns a new y coordinate depending on its \
-		whether the string is a sub and super script."
-		
-		# should this take into account angle, I think probably not
-		if self.sub == 1:
-			return y+(font.size * subFraction)	
-		elif self.super == 1:
-			return y-(font.size * superFraction)
-		else:
-			return y
-	
-	def dump(self):
-		print "StringSegment: ]%s[" % self.s
-		print "\tsuper = ", self.super
-		print "\tsub = ", self.sub
-		print "\tbold = ", self.bold
-		print "\titalic = ",self.italic
-		print "\tunderline = ", self.underline
-		print "\twidth = ", self.width
-		print "\tgreek = ", self.greek
-
-#------------------------------------------------------------------	
-# The StringFormatter will be able to format the following xml
-# tags:
-#      < b > < /b > - bold 
-#      < i > < /i > - italics 
-#      < u > < /u > - underline 
-#      < super > < /super > - superscript 
-#      < sub > < /sub > - subscript 
-#
-# It will also be able to handle any MathML specified Greek characters.
-#
-# Possible future additions: changing color and font 
-#       character-by-character
-#------------------------------------------------------------------	
-class StringFormatter(xmllib.XMLParser):
-	
-	#----------------------------------------------------------
-	# First we will define all of the xml tag handler functions.
-	#
-	# start_<tag>(attributes)
-	# end_<tag>()
-	#
-	# While parsing the xml StringFormatter will call these 
-	# functions to handle the string formatting tags.
-	# At the start of each tag the corresponding field will
-	# be set to 1 and at the end tag the corresponding field will
-	# be set to 0.  Then when handle_data is called the options
-	# for that data will be aparent by the current settings.
-	#----------------------------------------------------------
-	
-	#### bold
-	def start_b( self, attributes ):
-		self.bold = 1
-		
-	def end_b( self ):
-		self.bold = 0
-	
-	#### italics
-	def start_i( self, attributes ):
-		self.italic = 1
-	
-	def end_i( self ):
-		self.italic = 0
-	
-	#### underline
-	def start_u( self, attributes ):
-		self.underline = 1
-	
-	def end_u( self ):
-		self.underline = 0
-	
-	#### super script
-	def start_super( self, attributes ):
-		self.super = 1
-	
-	def end_super( self ):
-		self.super = 0
-	
-	#### sub script
-	def start_sub( self, attributes ):
-		self.sub = 1
-	
-	def end_sub( self ):
-		self.sub = 0
-	
-	#### greek script
-	def start_greek(self, attributes, letter):
-#		print "creating a greek letter... ", letter
-		self.greek = 1
-		self.handle_data(letter)
-
-	def end_greek(self):
-		self.greek = 0
-		
-	#----------------------------------------------------------------
-	def __init__(self):
-		xmllib.XMLParser.__init__(self)
-		
-		# initialize list of string segments to empty
-		self.segmentlist = []
-		
-		# initialize tag values
-		self.sub = 0
-		self.super = 0
-		self.bold = 0
-		self.italic = 0
-		self.underline = 0
-		
-		# set up handlers for various tags
-		self.elements = { 'b': (self.start_b, self.end_b),
-				  'u': (self.start_u, self.end_u),
-				  'i': (self.start_i, self.end_i),
-				  'super': (self.start_super, self.end_super),
-				  'sub': (self.start_sub, self.end_sub)
-		}
-		
-		# automatically add handlers for all of the greek characters
-		for item in greekchars.keys():
-			self.elements[item] = (lambda attr,self=self,letter=greekchars[item]: \
-				self.start_greek(attr,letter), self.end_greek)
-				
-		# flag for greek characters
-		self.greek = 0
-		# set up dictionary for greek characters, this is a class variable
-		# should I copy it and then update it?
-		for item in greekchars.keys():
-			self.entitydefs[item] = '<%s/>' % item
-
-	#----------------------------------------------------------------
-#	def syntax_error(self,message):
-#		print message
-
-	#----------------------------------------------------------------
-	def handle_data(self,data):
-		"Creates an intermediate representation of string segments."
-		
-		# segment first has data
-		segment = StringSegment()
-		segment.s = data
-		
-		# if sub and super are both one they will cancel each other out
-		if self.sub == 1 and self.super == 1:
-			segment.sub = 0
-			segment.super = 0
-		else:
-			segment.sub = self.sub
-			segment.super = self.super
-			
-		# bold, italic, and underline
-		segment.bold = self.bold
-		segment.italic = self.italic
-		segment.underline = self.underline
-		
-		# greek character
-		segment.greek = self.greek
-		
-		self.segmentlist.append(segment)
-
-	#----------------------------------------------------------------
-	def parseSegments(self,s):
-		"Given a formatted string will return a list of \
-		StringSegment objects with their calculated widths."
-		
-		# the xmlparser requires that all text be surrounded by xml
-		# tags, therefore we must throw some unused flags around the 
-		# given string
-		self.feed("<formattedstring>" + s + "</formattedstring>")
-		self.close()	# force parsing to complete
-		self.reset()	# get rid of any previous data
-		segmentlist = self.segmentlist
-		self.segmentlist = []
-		return segmentlist
-		
-#------------------------------------------------------------------------
-# These functions just implement an interface layer to PIDDLE
-
-def fontHeight(canvas, font=None):
-	"Find the total height (ascent + descent) of the given font."
-	return canvas.fontHeight(font)
-	
-def fontAscent(canvas, font=None):
-	"Find the ascent (height above base) of the given font."	
-	return canvas.fontAscent(font)
-	
-def fontDescent(canvas, font=None):
-	"Find the descent (extent below base) of the given font."
-	return canvas.fontDescent(font)
-	
-#------------------------------------------------------------------------
-# create an instantiation of the StringFormatter
-#sformatter = StringFormatter()
-
-#------------------------------------------------------------------------
-# stringWidth and drawString both have to parse the formatted strings
-
-def stringWidth(canvas, s, font=None):
-	"Return the logical width of the string if it were drawn \
-	in the current font (defaults to canvas.font)."
-
-	sformatter = StringFormatter()
-
-	segmentlist = sformatter.parseSegments(s)
-
-	# to calculate a new font the segments must be given an actual font
-	if not font:
-		font = canvas.defaultFont
-	
-	# sum up the string widths of each formatted segment	
-	sum = 0
-	for seg in segmentlist:
-		sum = sum + canvas.stringWidth(seg.s, seg.calcNewFont(font) )
-	return sum
-	
-def rotateXY( x, y, theta):
-	"Rotate (x,y) by theta degrees.  Got tranformation \
-	from page 299 in linear algebra book."
-	radians = theta * math.pi/180.0
-	# had to change the signs to deal with the fact that the y coordinate
-	# is positive going down the screen
-	return ( math.cos(radians)*x + math.sin(radians)*y, 
-		-(math.sin(radians)*x - math.cos(radians)*y))
-			
-def drawString( canvas, s, x, y, font=None, color=None, angle=0):
-	"Draw a formatted string starting at location x,y in canvas."
-	sformatter = StringFormatter()
-	
-	segmentlist = sformatter.parseSegments(s)
-	
-	# to calculate a new font the segments must be given an actual font
-	if not font:
-		font = canvas.defaultFont
-	
-	# have each formatted string segment specify its own font
-	startpos = x
-	for seg in segmentlist:
-		# calculate x and y for this segment based on the angle
-		# if the string wasn't at an angle then 
-		# (draw_x,draw_y) = (startpos, seg.calcNewY(font, y)) want to 
-		# rotate around original x and y
-		(delta_x, delta_y) = rotateXY(startpos-x, seg.calcNewY(font,y)-y, angle)
-		
-		canvas.drawString(seg.s,x+delta_x, y+delta_y,
-				seg.calcNewFont(font),color,angle)	
-				
-		# new x start position, startpos is calculated assuming no angle
-		startpos = startpos + canvas.stringWidth(seg.s,seg.calcNewFont(font))
-	
-
-#------------------------------------------------------------------------
-# Testing
-#------------------------------------------------------------------------
-import piddlePDF	
-
-def test1():
-	canvas = piddlePDF.PDFCanvas('test1.pdf')
-	drawString(canvas,"<u><b>hello there</b></u><super>hi</super>",10,20)
-	drawString(canvas,"hello!",10,40)
-
-	print "'hello!' width = ", stringWidth(canvas,"hello!")
-	print "'hello!' PIDDLE width = ", canvas.stringWidth("hello!")
-
-	drawString(canvas, "<b>hello!</b> goodbye", 10,60) 
-	print "'<b>hello!</b> goodbye' width = ", stringWidth(canvas,"<b>hello!</b> goodbye")
-	drawString(canvas, "hello!", 10,80, Font(bold=1)) 
-	print "'hello!' Font(bold=1) PIDDLE width = ", canvas.stringWidth("hello!",Font(bold=1))
-	drawString(canvas, " goodbye", 10,100) 
-	print "' goodbye' PIDDLE width = ", canvas.stringWidth(" goodbye")
-	canvas.flush()
-
-def test2():
-	canvas = piddlePDF.PDFCanvas('test2.pdf')
-	
-	drawString(canvas, "<alpha/>", 10, 10 )
-	
-#	drawString(canvas, "&amp;", 10, 10)
-	drawString(canvas, "&alpha;", 10,30)
-#	drawString(canvas, "a", 10, 50, Font(face="symbol"))
-#	drawString(canvas, "hello there!", 30, 90, angle= -90)
-#	drawString(canvas, "<b>goodbye!</b> <u>yall</u>", 100, 90, angle= 45)
-#	drawString(canvas, "there is a <u>time</u> and a <b>place</b><super>2</super>", 
-#		100, 90, angle= -75)
-	canvas.flush()
-	
-
-def allTagCombos(canvas,x, y, font=None, color=None, angle=0):
-	"""Try out all tags and various combinations of them. \ 
-	Starts at given x,y and returns possible next (x,y)."""
-
-	oldDefault = canvas.defaultFont
-	if font: canvas.defaultFont = font
-	
-	oldx = x
-	dx = stringWidth(canvas, " ")
-	dy = canvas.defaultFont.size*1.5
-	
-	drawString(canvas, "<b>bold</b>", x, y, color=color, angle=angle )
-	x = x + stringWidth(canvas,"<b>bold</b>") + dx
-
-	drawString(canvas, "<i>italic</i>", x, y, color=color, angle=angle )
-	x = x + stringWidth(canvas,"<i>italic</i>") + dx
-
-	drawString(canvas, "<u>underline</u>", x, y, color=color, angle=angle )
-	x = x + stringWidth(canvas,"<u>underline</u>") + dx
-
-	drawString(canvas, "<super>super</super>", x, y, color=color, angle=angle )
-	x = x + stringWidth(canvas,"<super>super</super>") + dx
-
-	drawString(canvas, "<sub>sub</sub>", x, y, color=color, angle=angle )
-	y = y + dy
-
-	drawString(canvas, "<b><u>bold+underline</u></b>", oldx, y, color=color, angle=angle )
-	x = oldx + stringWidth(canvas,"<b><u>bold+underline</u></b>") + dx
-	
-	drawString(canvas, "<super><i>super+italic</i></super>", x, y, color=color, angle=angle )
-	x = x + stringWidth(canvas,"<super><i>super+italic</i></super>") + dx
-	
-	drawString(canvas, "<b><sub>bold+sub</sub></b>", x, y, color=color, angle=angle )
-#	x = x + stringWidth(canvas,"<b><sub>bold+sub</sub></b>") + dx
-	y = y + dy
-
-	canvas.defaultFont = oldDefault
-	return (oldx, y)	
-	
-
-
-def stringformatTest():
-	
-	# change the following line only to try a different PIDDLE backend
-	canvas = piddlePDF.PDFCanvas('bigtest1.pdf')
-	
-	################################################### testing drawString tags
-	#      < b > < /b > - bold 
-	#      < i > < /i > - italics 
-	#      < u > < /u > - underline 
-	#      < super > < /super > - superscript 
-	#      < sub > < /sub > - subscript 
-
-	x = 10
-	y = canvas.defaultFont.size*1.5
-	
-	##### try out each possible tags and all combos
-	(x,y) = allTagCombos(canvas, x, y)
-	
-	##### now try various fonts
-	(x,y) = allTagCombos(canvas, x, y+30, Font(face="serif"))
-	(x,y) = allTagCombos(canvas, x, y+30, Font(face="monospaced"))	
-
-	# what about rotated
-	(x,y) = allTagCombos(canvas, x, y+30, Font(face="serif"), angle=-30)
-		
-	##### now try a couple of different font sizes
-	(x,y) = allTagCombos(canvas, x, y+30, Font(size=16))
-	(x,y) = allTagCombos(canvas, x, y+30, Font(size=9))
-
-	##### now try a different default style setting
-	(x,y) = allTagCombos(canvas, x, y+30, Font(underline=1))
-	
-	##### now try a combo of the above 4 and a different color
-	(x,y) = allTagCombos(canvas, x, y+30, color = red)
-
-	################################################### testing stringWidth tags
-	sfwidth = stringWidth(canvas,
-		"<b><sub>bold+sub</sub></b> hello <u><super>underline+super</super></u>")
-	
-	# break down the various string widths
-	print 'sw("<b><sub>bold+sub</sub></b>") = ', stringWidth(canvas,"<b><sub>bold+sub</sub></b>")
-	print 'sw(" hello ") = ', stringWidth(canvas," hello ")
-	print 'sw("<u><super>underline+super</super></u>") = ', \
-		stringWidth(canvas,"<u><super>underline+super</super></u>")
-	
-	pwidth1 = canvas.stringWidth("bold+sub",Font(size=canvas.defaultFont.size-sizedelta, bold=1))
-	print "pwidth1 = ", pwidth1
-	pwidth2 = canvas.stringWidth(" hello ")
-	print "pwidth2 = ", pwidth2
-	pwidth3 = canvas.stringWidth("underline+super",
-		Font(size=canvas.defaultFont.size-sizedelta,underline=1))
-	print "pwidth3 = ", pwidth3	
-
-	# these should be the same
-	print "sfwidth = ", sfwidth, " pwidth = ", pwidth1+pwidth2+pwidth3
-
-	################################################### testing greek characters
-	# looks better in a larger font
-	canvas = piddlePDF.PDFCanvas('bigtest2.pdf')
-	x = 10
-	y = canvas.defaultFont.size*1.5
-	drawString(canvas,"&alpha; &beta; <chi/> &Delta; <delta/>",x,y, Font(size=16), color = blue)
-	print "line starting with alpha should be font size 16"	
-	y = y+30
-	drawString(canvas,"&epsiv; &eta; &Gamma; <gamma/>",x,y, color = green)	
-	y = y+30
-	drawString(canvas,"&iota; &kappa; &Lambda; <lambda/>",x,y, color = blue)	
-	y = y+30
-	drawString(canvas,"<u>&mu;</u> &nu; <b>&Omega;</b> <omega/>",x,y, color = green)	
-	print "mu should be underlined, Omega should be big and bold"
-	y = y+30
-	drawString(canvas,"&omicron; &Phi; &phi; <phiv/>",x,y, color = blue)	
-	y = y+30
-	drawString(canvas,"&Pi; &pi; &piv; <Psi/> &psi; &rho;",x,y, color = green)	
-	y = y+30
-	drawString(canvas,"<u>&Sigma; &sigma; &sigmav; <tau/></u>",x,y, color = blue)	
-	print "line starting with sigma should be completely underlined"
-	y = y+30
-	drawString(canvas,"&Theta; &theta; &thetav; <Xi/> &xi; &zeta;",x,y, color = green)	
-	y= y+30
-	drawString(canvas,"That's &alpha;ll <u>folks</u><super>&omega;</super>",x,y)
-	
-	canvas.flush()
-		
-#test1()
-#test2()
-#stringformatTest()
-
diff -uNr bkchem-0.12.5/bkchem/PmwBlt.py bkchem-0.12.5-nopiddle/bkchem/PmwBlt.py
--- bkchem-0.12.5/bkchem/PmwBlt.py	2008-10-15 05:00:38.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/PmwBlt.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,643 +0,0 @@
-# Python interface to some of the commands of the 2.4 version of the
-# BLT extension to tcl.
-
-import string
-import types
-import Tkinter
-
-# Supported commands:
-_busyCommand = '::blt::busy'
-_vectorCommand = '::blt::vector'
-_graphCommand = '::blt::graph'
-_testCommand = '::blt::*'
-_chartCommand = '::blt::stripchart'
-_tabsetCommand = '::blt::tabset'
-
-_haveBlt = None
-_haveBltBusy = None
-
-def _checkForBlt(window):
-    global _haveBlt
-    global _haveBltBusy
-
-    # Blt may be a package which has not yet been loaded. Try to load it.
-    try:
-	window.tk.call('package', 'require', 'BLT')
-    except Tkinter.TclError:
-	# Another way to try to dynamically load blt:
-	try:
-	    window.tk.call('load', '', 'Blt')
-	except Tkinter.TclError:
-	    pass
-
-    _haveBlt= (window.tk.call('info', 'commands', _testCommand) != '')
-    _haveBltBusy = (window.tk.call('info', 'commands', _busyCommand) != '')
-
-def haveblt(window):
-    if _haveBlt is None:
-	_checkForBlt(window)
-    return _haveBlt
-
-def havebltbusy(window):
-    if _haveBlt is None:
-	_checkForBlt(window)
-    return _haveBltBusy
-
-def _loadBlt(window):
-    if _haveBlt is None:
-	if window is None:
-	    window = Tkinter._default_root
-	    if window is None:
-	    	window = Tkinter.Tk()
-	_checkForBlt(window)
-
-def busy_hold(window, cursor = None):
-    _loadBlt(window)
-    if cursor is None:
-        window.tk.call(_busyCommand, 'hold', window._w)
-    else:
-        window.tk.call(_busyCommand, 'hold', window._w, '-cursor', cursor)
-
-def busy_release(window):
-    _loadBlt(window)
-    window.tk.call(_busyCommand, 'release', window._w)
-
-def busy_forget(window):
-    _loadBlt(window)
-    window.tk.call(_busyCommand, 'forget', window._w)
-
-#=============================================================================
-# Interface to the blt vector command which makes it look like the
-# builtin python list type.
-# The -variable, -command, -watchunset creation options are not supported.
-# The dup, merge, notify, offset, populate, seq and variable methods
-# and the +, -, * and / operations are not supported.
-
-# Blt vector functions:
-def vector_expr(expression):
-    tk = Tkinter._default_root.tk
-    strList = tk.splitlist(tk.call(_vectorCommand, 'expr', expression))
-    return tuple(map(string.atof, strList))
-
-def vector_names(pattern = None):
-    tk = Tkinter._default_root.tk
-    return tk.splitlist(tk.call(_vectorCommand, 'names', pattern))
-
-class Vector:
-    _varnum = 0
-    def __init__(self, size=None, master=None):
-        # <size> can be either an integer size, or a string "first:last".
-	_loadBlt(master)
-	if master:
-	    self._master = master
-	else:
-	    self._master = Tkinter._default_root
-	self.tk = self._master.tk
-	self._name = 'PY_VEC' + str(Vector._varnum)
-	Vector._varnum = Vector._varnum + 1
-	if size is None:
-	    self.tk.call(_vectorCommand, 'create', self._name)
-	else:
-	  self.tk.call(_vectorCommand, 'create', '%s(%s)' % (self._name, size))
-    def __del__(self):
-	self.tk.call(_vectorCommand, 'destroy', self._name)
-    def __str__(self):
-	return self._name
-
-    def __repr__(self):
-	return '[' + string.join(map(str, self), ', ') + ']'
-    def __cmp__(self, list):
-	return cmp(self[:], list)
-
-    def __len__(self): 
-	return self.tk.getint(self.tk.call(self._name, 'length'))
-    def __getitem__(self, key): 
-	oldkey = key
-	if key < 0:
-	    key = key + len(self)
-	try:
-	    return self.tk.getdouble(self.tk.globalgetvar(self._name, str(key)))
-        except Tkinter.TclError:
-	    raise IndexError, oldkey
-    def __setitem__(self, key, value): 
-	if key < 0:
-	    key = key + len(self)
-	return self.tk.globalsetvar(self._name, str(key), float(value))
-
-    def __delitem__(self, key):
-	if key < 0:
-	    key = key + len(self)
-	return self.tk.globalunsetvar(self._name, str(key))
-
-    def __getslice__(self, start, end):
-	length = len(self)
-	if start < 0:
-	    start = 0
-	if end > length:
-	    end = length
-	if start >= end:
-	    return []
-	end = end - 1  # Blt vector slices include end point.
-	text = self.tk.globalgetvar(self._name, str(start) + ':' + str(end))
-	return map(self.tk.getdouble, self.tk.splitlist(text))
-
-    def __setslice__(self, start, end, list):
-	if start > end:
-	    end = start
-	self.set(self[:start] + list + self[end:])
-
-    def __delslice__(self, start, end):
-	if start < end:
-	    self.set(self[:start] + self[end:])
-
-    def __add__(self, list):
-	return self[:] + list
-    def __radd__(self, list):
-	return list + self[:]
-    def __mul__(self, n):
-	return self[:] * n
-    __rmul__ = __mul__
-
-    # Python builtin list methods:
-    def append(self, *args):
-	self.tk.call(self._name, 'append', args)
-    def count(self, obj):
-	return self[:].count(obj)
-    def index(self, value):
-	return self[:].index(value)
-    def insert(self, index, value):
-	self[index:index] = [value]
-    def remove(self, value):
-	del self[self.index(value)]
-    def reverse(self):
-	s = self[:]
-	s.reverse()
-	self.set(s)
-    def sort(self, *args):
-	s = self[:]
-	s.sort()
-	self.set(s)
-
-    # Blt vector instance methods:
-    # append - same as list method above
-    def clear(self):
-	self.tk.call(self._name, 'clear')
-    def delete(self, *args):
-	self.tk.call((self._name, 'delete') + args)
-    def expr(self, expression):
-	self.tk.call(self._name, 'expr', expression)
-    def length(self, newSize=None): 
-	return self.tk.getint(self.tk.call(self._name, 'length', newSize))
-    def range(self, first, last=None):
-	# Note that, unlike self[first:last], this includes the last
-	# item in the returned range.
-	text = self.tk.call(self._name, 'range', first, last)
-	return map(self.tk.getdouble, self.tk.splitlist(text))
-    def search(self, start, end=None):
-	return self._master._getints(self.tk.call(
-		self._name, 'search', start, end))
-    def set(self, list):
-	if type(list) != types.TupleType:
-	    list = tuple(list)
-	self.tk.call(self._name, 'set', list)
-
-    # The blt vector sort method has different semantics to the python
-    # list sort method.  Call these blt_sort:
-    def blt_sort(self, *args):
-	self.tk.call((self._name, 'sort') + args)
-    def blt_sort_reverse(self, *args):
-	self.tk.call((self._name, 'sort', '-reverse') + args)
-
-    # Special blt vector indexes:
-    def min(self):
-	return self.tk.getdouble(self.tk.globalgetvar(self._name, 'min'))
-    def max(self):
-	return self.tk.getdouble(self.tk.globalgetvar(self._name, 'max'))
-
-    # Method borrowed from Tkinter.Var class:
-    def get(self):
-	return self[:]
-
-#=============================================================================
-
-# This is a general purpose configure routine which can handle the
-# configuration of widgets, items within widgets, etc.  Supports the
-# forms configure() and configure('font') for querying and
-# configure(font = 'fixed', text = 'hello') for setting.
-
-def _doConfigure(widget, subcommand, option, kw):
-
-    if not option and not kw:
-        # Return a description of all options.
-        ret = {}
-        options = widget.tk.splitlist(widget.tk.call(subcommand))
-        for optionString in options:
-            optionInfo = widget.tk.splitlist(optionString)
-            option = optionInfo[0][1:]
-            ret[option] = (option,) + optionInfo[1:]
-        return ret
-
-    if option:
-        # Return a description of the option given by <option>.
-        if kw:
-            # Having keywords implies setting configuration options.
-            # Can't set and get in one command!
-            raise ValueError, 'cannot have option argument with keywords'
-        option = '-' + option
-        optionInfo = widget.tk.splitlist(widget.tk.call(subcommand + (option,)))
-        return (optionInfo[0][1:],) + optionInfo[1:]
-
-    # Otherwise, set the given configuration options.
-    widget.tk.call(subcommand + widget._options(kw))
-
-#=============================================================================
-
-class Graph(Tkinter.Widget):
-    # Wrapper for the blt graph widget, version 2.4.
-
-    def __init__(self, master=None, cnf={}, **kw):
-	_loadBlt(master)
-	Tkinter.Widget.__init__(self, master, _graphCommand, cnf, kw)
-
-    def bar_create(self, name, **kw):
-	self.tk.call((self._w, 'bar', 'create', name) + self._options(kw))
-
-    def line_create(self, name, **kw):
-	self.tk.call((self._w, 'line', 'create', name) + self._options(kw))
-
-    def extents(self, item):
-	return self.tk.getint(self.tk.call(self._w, 'extents', item))
-
-    def invtransform(self, winX, winY):
-	return self._getdoubles(
-		self.tk.call(self._w, 'invtransform', winX, winY))
-
-    def inside(self, x, y):
-	return self.tk.getint(self.tk.call(self._w, 'inside', x, y))
-
-    def snap(self, photoName):
-	self.tk.call(self._w, 'snap', photoName)
-
-    def transform(self, x, y):
-	return self._getdoubles(self.tk.call(self._w, 'transform', x, y))
-
-    def axis_cget(self, axisName, key):
-	return self.tk.call(self._w, 'axis', 'cget', axisName, '-' + key)
-    def axis_configure(self, axes, option=None, **kw):
-        # <axes> may be a list of axisNames.
-	if type(axes) == types.StringType:
-            axes = [axes]
-	subcommand = (self._w, 'axis', 'configure') + tuple(axes)
-	return _doConfigure(self, subcommand, option, kw)
-    def axis_create(self, axisName, **kw):
-	self.tk.call((self._w, 'axis', 'create', axisName) + self._options(kw))
-    def axis_delete(self, *args):
-	self.tk.call((self._w, 'axis', 'delete') + args)
-    def axis_invtransform(self, axisName, value):
-	return self.tk.getdouble(self.tk.call(
-		self._w, 'axis', 'invtransform', axisName, value))
-    def axis_limits(self, axisName):
-	return self._getdoubles(self.tk.call(
-		self._w, 'axis', 'limits', axisName))
-    def axis_names(self, *args):
-        return self.tk.splitlist(
-                self.tk.call((self._w, 'axis', 'names') + args))
-    def axis_transform(self, axisName, value):
-	return self.tk.getint(self.tk.call(
-		self._w, 'axis', 'transform', axisName, value))
-
-    def xaxis_cget(self, key):
-	return self.tk.call(self._w, 'xaxis', 'cget', '-' + key)
-    def xaxis_configure(self, option=None, **kw):
-	subcommand = (self._w, 'xaxis', 'configure')
-	return _doConfigure(self, subcommand, option, kw)
-    def xaxis_invtransform(self, value):
-	return self.tk.getdouble(self.tk.call(
-		self._w, 'xaxis', 'invtransform', value))
-    def xaxis_limits(self):
-	return self._getdoubles(self.tk.call(self._w, 'xaxis', 'limits'))
-    def xaxis_transform(self, value):
-	return self.tk.getint(self.tk.call(
-		self._w, 'xaxis', 'transform', value))
-    def xaxis_use(self, axisName = None):
-	return self.tk.call(self._w, 'xaxis', 'use', axisName)
-
-    def x2axis_cget(self, key):
-	return self.tk.call(self._w, 'x2axis', 'cget', '-' + key)
-    def x2axis_configure(self, option=None, **kw):
-	subcommand = (self._w, 'x2axis', 'configure')
-	return _doConfigure(self, subcommand, option, kw)
-    def x2axis_invtransform(self, value):
-	return self.tk.getdouble(self.tk.call(
-		self._w, 'x2axis', 'invtransform', value))
-    def x2axis_limits(self):
-	return self._getdoubles(self.tk.call(self._w, 'x2axis', 'limits'))
-    def x2axis_transform(self, value):
-	return self.tk.getint(self.tk.call(
-		self._w, 'x2axis', 'transform', value))
-    def x2axis_use(self, axisName = None):
-	return self.tk.call(self._w, 'x2axis', 'use', axisName)
-
-    def yaxis_cget(self, key):
-	return self.tk.call(self._w, 'yaxis', 'cget', '-' + key)
-    def yaxis_configure(self, option=None, **kw):
-	subcommand = (self._w, 'yaxis', 'configure')
-	return _doConfigure(self, subcommand, option, kw)
-    def yaxis_invtransform(self, value):
-	return self.tk.getdouble(self.tk.call(
-		self._w, 'yaxis', 'invtransform', value))
-    def yaxis_limits(self):
-	return self._getdoubles(self.tk.call(self._w, 'yaxis', 'limits'))
-    def yaxis_transform(self, value):
-	return self.tk.getint(self.tk.call(
-		self._w, 'yaxis', 'transform', value))
-    def yaxis_use(self, axisName = None):
-	return self.tk.call(self._w, 'yaxis', 'use', axisName)
-
-    def y2axis_cget(self, key):
-	return self.tk.call(self._w, 'y2axis', 'cget', '-' + key)
-    def y2axis_configure(self, option=None, **kw):
-	subcommand = (self._w, 'y2axis', 'configure')
-	return _doConfigure(self, subcommand, option, kw)
-    def y2axis_invtransform(self, value):
-	return self.tk.getdouble(self.tk.call(
-		self._w, 'y2axis', 'invtransform', value))
-    def y2axis_limits(self):
-	return self._getdoubles(self.tk.call(self._w, 'y2axis', 'limits'))
-    def y2axis_transform(self, value):
-	return self.tk.getint(self.tk.call(
-		self._w, 'y2axis', 'transform', value))
-    def y2axis_use(self, axisName = None):
-	return self.tk.call(self._w, 'y2axis', 'use', axisName)
-
-    def crosshairs_cget(self, key):
-	return self.tk.call(self._w, 'crosshairs', 'cget', '-' + key)
-    def crosshairs_configure(self, option=None, **kw):
-	subcommand = (self._w, 'crosshairs', 'configure')
-	return _doConfigure(self, subcommand, option, kw)
-    def crosshairs_off(self):
-	self.tk.call(self._w, 'crosshairs', 'off')
-    def crosshairs_on(self):
-	self.tk.call(self._w, 'crosshairs', 'on')
-    def crosshairs_toggle(self):
-	self.tk.call(self._w, 'crosshairs', 'toggle')
-
-    def element_activate(self, name, *args):
-	self.tk.call((self._w, 'element', 'activate', name) + args)
-    def element_bind(self, tagName, sequence=None, func=None, add=None):
-        return self._bind((self._w, 'element', 'bind', tagName),
-                sequence, func, add)
-    def element_unbind(self, tagName, sequence, funcid=None):
-        self.tk.call(self._w, 'element', 'bind', tagName, sequence, '')
-        if funcid:
-            self.deletecommand(funcid)
-
-    def element_cget(self, name, key):
-	return self.tk.call(self._w, 'element', 'cget', name, '-' + key)
-
-    def element_closest(self, x, y, *args, **kw):
-        var = 'python_private_1'
-	success = self.tk.getint(self.tk.call(
-                (self._w, 'element', 'closest', x, y, var) +
-                        self._options(kw) + args))
-	if success:
-	    rtn = {}
-	    rtn['dist'] = self.tk.getdouble(self.tk.globalgetvar(var, 'dist'))
-	    rtn['x'] = self.tk.getdouble(self.tk.globalgetvar(var, 'x'))
-	    rtn['y'] = self.tk.getdouble(self.tk.globalgetvar(var, 'y'))
-	    rtn['index'] = self.tk.getint(self.tk.globalgetvar(var, 'index'))
-	    rtn['name'] = self.tk.globalgetvar(var, 'name')
-	    return rtn
-	else:
-	    return None
-
-    def element_configure(self, names, option=None, **kw):
-        # <names> may be a list of elemNames.
-	if type(names) == types.StringType:
-            names = [names]
-	subcommand = (self._w, 'element', 'configure') + tuple(names)
-	return _doConfigure(self, subcommand, option, kw)
-
-    def element_deactivate(self, *args):
-	self.tk.call((self._w, 'element', 'deactivate') + args)
-
-    def element_delete(self, *args):
-	self.tk.call((self._w, 'element', 'delete') + args)
-    def element_exists(self, name):
-	return self.tk.getboolean(
-		self.tk.call(self._w, 'element', 'exists', name))
-
-    def element_names(self, *args):
-        return self.tk.splitlist(
-                self.tk.call((self._w, 'element', 'names') + args))
-    def element_show(self, nameList=None):
-	if nameList is not None:
-	    nameList = tuple(nameList)
-	return self.tk.splitlist(
-		self.tk.call(self._w, 'element', 'show', nameList))
-    def element_type(self, name):
-	return self.tk.call(self._w, 'element', 'type', name)
-
-    def grid_cget(self, key):
-	return self.tk.call(self._w, 'grid', 'cget', '-' + key)
-    def grid_configure(self, option=None, **kw):
-	subcommand = (self._w, 'grid', 'configure')
-	return _doConfigure(self, subcommand, option, kw)
-
-    def grid_off(self):
-	self.tk.call(self._w, 'grid', 'off')
-    def grid_on(self):
-	self.tk.call(self._w, 'grid', 'on')
-    def grid_toggle(self):
-	self.tk.call(self._w, 'grid', 'toggle')
-
-    def legend_activate(self, *args):
-	self.tk.call((self._w, 'legend', 'activate') + args)
-    def legend_bind(self, tagName, sequence=None, func=None, add=None):
-        return self._bind((self._w, 'legend', 'bind', tagName),
-                sequence, func, add)
-    def legend_unbind(self, tagName, sequence, funcid=None):
-        self.tk.call(self._w, 'legend', 'bind', tagName, sequence, '')
-        if funcid:
-            self.deletecommand(funcid)
-
-    def legend_cget(self, key):
-	return self.tk.call(self._w, 'legend', 'cget', '-' + key)
-    def legend_configure(self, option=None, **kw):
-	subcommand = (self._w, 'legend', 'configure')
-	return _doConfigure(self, subcommand, option, kw)
-    def legend_deactivate(self, *args):
-	self.tk.call((self._w, 'legend', 'deactivate') + args)
-    def legend_get(self, pos):
-	return self.tk.call(self._w, 'legend', 'get', pos)
-
-    def pen_cget(self, name, key):
-	return self.tk.call(self._w, 'pen', 'cget', name, '-' + key)
-    def pen_configure(self, names, option=None, **kw):
-        # <names> may be a list of penNames.
-	if type(names) == types.StringType:
-            names = [names]
-	subcommand = (self._w, 'pen', 'configure') + tuple(names)
-	return _doConfigure(self, subcommand, option, kw)
-    def pen_create(self, name, **kw):
-	self.tk.call((self._w, 'pen', 'create', name) + self._options(kw))
-    def pen_delete(self, *args):
-	self.tk.call((self._w, 'pen', 'delete') + args)
-    def pen_names(self, *args):
-        return self.tk.splitlist(self.tk.call((self._w, 'pen', 'names') + args))
-
-    def postscript_cget(self, key):
-	return self.tk.call(self._w, 'postscript', 'cget', '-' + key)
-    def postscript_configure(self, option=None, **kw):
-	subcommand = (self._w, 'postscript', 'configure')
-	return _doConfigure(self, subcommand, option, kw)
-    def postscript_output(self, fileName=None, **kw):
-	prefix = (self._w, 'postscript', 'output')
-	if fileName is None:
-	    return self.tk.call(prefix + self._options(kw))
-	else:
-	    self.tk.call(prefix + (fileName,) + self._options(kw))
-
-    def marker_after(self, first, second=None):
-	self.tk.call(self._w, 'marker', 'after', first, second)
-    def marker_before(self, first, second=None):
-	self.tk.call(self._w, 'marker', 'before', first, second)
-    def marker_bind(self, tagName, sequence=None, func=None, add=None):
-        return self._bind((self._w, 'marker', 'bind', tagName),
-                sequence, func, add)
-    def marker_unbind(self, tagName, sequence, funcid=None):
-        self.tk.call(self._w, 'marker', 'bind', tagName, sequence, '')
-        if funcid:
-            self.deletecommand(funcid)
-
-    def marker_cget(self, name, key):
-	return self.tk.call(self._w, 'marker', 'cget', name, '-' + key)
-    def marker_configure(self, names, option=None, **kw):
-        # <names> may be a list of markerIds.
-	if type(names) == types.StringType:
-            names = [names]
-	subcommand = (self._w, 'marker', 'configure') + tuple(names)
-	return _doConfigure(self, subcommand, option, kw)
-    def marker_create(self, type, **kw):
-	return self.tk.call(
-                (self._w, 'marker', 'create', type) + self._options(kw))
-
-    def marker_delete(self, *args):
-	self.tk.call((self._w, 'marker', 'delete') + args)
-    def marker_exists(self, name):
-	return self.tk.getboolean(
-		self.tk.call(self._w, 'marker', 'exists', name))
-    def marker_names(self, *args):
-        return self.tk.splitlist(
-                self.tk.call((self._w, 'marker', 'names') + args))
-    def marker_type(self, name):
-	type = self.tk.call(self._w, 'marker', 'type', name)
-	if type == '':
-	    type = None
-	return type
-
-#=============================================================================
-class Stripchart(Graph):
-    # Wrapper for the blt stripchart widget, version 2.4.
-
-    def __init__(self, master=None, cnf={}, **kw):
-	_loadBlt(master)
-	Tkinter.Widget.__init__(self, master, _chartCommand, cnf, kw)
-
-#=============================================================================
-class Tabset(Tkinter.Widget): 
-
-    # Wrapper for the blt TabSet widget, version 2.4.
-
-    def __init__(self, master=None, cnf={}, **kw):
-	_loadBlt(master)
-	Tkinter.Widget.__init__(self, master, _tabsetCommand, cnf, kw)
-
-    def activate(self, tabIndex):
-        self.tk.call(self._w, 'activate', tabIndex)
-
-    # This is the 'bind' sub-command:
-    def tag_bind(self, tagName, sequence=None, func=None, add=None):
-        return self._bind((self._w, 'bind', tagName), sequence, func, add)
-
-    def tag_unbind(self, tagName, sequence, funcid=None):
-        self.tk.call(self._w, 'bind', tagName, sequence, '')
-        if funcid:
-            self.deletecommand(funcid)
-
-    def delete(self, first, last = None):
-    	self.tk.call(self._w, 'delete', first, last)
-
-    # This is the 'focus' sub-command:
-    def tab_focus(self, tabIndex):
-    	self.tk.call(self._w, 'focus', tabIndex)
-    	
-    def get(self, tabIndex):
-	return self.tk.call(self._w, 'get', tabIndex)
-
-    def index(self, tabIndex):
-	index = self.tk.call(self._w, 'index', tabIndex)
-        if index == '':
-            return None
-        else:
-            return self.tk.getint(self.tk.call(self._w, 'index', tabIndex))
-
-    def insert(self, position, name1, *names, **kw):
-	self.tk.call(
-            (self._w, 'insert', position, name1) + names + self._options(kw))
-
-    def invoke(self, tabIndex):
-    	return self.tk.call(self._w, 'invoke', tabIndex)
-
-    def move(self, tabIndex1, beforeOrAfter, tabIndex2):
-    	self.tk.call(self._w, 'move', tabIndex1, beforeOrAfter, tabIndex2)
-    	
-    def nearest(self, x, y):
-    	return self.tk.call(self._w, 'nearest', x, y)
-
-    def scan_mark(self, x, y):
-        self.tk.call(self._w, 'scan', 'mark', x, y)
-
-    def scan_dragto(self, x, y):
-        self.tk.call(self._w, 'scan', 'dragto', x, y)
-
-    def see(self, index):
-        self.tk.call(self._w, 'see', index)
-    	
-    def see(self, tabIndex):
-    	self.tk.call(self._w,'see',tabIndex)
-    	
-    def size(self):
-	return self.tk.getint(self.tk.call(self._w, 'size'))
-
-    def tab_cget(self, tabIndex, option):
-        if option[:1] != '-':
-            option = '-' + option
-        if option[-1:] == '_':
-            option = option[:-1]
-        return self.tk.call(self._w, 'tab', 'cget', tabIndex, option)
-
-    def tab_configure(self, tabIndexes, option=None, **kw):
-        # <tabIndexes> may be a list of tabs.
-	if type(tabIndexes) in (types.StringType, types.IntType):
-            tabIndexes = [tabIndexes]
-	subcommand = (self._w, 'tab', 'configure') + tuple(tabIndexes)
-	return _doConfigure(self, subcommand, option, kw)
-
-    def tab_names(self, *args):
-        return self.tk.splitlist(self.tk.call((self._w, 'tab', 'names') + args))
-
-    def tab_tearoff(self, tabIndex, newName = None):
-        if newName is None:
-            name = self.tk.call(self._w, 'tab', 'tearoff', tabIndex)
-            return self.nametowidget(name)
-        else:
-            self.tk.call(self._w, 'tab', 'tearoff', tabIndex, newName)
-
-    def view(self):
-        s = self.tk.call(self._w, 'view')
-        return tuple(map(self.tk.getint, self.tk.splitlist(s)))
-    def view_moveto(self, fraction):
-        self.tk.call(self._w, 'view', 'moveto', fraction)
-    def view_scroll(self, number, what):
-        self.tk.call(self._w, 'view', 'scroll', number, what)
diff -uNr bkchem-0.12.5/bkchem/PmwColor.py bkchem-0.12.5-nopiddle/bkchem/PmwColor.py
--- bkchem-0.12.5/bkchem/PmwColor.py	2008-10-15 05:00:38.000000000 -0300
+++ bkchem-0.12.5-nopiddle/bkchem/PmwColor.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,361 +0,0 @@
-# Functions for converting colors and modifying the color scheme of
-# an application.
-
-import math
-import string
-import sys
-import Tkinter
-
-_PI = math.pi
-_TWO_PI = _PI * 2
-_THIRD_PI = _PI / 3
-_SIXTH_PI = _PI / 6
-_MAX_RGB = float(256 * 256 - 1) # max size of rgb values returned from Tk
-
-def setscheme(root, background=None, **kw):
-    root = root._root()
-    palette = apply(_calcPalette, (root, background,), kw)
-    for option, value in palette.items():
-	root.option_add('*' + option, value, 'widgetDefault')
-
-def getdefaultpalette(root):
-    # Return the default values of all options, using the defaults
-    # from a few widgets.
-
-    ckbtn = Tkinter.Checkbutton(root)
-    entry = Tkinter.Entry(root)
-    scbar = Tkinter.Scrollbar(root)
-
-    orig = {}
-    orig['activeBackground'] = str(ckbtn.configure('activebackground')[4])
-    orig['activeForeground'] = str(ckbtn.configure('activeforeground')[4])
-    orig['background'] = str(ckbtn.configure('background')[4])
-    orig['disabledForeground'] = str(ckbtn.configure('disabledforeground')[4])
-    orig['foreground'] = str(ckbtn.configure('foreground')[4])
-    orig['highlightBackground'] = str(ckbtn.configure('highlightbackground')[4])
-    orig['highlightColor'] = str(ckbtn.configure('highlightcolor')[4])
-    orig['insertBackground'] = str(entry.configure('insertbackground')[4])
-    orig['selectColor'] = str(ckbtn.configure('selectcolor')[4])
-    orig['selectBackground'] = str(entry.configure('selectbackground')[4])
-    orig['selectForeground'] = str(entry.configure('selectforeground')[4])
-    orig['troughColor'] = str(scbar.configure('troughcolor')[4])
-
-    ckbtn.destroy()
-    entry.destroy()
-    scbar.destroy()
-
-    return orig
-
-#======================================================================
-
-# Functions dealing with brightness, hue, saturation and intensity of colors.
-
-def changebrightness(root, colorName, brightness):
-    # Convert the color name into its hue and back into a color of the
-    # required brightness.
-
-    rgb = name2rgb(root, colorName)
-    hue, saturation, intensity = rgb2hsi(rgb)
-    if saturation == 0.0:
-        hue = None
-    return hue2name(hue, brightness)
-
-def hue2name(hue, brightness = None):
-    # Convert the requested hue and brightness into a color name.  If
-    # hue is None, return a grey of the requested brightness.
-
-    if hue is None:
-	rgb = hsi2rgb(0.0, 0.0, brightness)
-    else:
-	while hue < 0:
-	    hue = hue + _TWO_PI
-	while hue >= _TWO_PI:
-	    hue = hue - _TWO_PI
-
-	rgb = hsi2rgb(hue, 1.0, 1.0)
-	if brightness is not None:
-	    b = rgb2brightness(rgb)
-	    i = 1.0 - (1.0 - brightness) * b
-	    s = bhi2saturation(brightness, hue, i)
-	    rgb = hsi2rgb(hue, s, i)
-
-    return rgb2name(rgb)
-
-def bhi2saturation(brightness, hue, intensity):
-    while hue < 0:
-        hue = hue + _TWO_PI
-    while hue >= _TWO_PI:
-        hue = hue - _TWO_PI
-    hue = hue / _THIRD_PI
-    f = hue - math.floor(hue)
-
-    pp = intensity
-    pq = intensity * f
-    pt = intensity - intensity * f
-    pv = 0
-
-    hue = int(hue)
-    if   hue == 0: rgb = (pv, pt, pp)
-    elif hue == 1: rgb = (pq, pv, pp)
-    elif hue == 2: rgb = (pp, pv, pt)
-    elif hue == 3: rgb = (pp, pq, pv)
-    elif hue == 4: rgb = (pt, pp, pv)
-    elif hue == 5: rgb = (pv, pp, pq)
-
-    return (intensity - brightness) / rgb2brightness(rgb)
-
-def hsi2rgb(hue, saturation, intensity):
-    i = intensity
-    if saturation == 0:
-	rgb = [i, i, i]
-    else:
-	while hue < 0:
-	    hue = hue + _TWO_PI
-	while hue >= _TWO_PI:
-	    hue = hue - _TWO_PI
-	hue = hue / _THIRD_PI
-	f = hue - math.floor(hue)
-	p = i * (1.0 - saturation)
-	q = i * (1.0 - saturation * f)
-	t = i * (1.0 - saturation * (1.0 - f))
-
-	hue = int(hue)
-	if   hue == 0: rgb = [i, t, p]
-	elif hue == 1: rgb = [q, i, p]
-	elif hue == 2: rgb = [p, i, t]
-	elif hue == 3: rgb = [p, q, i]
-	elif hue == 4: rgb = [t, p, i]
-	elif hue == 5: rgb = [i, p, q]
-
-    for index in range(3):
-	val = rgb[index]
-	if val < 0.0:
-	    val = 0.0
-	if val > 1.0:
-	    val = 1.0
-	rgb[index] = val
-
-    return rgb
-
-def average(rgb1, rgb2, fraction):
-    return (
-	rgb2[0] * fraction + rgb1[0] * (1.0 - fraction),
-	rgb2[1] * fraction + rgb1[1] * (1.0 - fraction),
-	rgb2[2] * fraction + rgb1[2] * (1.0 - fraction)
-    )
-
-def rgb2name(rgb):
-    return '#%02x%02x%02x' % \
-        (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
-
-def rgb2brightness(rgb):
-    # Return the perceived grey level of the color
-    # (0.0 == black, 1.0 == white).
-
-    rf = 0.299
-    gf = 0.587
-    bf = 0.114
-    return rf * rgb[0] + gf * rgb[1] + bf * rgb[2]
-
-def rgb2hsi(rgb):
-    maxc = max(rgb[0], rgb[1], rgb[2])
-    minc = min(rgb[0], rgb[1], rgb[2])
-
-    intensity = maxc
-    if maxc != 0:
-      saturation  = (maxc - minc) / maxc
-    else:
-      saturation = 0.0
-
-    hue = 0.0
-    if saturation != 0.0:
-	c = []
-	for index in range(3):
-	    c.append((maxc - rgb[index]) / (maxc - minc))
-
-	if rgb[0] == maxc:
-	    hue = c[2] - c[1]
-	elif rgb[1] == maxc:
-	    hue = 2 + c[0] - c[2]
-	elif rgb[2] == maxc:
-	    hue = 4 + c[1] - c[0]
-
-	hue = hue * _THIRD_PI
-	if hue < 0.0:
-	    hue = hue + _TWO_PI
-
-    return (hue, saturation, intensity)
-
-def name2rgb(root, colorName, asInt = 0):
-    if colorName[0] == '#':
-	# Extract rgb information from the color name itself, assuming
-	# it is either #rgb, #rrggbb, #rrrgggbbb, or #rrrrggggbbbb
-	# This is useful, since tk may return incorrect rgb values if
-	# the colormap is full - it will return the rbg values of the
-	# closest color available.
-        colorName = colorName[1:]
-        digits = len(colorName) / 3
-        factor = 16 ** (4 - digits)
-        rgb = (
-            string.atoi(colorName[0:digits], 16) * factor,
-            string.atoi(colorName[digits:digits * 2], 16) * factor,
-            string.atoi(colorName[digits * 2:digits * 3], 16) * factor,
-        )
-    else:
-	# We have no choice but to ask Tk what the rgb values are.
-	rgb = root.winfo_rgb(colorName)
-
-    if not asInt:
-        rgb = (rgb[0] / _MAX_RGB, rgb[1] / _MAX_RGB, rgb[2] / _MAX_RGB)
-    return rgb
-
-def _calcPalette(root, background=None, **kw):
-    # Create a map that has the complete new palette.  If some colors
-    # aren't specified, compute them from other colors that are specified.
-    new = {}
-    for key, value in kw.items():
-	new[key] = value
-    if background is not None:
-	new['background'] = background
-    if not new.has_key('background'):
-	raise ValueError, 'must specify a background color'
-
-    if not new.has_key('foreground'):
-	new['foreground'] = 'black'
-
-    bg = name2rgb(root, new['background'])
-    fg = name2rgb(root, new['foreground'])
-
-    for i in ('activeForeground', 'insertBackground', 'selectForeground',
-	    'highlightColor'):
-	if not new.has_key(i):
-	    new[i] = new['foreground']
-
-    if not new.has_key('disabledForeground'):
-	newCol = average(bg, fg, 0.3)
-	new['disabledForeground'] = rgb2name(newCol)
-
-    if not new.has_key('highlightBackground'):
-	new['highlightBackground'] = new['background']
-
-    # Set <lighterBg> to a color that is a little lighter that the
-    # normal background.  To do this, round each color component up by
-    # 9% or 1/3 of the way to full white, whichever is greater.
-    lighterBg = []
-    for i in range(3):
-	lighterBg.append(bg[i])
-	inc1 = lighterBg[i] * 0.09
-	inc2 = (1.0 - lighterBg[i]) / 3
-	if inc1 > inc2:
-	    lighterBg[i] = lighterBg[i] + inc1
-	else:
-	    lighterBg[i] = lighterBg[i] + inc2
-	if lighterBg[i] > 1.0:
-	    lighterBg[i] = 1.0
-
-    # Set <darkerBg> to a color that is a little darker that the
-    # normal background.
-    darkerBg = (bg[0] * 0.9, bg[1] * 0.9, bg[2] * 0.9)
-
-    if not new.has_key('activeBackground'):
-	# If the foreground is dark, pick a light active background.
-	# If the foreground is light, pick a dark active background.
-	# XXX This has been disabled, since it does not look very
-	# good with dark backgrounds. If this is ever fixed, the
-	# selectBackground and troughColor options should also be fixed.
-
-	if rgb2brightness(fg) < 0.5:
-	    new['activeBackground'] = rgb2name(lighterBg)
-	else:
-	    new['activeBackground'] = rgb2name(lighterBg)
-
-    if not new.has_key('selectBackground'):
-	new['selectBackground'] = rgb2name(darkerBg)
-    if not new.has_key('troughColor'):
-	new['troughColor'] = rgb2name(darkerBg)
-    if not new.has_key('selectColor'):
-	new['selectColor'] = 'yellow'
-
-    return new
-
-def spectrum(numColors, correction = 1.0, saturation = 1.0, intensity = 1.0,
-	extraOrange = 1, returnHues = 0):
-    colorList = []
-    division = numColors / 7.0
-    for index in range(numColors):
-	if extraOrange:
-	    if index < 2 * division:
-		hue = index / division
-	    else:
-		hue = 2 + 2 * (index - 2 * division) / division
-	    hue = hue * _SIXTH_PI
-	else:
-	    hue = index * _TWO_PI / numColors
-	if returnHues:
-	    colorList.append(hue)
-	else:
-	    rgb = hsi2rgb(hue, saturation, intensity)
-	    if correction != 1.0:
-		rgb = correct(rgb, correction)
-	    name = rgb2name(rgb)
-	    colorList.append(name)
-    return colorList
-
-def correct(rgb, correction):
-    correction = float(correction)
-    rtn = []
-    for index in range(3):
-	rtn.append((1 - (1 - rgb[index]) ** correction) ** (1 / correction))
-    return rtn
-
-#==============================================================================
-
-def _recolorTree(widget, oldpalette, newcolors):
-    # Change the colors in a widget and its descendants.
-
-    # Change the colors in <widget> and all of its descendants,
-    # according to the <newcolors> dictionary.  It only modifies
-    # colors that have their default values as specified by the
-    # <oldpalette> variable.  The keys of the <newcolors> dictionary
-    # are named after widget configuration options and the values are
-    # the new value for that option.
-
-    for dbOption in newcolors.keys():
-        option = string.lower(dbOption)
-        try:
-            value = str(widget.cget(option))
-        except:
-            continue
-        if oldpalette is None or value == oldpalette[dbOption]:
-            apply(widget.configure, (), {option : newcolors[dbOption]})
-
-    for child in widget.winfo_children():
-       _recolorTree(child, oldpalette, newcolors)
-
-def changecolor(widget, background=None, **kw):
-     root = widget._root()
-     if not hasattr(widget, '_Pmw_oldpalette'):
-	 widget._Pmw_oldpalette = getdefaultpalette(root)
-     newpalette = apply(_calcPalette, (root, background,), kw)
-     _recolorTree(widget, widget._Pmw_oldpalette, newpalette)
-     widget._Pmw_oldpalette = newpalette
-
-def bordercolors(root, colorName):
-    # This is the same method that Tk uses for shadows, in TkpGetShadows.
-
-    lightRGB = []
-    darkRGB = []
-    for value in name2rgb(root, colorName, 1):
-        value40pc = (14 * value) / 10
-        if value40pc > _MAX_RGB:
-            value40pc = _MAX_RGB
-        valueHalfWhite = (_MAX_RGB + value) / 2;
-        lightRGB.append(max(value40pc, valueHalfWhite))
-
-        darkValue = (60 * value) / 100
-        darkRGB.append(darkValue)
-
-    return (
-        '#%04x%04x%04x' % (lightRGB[0], lightRGB[1], lightRGB[2]),
-        '#%04x%04x%04x' % (darkRGB[0], darkRGB[1], darkRGB[2])
-    )
diff -uNr bkchem-0.12.5/bkchem/Pmw.py bkchem-0.12.5-nopiddle/bkchem/Pmw.py
--- bkchem-0.12.5/bkchem/Pmw.py	2008-11-25 06:54:27.000000000 -0200
+++ bkchem-0.12.5-nopiddle/bkchem/Pmw.py	1969-12-31 21:00:00.000000000 -0300
@@ -1,9239 +0,0 @@
-
-import PmwColor
-Color = PmwColor
-del PmwColor
-
-import PmwBlt
-Blt = PmwBlt
-del PmwBlt
-
-
-# by BK to support non-ascii menus etc.
-str = unicode
-
-
-### Loader functions:
-
-_VERSION = '1.2'
-
-def setversion(version):
-    if version != _VERSION:
-        raise ValueError, 'Dynamic versioning not available'
-
-def setalphaversions(*alpha_versions):
-    if alpha_versions != ():
-	raise ValueError, 'Dynamic versioning not available'
-
-def version(alpha = 0):
-    if alpha:
-        return ()
-    else:
-        return _VERSION
-
-def installedversions(alpha = 0):
-    if alpha:
-        return ()
-    else:
-        return (_VERSION,)
-
-
-######################################################################
-### File: PmwBase.py
-# Pmw megawidget base classes.
-
-# This module provides a foundation for building megawidgets.  It
-# contains the MegaArchetype class which manages component widgets and
-# configuration options.  Also provided are the MegaToplevel and
-# MegaWidget classes, derived from the MegaArchetype class.  The
-# MegaToplevel class contains a Tkinter Toplevel widget to act as the
-# container of the megawidget.  This is used as the base class of all
-# megawidgets that are contained in their own top level window, such
-# as a Dialog window.  The MegaWidget class contains a Tkinter Frame
-# to act as the container of the megawidget.  This is used as the base
-# class of all other megawidgets, such as a ComboBox or ButtonBox.
-#
-# Megawidgets are built by creating a class that inherits from either
-# the MegaToplevel or MegaWidget class.
-
-import os
-import string
-import sys
-import traceback
-import types
-import Tkinter
-
-# Special values used in index() methods of several megawidgets.
-END = ['end']
-SELECT = ['select']
-DEFAULT = ['default']
-
-# Constant used to indicate that an option can only be set by a call
-# to the constructor.
-INITOPT = ['initopt']
-_DEFAULT_OPTION_VALUE = ['default_option_value']
-_useTkOptionDb = 0
-
-# Symbolic constants for the indexes into an optionInfo list.
-_OPT_DEFAULT         = 0
-_OPT_VALUE           = 1
-_OPT_FUNCTION        = 2
-
-# Stacks
-
-_busyStack = []
-    # Stack which tracks nested calls to show/hidebusycursor (called
-    # either directly or from activate()/deactivate()).  Each element
-    # is a dictionary containing:
-    #   'newBusyWindows' :  List of windows which had busy_hold called
-    #                       on them during a call to showbusycursor(). 
-    #                       The corresponding call to hidebusycursor()
-    #                       will call busy_release on these windows.
-    #   'busyFocus' :       The blt _Busy window which showbusycursor()
-    #                       set the focus to.
-    #   'previousFocus' :   The focus as it was when showbusycursor()
-    #                       was called.  The corresponding call to
-    #                       hidebusycursor() will restore this focus if
-    #                       the focus has not been changed from busyFocus.
-
-_grabStack = []
-    # Stack of grabbed windows.  It tracks calls to push/popgrab()
-    # (called either directly or from activate()/deactivate()).  The
-    # window on the top of the stack is the window currently with the
-    # grab.  Each element is a dictionary containing:
-    #   'grabWindow' :      The window grabbed by pushgrab().  The
-    #                       corresponding call to popgrab() will release
-    #                       the grab on this window and restore the grab
-    #                       on the next window in the stack (if there is one).
-    #   'globalMode' :      True if the grabWindow was grabbed with a
-    #                       global grab, false if the grab was local
-    #                       and 'nograb' if no grab was performed.
-    #   'previousFocus' :   The focus as it was when pushgrab()
-    #                       was called.  The corresponding call to
-    #                       popgrab() will restore this focus.
-    #   'deactivateFunction' :
-    #       The function to call (usually grabWindow.deactivate) if
-    #       popgrab() is called (usually from a deactivate() method)
-    #       on a window which is not at the top of the stack (that is,
-    #       does not have the grab or focus).  For example, if a modal
-    #       dialog is deleted by the window manager or deactivated by
-    #       a timer.  In this case, all dialogs above and including
-    #       this one are deactivated, starting at the top of the
-    #       stack.
-
-    # Note that when dealing with focus windows, the name of the Tk
-    # widget is used, since it may be the '_Busy' window, which has no
-    # python instance associated with it.
-
-#=============================================================================
-
-# Functions used to forward methods from a class to a component.
-
-# Fill in a flattened method resolution dictionary for a class (attributes are 
-# filtered out). Flattening honours the MI method resolution rules 
-# (depth-first search of bases in order). The dictionary has method names
-# for keys and functions for values.
-def __methodDict(cls, dict):
-
-    # the strategy is to traverse the class in the _reverse_ of the normal
-    # order, and overwrite any duplicates.
-    baseList = list(cls.__bases__)
-    baseList.reverse()
-    
-    # do bases in reverse order, so first base overrides last base
-    for super in baseList:
-	__methodDict(super, dict)
-
-    # do my methods last to override base classes
-    for key, value in cls.__dict__.items():
-	# ignore class attributes
-	if type(value) == types.FunctionType:
-	    dict[key] = value
-
-def __methods(cls):
-    # Return all method names for a class.
-
-    # Return all method names for a class (attributes are filtered
-    # out).  Base classes are searched recursively.
-
-    dict = {}
-    __methodDict(cls, dict)
-    return dict.keys()
-	
-# Function body to resolve a forwarding given the target method name and the 
-# attribute name. The resulting lambda requires only self, but will forward 
-# any other parameters.
-__stringBody = (
-    'def %(method)s(this, *args, **kw): return ' +
-    'apply(this.%(attribute)s.%(method)s, args, kw)')
-
-# Get a unique id
-__counter = 0
-def __unique():
-  global __counter
-  __counter = __counter + 1
-  return str(__counter)
-
-# Function body to resolve a forwarding given the target method name and the
-# index of the resolution function. The resulting lambda requires only self, 
-# but will forward any other parameters. The target instance is identified 
-# by invoking the resolution function.
-__funcBody = (
-    'def %(method)s(this, *args, **kw): return ' +
-    'apply(this.%(forwardFunc)s().%(method)s, args, kw)')
-
-def forwardmethods(fromClass, toClass, toPart, exclude = ()):
-    # Forward all methods from one class to another.
-
-    # Forwarders will be created in fromClass to forward method
-    # invocations to toClass.  The methods to be forwarded are
-    # identified by flattening the interface of toClass, and excluding
-    # methods identified in the exclude list.  Methods already defined
-    # in fromClass, or special methods with one or more leading or
-    # trailing underscores will not be forwarded.
-
-    # For a given object of class fromClass, the corresponding toClass
-    # object is identified using toPart.  This can either be a String
-    # denoting an attribute of fromClass objects, or a function taking
-    # a fromClass object and returning a toClass object.
-
-    # Example:
-    #     class MyClass:
-    #     ...
-    #         def __init__(self):
-    #             ...
-    #             self.__target = TargetClass()
-    #             ...
-    #         def findtarget(self):
-    #             return self.__target
-    #     forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2'])
-    #     # ...or...
-    #     forwardmethods(MyClass, TargetClass, MyClass.findtarget, 
-    #             ['dangerous1', 'dangerous2'])
-
-    # In both cases, all TargetClass methods will be forwarded from
-    # MyClass except for dangerous1, dangerous2, special methods like
-    # __str__, and pre-existing methods like findtarget.
-
-
-    # Allow an attribute name (String) or a function to determine the instance
-    if type(toPart) != types.StringType:
-
-	# check that it is something like a function
-	if callable(toPart):
-
-	    # If a method is passed, use the function within it
-	    if hasattr(toPart, 'im_func'):
-		toPart = toPart.im_func
-		
-	    # After this is set up, forwarders in this class will use
-	    # the forwarding function. The forwarding function name is
-	    # guaranteed to be unique, so that it can't be hidden by subclasses
-	    forwardName = '__fwdfunc__' + __unique()
-	    fromClass.__dict__[forwardName] = toPart
-
-	# It's not a valid type
-	else:
-	    raise TypeError, 'toPart must be attribute name, function or method'
-
-    # get the full set of candidate methods
-    dict = {}
-    __methodDict(toClass, dict)
-
-    # discard special methods
-    for ex in dict.keys():
-	if ex[:1] == '_' or ex[-1:] == '_':
-	    del dict[ex]
-    # discard dangerous methods supplied by the caller
-    for ex in exclude:
-	if dict.has_key(ex):
-	    del dict[ex]
-    # discard methods already defined in fromClass
-    for ex in __methods(fromClass):
-	if dict.has_key(ex):
-	    del dict[ex]
-
-    for method, func in dict.items():
-	d = {'method': method, 'func': func}
-	if type(toPart) == types.StringType:
-	    execString = \
-		__stringBody % {'method' : method, 'attribute' : toPart}
-	else:
-	    execString = \
-		__funcBody % {'forwardFunc' : forwardName, 'method' : method}
-
-	exec execString in d
-
-	# this creates a method
-	fromClass.__dict__[method] = d[method]
-
-#=============================================================================
-
-def setgeometryanddeiconify(window, geom):
-    # To avoid flashes on X and to position the window correctly on NT
-    # (caused by Tk bugs).
-
-    if os.name == 'nt' or \
-            (os.name == 'posix' and sys.platform[:6] == 'cygwin'):
-        # Require overrideredirect trick to stop window frame
-        # appearing momentarily.
-        redirect = window.overrideredirect()
-        if not redirect:
-            window.overrideredirect(1)
-        window.deiconify()
-        if geom is not None:
-            window.geometry(geom)
-        # Call update_idletasks to ensure NT moves the window to the
-        # correct position it is raised.
-        window.update_idletasks()
-        window.tkraise()
-        if not redirect:
-            window.overrideredirect(0)
-    else:
-        if geom is not None:
-            window.geometry(geom)
-
-        # Problem!?  Which way around should the following two calls
-        # go?  If deiconify() is called first then I get complaints
-        # from people using the enlightenment or sawfish window
-        # managers that when a dialog is activated it takes about 2
-        # seconds for the contents of the window to appear.  But if
-        # tkraise() is called first then I get complaints from people
-        # using the twm window manager that when a dialog is activated
-        # it appears in the top right corner of the screen and also
-        # takes about 2 seconds to appear.
-
-        #window.tkraise()
-        # Call update_idletasks to ensure certain window managers (eg: 
-        # enlightenment and sawfish) do not cause Tk to delay for
-        # about two seconds before displaying window.
-        #window.update_idletasks()
-        #window.deiconify()
-
-        window.deiconify()
-        if window.overrideredirect():
-            # The window is not under the control of the window manager
-            # and so we need to raise it ourselves.
-            window.tkraise()
-
-#=============================================================================
-
-class MegaArchetype:
-    # Megawidget abstract root class.
-
-    # This class provides methods which are inherited by classes
-    # implementing useful bases (this class doesn't provide a
-    # container widget inside which the megawidget can be built).
-
-    def __init__(self, parent = None, hullClass = None):
-
-	# Mapping from each megawidget option to a list of information
-	# about the option
-	#   - default value
-	#   - current value
-	#   - function to call when the option is initialised in the
-	#     call to initialiseoptions() in the constructor or
-	#     modified via configure().  If this is INITOPT, the
-	#     option is an initialisation option (an option that can
-	#     be set by the call to the constructor but can not be
-	#     used with configure).
-	# This mapping is not initialised here, but in the call to
-	# defineoptions() which precedes construction of this base class.
-	#
-	# self._optionInfo = {}
-
-	# Mapping from each component name to a tuple of information
-	# about the component.
-	#   - component widget instance
-	#   - configure function of widget instance
-	#   - the class of the widget (Frame, EntryField, etc)
-	#   - cget function of widget instance
-	#   - the name of the component group of this component, if any
-	self.__componentInfo = {}
-
-	# Mapping from alias names to the names of components or
-	# sub-components.
-	self.__componentAliases = {}
-
-	# Contains information about the keywords provided to the
-	# constructor.  It is a mapping from the keyword to a tuple
-	# containing:
-	#    - value of keyword
-	#    - a boolean indicating if the keyword has been used.
-	# A keyword is used if, during the construction of a megawidget,
-	#    - it is defined in a call to defineoptions() or addoptions(), or
-	#    - it references, by name, a component of the megawidget, or
-	#    - it references, by group, at least one component
-	# At the end of megawidget construction, a call is made to
-	# initialiseoptions() which reports an error if there are
-	# unused options given to the constructor.
-        #
-        # After megawidget construction, the dictionary contains
-        # keywords which refer to a dynamic component group, so that
-        # these components can be created after megawidget
-        # construction and still use the group options given to the
-        # constructor.
-	#
-	# self._constructorKeywords = {}
-
-        # List of dynamic component groups.  If a group is included in
-        # this list, then it not an error if a keyword argument for
-        # the group is given to the constructor or to configure(), but
-        # no components with this group have been created.
-        # self._dynamicGroups = ()
-
-	if hullClass is None:
-	    self._hull = None
-	else:
-	    if parent is None:
-		parent = Tkinter._default_root
-
-	    # Create the hull.
-	    self._hull = self.createcomponent('hull',
-		    (), None,
-		    hullClass, (parent,))
-	    _hullToMegaWidget[self._hull] = self
-
-	    if _useTkOptionDb:
-		# Now that a widget has been created, query the Tk
-		# option database to get the default values for the
-		# options which have not been set in the call to the
-		# constructor.  This assumes that defineoptions() is
-		# called before the __init__().
-		option_get = self.option_get
-		_VALUE = _OPT_VALUE
-		_DEFAULT = _OPT_DEFAULT
-		for name, info in self._optionInfo.items():
-		    value = info[_VALUE]
-		    if value is _DEFAULT_OPTION_VALUE:
-			resourceClass = string.upper(name[0]) + name[1:]
-			value = option_get(name, resourceClass)
-			if value != '':
-			    try:
-				# Convert the string to int/float/tuple, etc
-				value = eval(value, {'__builtins__': {}})
-			    except:
-				pass
-			    info[_VALUE] = value
-			else:
-			    info[_VALUE] = info[_DEFAULT]
-
-    def destroy(self):
-        # Clean up optionInfo in case it contains circular references
-        # in the function field, such as self._settitle in class
-        # MegaToplevel.
-
-	self._optionInfo = {}
-        if self._hull is not None:
-            del _hullToMegaWidget[self._hull]
-            self._hull.destroy()
-
-    #======================================================================
-    # Methods used (mainly) during the construction of the megawidget.
-
-    def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
-	# Create options, providing the default value and the method
-	# to call when the value is changed.  If any option created by
-	# base classes has the same name as one in <optionDefs>, the
-	# base class's value and function will be overriden.
-
-	# This should be called before the constructor of the base
-	# class, so that default values defined in the derived class
-	# override those in the base class.
-
-	if not hasattr(self, '_constructorKeywords'):
-            # First time defineoptions has been called.
-	    tmp = {}
-	    for option, value in keywords.items():
-		tmp[option] = [value, 0]
-	    self._constructorKeywords = tmp
-	    self._optionInfo = {}
-	    self._initialiseoptions_counter = 0
-        self._initialiseoptions_counter = self._initialiseoptions_counter + 1
-
-        if not hasattr(self, '_dynamicGroups'):
-            self._dynamicGroups = ()
-        self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
-	self.addoptions(optionDefs)
-
-    def addoptions(self, optionDefs):
-	# Add additional options, providing the default value and the
-	# method to call when the value is changed.  See
-	# "defineoptions" for more details
-
-	# optimisations:
-	optionInfo = self._optionInfo
-	optionInfo_has_key = optionInfo.has_key
-	keywords = self._constructorKeywords
-	keywords_has_key = keywords.has_key
-	FUNCTION = _OPT_FUNCTION
-
-	for name, default, function in optionDefs:
-	    if '_' not in name:
-		# The option will already exist if it has been defined
-		# in a derived class.  In this case, do not override the
-		# default value of the option or the callback function
-		# if it is not None.
-		if not optionInfo_has_key(name):
-		    if keywords_has_key(name):
-			value = keywords[name][0]
-			optionInfo[name] = [default, value, function]
-			del keywords[name]
-		    else:
-			if _useTkOptionDb:
-			    optionInfo[name] = \
-				    [default, _DEFAULT_OPTION_VALUE, function]
-			else:
-			    optionInfo[name] = [default, default, function]
-		elif optionInfo[name][FUNCTION] is None:
-		    optionInfo[name][FUNCTION] = function
-	    else:
-		# This option is of the form "component_option".  If this is
-		# not already defined in self._constructorKeywords add it.
-		# This allows a derived class to override the default value
-		# of an option of a component of a base class.
-		if not keywords_has_key(name):
-		    keywords[name] = [default, 0]
-
-    def createcomponent(self, componentName, componentAliases,
-            componentGroup, widgetClass, *widgetArgs, **kw):
-	# Create a component (during construction or later).
-
-	if self.__componentInfo.has_key(componentName):
-	    raise ValueError, 'Component "%s" already exists' % componentName
-
-	if '_' in componentName:
-	    raise ValueError, \
-                    'Component name "%s" must not contain "_"' % componentName
-
-	if hasattr(self, '_constructorKeywords'):
-	    keywords = self._constructorKeywords
-	else:
-	    keywords = {}
-	for alias, component in componentAliases:
-	    # Create aliases to the component and its sub-components.
-	    index = string.find(component, '_')
-	    if index < 0:
-		self.__componentAliases[alias] = (component, None)
-	    else:
-		mainComponent = component[:index]
-		subComponent = component[(index + 1):]
-		self.__componentAliases[alias] = (mainComponent, subComponent)
-
-	    # Remove aliases from the constructor keyword arguments by
-	    # replacing any keyword arguments that begin with *alias*
-	    # with corresponding keys beginning with *component*.
-
-	    alias = alias + '_'
-	    aliasLen = len(alias)
-	    for option in keywords.keys():
-		if len(option) > aliasLen and option[:aliasLen] == alias:
-		    newkey = component + '_' + option[aliasLen:]
-		    keywords[newkey] = keywords[option]
-		    del keywords[option]
-
-	componentPrefix = componentName + '_'
-	nameLen = len(componentPrefix)
-	for option in keywords.keys():
-	    if len(option) > nameLen and option[:nameLen] == componentPrefix:
-		# The keyword argument refers to this component, so add
-		# this to the options to use when constructing the widget.
-		kw[option[nameLen:]] = keywords[option][0]
-		del keywords[option]
-	    else:
-		# Check if this keyword argument refers to the group
-		# of this component.  If so, add this to the options
-		# to use when constructing the widget.  Mark the
-		# keyword argument as being used, but do not remove it
-		# since it may be required when creating another
-		# component.
-		index = string.find(option, '_')
-		if index >= 0 and componentGroup == option[:index]:
-		    rest = option[(index + 1):]
-		    kw[rest] = keywords[option][0]
-		    keywords[option][1] = 1
-
-	if kw.has_key('pyclass'):
-	    widgetClass = kw['pyclass']
-	    del kw['pyclass']
-	if widgetClass is None:
-	    return None
-        if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType:
-            # Arguments to the constructor can be specified as either
-            # multiple trailing arguments to createcomponent() or as a
-            # single tuple argument.
-            widgetArgs = widgetArgs[0]
-	widget = apply(widgetClass, widgetArgs, kw)
-	componentClass = widget.__class__.__name__
-	self.__componentInfo[componentName] = (widget, widget.configure,
-		componentClass, widget.cget, componentGroup)
-
-	return widget
-
-    def destroycomponent(self, name):
-	# Remove a megawidget component.
-
-	# This command is for use by megawidget designers to destroy a
-	# megawidget component.
-
-	self.__componentInfo[name][0].destroy()
-	del self.__componentInfo[name]
-
-    def createlabel(self, parent, childCols = 1, childRows = 1):
-
-	labelpos = self['labelpos']
-	labelmargin = self['labelmargin']
-	if labelpos is None:
-	    return
-
-	label = self.createcomponent('label',
-		(), None,
-		Tkinter.Label, (parent,))
-
-	if labelpos[0] in 'ns':
-	    # vertical layout
-	    if labelpos[0] == 'n':
-		row = 0
-		margin = 1
-	    else:
-		row = childRows + 3
-		margin = row - 1
-	    label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos)
-	    parent.grid_rowconfigure(margin, minsize=labelmargin)
-	else:
-	    # horizontal layout
-	    if labelpos[0] == 'w':
-		col = 0
-		margin = 1
-	    else:
-		col = childCols + 3
-		margin = col - 1
-	    label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos)
-	    parent.grid_columnconfigure(margin, minsize=labelmargin)
-
-    def initialiseoptions(self, dummy = None):
-        self._initialiseoptions_counter = self._initialiseoptions_counter - 1
-	if self._initialiseoptions_counter == 0:
-	    unusedOptions = []
-	    keywords = self._constructorKeywords
-	    for name in keywords.keys():
-		used = keywords[name][1]
-		if not used:
-                    # This keyword argument has not been used.  If it
-                    # does not refer to a dynamic group, mark it as
-                    # unused.
-                    index = string.find(name, '_')
-                    if index < 0 or name[:index] not in self._dynamicGroups:
-                        unusedOptions.append(name)
-	    if len(unusedOptions) > 0:
-		if len(unusedOptions) == 1:
-		    text = 'Unknown option "'
-		else:
-		    text = 'Unknown options "'
-		raise KeyError, text + string.join(unusedOptions, ', ') + \
-			'" for ' + self.__class__.__name__
-
-	    # Call the configuration callback function for every option.
-	    FUNCTION = _OPT_FUNCTION
-	    for info in self._optionInfo.values():
-		func = info[FUNCTION]
-		if func is not None and func is not INITOPT:
-		    func()
-
-    #======================================================================
-    # Method used to configure the megawidget.
-
-    def configure(self, option=None, **kw):
-	# Query or configure the megawidget options.
-	#
-	# If not empty, *kw* is a dictionary giving new
-	# values for some of the options of this megawidget or its
-	# components.  For options defined for this megawidget, set
-	# the value of the option to the new value and call the
-	# configuration callback function, if any.  For options of the
-	# form <component>_<option>, where <component> is a component
-	# of this megawidget, call the configure method of the
-	# component giving it the new value of the option.  The
-	# <component> part may be an alias or a component group name.
-	#
-	# If *option* is None, return all megawidget configuration
-	# options and settings.  Options are returned as standard 5
-	# element tuples
-	#
-	# If *option* is a string, return the 5 element tuple for the
-	# given configuration option.
-
-	# First, deal with the option queries.
-	if len(kw) == 0:
-	    # This configure call is querying the values of one or all options.
-	    # Return 5-tuples:
-	    #     (optionName, resourceName, resourceClass, default, value)
-	    if option is None:
-		rtn = {}
-		for option, config in self._optionInfo.items():
-		    resourceClass = string.upper(option[0]) + option[1:]
-		    rtn[option] = (option, option, resourceClass,
-			    config[_OPT_DEFAULT], config[_OPT_VALUE])
-		return rtn
-	    else:
-		config = self._optionInfo[option]
-		resourceClass = string.upper(option[0]) + option[1:]
-		return (option, option, resourceClass, config[_OPT_DEFAULT],
-			config[_OPT_VALUE])
-
-	# optimisations:
-	optionInfo = self._optionInfo
-	optionInfo_has_key = optionInfo.has_key
-	componentInfo = self.__componentInfo
-	componentInfo_has_key = componentInfo.has_key
-	componentAliases = self.__componentAliases
-	componentAliases_has_key = componentAliases.has_key
-	VALUE = _OPT_VALUE
-	FUNCTION = _OPT_FUNCTION
-
-	# This will contain a list of options in *kw* which
-	# are known to this megawidget.
-	directOptions = []
-
-	# This will contain information about the options in
-	# *kw* of the form <component>_<option>, where
-	# <component> is a component of this megawidget.  It is a
-	# dictionary whose keys are the configure method of each
-	# component and whose values are a dictionary of options and
-	# values for the component.
-	indirectOptions = {}
-	indirectOptions_has_key = indirectOptions.has_key
-
-	for option, value in kw.items():
-	    if optionInfo_has_key(option):
-		# This is one of the options of this megawidget. 
-		# Make sure it is not an initialisation option.
-		if optionInfo[option][FUNCTION] is INITOPT:
-		    raise KeyError, \
-			    'Cannot configure initialisation option "' \
-			    + option + '" for ' + self.__class__.__name__
-		optionInfo[option][VALUE] = value
-		directOptions.append(option)
-	    else:
-		index = string.find(option, '_')
-		if index >= 0:
-		    # This option may be of the form <component>_<option>.
-		    component = option[:index]
-		    componentOption = option[(index + 1):]
-
-		    # Expand component alias
-		    if componentAliases_has_key(component):
-			component, subComponent = componentAliases[component]
-			if subComponent is not None:
-			    componentOption = subComponent + '_' \
-				    + componentOption
-
-			# Expand option string to write on error
-			option = component + '_' + componentOption
-
-		    if componentInfo_has_key(component):
-			# Configure the named component
-			componentConfigFuncs = [componentInfo[component][1]]
-		    else:
-			# Check if this is a group name and configure all
-			# components in the group.
-			componentConfigFuncs = []
-			for info in componentInfo.values():
-			    if info[4] == component:
-			        componentConfigFuncs.append(info[1])
-
-                        if len(componentConfigFuncs) == 0 and \
-                                component not in self._dynamicGroups:
-			    raise KeyError, 'Unknown option "' + option + \
-				    '" for ' + self.__class__.__name__
-
-		    # Add the configure method(s) (may be more than
-		    # one if this is configuring a component group)
-		    # and option/value to dictionary.
-		    for componentConfigFunc in componentConfigFuncs:
-			if not indirectOptions_has_key(componentConfigFunc):
-			    indirectOptions[componentConfigFunc] = {}
-			indirectOptions[componentConfigFunc][componentOption] \
-				= value
-		else:
-		    raise KeyError, 'Unknown option "' + option + \
-			    '" for ' + self.__class__.__name__
-
-	# Call the configure methods for any components.
-	map(apply, indirectOptions.keys(),
-		((),) * len(indirectOptions), indirectOptions.values())
-
-	# Call the configuration callback function for each option.
-	for option in directOptions:
-	    info = optionInfo[option]
-	    func = info[_OPT_FUNCTION]
-	    if func is not None:
-	      func()
-
-    def __setitem__(self, key, value):
-        apply(self.configure, (), {key: value})
-
-    #======================================================================
-    # Methods used to query the megawidget.
-
-    def component(self, name):
-	# Return a component widget of the megawidget given the
-	# component's name
-	# This allows the user of a megawidget to access and configure
-	# widget components directly.
-
-	# Find the main component and any subcomponents
-	index = string.find(name, '_')
-	if index < 0:
-	    component = name
-	    remainingComponents = None
-	else:
-	    component = name[:index]
-	    remainingComponents = name[(index + 1):]
-
-	# Expand component alias
-	if self.__componentAliases.has_key(component):
-	    component, subComponent = self.__componentAliases[component]
-	    if subComponent is not None:
-		if remainingComponents is None:
-		    remainingComponents = subComponent
-		else:
-		    remainingComponents = subComponent + '_' \
-			    + remainingComponents
-
-	widget = self.__componentInfo[component][0]
-	if remainingComponents is None:
-	    return widget
-	else:
-	    return widget.component(remainingComponents)
-
-    def interior(self):
-	return self._hull
-
-    def hulldestroyed(self):
-	return not _hullToMegaWidget.has_key(self._hull)
-
-    def __str__(self):
-	return str(self._hull)
-
-    def cget(self, option):
-	# Get current configuration setting.
-
-	# Return the value of an option, for example myWidget['font']. 
-
-	if self._optionInfo.has_key(option):
-	    return self._optionInfo[option][_OPT_VALUE]
-	else:
-	    index = string.find(option, '_')
-	    if index >= 0:
-		component = option[:index]
-		componentOption = option[(index + 1):]
-
-		# Expand component alias
-		if self.__componentAliases.has_key(component):
-		    component, subComponent = self.__componentAliases[component]
-		    if subComponent is not None:
-			componentOption = subComponent + '_' + componentOption
-
-		    # Expand option string to write on error
-		    option = component + '_' + componentOption
-
-		if self.__componentInfo.has_key(component):
-		    # Call cget on the component.
-		    componentCget = self.__componentInfo[component][3]
-		    return componentCget(componentOption)
-		else:
-		    # If this is a group name, call cget for one of
-		    # the components in the group.
-		    for info in self.__componentInfo.values():
-			if info[4] == component:
-			    componentCget = info[3]
-			    return componentCget(componentOption)
-
-	raise KeyError, 'Unknown option "' + option + \
-		'" for ' + self.__class__.__name__
-
-    __getitem__ = cget
-
-    def isinitoption(self, option):
-	return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
-
-    def options(self):
-	options = []
-	if hasattr(self, '_optionInfo'):
-	    for option, info in self._optionInfo.items():
-		isinit = info[_OPT_FUNCTION] is INITOPT
-		default = info[_OPT_DEFAULT]
-		options.append((option, default, isinit))
-	    options.sort()
-	return options
-
-    def components(self):
-	# Return a list of all components.
-
-	# This list includes the 'hull' component and all widget subcomponents
-
-	names = self.__componentInfo.keys()
-	names.sort()
-	return names
-
-    def componentaliases(self):
-	# Return a list of all component aliases.
-
-	componentAliases = self.__componentAliases
-
-	names = componentAliases.keys()
-	names.sort()
-	rtn = []
-	for alias in names:
-	    (mainComponent, subComponent) = componentAliases[alias]
-	    if subComponent is None:
-		rtn.append((alias, mainComponent))
-	    else:
-		rtn.append((alias, mainComponent + '_' + subComponent))
-	    
-	return rtn
-
-    def componentgroup(self, name):
-	return self.__componentInfo[name][4]
-
-#=============================================================================
-
-# The grab functions are mainly called by the activate() and
-# deactivate() methods.
-#
-# Use pushgrab() to add a new window to the grab stack.  This
-# releases the grab by the window currently on top of the stack (if
-# there is one) and gives the grab and focus to the new widget.
-#
-# To remove the grab from the window on top of the grab stack, call
-# popgrab().
-#
-# Use releasegrabs() to release the grab and clear the grab stack.
-
-def pushgrab(grabWindow, globalMode, deactivateFunction):
-    prevFocus = grabWindow.tk.call('focus')
-    grabInfo = {
-        'grabWindow' : grabWindow,
-        'globalMode' : globalMode,
-        'previousFocus' : prevFocus,
-        'deactivateFunction' : deactivateFunction,
-    }
-    _grabStack.append(grabInfo)
-    _grabtop()
-    grabWindow.focus_set()
-
-def popgrab(window):
-    # Return the grab to the next window in the grab stack, if any.
-
-    # If this window is not at the top of the grab stack, then it has
-    # just been deleted by the window manager or deactivated by a
-    # timer.  Call the deactivate method for the modal dialog above
-    # this one on the stack. 
-    if _grabStack[-1]['grabWindow'] != window:
-        for index in range(len(_grabStack)):
-            if _grabStack[index]['grabWindow'] == window:
-                _grabStack[index + 1]['deactivateFunction']()
-                break
-
-    grabInfo = _grabStack[-1]
-    del _grabStack[-1]
-
-    topWidget = grabInfo['grabWindow']
-    prevFocus = grabInfo['previousFocus']
-    globalMode = grabInfo['globalMode']
-
-    if globalMode != 'nograb':
-        topWidget.grab_release()
-
-    if len(_grabStack) > 0:
-        _grabtop()
-    if prevFocus != '':
-        try:
-            topWidget.tk.call('focus', prevFocus)
-        except Tkinter.TclError:
-            # Previous focus widget has been deleted. Set focus
-            # to root window.
-            Tkinter._default_root.focus_set()
-    else:
-        # Make sure that focus does not remain on the released widget.
-        if len(_grabStack) > 0:
-            topWidget = _grabStack[-1]['grabWindow']
-            topWidget.focus_set()
-        else:
-            Tkinter._default_root.focus_set()
-
-def grabstacktopwindow():
-    if len(_grabStack) == 0:
-        return None
-    else:
-        return _grabStack[-1]['grabWindow']
-
-def releasegrabs():
-    # Release grab and clear the grab stack.
-
-    current = Tkinter._default_root.grab_current()
-    if current is not None:
-        current.grab_release()
-    _grabStack[:] = []
-
-def _grabtop():
-    grabInfo = _grabStack[-1]
-    topWidget = grabInfo['grabWindow']
-    globalMode = grabInfo['globalMode']
-
-    if globalMode == 'nograb':
-        return
-
-    while 1:
-        try:
-            if globalMode:
-                topWidget.grab_set_global()
-            else:
-                topWidget.grab_set()
-            break
-        except Tkinter.TclError:
-            # Another application has grab.  Keep trying until
-            # grab can succeed.
-            topWidget.after(100)
-
-#=============================================================================
-
-class MegaToplevel(MegaArchetype):
-
-    def __init__(self, parent = None, **kw):
-	# Define the options for this megawidget.
-	optiondefs = (
-            ('activatecommand',   None,                     None),
-            ('deactivatecommand', None,                     None),
-            ('master',            None,                     None),
-            ('title',             None,                     self._settitle),
-            ('hull_class',        self.__class__.__name__,  None),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaArchetype.__init__(self, parent, Tkinter.Toplevel)
-
-	# Initialise instance.
-
-        # Set WM_DELETE_WINDOW protocol, deleting any old callback, so
-        # memory does not leak.
-        if hasattr(self._hull, '_Pmw_WM_DELETE_name'):
-            self._hull.tk.deletecommand(self._hull._Pmw_WM_DELETE_name)
-        self._hull._Pmw_WM_DELETE_name = \
-                self.register(self._userDeleteWindow, needcleanup = 0)
-	self.protocol('WM_DELETE_WINDOW', self._hull._Pmw_WM_DELETE_name)
-
-	# Initialise instance variables.
-
-	self._firstShowing = 1
-	# Used by show() to ensure window retains previous position on screen.
-
-	# The IntVar() variable to wait on during a modal dialog.
-	self._wait = None
-
-	self._active = 0
-	self._userDeleteFunc = self.destroy
-	self._userModalDeleteFunc = self.deactivate
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def _settitle(self):
-	title = self['title']
-	if title is not None:
-	    self.title(title)
-
-    def userdeletefunc(self, func=None):
-        if func:
-	    self._userDeleteFunc = func
-	else:
-	    return self._userDeleteFunc
-
-    def usermodaldeletefunc(self, func=None):
-        if func:
-	    self._userModalDeleteFunc = func
-	else:
-	    return self._userModalDeleteFunc
-
-    def _userDeleteWindow(self):
-	if self.active():
-	    self._userModalDeleteFunc()
-	else:
-	    self._userDeleteFunc()
-
-    def destroy(self):
-	# Allow this to be called more than once.
-	if _hullToMegaWidget.has_key(self._hull):
-	    self.deactivate()
-
-            # Remove circular references, so that object can get cleaned up.
-            del self._userDeleteFunc
-            del self._userModalDeleteFunc
-
-            MegaArchetype.destroy(self)
-
-    def show(self, master = None):
-	if self.state() != 'normal':
-	    if self._firstShowing:
-		# Just let the window manager determine the window
-		# position for the first time.
-		geom = None
-	    else:
-		# Position the window at the same place it was last time.
-		geom = self._sameposition()
-            setgeometryanddeiconify(self, geom)
-
-        if self._firstShowing:
-            self._firstShowing = 0
-        else:
-            if self.transient() == '':
-                self.tkraise()
-
-        # Do this last, otherwise get flashing on NT:
-        if master is not None:
-            if master == 'parent':
-                parent = self.winfo_parent()
-                # winfo_parent() should return the parent widget, but the
-                # the current version of Tkinter returns a string.
-                if type(parent) == types.StringType:
-                    parent = self._hull._nametowidget(parent)
-                master = parent.winfo_toplevel()
-            self.transient(master)
-
-        self.focus()
-
-    def _centreonscreen(self):
-	# Centre the window on the screen.  (Actually halfway across
-	# and one third down.)
-
-        parent = self.winfo_parent()
-        if type(parent) == types.StringType:
-            parent = self._hull._nametowidget(parent)
-
-        # Find size of window.
-	self.update_idletasks()
-        width = self.winfo_width()
-        height = self.winfo_height()
-        if width == 1 and height == 1:
-            # If the window has not yet been displayed, its size is
-            # reported as 1x1, so use requested size.
-            width = self.winfo_reqwidth()
-            height = self.winfo_reqheight()
-
-        # Place in centre of screen:
-	x = (self.winfo_screenwidth() - width) / 2 - parent.winfo_vrootx()
-	y = (self.winfo_screenheight() - height) / 3 - parent.winfo_vrooty()
-	if x < 0:
-	    x = 0
-	if y < 0:
-	    y = 0
-        return '+%d+%d' % (x, y)
-
-    def _sameposition(self):
-	# Position the window at the same place it was last time.
-
-	geometry = self.geometry()
-	index = string.find(geometry, '+')
-	if index >= 0:
-	    return geometry[index:]
-        else:
-	    return None
-
-    def activate(self, globalMode = 0, geometry = 'centerscreenfirst'):
-	if self._active:
-	    raise ValueError, 'Window is already active'
-	if self.state() == 'normal':
-	    self.withdraw()
-
-	self._active = 1
-
-	showbusycursor()
-
-	if self._wait is None:
-	    self._wait = Tkinter.IntVar()
-	self._wait.set(0)
-
-	if geometry == 'centerscreenalways':
-	    geom = self._centreonscreen()
-	elif geometry == 'centerscreenfirst':
-	    if self._firstShowing:
-		# Centre the window the first time it is displayed.
-		geom = self._centreonscreen()
-	    else:
-		# Position the window at the same place it was last time.
-		geom = self._sameposition()
-	elif geometry[:5] == 'first':
-	    if self._firstShowing:
-                geom = geometry[5:]
-	    else:
-		# Position the window at the same place it was last time.
-		geom = self._sameposition()
-        else:
-            geom = geometry
-
-	self._firstShowing = 0
-
-        setgeometryanddeiconify(self, geom)
-
-        # Do this last, otherwise get flashing on NT:
-        master = self['master']
-        if master is not None:
-            if master == 'parent':
-                parent = self.winfo_parent()
-                # winfo_parent() should return the parent widget, but the
-                # the current version of Tkinter returns a string.
-                if type(parent) == types.StringType:
-                    parent = self._hull._nametowidget(parent)
-                master = parent.winfo_toplevel()
-            self.transient(master)
-
-        pushgrab(self._hull, globalMode, self.deactivate)
-	command = self['activatecommand']
-	if callable(command):
-	    command()
-	self.wait_variable(self._wait)
-
-	return self._result
-
-    def deactivate(self, result=None):
-	if not self._active:
-	    return
-	self._active = 0
-
-        # Restore the focus before withdrawing the window, since
-        # otherwise the window manager may take the focus away so we
-        # can't redirect it.  Also, return the grab to the next active
-        # window in the stack, if any.
-        popgrab(self._hull)
-
-        command = self['deactivatecommand']
-        if callable(command):
-            command()
-
-        self.withdraw()
-        hidebusycursor(forceFocusRestore = 1)
-
-        self._result = result
-        self._wait.set(1)
-
-    def active(self):
-	return self._active
-
-forwardmethods(MegaToplevel, Tkinter.Toplevel, '_hull')
-
-#=============================================================================
-
-class MegaWidget(MegaArchetype):
-    def __init__(self, parent = None, **kw):
-	# Define the options for this megawidget.
-	optiondefs = (
-	    ('hull_class',       self.__class__.__name__,  None),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaArchetype.__init__(self, parent, Tkinter.Frame)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-forwardmethods(MegaWidget, Tkinter.Frame, '_hull')
-
-#=============================================================================
-
-# Public functions
-#-----------------
-
-_traceTk = 0
-def tracetk(root = None, on = 1, withStackTrace = 0, file=None):
-    global _withStackTrace
-    global _traceTkFile
-    global _traceTk
-
-    if root is None:
-        root = Tkinter._default_root
-
-    _withStackTrace = withStackTrace
-    _traceTk = on
-    if on:
-	if hasattr(root.tk, '__class__'):
-	    # Tracing already on
-	    return
-	if file is None:
-	    _traceTkFile = sys.stderr
-	else:
-	    _traceTkFile = file
-	tk = _TraceTk(root.tk)
-    else:
-	if not hasattr(root.tk, '__class__'):
-	    # Tracing already off
-	    return
-	tk = root.tk.getTclInterp()
-    _setTkInterps(root, tk)
-
-def showbusycursor():
-
-    _addRootToToplevelBusyInfo()
-    root = Tkinter._default_root
-
-    busyInfo = {
-        'newBusyWindows' : [],
-        'previousFocus' : None,
-        'busyFocus' : None,
-    }
-    _busyStack.append(busyInfo)
-
-    if _disableKeyboardWhileBusy:
-        # Remember the focus as it is now, before it is changed.
-        busyInfo['previousFocus'] = root.tk.call('focus')
-
-    if not _havebltbusy(root):
-        # No busy command, so don't call busy hold on any windows.
-        return
-    return
-
-    for (window, winInfo) in _toplevelBusyInfo.items():
-	if (window.state() != 'withdrawn' and not winInfo['isBusy']
-                and not winInfo['excludeFromBusy']):
-            busyInfo['newBusyWindows'].append(window)
-            winInfo['isBusy'] = 1
-            _busy_hold(window, winInfo['busyCursorName'])
-
-            # Make sure that no events for the busy window get
-            # through to Tkinter, otherwise it will crash in
-            # _nametowidget with a 'KeyError: _Busy' if there is
-            # a binding on the toplevel window.
-            window.tk.call('bindtags', winInfo['busyWindow'], 'Pmw_Dummy_Tag')
-
-            if _disableKeyboardWhileBusy:
-                # Remember previous focus widget for this toplevel window
-                # and set focus to the busy window, which will ignore all
-                # keyboard events.
-                winInfo['windowFocus'] = \
-                        window.tk.call('focus', '-lastfor', window._w)
-                window.tk.call('focus', winInfo['busyWindow'])
-                busyInfo['busyFocus'] = winInfo['busyWindow']
-
-    if len(busyInfo['newBusyWindows']) > 0:
-        if os.name == 'nt':
-            # NT needs an "update" before it will change the cursor.
-            window.update()
-        else:
-            window.update_idletasks()
-
-def hidebusycursor(forceFocusRestore = 0):
-
-    # Remember the focus as it is now, before it is changed.
-    root = Tkinter._default_root
-    if _disableKeyboardWhileBusy:
-        currentFocus = root.tk.call('focus')
-
-    # Pop the busy info off the stack.
-    busyInfo = _busyStack[-1]
-    del _busyStack[-1]
-
-    for window in busyInfo['newBusyWindows']:
-        # If this window has not been deleted, release the busy cursor.
-        if _toplevelBusyInfo.has_key(window):
-            winInfo = _toplevelBusyInfo[window]
-            winInfo['isBusy'] = 0
-            _busy_release(window)
-
-            if _disableKeyboardWhileBusy:
-                # Restore previous focus window for this toplevel window,
-                # but only if is still set to the busy window (it may have
-                # been changed).
-                windowFocusNow = window.tk.call('focus', '-lastfor', window._w)
-                if windowFocusNow == winInfo['busyWindow']:
-                    try:
-                        window.tk.call('focus', winInfo['windowFocus'])
-                    except Tkinter.TclError:
-                        # Previous focus widget has been deleted. Set focus
-                        # to toplevel window instead (can't leave focus on
-                        # busy window).
-                        window.focus_set()
-
-    if _disableKeyboardWhileBusy:
-        # Restore the focus, depending on whether the focus had changed
-        # between the calls to showbusycursor and hidebusycursor.
-        if forceFocusRestore or busyInfo['busyFocus'] == currentFocus:
-            # The focus had not changed, so restore it to as it was before
-            # the call to showbusycursor,
-            previousFocus = busyInfo['previousFocus']
-            if previousFocus is not None:
-                try:
-                    root.tk.call('focus', previousFocus)
-                except Tkinter.TclError:
-                    # Previous focus widget has been deleted; forget it.
-                    pass
-        else:
-            # The focus had changed, so restore it to what it had been
-            # changed to before the call to hidebusycursor.
-            root.tk.call('focus', currentFocus)
-
-def clearbusycursor():
-    while len(_busyStack) > 0:
-        hidebusycursor()
-
-def setbusycursorattributes(window, **kw):
-    _addRootToToplevelBusyInfo()
-    for name, value in kw.items():
-        if name == 'exclude':
-            _toplevelBusyInfo[window]['excludeFromBusy'] = value
-        elif name == 'cursorName':
-            _toplevelBusyInfo[window]['busyCursorName'] = value
-        else:
-            raise KeyError, 'Unknown busycursor attribute "' + name + '"'
-
-def _addRootToToplevelBusyInfo():
-    # Include the Tk root window in the list of toplevels.  This must
-    # not be called before Tkinter has had a chance to be initialised by
-    # the application.
-
-    root = Tkinter._default_root
-    if root == None:
-        root = Tkinter.Tk()
-    if not _toplevelBusyInfo.has_key(root):
-        _addToplevelBusyInfo(root)
-
-def busycallback(command, updateFunction = None):
-    if not callable(command):
-	raise ValueError, \
-	    'cannot register non-command busy callback %s %s' % \
-	        (repr(command), type(command))
-    wrapper = _BusyWrapper(command, updateFunction)
-    return wrapper.callback
-
-_errorReportFile = None
-_errorWindow = None
-
-def reporterrorstofile(file = None):
-    global _errorReportFile
-    _errorReportFile = file
-
-def displayerror(text):
-    global _errorWindow
-
-    if _errorReportFile is not None:
-	_errorReportFile.write(text + '\n')
-    else:
-        # Print error on standard error as well as to error window. 
-        # Useful if error window fails to be displayed, for example
-        # when exception is triggered in a <Destroy> binding for root
-        # window.
-        sys.stderr.write(text + '\n')
-
-	if _errorWindow is None:
-	    # The error window has not yet been created.
-	    _errorWindow = _ErrorWindow()
-
-	_errorWindow.showerror(text)
-
-_root = None
-_disableKeyboardWhileBusy = 1
-
-def initialise(
-        root = None,
-        size = None,
-        fontScheme = None,
-        useTkOptionDb = 0,
-        noBltBusy = 0,
-        disableKeyboardWhileBusy = None,
-):
-    # Remember if show/hidebusycursor should ignore keyboard events.
-    global _disableKeyboardWhileBusy
-    if disableKeyboardWhileBusy is not None:
-        _disableKeyboardWhileBusy = disableKeyboardWhileBusy
-
-    # Do not use blt busy command if noBltBusy is set.  Otherwise,
-    # use blt busy if it is available.
-    global _haveBltBusy
-    if noBltBusy:
-        _haveBltBusy = 0
-
-    # Save flag specifying whether the Tk option database should be
-    # queried when setting megawidget option default values.
-    global _useTkOptionDb
-    _useTkOptionDb = useTkOptionDb
-
-    # If we haven't been given a root window, use the default or
-    # create one.
-    if root is None:
-	if Tkinter._default_root is None:
-	    root = Tkinter.Tk()
-	else:
-	    root = Tkinter._default_root
-
-    # If this call is initialising a different Tk interpreter than the
-    # last call, then re-initialise all global variables.  Assume the
-    # last interpreter has been destroyed - ie:  Pmw does not (yet)
-    # support multiple simultaneous interpreters.
-    global _root
-    if _root is not None and _root != root:
-        global _busyStack
-        global _errorWindow
-        global _grabStack
-        global _hullToMegaWidget
-        global _toplevelBusyInfo
-        _busyStack = []
-        _errorWindow = None
-        _grabStack = []
-        _hullToMegaWidget = {}
-        _toplevelBusyInfo = {}
-    _root = root
-
-    # Trap Tkinter Toplevel constructors so that a list of Toplevels
-    # can be maintained.
-    Tkinter.Toplevel.title = __TkinterToplevelTitle
-
-    # Trap Tkinter widget destruction so that megawidgets can be
-    # destroyed when their hull widget is destoyed and the list of
-    # Toplevels can be pruned.
-    Tkinter.Toplevel.destroy = __TkinterToplevelDestroy
-    Tkinter.Widget.destroy = __TkinterWidgetDestroy
-
-    # Modify Tkinter's CallWrapper class to improve the display of
-    # errors which occur in callbacks.
-    Tkinter.CallWrapper = __TkinterCallWrapper
-
-    # Make sure we get to know when the window manager deletes the
-    # root window.  Only do this if the protocol has not yet been set. 
-    # This is required if there is a modal dialog displayed and the
-    # window manager deletes the root window.  Otherwise the
-    # application will not exit, even though there are no windows.
-    if root.protocol('WM_DELETE_WINDOW') == '':
-	root.protocol('WM_DELETE_WINDOW', root.destroy)
-
-    # Set the base font size for the application and set the
-    # Tk option database font resources.
-    
-    _font_initialise(root, size, fontScheme)
-
-    return root
-
-def alignlabels(widgets, sticky = None):
-    if len(widgets) == 0:
-    	return
-
-    widgets[0].update_idletasks()
-
-    # Determine the size of the maximum length label string.
-    maxLabelWidth = 0
-    for iwid in widgets:
-	labelWidth = iwid.grid_bbox(0, 1)[2]
-	if labelWidth > maxLabelWidth:
-	    maxLabelWidth = labelWidth
-
-    # Adjust the margins for the labels such that the child sites and
-    # labels line up.
-    for iwid in widgets:
-	if sticky is not None:
-	    iwid.component('label').grid(sticky=sticky)
-	iwid.grid_columnconfigure(0, minsize = maxLabelWidth)
-#=============================================================================
-
-# Private routines
-#-----------------
-_callToTkReturned = 1
-_recursionCounter = 1
-
-class _TraceTk:
-    def __init__(self, tclInterp):
-        self.tclInterp = tclInterp
-
-    def getTclInterp(self):
-        return self.tclInterp
-
-    # Calling from python into Tk.
-    def call(self, *args, **kw):
-        global _callToTkReturned
-        global _recursionCounter
-
-        _callToTkReturned = 0
-        if len(args) == 1 and type(args[0]) == types.TupleType:
-            argStr = str(args[0])
-        else:
-            argStr = str(args)
-	_traceTkFile.write('CALL  TK> %d:%s%s' %
-                (_recursionCounter, '  ' * _recursionCounter, argStr))
-	_recursionCounter = _recursionCounter + 1
-        try:
-            result = apply(self.tclInterp.call, args, kw)
-	except Tkinter.TclError, errorString:
-            _callToTkReturned = 1
-            _recursionCounter = _recursionCounter - 1
-            _traceTkFile.write('\nTK ERROR> %d:%s-> %s\n' %
-                    (_recursionCounter, '  ' * _recursionCounter,
-                            repr(errorString)))
-            if _withStackTrace:
-                _traceTkFile.write('CALL  TK> stack:\n')
-                traceback.print_stack()
-            raise Tkinter.TclError, errorString
-
-        _recursionCounter = _recursionCounter - 1
-        if _callToTkReturned:
-            _traceTkFile.write('CALL RTN> %d:%s-> %s' %
-                    (_recursionCounter, '  ' * _recursionCounter, repr(result)))
-        else:
-            _callToTkReturned = 1
-            if result:
-                _traceTkFile.write(' -> %s' % repr(result))
-        _traceTkFile.write('\n')
-        if _withStackTrace:
-            _traceTkFile.write('CALL  TK> stack:\n')
-            traceback.print_stack()
-
-        _traceTkFile.flush()
-        return result
-
-    def __getattr__(self, key):
-        return getattr(self.tclInterp, key)
-
-def _setTkInterps(window, tk):
-    window.tk = tk
-    for child in window.children.values():
-      _setTkInterps(child, tk)
-
-#=============================================================================
-
-# Functions to display a busy cursor.  Keep a list of all toplevels
-# and display the busy cursor over them.  The list will contain the Tk
-# root toplevel window as well as all other toplevel windows.
-# Also keep a list of the widget which last had focus for each
-# toplevel.
-
-# Map from toplevel windows to
-#     {'isBusy', 'windowFocus', 'busyWindow',
-#         'excludeFromBusy', 'busyCursorName'}
-_toplevelBusyInfo = {}
-
-# Pmw needs to know all toplevel windows, so that it can call blt busy
-# on them.  This is a hack so we get notified when a Tk topevel is
-# created.  Ideally, the __init__ 'method' should be overridden, but
-# it is a 'read-only special attribute'.  Luckily, title() is always
-# called from the Tkinter Toplevel constructor.
-
-def _addToplevelBusyInfo(window):
-    if window._w == '.':
-        busyWindow = '._Busy'
-    else:
-        busyWindow = window._w + '._Busy'
-
-    _toplevelBusyInfo[window] = {
-        'isBusy' : 0,
-        'windowFocus' : None,
-        'busyWindow' : busyWindow,
-        'excludeFromBusy' : 0,
-        'busyCursorName' : None,
-    }
-
-def __TkinterToplevelTitle(self, *args):
-    # If this is being called from the constructor, include this
-    # Toplevel in the list of toplevels and set the initial
-    # WM_DELETE_WINDOW protocol to destroy() so that we get to know
-    # about it.
-    if not _toplevelBusyInfo.has_key(self):
-        _addToplevelBusyInfo(self)
-        self._Pmw_WM_DELETE_name = self.register(self.destroy, None, 0)
-	self.protocol('WM_DELETE_WINDOW', self._Pmw_WM_DELETE_name)
-
-    return apply(Tkinter.Wm.title, (self,) + args)
-
-_haveBltBusy = None
-def _havebltbusy(window):
-    global _busy_hold, _busy_release, _haveBltBusy
-    if _haveBltBusy is None:
-        import PmwBlt
-        _haveBltBusy = PmwBlt.havebltbusy(window)
-        _busy_hold = PmwBlt.busy_hold
-        if os.name == 'nt':
-            # There is a bug in Blt 2.4i on NT where the busy window
-            # does not follow changes in the children of a window.
-            # Using forget works around the problem.
-            _busy_release = PmwBlt.busy_forget
-        else:
-            _busy_release = PmwBlt.busy_release
-    return _haveBltBusy
-
-class _BusyWrapper:
-    def __init__(self, command, updateFunction):
-	self._command = command
-	self._updateFunction = updateFunction
-
-    def callback(self, *args):
-	showbusycursor()
-	rtn = apply(self._command, args)
-
-	# Call update before hiding the busy windows to clear any
-	# events that may have occurred over the busy windows.
-	if callable(self._updateFunction):
-	    self._updateFunction()
-
-	hidebusycursor()
-	return rtn
-
-#=============================================================================
-
-def drawarrow(canvas, color, direction, tag, baseOffset = 0.25, edgeOffset = 0.15):
-    canvas.delete(tag)
-
-    bw = (string.atoi(canvas['borderwidth']) + 
-            string.atoi(canvas['highlightthickness']))
-    width = string.atoi(canvas['width'])
-    height = string.atoi(canvas['height'])
-
-    if direction in ('up', 'down'):
-        majorDimension = height
-        minorDimension = width
-    else:
-        majorDimension = width
-        minorDimension = height
-
-    offset = round(baseOffset * majorDimension)
-    if direction in ('down', 'right'):
-        base = bw + offset
-        apex = bw + majorDimension - offset
-    else:
-        base = bw + majorDimension - offset
-        apex = bw + offset
-
-    if minorDimension > 3 and minorDimension % 2 == 0:
-        minorDimension = minorDimension - 1
-    half = int(minorDimension * (1 - 2 * edgeOffset)) / 2
-    low = round(bw + edgeOffset * minorDimension)
-    middle = low + half
-    high = low + 2 * half
-
-    if direction in ('up', 'down'):
-        coords = (low, base, high, base, middle, apex)
-    else:
-        coords = (base, low, base, high, apex, middle)
-    kw = {'fill' : color, 'outline' : color, 'tag' : tag}
-    apply(canvas.create_polygon, coords, kw)
-
-#=============================================================================
-
-# Modify the Tkinter destroy methods so that it notifies us when a Tk
-# toplevel or frame is destroyed.
-
-# A map from the 'hull' component of a megawidget to the megawidget. 
-# This is used to clean up a megawidget when its hull is destroyed.
-_hullToMegaWidget = {}
-
-def __TkinterToplevelDestroy(tkWidget):
-    if _hullToMegaWidget.has_key(tkWidget):
-        mega = _hullToMegaWidget[tkWidget]
-        try:
-	    mega.destroy()
-        except:
-	    _reporterror(mega.destroy, ())
-    else:
-        # Delete the busy info structure for this toplevel (if the
-        # window was created before initialise() was called, it
-        # will not have any.
-        if _toplevelBusyInfo.has_key(tkWidget):
-            del _toplevelBusyInfo[tkWidget]
-        if hasattr(tkWidget, '_Pmw_WM_DELETE_name'):
-            tkWidget.tk.deletecommand(tkWidget._Pmw_WM_DELETE_name)
-            del tkWidget._Pmw_WM_DELETE_name
-        Tkinter.BaseWidget.destroy(tkWidget)
-
-def __TkinterWidgetDestroy(tkWidget):
-    if _hullToMegaWidget.has_key(tkWidget):
-        mega = _hullToMegaWidget[tkWidget]
-        try:
-	    mega.destroy()
-        except:
-	    _reporterror(mega.destroy, ())
-    else:
-        Tkinter.BaseWidget.destroy(tkWidget)
-
-#=============================================================================
-
-# Add code to Tkinter to improve the display of errors which occur in
-# callbacks.
-
-class __TkinterCallWrapper:
-    def __init__(self, func, subst, widget):
-	self.func = func
-	self.subst = subst
-	self.widget = widget
-
-    # Calling back from Tk into python.
-    def __call__(self, *args):
-	try:
-	    if self.subst:
-		args = apply(self.subst, args)
-            if _traceTk:
-                if not _callToTkReturned:
-                    _traceTkFile.write('\n')
-                if hasattr(self.func, 'im_class'):
-                    name = self.func.im_class.__name__ + '.' + \
-                        self.func.__name__
-                else:
-                    name = self.func.__name__
-                if len(args) == 1 and hasattr(args[0], 'type'):
-                    # The argument to the callback is an event.
-                    eventName = _eventTypeToName[string.atoi(args[0].type)]
-                    if eventName in ('KeyPress', 'KeyRelease',):
-                        argStr = '(%s %s Event: %s)' % \
-                            (eventName, args[0].keysym, args[0].widget)
-                    else:
-                        argStr = '(%s Event, %s)' % (eventName, args[0].widget)
-                else:
-                    argStr = str(args)
-                _traceTkFile.write('CALLBACK> %d:%s%s%s\n' %
-                    (_recursionCounter, '  ' * _recursionCounter, name, argStr))
-                _traceTkFile.flush()
-	    return apply(self.func, args)
-	except SystemExit, msg:
-	    raise SystemExit, msg
-	except:
-	    _reporterror(self.func, args)
-
-_eventTypeToName = {
-    2 : 'KeyPress',         15 : 'VisibilityNotify',   28 : 'PropertyNotify',
-    3 : 'KeyRelease',       16 : 'CreateNotify',       29 : 'SelectionClear',
-    4 : 'ButtonPress',      17 : 'DestroyNotify',      30 : 'SelectionRequest',
-    5 : 'ButtonRelease',    18 : 'UnmapNotify',        31 : 'SelectionNotify',
-    6 : 'MotionNotify',     19 : 'MapNotify',          32 : 'ColormapNotify',
-    7 : 'EnterNotify',      20 : 'MapRequest',         33 : 'ClientMessage',
-    8 : 'LeaveNotify',      21 : 'ReparentNotify',     34 : 'MappingNotify',
-    9 : 'FocusIn',          22 : 'ConfigureNotify',    35 : 'VirtualEvents',
-    10 : 'FocusOut',        23 : 'ConfigureRequest',   36 : 'ActivateNotify',
-    11 : 'KeymapNotify',    24 : 'GravityNotify',      37 : 'DeactivateNotify',
-    12 : 'Expose',          25 : 'ResizeRequest',      38 : 'MouseWheelEvent',
-    13 : 'GraphicsExpose',  26 : 'CirculateNotify',
-    14 : 'NoExpose',        27 : 'CirculateRequest',
-}
-
-def _reporterror(func, args):
-    # Fetch current exception values.
-    exc_type, exc_value, exc_traceback = sys.exc_info()
-
-    # Give basic information about the callback exception.
-    if type(exc_type) == types.ClassType:
-	# Handle python 1.5 class exceptions.
-	exc_type = exc_type.__name__
-    # str here - by BK, it does not work in python 2.5 without it    
-    msg = str(exc_type) + ' Exception in Tk callback\n'
-    msg = msg + '  Function: %s (type: %s)\n' % (repr(func), type(func))
-    msg = msg + '  Args: %s\n' % str(args)
-
-    if type(args) == types.TupleType and len(args) > 0 and \
-	    hasattr(args[0], 'type'):
-        eventArg = 1
-    else:
-        eventArg = 0
-
-    # If the argument to the callback is an event, add the event type.
-    if eventArg:
-	eventNum = string.atoi(args[0].type)
-        if eventNum in _eventTypeToName.keys():
-            msg = msg + '  Event type: %s (type num: %d)\n' % \
-                    (_eventTypeToName[eventNum], eventNum)
-        else:
-            msg = msg + '  Unknown event type (type num: %d)\n' % eventNum
-
-    # Add the traceback.
-    msg = msg + 'Traceback (innermost last):\n'
-    for tr in traceback.extract_tb(exc_traceback):
-	msg = msg + '  File "%s", line %s, in %s\n' % (tr[0], tr[1], tr[2])
-	msg = msg + '    %s\n' % tr[3]
-    msg = msg + '%s: %s\n' % (exc_type, exc_value)
-
-    # If the argument to the callback is an event, add the event contents.
-    if eventArg:
-	msg = msg + '\n================================================\n'
-	msg = msg + '  Event contents:\n'
-	keys = args[0].__dict__.keys()
-	keys.sort()
-	for key in keys:
-	    msg = msg + '    %s: %s\n' % (key, args[0].__dict__[key])
-
-    clearbusycursor()
-    try:
-	displayerror(msg)
-    except:
-        pass
-
-class _ErrorWindow:
-    def __init__(self):
-
-	self._errorQueue = []
-	self._errorCount = 0
-	self._open = 0
-        self._firstShowing = 1
-
-	# Create the toplevel window
-	self._top = Tkinter.Toplevel()
-	self._top.protocol('WM_DELETE_WINDOW', self._hide)
-	self._top.title('Error in background function')
-	self._top.iconname('Background error')
-
-	# Create the text widget and scrollbar in a frame
-	upperframe = Tkinter.Frame(self._top)
-
-	scrollbar = Tkinter.Scrollbar(upperframe, orient='vertical')
-	scrollbar.pack(side = 'right', fill = 'y')
-
-	self._text = Tkinter.Text(upperframe, yscrollcommand=scrollbar.set)
-	self._text.pack(fill = 'both', expand = 1)
-	scrollbar.configure(command=self._text.yview)
-
-	# Create the buttons and label in a frame
-	lowerframe = Tkinter.Frame(self._top)
-
-	ignore = Tkinter.Button(lowerframe,
-	        text = 'Ignore remaining errors', command = self._hide)
-	ignore.pack(side='left')
-
-	self._nextError = Tkinter.Button(lowerframe,
-	        text = 'Show next error', command = self._next)
-	self._nextError.pack(side='left')
-
-	self._label = Tkinter.Label(lowerframe, relief='ridge')
-	self._label.pack(side='left', fill='x', expand=1)
-
-	# Pack the lower frame first so that it does not disappear
-	# when the window is resized.
-	lowerframe.pack(side = 'bottom', fill = 'x')
-	upperframe.pack(side = 'bottom', fill = 'both', expand = 1)
-
-    def showerror(self, text):
-	if self._open:
-	    self._errorQueue.append(text)
-	else:
-	    self._display(text)
-	    self._open = 1
-
-	# Display the error window in the same place it was before.
-	if self._top.state() == 'normal':
-	    # If update_idletasks is not called here, the window may
-	    # be placed partially off the screen.  Also, if it is not
-	    # called and many errors are generated quickly in
-	    # succession, the error window may not display errors
-	    # until the last one is generated and the interpreter
-	    # becomes idle.
-	    # XXX: remove this, since it causes omppython to go into an
-	    # infinite loop if an error occurs in an omp callback.
-	    # self._top.update_idletasks()
-
-            pass
-	else:
-	    if self._firstShowing:
-		geom = None
-	    else:
-                geometry = self._top.geometry()
-                index = string.find(geometry, '+')
-                if index >= 0:
-                    geom = geometry[index:]
-                else:
-                    geom = None
-            setgeometryanddeiconify(self._top, geom)
-
-        if self._firstShowing:
-            self._firstShowing = 0
-        else:
-            self._top.tkraise()
-
-        self._top.focus()
-
-	self._updateButtons()
-
-	# Release any grab, so that buttons in the error window work.
-        releasegrabs()
-
-    def _hide(self):
-	self._errorCount = self._errorCount + len(self._errorQueue)
-	self._errorQueue = []
-	self._top.withdraw()
-	self._open = 0
-
-    def _next(self):
-	# Display the next error in the queue. 
-
-	text = self._errorQueue[0]
-	del self._errorQueue[0]
-
-	self._display(text)
-	self._updateButtons()
-
-    def _display(self, text):
-	self._errorCount = self._errorCount + 1
-	text = 'Error: %d\n%s' % (self._errorCount, text)
-	self._text.delete('1.0', 'end')
-	self._text.insert('end', text)
-
-    def _updateButtons(self):
-	numQueued = len(self._errorQueue)
-	if numQueued > 0:
-	    self._label.configure(text='%d more errors' % numQueued)
-	    self._nextError.configure(state='normal')
-	else:
-	    self._label.configure(text='No more errors')
-	    self._nextError.configure(state='disabled')
-
-######################################################################
-### File: PmwDialog.py
-# Based on iwidgets2.2.0/dialog.itk and iwidgets2.2.0/dialogshell.itk code.
-
-# Convention:
-#   Each dialog window should have one of these as the rightmost button:
-#     Close         Close a window which only displays information.
-#     Cancel        Close a window which may be used to change the state of
-#                   the application.
-
-import sys
-import types
-import Tkinter
-
-
-# A Toplevel with a ButtonBox and child site.
-
-class Dialog(MegaToplevel):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('buttonbox_hull_borderwidth',   1,         None),
-	    ('buttonbox_hull_relief',        'raised',  None),
-	    ('buttonboxpos',                 's',       INITOPT),
-	    ('buttons',                      ('OK',),   self._buttons),
-	    ('command',                      None,      None),
-	    ('dialogchildsite_borderwidth',  1,         None),
-	    ('dialogchildsite_relief',       'raised',  None),
-	    ('defaultbutton',                None,      self._defaultButton),
-            ('master',                       'parent',  None),
-	    ('separatorwidth',               0,         INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaToplevel.__init__(self, parent)
-
-	# Create the components.
-
-	oldInterior = MegaToplevel.interior(self)
-
-	# Set up pack options according to the position of the button box.
-        pos = self['buttonboxpos']
-	if pos not in 'nsew':
-	    raise ValueError, \
-	        'bad buttonboxpos option "%s":  should be n, s, e, or w' \
-		    % pos
-
-	if pos in 'ns':
-	    orient = 'horizontal'
-	    fill = 'x'
-	    if pos == 'n':
-	        side = 'top'
-	    else:
-	        side = 'bottom'
-	else:
-	    orient = 'vertical'
-	    fill = 'y'
-	    if pos == 'w':
-	        side = 'left'
-	    else:
-	        side = 'right'
-
-	# Create the button box.
-	self._buttonBox = self.createcomponent('buttonbox',
-		(), None,
-		ButtonBox, (oldInterior,), orient = orient)
-	self._buttonBox.pack(side = side, fill = fill)
-
-	# Create the separating line.
-	width = self['separatorwidth']
-	if width > 0:
-	    self._separator = self.createcomponent('separator',
-		    (), None,
-		    Tkinter.Frame, (oldInterior,), relief = 'sunken',
-		    height = width, width = width, borderwidth = width / 2)
-	    self._separator.pack(side = side, fill = fill)
-	
-	# Create the child site.
-	self.__dialogChildSite = self.createcomponent('dialogchildsite',
-		(), None,
-		Tkinter.Frame, (oldInterior,))
-	self.__dialogChildSite.pack(side=side, fill='both', expand=1)
-
-	self.oldButtons = ()
-	self.oldDefault = None
-
-	self.bind('<Return>', self._invokeDefault)
-        self.userdeletefunc(self._doCommand)
-        self.usermodaldeletefunc(self._doCommand)
-	
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def interior(self):
-	return self.__dialogChildSite
-
-    def invoke(self, index = DEFAULT):
-	return self._buttonBox.invoke(index)
-
-    def _invokeDefault(self, event):
-	try:
-	    self._buttonBox.index(DEFAULT)
-	except ValueError:
-	    return
-	self._buttonBox.invoke()
-
-    def _doCommand(self, name = None):
-        if name is not None and self.active() and \
-                grabstacktopwindow() != self.component('hull'):
-            # This is a modal dialog but is not on the top of the grab
-            # stack (ie:  should not have the grab), so ignore this
-            # event.  This seems to be a bug in Tk and may occur in
-            # nested modal dialogs.
-            #
-            # An example is the PromptDialog demonstration.  To
-            # trigger the problem, start the demo, then move the mouse
-            # to the main window, hit <TAB> and then <TAB> again.  The
-            # highlight border of the "Show prompt dialog" button
-            # should now be displayed.  Now hit <SPACE>, <RETURN>,
-            # <RETURN> rapidly several times.  Eventually, hitting the
-            # return key invokes the password dialog "OK" button even
-            # though the confirm dialog is active (and therefore
-            # should have the keyboard focus).  Observed under Solaris
-            # 2.5.1, python 1.5.2 and Tk8.0.
-
-            # TODO:  Give focus to the window on top of the grabstack.
-            return
-
-	command = self['command']
-	if callable(command):
-	    return command(name)
-	else:
-	    if self.active():
-	        self.deactivate(name)
-	    else:
-	        self.withdraw()
-
-    def _buttons(self):
-	buttons = self['buttons']
-	if type(buttons) != types.TupleType and type(buttons) != types.ListType:
-	    raise ValueError, \
-	        'bad buttons option "%s": should be a tuple' % str(buttons)
-	if self.oldButtons == buttons:
-	  return
-
-	self.oldButtons = buttons
-
-	for index in range(self._buttonBox.numbuttons()):
-	    self._buttonBox.delete(0)
-	for name in buttons:
-	    self._buttonBox.add(name,
-		command=lambda self=self, name=name: self._doCommand(name))
-
-	if len(buttons) > 0:
-	    defaultbutton = self['defaultbutton']
-	    if defaultbutton is None:
-		self._buttonBox.setdefault(None)
-	    else:
-		try:
-		    self._buttonBox.index(defaultbutton)
-		except ValueError:
-		    pass
-		else:
-		    self._buttonBox.setdefault(defaultbutton)
-	self._buttonBox.alignbuttons()
-
-    def _defaultButton(self):
-	defaultbutton = self['defaultbutton']
-	if self.oldDefault == defaultbutton:
-	  return
-
-	self.oldDefault = defaultbutton
-
-	if len(self['buttons']) > 0:
-	    if defaultbutton is None:
-		self._buttonBox.setdefault(None)
-	    else:
-		try:
-		    self._buttonBox.index(defaultbutton)
-		except ValueError:
-		    pass
-		else:
-		    self._buttonBox.setdefault(defaultbutton)
-
-######################################################################
-### File: PmwTimeFuncs.py
-# Functions for dealing with dates and times.
-
-import re
-import string
-
-def timestringtoseconds(text, separator = ':'):
-  inputList = string.split(string.strip(text), separator)
-  if len(inputList) != 3:
-    raise ValueError, 'invalid value: ' + text
-
-  sign = 1
-  if len(inputList[0]) > 0 and inputList[0][0] in ('+', '-'):
-    if inputList[0][0] == '-':
-      sign = -1
-    inputList[0] = inputList[0][1:]
-
-  if re.search('[^0-9]', string.join(inputList, '')) is not None:
-    raise ValueError, 'invalid value: ' + text
-
-  hour = string.atoi(inputList[0])
-  minute = string.atoi(inputList[1])
-  second = string.atoi(inputList[2])
-
-  if minute >= 60 or second >= 60:
-    raise ValueError, 'invalid value: ' + text
-  return sign * (hour * 60 * 60 + minute * 60 + second)
-
-_year_pivot = 50
-_century = 2000
-
-def setyearpivot(pivot, century = None):
-    global _year_pivot
-    global _century
-    oldvalues = (_year_pivot, _century)
-    _year_pivot = pivot
-    if century is not None:
-	_century = century
-    return oldvalues
-
-def datestringtojdn(text, format = 'ymd', separator = '/'):
-  inputList = string.split(string.strip(text), separator)
-  if len(inputList) != 3:
-    raise ValueError, 'invalid value: ' + text
-
-  if re.search('[^0-9]', string.join(inputList, '')) is not None:
-    raise ValueError, 'invalid value: ' + text
-  formatList = list(format)
-  day = string.atoi(inputList[formatList.index('d')])
-  month = string.atoi(inputList[formatList.index('m')])
-  year = string.atoi(inputList[formatList.index('y')])
-
-  if _year_pivot is not None:
-    if year >= 0 and year < 100:
-      if year <= _year_pivot:
-	year = year + _century
-      else:
-	year = year + _century - 100
-
-  jdn = ymdtojdn(year, month, day)
-  if jdntoymd(jdn) != (year, month, day):
-    raise ValueError, 'invalid value: ' + text
-  return jdn
-
-def _cdiv(a, b):
-    # Return a / b as calculated by most C language implementations,
-    # assuming both a and b are integers.
-
-    if a * b > 0:
-	return a / b
-    else:
-	return -(abs(a) / abs(b))
-
-def ymdtojdn(year, month, day, julian = -1, papal = 1):
-
-    # set Julian flag if auto set
-    if julian < 0:
-	if papal:                          # Pope Gregory XIII's decree
-	    lastJulianDate = 15821004L     # last day to use Julian calendar
-	else:                              # British-American usage
-	    lastJulianDate = 17520902L     # last day to use Julian calendar
-
-	julian = ((year * 100L) + month) * 100 + day  <=  lastJulianDate
-
-    if year < 0:
-	# Adjust BC year
-	year = year + 1
-
-    if julian:
-	return 367L * year - _cdiv(7 * (year + 5001L + _cdiv((month - 9), 7)), 4) + \
-	    _cdiv(275 * month, 9) + day + 1729777L
-    else:
-	return (day - 32076L) + \
-	    _cdiv(1461L * (year + 4800L + _cdiv((month - 14), 12)), 4) + \
-	    _cdiv(367 * (month - 2 - _cdiv((month - 14), 12) * 12), 12) - \
-	    _cdiv((3 * _cdiv((year + 4900L + _cdiv((month - 14), 12)), 100)), 4) + \
-	    1            # correction by rdg
-
-def jdntoymd(jdn, julian = -1, papal = 1):
-
-    # set Julian flag if auto set
-    if julian < 0:
-	if papal:                          # Pope Gregory XIII's decree
-	    lastJulianJdn = 2299160L       # last jdn to use Julian calendar
-	else:                              # British-American usage
-	    lastJulianJdn = 2361221L       # last jdn to use Julian calendar
-
-	julian = (jdn <= lastJulianJdn);
-
-    x = jdn + 68569L
-    if julian:
-	x = x + 38
-	daysPer400Years = 146100L
-	fudgedDaysPer4000Years = 1461000L + 1
-    else:
-	daysPer400Years = 146097L
-	fudgedDaysPer4000Years = 1460970L + 31
-
-    z = _cdiv(4 * x, daysPer400Years)
-    x = x - _cdiv((daysPer400Years * z + 3), 4)
-    y = _cdiv(4000 * (x + 1), fudgedDaysPer4000Years)
-    x = x - _cdiv(1461 * y, 4) + 31
-    m = _cdiv(80 * x, 2447)
-    d = x - _cdiv(2447 * m, 80)
-    x = _cdiv(m, 11)
-    m = m + 2 - 12 * x
-    y = 100 * (z - 49) + y + x
-
-    # Convert from longs to integers.
-    yy = int(y)
-    mm = int(m)
-    dd = int(d)
-
-    if yy <= 0:
-	# Adjust BC years.
-	    yy = yy - 1
-
-    return (yy, mm, dd)
-
-def stringtoreal(text, separator = '.'):
-    if separator != '.':
-	if string.find(text, '.') >= 0:
-	    raise ValueError, 'invalid value: ' + text
-	index = string.find(text, separator)
-	if index >= 0:
-	    text = text[:index] + '.' + text[index + 1:]
-    return string.atof(text)
-
-######################################################################
-### File: PmwBalloon.py
-import os
-import string
-import Tkinter
-
-
-class Balloon(MegaToplevel):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	optiondefs = (
-            ('initwait',                 500,            None), # milliseconds
-            ('label_background',         'lightyellow',  None),
-            ('label_foreground',         'black',        None),
-            ('label_justify',            'left',         None),
-            ('master',                   'parent',       None),
-            ('relmouse',                 'none',         self._relmouse),
-            ('state',                    'both',         self._state),
-            ('statuscommand',            None,           None),
-            ('xoffset',                  20,             None), # pixels
-            ('yoffset',                  1,              None), # pixels
-	    ('hull_highlightthickness',  1,              None),
-	    ('hull_highlightbackground', 'black',        None),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaToplevel.__init__(self, parent)
-
-	self.withdraw()
-	self.overrideredirect(1)
-
-	# Create the components.
-	interior = self.interior()
-	self._label = self.createcomponent('label',
-		(), None,
-		Tkinter.Label, (interior,))
-	self._label.pack()
-
-        # The default hull configuration options give a black border
-        # around the balloon, but avoids a black 'flash' when the
-        # balloon is deiconified, before the text appears.
-        if not kw.has_key('hull_background'):
-            self.configure(hull_background = \
-                    str(self._label.cget('background')))
-
-	# Initialise instance variables.
-	self._timer = None
-
-        # The widget or item that is currently triggering the balloon. 
-        # It is None if the balloon is not being displayed.  It is a
-        # one-tuple if the balloon is being displayed in response to a
-        # widget binding (value is the widget).  It is a two-tuple if
-        # the balloon is being displayed in response to a canvas or
-        # text item binding (value is the widget and the item).
-        self._currentTrigger = None
-	
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	if self._timer is not None:
-	    self.after_cancel(self._timer)
-	    self._timer = None
-	MegaToplevel.destroy(self)
-
-    def bind(self, widget, balloonHelp, statusHelp = None):
-
-        # If a previous bind for this widget exists, remove it.
-        self.unbind(widget)
-
-	if balloonHelp is None and statusHelp is None:
-	    return
-
-	if statusHelp is None:
-	    statusHelp = balloonHelp
-	enterId = widget.bind('<Enter>', 
-		lambda event, self = self, w = widget,
-			sHelp = statusHelp, bHelp = balloonHelp:
-				self._enter(event, w, sHelp, bHelp, 0))
-
-        # Set Motion binding so that if the pointer remains at rest
-        # within the widget until the status line removes the help and
-        # then the pointer moves again, then redisplay the help in the
-        # status line.
-	# Note:  The Motion binding only works for basic widgets, and
-        # the hull of megawidgets but not for other megawidget components.
-	motionId = widget.bind('<Motion>', 
-		lambda event = None, self = self, statusHelp = statusHelp:
-			self.showstatus(statusHelp))
-
-	leaveId = widget.bind('<Leave>', self._leave)
-	buttonId = widget.bind('<ButtonPress>', self._buttonpress)
-
-        # Set Destroy binding so that the balloon can be withdrawn and
-        # the timer can be cancelled if the widget is destroyed.
-	destroyId = widget.bind('<Destroy>', self._destroy)
-
-        # Use the None item in the widget's private Pmw dictionary to
-        # store the widget's bind callbacks, for later clean up.
-        if not hasattr(widget, '_Pmw_BalloonBindIds'):
-            widget._Pmw_BalloonBindIds = {}
-        widget._Pmw_BalloonBindIds[None] = \
-                (enterId, motionId, leaveId, buttonId, destroyId)
-
-    def unbind(self, widget):
-        if hasattr(widget, '_Pmw_BalloonBindIds'):
-            if widget._Pmw_BalloonBindIds.has_key(None):
-                (enterId, motionId, leaveId, buttonId, destroyId) = \
-                        widget._Pmw_BalloonBindIds[None]
-                # Need to pass in old bindings, so that Tkinter can
-                # delete the commands.  Otherwise, memory is leaked.
-                widget.unbind('<Enter>', enterId)
-                widget.unbind('<Motion>', motionId)
-                widget.unbind('<Leave>', leaveId)
-                widget.unbind('<ButtonPress>', buttonId)
-                widget.unbind('<Destroy>', destroyId)
-                del widget._Pmw_BalloonBindIds[None]
-
-        if self._currentTrigger is not None and len(self._currentTrigger) == 1:
-            # The balloon is currently being displayed and the current
-            # trigger is a widget.
-            triggerWidget = self._currentTrigger[0]
-            if triggerWidget == widget:
-                if self._timer is not None:
-                    self.after_cancel(self._timer)
-                    self._timer = None
-                self.withdraw()
-                self.clearstatus()
-                self._currentTrigger = None
-
-    def tagbind(self, widget, tagOrItem, balloonHelp, statusHelp = None):
-
-        # If a previous bind for this widget's tagOrItem exists, remove it.
-        self.tagunbind(widget, tagOrItem)
-
-	if balloonHelp is None and statusHelp is None:
-	    return
-
-	if statusHelp is None:
-	    statusHelp = balloonHelp
-	enterId = widget.tag_bind(tagOrItem, '<Enter>', 
-		lambda event, self = self, w = widget,
-			sHelp = statusHelp, bHelp = balloonHelp:
-				self._enter(event, w, sHelp, bHelp, 1))
-	motionId = widget.tag_bind(tagOrItem, '<Motion>', 
-		lambda event = None, self = self, statusHelp = statusHelp:
-			self.showstatus(statusHelp))
-	leaveId = widget.tag_bind(tagOrItem, '<Leave>', self._leave)
-	buttonId = widget.tag_bind(tagOrItem, '<ButtonPress>', self._buttonpress)
-
-        # Use the tagOrItem item in the widget's private Pmw dictionary to
-        # store the tagOrItem's bind callbacks, for later clean up.
-        if not hasattr(widget, '_Pmw_BalloonBindIds'):
-            widget._Pmw_BalloonBindIds = {}
-        widget._Pmw_BalloonBindIds[tagOrItem] = \
-                (enterId, motionId, leaveId, buttonId)
-
-    def tagunbind(self, widget, tagOrItem):
-        if hasattr(widget, '_Pmw_BalloonBindIds'):
-            if widget._Pmw_BalloonBindIds.has_key(tagOrItem):
-                (enterId, motionId, leaveId, buttonId) = \
-                        widget._Pmw_BalloonBindIds[tagOrItem]
-                widget.tag_unbind(tagOrItem, '<Enter>', enterId)
-                widget.tag_unbind(tagOrItem, '<Motion>', motionId)
-                widget.tag_unbind(tagOrItem, '<Leave>', leaveId)
-                widget.tag_unbind(tagOrItem, '<ButtonPress>', buttonId)
-                del widget._Pmw_BalloonBindIds[tagOrItem]
-
-        if self._currentTrigger is None:
-            # The balloon is not currently being displayed.
-            return
-
-        if len(self._currentTrigger) == 1:
-            # The current trigger is a widget.
-            return
-
-        if len(self._currentTrigger) == 2:
-            # The current trigger is a canvas item.
-            (triggerWidget, triggerItem) = self._currentTrigger
-            if triggerWidget == widget and triggerItem == tagOrItem:
-                if self._timer is not None:
-                    self.after_cancel(self._timer)
-                    self._timer = None
-                self.withdraw()
-                self.clearstatus()
-                self._currentTrigger = None
-        else: # The current trigger is a text item.
-            (triggerWidget, x, y) = self._currentTrigger
-            if triggerWidget == widget:
-                currentPos = widget.index('@%d,%d' % (x, y))
-                currentTags = widget.tag_names(currentPos)
-                if tagOrItem in currentTags:
-                    if self._timer is not None:
-                        self.after_cancel(self._timer)
-                        self._timer = None
-                    self.withdraw()
-                    self.clearstatus()
-                    self._currentTrigger = None
-
-    def showstatus(self, statusHelp):
-	if self['state'] in ('status', 'both'):
-	    cmd = self['statuscommand']
-	    if callable(cmd):
-		cmd(statusHelp)
-
-    def clearstatus(self):
-        self.showstatus(None)
-
-    def _state(self):
-	if self['state'] not in ('both', 'balloon', 'status', 'none'):
-	    raise ValueError, 'bad state option ' + repr(self['state']) + \
-		': should be one of \'both\', \'balloon\', ' + \
-		'\'status\' or \'none\''
-
-    def _relmouse(self):
-	if self['relmouse'] not in ('both', 'x', 'y', 'none'):
-	    raise ValueError, 'bad relmouse option ' + repr(self['relmouse'])+ \
-		': should be one of \'both\', \'x\', ' + '\'y\' or \'none\''
-
-    def _enter(self, event, widget, statusHelp, balloonHelp, isItem):
-
-        # Do not display balloon if mouse button is pressed.  This
-        # will only occur if the button was pressed inside a widget,
-        # then the mouse moved out of and then back into the widget,
-        # with the button still held down.  The number 0x1f00 is the
-        # button mask for the 5 possible buttons in X.
-        buttonPressed = (event.state & 0x1f00) != 0
-
-	if not buttonPressed and balloonHelp is not None and \
-                self['state'] in ('balloon', 'both'):
-	    if self._timer is not None:
-		self.after_cancel(self._timer)
-		self._timer = None
-
-	    self._timer = self.after(self['initwait'], 
-		    lambda self = self, widget = widget, help = balloonHelp,
-			    isItem = isItem:
-			    self._showBalloon(widget, help, isItem))
-
-        if isItem:
-            if hasattr(widget, 'canvasx'):
-		# The widget is a canvas.
-                item = widget.find_withtag('current')
-                if len(item) > 0:
-                    item = item[0]
-                else:
-                    item = None
-                self._currentTrigger = (widget, item)
-            else:
-		# The widget is a text widget.
-                self._currentTrigger = (widget, event.x, event.y)
-        else:
-            self._currentTrigger = (widget,)
-
-	self.showstatus(statusHelp)
-
-    def _leave(self, event):
-	if self._timer is not None:
-	    self.after_cancel(self._timer)
-	    self._timer = None
-	self.withdraw()
-	self.clearstatus()
-        self._currentTrigger = None
-
-    def _destroy(self, event):
-
-        # Only withdraw the balloon and cancel the timer if the widget
-        # being destroyed is the widget that triggered the balloon. 
-        # Note that in a Tkinter Destroy event, the widget field is a
-        # string and not a widget as usual.
-
-        if self._currentTrigger is None:
-            # The balloon is not currently being displayed
-            return
-
-        if len(self._currentTrigger) == 1:
-            # The current trigger is a widget (not an item)
-            triggerWidget = self._currentTrigger[0]
-            if str(triggerWidget) == event.widget:
-                if self._timer is not None:
-                    self.after_cancel(self._timer)
-                    self._timer = None
-                self.withdraw()
-                self.clearstatus()
-                self._currentTrigger = None
-
-    def _buttonpress(self, event):
-	if self._timer is not None:
-	    self.after_cancel(self._timer)
-	    self._timer = None
-	self.withdraw()
-        self._currentTrigger = None
-
-    def _showBalloon(self, widget, balloonHelp, isItem):
-
-	self._label.configure(text = balloonHelp)
-
-        # First, display the balloon offscreen to get dimensions.
-        screenWidth = self.winfo_screenwidth()
-        screenHeight = self.winfo_screenheight()
-        self.geometry('+%d+0' % (screenWidth + 1))
-        self.update_idletasks()
-
-	if isItem:
-            # Get the bounding box of the current item.
-            bbox = widget.bbox('current')
-            if bbox is None:
-                # The item that triggered the balloon has disappeared,
-                # perhaps by a user's timer event that occured between
-                # the <Enter> event and the 'initwait' timer calling
-                # this method.
-                return
-
-	    # The widget is either a text or canvas.  The meaning of
-	    # the values returned by the bbox method is different for
-	    # each, so use the existence of the 'canvasx' method to
-	    # distinguish between them.
-	    if hasattr(widget, 'canvasx'):
-		# The widget is a canvas.  Place balloon under canvas
-                # item.  The positions returned by bbox are relative
-                # to the entire canvas, not just the visible part, so
-                # need to convert to window coordinates.
-                leftrel = bbox[0] - widget.canvasx(0)
-                toprel = bbox[1] - widget.canvasy(0)
-                bottomrel = bbox[3] - widget.canvasy(0)
-	    else:
-		# The widget is a text widget.  Place balloon under
-                # the character closest to the mouse.  The positions
-                # returned by bbox are relative to the text widget
-                # window (ie the visible part of the text only).
-		leftrel = bbox[0]
-                toprel = bbox[1]
-		bottomrel = bbox[1] + bbox[3]
-	else:
-	    leftrel = 0
-            toprel = 0
-	    bottomrel = widget.winfo_height()
-
-        xpointer, ypointer = widget.winfo_pointerxy()   # -1 if off screen
-
-        if xpointer >= 0 and self['relmouse'] in ('both', 'x'):
-            x = xpointer
-        else:
-            x = leftrel + widget.winfo_rootx()
-        x = x + self['xoffset']
-
-        if ypointer >= 0 and self['relmouse'] in ('both', 'y'):
-            y = ypointer
-        else:
-            y = bottomrel + widget.winfo_rooty()
-        y = y + self['yoffset']
-
-        edges = (string.atoi(str(self.cget('hull_highlightthickness'))) +
-            string.atoi(str(self.cget('hull_borderwidth')))) * 2
-        if x + self._label.winfo_reqwidth() + edges > screenWidth:
-            x = screenWidth - self._label.winfo_reqwidth() - edges
-
-        if y + self._label.winfo_reqheight() + edges > screenHeight:
-            if ypointer >= 0 and self['relmouse'] in ('both', 'y'):
-                y = ypointer
-            else:
-                y = toprel + widget.winfo_rooty()
-            y = y - self._label.winfo_reqheight() - self['yoffset'] - edges
-
-        setgeometryanddeiconify(self, '+%d+%d' % (x, y))
-
-######################################################################
-### File: PmwButtonBox.py
-# Based on iwidgets2.2.0/buttonbox.itk code.
-
-import types
-import Tkinter
-
-
-class ButtonBox(MegaWidget):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('labelmargin',       0,              INITOPT),
-	    ('labelpos',          None,           INITOPT),
-	    ('orient',            'horizontal',   INITOPT),
-	    ('padx',              3,              INITOPT),
-	    ('pady',              3,              INITOPT),
-	)
-	self.defineoptions(kw, optiondefs, dynamicGroups = ('Button',))
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-	if self['labelpos'] is None:
-	    self._buttonBoxFrame = self._hull
-	    columnOrRow = 0
-	else:
-	    self._buttonBoxFrame = self.createcomponent('frame',
-		    (), None,
-		    Tkinter.Frame, (interior,))
-	    self._buttonBoxFrame.grid(column=2, row=2, sticky='nsew')
-	    columnOrRow = 2
-
-	    self.createlabel(interior)
-
-	orient = self['orient']
-	if orient == 'horizontal':
-	    interior.grid_columnconfigure(columnOrRow, weight = 1)
-	elif orient == 'vertical':
-	    interior.grid_rowconfigure(columnOrRow, weight = 1)
-	else:
-	    raise ValueError, 'bad orient option ' + repr(orient) + \
-		': must be either \'horizontal\' or \'vertical\''
-
-	# Initialise instance variables.
-
-	# List of tuples describing the buttons:
-	#   - name
-	#   - button widget
-	self._buttonList = []
-
-	# The index of the default button.
-	self._defaultButton = None
-
-	self._timerId = None
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	if self._timerId:
-	    self.after_cancel(self._timerId)
-	    self._timerId = None
-	MegaWidget.destroy(self)
-
-    def numbuttons(self):
-        return len(self._buttonList)
-
-    def index(self, index, forInsert = 0):
-	listLength = len(self._buttonList)
-	if type(index) == types.IntType:
-	    if forInsert and index <= listLength:
-		return index
-	    elif not forInsert and index < listLength:
-		return index
-	    else:
-		raise ValueError, 'index "%s" is out of range' % index
-	elif index is END:
-	    if forInsert:
-		return listLength
-	    elif listLength > 0:
-		return listLength - 1
-	    else:
-		raise ValueError, 'ButtonBox has no buttons'
-	elif index is DEFAULT:
-	    if self._defaultButton is not None:
-		return self._defaultButton
-	    raise ValueError, 'ButtonBox has no default'
-	else:
-            names = map(lambda t: t[0], self._buttonList)
-            if index in names:
-                return names.index(index)
-	    validValues = 'a name, a number, END or DEFAULT'
-	    raise ValueError, \
-		'bad index "%s": must be %s' % (index, validValues)
-
-    def insert(self, componentName, beforeComponent = 0, **kw):
-	if componentName in self.components():
-	    raise ValueError, 'button "%s" already exists' % componentName
-	if not kw.has_key('text'):
-	    kw['text'] = componentName
-        kw['default'] = 'normal'
-	button = apply(self.createcomponent, (componentName,
-		(), 'Button',
-		Tkinter.Button, (self._buttonBoxFrame,)), kw)
-
-	index = self.index(beforeComponent, 1)
-	horizontal = self['orient'] == 'horizontal'
-	numButtons = len(self._buttonList)
-
-	# Shift buttons up one position.
-	for i in range(numButtons - 1, index - 1, -1):
-	    widget = self._buttonList[i][1]
-	    pos = i * 2 + 3
-	    if horizontal:
-		widget.grid(column = pos, row = 0)
-	    else:
-		widget.grid(column = 0, row = pos)
-
-	# Display the new button.
-	if horizontal:
-	    button.grid(column = index * 2 + 1, row = 0, sticky = 'ew',
-		    padx = self['padx'], pady = self['pady'])
-	    self._buttonBoxFrame.grid_columnconfigure(
-		    numButtons * 2 + 2, weight = 1)
-	else:
-	    button.grid(column = 0, row = index * 2 + 1, sticky = 'ew',
-		    padx = self['padx'], pady = self['pady'])
-	    self._buttonBoxFrame.grid_rowconfigure(
-		    numButtons * 2 + 2, weight = 1)
-	self._buttonList.insert(index, (componentName, button))
-
-	return button
-
-    def add(self, componentName, **kw):
-        return apply(self.insert, (componentName, len(self._buttonList)), kw)
-
-    def delete(self, index):
-        index = self.index(index)
-	(name, widget) = self._buttonList[index]
-	widget.grid_forget()
-	self.destroycomponent(name)
-
-	numButtons = len(self._buttonList)
-
-	# Shift buttons down one position.
-	horizontal = self['orient'] == 'horizontal'
-	for i in range(index + 1, numButtons):
-	    widget = self._buttonList[i][1]
-	    pos = i * 2 - 1
-	    if horizontal:
-		widget.grid(column = pos, row = 0)
-	    else:
-		widget.grid(column = 0, row = pos)
-
-	if horizontal:
-	    self._buttonBoxFrame.grid_columnconfigure(numButtons * 2 - 1,
-		    minsize = 0)
-	    self._buttonBoxFrame.grid_columnconfigure(numButtons * 2, weight = 0)
-	else:
-	    self._buttonBoxFrame.grid_rowconfigure(numButtons * 2, weight = 0)
-	del self._buttonList[index]
-
-    def setdefault(self, index):
-	# Turn off the default ring around the current default button.
-	if self._defaultButton is not None:
-	    button = self._buttonList[self._defaultButton][1]
-	    button.configure(default = 'normal')
-	    self._defaultButton = None
-
-	# Turn on the default ring around the new default button.
-	if index is not None:
-	    index = self.index(index)
-	    self._defaultButton = index
-	    button = self._buttonList[index][1]
-	    button.configure(default = 'active')
-
-    def invoke(self, index = DEFAULT, noFlash = 0):
-	# Invoke the callback associated with the *index* button.  If
-	# *noFlash* is not set, flash the button to indicate to the
-	# user that something happened.
-
-	button = self._buttonList[self.index(index)][1]
-	if not noFlash:
-	    state = button.cget('state')
-	    relief = button.cget('relief')
-	    button.configure(state = 'active', relief = 'sunken')
-	    self.update_idletasks()
-	    self.after(100)
-	    button.configure(state = state, relief = relief)
-	return button.invoke()
-
-    def button(self, buttonIndex):
-	return self._buttonList[self.index(buttonIndex)][1]
-
-    def alignbuttons(self, when = 'later'):
-	if when == 'later':
-	    if not self._timerId:
-		self._timerId = self.after_idle(self.alignbuttons, 'now')
-	    return
-	self.update_idletasks()
-	self._timerId = None
-
-        # Determine the width of the maximum length button.
-        max = 0
-        horizontal = (self['orient'] == 'horizontal')
-        for index in range(len(self._buttonList)):
-            gridIndex = index * 2 + 1
-            if horizontal:
-                width = self._buttonBoxFrame.grid_bbox(gridIndex, 0)[2]
-            else:
-                width = self._buttonBoxFrame.grid_bbox(0, gridIndex)[2]
-            if width > max:
-                max = width
-
-        # Set the width of all the buttons to be the same.
-	if horizontal:
-	    for index in range(len(self._buttonList)):
-		self._buttonBoxFrame.grid_columnconfigure(index * 2 + 1,
-			minsize = max)
-	else:
-	    self._buttonBoxFrame.grid_columnconfigure(0, minsize = max)
-
-######################################################################
-### File: PmwEntryField.py
-# Based on iwidgets2.2.0/entryfield.itk code.
-
-import re
-import string
-import types
-import Tkinter
-
-
-# Possible return values of validation functions.
-OK = 1
-ERROR = 0
-PARTIAL = -1
-
-class EntryField(MegaWidget):
-    _classBindingsDefinedFor = 0
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('command',           None,        None),
-	    ('errorbackground',   'pink',      None),
-	    ('invalidcommand',    self.bell,   None),
-	    ('labelmargin',       0,           INITOPT),
-	    ('labelpos',          None,        INITOPT),
-	    ('modifiedcommand',   None,        None),
-	    ('sticky',            'ew',        INITOPT),
-	    ('validate',          None,        self._validate),
-	    ('extravalidators',   {},          None),
-	    ('value',             '',          INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-	self._entryFieldEntry = self.createcomponent('entry',
-		(), None,
-		Tkinter.Entry, (interior,))
-	self._entryFieldEntry.grid(column=2, row=2, sticky=self['sticky'])
-	if self['value'] != '':
-	    self.__setEntry(self['value'])
-	interior.grid_columnconfigure(2, weight=1)
-	interior.grid_rowconfigure(2, weight=1)
-
-	self.createlabel(interior)
-
-	# Initialise instance variables.
-
-	self.normalBackground = None
-        self._previousText = None
-
-	# Initialise instance.
-
-	_registerEntryField(self._entryFieldEntry, self)
-
-        # Establish the special class bindings if not already done.
-        # Also create bindings if the Tkinter default interpreter has
-        # changed.  Use Tkinter._default_root to create class
-        # bindings, so that a reference to root is created by
-        # bind_class rather than a reference to self, which would
-        # prevent object cleanup.
-        if EntryField._classBindingsDefinedFor != Tkinter._default_root:
-	    tagList = self._entryFieldEntry.bindtags()
-            root  = Tkinter._default_root
-	    	    
-	    allSequences = {}
-	    for tag in tagList:
-
-                sequences = root.bind_class(tag)
-                if type(sequences) is types.StringType:
-                    # In old versions of Tkinter, bind_class returns a string
-                    sequences = root.tk.splitlist(sequences)
-
-		for sequence in sequences:
-		    allSequences[sequence] = None
-	    for sequence in allSequences.keys():
-		root.bind_class('EntryFieldPre', sequence, _preProcess)
-		root.bind_class('EntryFieldPost', sequence, _postProcess)
-
-	    EntryField._classBindingsDefinedFor = root
-
-	self._entryFieldEntry.bindtags(('EntryFieldPre',) +
-		self._entryFieldEntry.bindtags() + ('EntryFieldPost',))
-	self._entryFieldEntry.bind('<Return>', self._executeCommand)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	_deregisterEntryField(self._entryFieldEntry)
-        MegaWidget.destroy(self)
-
-    def _getValidatorFunc(self, validator, index):
-	# Search the extra and standard validator lists for the
-	# given 'validator'.  If 'validator' is an alias, then
-	# continue the search using the alias.  Make sure that
-	# self-referencial aliases do not cause infinite loops.
-
-	extraValidators = self['extravalidators']
-	traversedValidators = []
-
-	while 1:
-	    traversedValidators.append(validator)
-	    if extraValidators.has_key(validator):
-		validator = extraValidators[validator][index]
-	    elif _standardValidators.has_key(validator):
-		validator = _standardValidators[validator][index]
-	    else:
-		return validator
-	    if validator in traversedValidators:
-		return validator
-
-    def _validate(self):
-	dict = {
-	    'validator' : None,
-	    'min' : None,
-	    'max' : None,
-	    'minstrict' : 1,
-	    'maxstrict' : 1,
-	}
-	opt = self['validate']
-	if type(opt) is types.DictionaryType:
-	    dict.update(opt)
-	else:
-	    dict['validator'] = opt
-
-	# Look up validator maps and replace 'validator' field with
-	# the corresponding function.
-	validator = dict['validator']
-	valFunction = self._getValidatorFunc(validator, 0)
-	self._checkValidateFunction(valFunction, 'validate', validator)
-	dict['validator'] = valFunction
-
-	# Look up validator maps and replace 'stringtovalue' field
-	# with the corresponding function.
-	if dict.has_key('stringtovalue'):
-	    stringtovalue = dict['stringtovalue'] 
-	    strFunction = self._getValidatorFunc(stringtovalue, 1)
-	    self._checkValidateFunction(
-		    strFunction, 'stringtovalue', stringtovalue)
-	else:
-	    strFunction = self._getValidatorFunc(validator, 1)
-	    if strFunction == validator:
-		strFunction = len
-	dict['stringtovalue'] = strFunction
-
-	self._validationInfo = dict
-	args = dict.copy()
-	del args['validator']
-	del args['min']
-	del args['max']
-	del args['minstrict']
-	del args['maxstrict']
-	del args['stringtovalue']
-	self._validationArgs = args
-        self._previousText = None
-
-	if type(dict['min']) == types.StringType and strFunction is not None:
-	    dict['min'] = apply(strFunction, (dict['min'],), args)
-	if type(dict['max']) == types.StringType and strFunction is not None:
-	    dict['max'] = apply(strFunction, (dict['max'],), args)
-
-	self._checkValidity()
-
-    def _checkValidateFunction(self, function, option, validator):
-	# Raise an error if 'function' is not a function or None.
-
-	if function is not None and not callable(function):
-	    extraValidators = self['extravalidators']
-	    extra = extraValidators.keys()
-	    extra.sort()
-	    extra = tuple(extra)
-	    standard = _standardValidators.keys()
-	    standard.sort()
-	    standard = tuple(standard)
-	    msg = 'bad %s value "%s":  must be a function or one of ' \
-		'the standard validators %s or extra validators %s'
-	    raise ValueError, msg % (option, validator, standard, extra)
-
-    def _executeCommand(self, event = None):
-	cmd = self['command']
-	if callable(cmd):
-            if event is None:
-                # Return result of command for invoke() method.
-                return cmd()
-            else:
-                cmd()
-	    
-    def _preProcess(self):
-
-        self._previousText = self._entryFieldEntry.get()
-        self._previousICursor = self._entryFieldEntry.index('insert')
-        self._previousXview = self._entryFieldEntry.index('@0')
-	if self._entryFieldEntry.selection_present():
-	    self._previousSel= (self._entryFieldEntry.index('sel.first'),
-		self._entryFieldEntry.index('sel.last'))
-	else:
-	    self._previousSel = None
-
-    def _postProcess(self):
-
-	# No need to check if text has not changed.
-	previousText = self._previousText
-	if previousText == self._entryFieldEntry.get():
-	    return self.valid()
-
-	valid = self._checkValidity()
-        if self.hulldestroyed():
-            # The invalidcommand called by _checkValidity() destroyed us.
-            return valid
-
-	cmd = self['modifiedcommand']
-	if callable(cmd) and previousText != self._entryFieldEntry.get():
-	    cmd()
-	return valid
-	    
-    def checkentry(self):
-	# If there is a variable specified by the entry_textvariable
-	# option, checkentry() should be called after the set() method
-	# of the variable is called.
-
-	self._previousText = None
-	return self._postProcess()
-
-    def _getValidity(self):
-	text = self._entryFieldEntry.get()
-	dict = self._validationInfo
-	args = self._validationArgs
-
-	if dict['validator'] is not None:
-	    status = apply(dict['validator'], (text,), args)
-	    if status != OK:
-		return status
-
-	# Check for out of (min, max) range.
-	if dict['stringtovalue'] is not None:
-	    min = dict['min']
-	    max = dict['max']
-	    if min is None and max is None:
-		return OK
-	    val = apply(dict['stringtovalue'], (text,), args)
-	    if min is not None and val < min:
-		if dict['minstrict']:
-		    return ERROR
-		else:
-		    return PARTIAL
-	    if max is not None and val > max:
-		if dict['maxstrict']:
-		    return ERROR
-		else:
-		    return PARTIAL
-	return OK
-
-    def _checkValidity(self):
-	valid = self._getValidity()
-	oldValidity = valid
-
-	if valid == ERROR:
-	    # The entry is invalid.
-	    cmd = self['invalidcommand']
-	    if callable(cmd):
-		cmd()
-            if self.hulldestroyed():
-                # The invalidcommand destroyed us.
-                return oldValidity
-
-	    # Restore the entry to its previous value.
-	    if self._previousText is not None:
-		self.__setEntry(self._previousText)
-		self._entryFieldEntry.icursor(self._previousICursor)
-		self._entryFieldEntry.xview(self._previousXview)
-		if self._previousSel is not None:
-		    self._entryFieldEntry.selection_range(self._previousSel[0],
-			self._previousSel[1])
-
-		# Check if the saved text is valid as well.
-		valid = self._getValidity()
-
-	self._valid = valid
-
-        if self.hulldestroyed():
-            # The validator or stringtovalue commands called by
-            # _checkValidity() destroyed us.
-            return oldValidity
-
-	if valid == OK:
-	    if self.normalBackground is not None:
-		self._entryFieldEntry.configure(
-			background = self.normalBackground)
-		self.normalBackground = None
-	else:
-	    if self.normalBackground is None:
-		self.normalBackground = self._entryFieldEntry.cget('background')
-		self._entryFieldEntry.configure(
-			background = self['errorbackground'])
-
-        return oldValidity
-
-    def invoke(self):
-	return self._executeCommand()
-
-    def valid(self):
-        return self._valid == OK
-
-    def clear(self):
-        self.setentry('')
-
-    def __setEntry(self, text):
-	oldState = str(self._entryFieldEntry.cget('state'))
-	if oldState != 'normal':
-	    self._entryFieldEntry.configure(state='normal')
-	self._entryFieldEntry.delete(0, 'end')
-	self._entryFieldEntry.insert(0, text)
-	if oldState != 'normal':
-	    self._entryFieldEntry.configure(state=oldState)
-
-    def setentry(self, text):
-	self._preProcess()
-        self.__setEntry(text)
-	return self._postProcess()
-
-    def getvalue(self):
-        return self._entryFieldEntry.get()
-
-    def setvalue(self, text):
-        return self.setentry(text)
-
-forwardmethods(EntryField, Tkinter.Entry, '_entryFieldEntry')
-
-# ======================================================================
-
-
-# Entry field validation functions
-
-_numericregex = re.compile('^[0-9]*$')
-_alphabeticregex = re.compile('^[a-z]*$', re.IGNORECASE)
-_alphanumericregex = re.compile('^[0-9a-z]*$', re.IGNORECASE)
-
-def numericvalidator(text):
-    if text == '':
-        return PARTIAL
-    else:
-	if _numericregex.match(text) is None:
-	    return ERROR
-	else:
-	    return OK
-    
-def integervalidator(text):
-    if text in ('', '-', '+'):
-        return PARTIAL
-    try:
-	string.atol(text)
-	return OK
-    except ValueError:
-	return ERROR
-    
-def alphabeticvalidator(text):
-    if _alphabeticregex.match(text) is None:
-	return ERROR
-    else:
-	return OK
-    
-def alphanumericvalidator(text):
-    if _alphanumericregex.match(text) is None:
-	return ERROR
-    else:
-	return OK
-    
-def hexadecimalvalidator(text):
-    if text in ('', '0x', '0X', '+', '+0x', '+0X', '-', '-0x', '-0X'):
-        return PARTIAL
-    try:
-	string.atol(text, 16)
-	return OK
-    except ValueError:
-	return ERROR
-    
-def realvalidator(text, separator = '.'):
-    if separator != '.':
-	if string.find(text, '.') >= 0:
-	    return ERROR
-	index = string.find(text, separator)
-	if index >= 0:
-	    text = text[:index] + '.' + text[index + 1:]
-    try:
-	string.atof(text)
-	return OK
-    except ValueError:
-	# Check if the string could be made valid by appending a digit
-	# eg ('-', '+', '.', '-.', '+.', '1.23e', '1E-').
-	if len(text) == 0:
-	    return PARTIAL
-	if text[-1] in string.digits:
-	    return ERROR
-	try:
-	    string.atof(text + '0')
-	    return PARTIAL
-	except ValueError:
-	    return ERROR
-    
-def timevalidator(text, separator = ':'):
-    try:
-	timestringtoseconds(text, separator)
-	return OK
-    except ValueError:
-	if len(text) > 0 and text[0] in ('+', '-'):
-	    text = text[1:]
-	if re.search('[^0-9' + separator + ']', text) is not None:
-	    return ERROR
-	return PARTIAL
-
-def datevalidator(text, format = 'ymd', separator = '/'):
-    try:
-	datestringtojdn(text, format, separator)
-	return OK
-    except ValueError:
-	if re.search('[^0-9' + separator + ']', text) is not None:
-	    return ERROR
-	return PARTIAL
-
-_standardValidators = {
-    'numeric'      : (numericvalidator,      string.atol),
-    'integer'      : (integervalidator,      string.atol),
-    'hexadecimal'  : (hexadecimalvalidator,  lambda s: string.atol(s, 16)),
-    'real'         : (realvalidator,         stringtoreal),
-    'alphabetic'   : (alphabeticvalidator,   len),
-    'alphanumeric' : (alphanumericvalidator, len),
-    'time'         : (timevalidator,         timestringtoseconds),
-    'date'         : (datevalidator,         datestringtojdn),
-}
-
-_entryCache = {}
-
-def _registerEntryField(entry, entryField):
-    # Register an EntryField widget for an Entry widget
-
-    _entryCache[entry] = entryField
-
-def _deregisterEntryField(entry):
-    # Deregister an Entry widget
-    del _entryCache[entry]
-
-def _preProcess(event):
-    # Forward preprocess events for an Entry to it's EntryField
-
-    _entryCache[event.widget]._preProcess()
-
-def _postProcess(event):
-    # Forward postprocess events for an Entry to it's EntryField
-
-    # The function specified by the 'command' option may have destroyed
-    # the megawidget in a binding earlier in bindtags, so need to check.
-    if _entryCache.has_key(event.widget):
-        _entryCache[event.widget]._postProcess()
-
-######################################################################
-### File: PmwGroup.py
-import string
-import Tkinter
-
-
-def aligngrouptags(groups):
-    # Adjust the y position of the tags in /groups/ so that they all
-    # have the height of the highest tag.
-
-    maxTagHeight = 0
-    for group in groups:
-	if group._tag is None:
-	    height = (string.atoi(str(group._ring.cget('borderwidth'))) +
-		    string.atoi(str(group._ring.cget('highlightthickness'))))
-	else:
-	    height = group._tag.winfo_reqheight()
-	if maxTagHeight < height:
-	    maxTagHeight = height
-
-    for group in groups:
-	ringBorder = (string.atoi(str(group._ring.cget('borderwidth'))) +
-		string.atoi(str(group._ring.cget('highlightthickness'))))
-	topBorder = maxTagHeight / 2 - ringBorder / 2
-	group._hull.grid_rowconfigure(0, minsize = topBorder)
-	group._ring.grid_rowconfigure(0,
-		minsize = maxTagHeight - topBorder - ringBorder)
-	if group._tag is not None:
-	    group._tag.place(y = maxTagHeight / 2)
-
-class Group( MegaWidget ):
-    def __init__(self, parent = None, **kw):
-
-        # Define the megawidget options.
-	
-        optiondefs = (
-	    ('collapsedsize',    6,         INITOPT),
-	    ('ring_borderwidth', 2,         None),
-	    ('ring_relief',      'groove',  None),
-	    ('tagindent',        10,        INITOPT),
-        )
-        self.defineoptions(kw, optiondefs)
-
-        # Initialise the base class (after defining the options).
-        MegaWidget.__init__(self, parent)
-
-        # Create the components.
-        interior = MegaWidget.interior(self)
-
-	self._ring = self.createcomponent(
-	    'ring', 
-	    (), None,
-	    Tkinter.Frame, (interior,), 
-	    )
-
-	self._groupChildSite = self.createcomponent(
-	    'groupchildsite',
-	    (), None,
-	    Tkinter.Frame, (self._ring,)
-	    )
-
-        self._tag = self.createcomponent(
-	    'tag',
-	    (), None,
-	    Tkinter.Label, (interior,),
-	    )
-
-	ringBorder = (string.atoi(str(self._ring.cget('borderwidth'))) +
-		string.atoi(str(self._ring.cget('highlightthickness'))))
-	if self._tag is None:
-	    tagHeight = ringBorder
-	else:
-	    tagHeight = self._tag.winfo_reqheight()
-	    self._tag.place(
-		    x = ringBorder + self['tagindent'],
-		    y = tagHeight / 2,
-		    anchor = 'w')
-
-	topBorder = tagHeight / 2 - ringBorder / 2
-	self._ring.grid(column = 0, row = 1, sticky = 'nsew')
-	interior.grid_columnconfigure(0, weight = 1)
-	interior.grid_rowconfigure(1, weight = 1)
-	interior.grid_rowconfigure(0, minsize = topBorder)
-
-	self._groupChildSite.grid(column = 0, row = 1, sticky = 'nsew')
-	self._ring.grid_columnconfigure(0, weight = 1)
-	self._ring.grid_rowconfigure(1, weight = 1)
-	self._ring.grid_rowconfigure(0,
-		minsize = tagHeight - topBorder - ringBorder)
-
-        self.showing = 1
-
-        # Check keywords and initialise options.
-        self.initialiseoptions()
-
-    def toggle(self):
-        if self.showing:
-            self.collapse()
-        else:
-            self.expand()
-        self.showing = not self.showing
-
-    def expand(self):
-        self._groupChildSite.grid(column = 0, row = 1, sticky = 'nsew')
-
-    def collapse(self):
-        self._groupChildSite.grid_forget()
-	if self._tag is None:
-	    tagHeight = 0
-	else:
-            tagHeight = self._tag.winfo_reqheight()
-        self._ring.configure(height=(tagHeight / 2) + self['collapsedsize'])
-
-    def interior(self):
-        return self._groupChildSite
-
-######################################################################
-### File: PmwLabeledWidget.py
-import Tkinter
-
-
-class LabeledWidget(MegaWidget):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('labelmargin',            0,      INITOPT),
-	    ('labelpos',               None,   INITOPT),
-	    ('sticky',                 'nsew', INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = MegaWidget.interior(self)
-	self._labelChildSite = self.createcomponent('labelchildsite',
-		(), None,
-		Tkinter.Frame, (interior,))
-	self._labelChildSite.grid(column=2, row=2, sticky=self['sticky'])
-	interior.grid_columnconfigure(2, weight=1)
-	interior.grid_rowconfigure(2, weight=1)
-
-	self.createlabel(interior)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def interior(self):
-	return self._labelChildSite
-
-######################################################################
-### File: PmwMainMenuBar.py
-# Main menubar
-
-import string
-import types
-import Tkinter
-
-
-class MainMenuBar(MegaArchetype):
-
-    def __init__(self, parent = None, **kw):
-
-        # Define the megawidget options.
-        
-        optiondefs = (
-            ('balloon',      None,       None),
-            ('hotkeys',      1,          INITOPT),
-            ('hull_tearoff', 0,          None),
-        )
-        self.defineoptions(kw, optiondefs, dynamicGroups = ('Menu',))
-
-        # Initialise the base class (after defining the options).
-        MegaArchetype.__init__(self, parent, Tkinter.Menu)
-
-        self._menuInfo = {}
-        self._menuInfo[None] = (None, [])
-        # Map from a menu name to a tuple of information about the menu.
-        # The first item in the tuple is the name of the parent menu (for
-        # toplevel menus this is None). The second item in the tuple is
-        # a list of status help messages for each item in the menu.
-        # The key for the information for the main menubar is None.
-
-        self._menu = self.interior()
-        self._menu.bind('<Leave>', self._resetHelpmessage)
-        self._menu.bind('<Motion>',
-            lambda event=None, self=self: self._menuHelp(event, None))
-
-        # Check keywords and initialise options.
-        self.initialiseoptions()
-
-    def deletemenuitems(self, menuName, start, end = None):
-        self.component(menuName).delete(start, end)
-        if end is None:
-            del self._menuInfo[menuName][1][start]
-        else:
-            self._menuInfo[menuName][1][start:end+1] = []
-
-    def deletemenu(self, menuName):
-        """Delete should be called for cascaded menus before main menus.
-        """
-
-        parentName = self._menuInfo[menuName][0]
-        del self._menuInfo[menuName]
-        if parentName is None:
-            parentMenu = self._menu
-        else:
-            parentMenu = self.component(parentName)
-
-        menu = self.component(menuName)
-        menuId = str(menu)
-        for item in range(parentMenu.index('end') + 1):
-            if parentMenu.type(item) == 'cascade':
-                itemMenu = str(parentMenu.entrycget(item, 'menu'))
-                if itemMenu == menuId:
-                    parentMenu.delete(item)
-                    del self._menuInfo[parentName][1][item]
-                    break
-
-        self.destroycomponent(menuName)
-
-    def disableall(self):
-        for index in range(len(self._menuInfo[None][1])):
-            self.entryconfigure(index, state = 'disabled')
-
-    def enableall(self):
-        for index in range(len(self._menuInfo[None][1])):
-            self.entryconfigure(index, state = 'normal')
-
-    def addmenu(self, menuName, balloonHelp, statusHelp = None,
-            traverseSpec = None, **kw):
-        if statusHelp is None:
-            statusHelp = balloonHelp
-        self._addmenu(None, menuName, balloonHelp, statusHelp,
-            traverseSpec, kw)
-
-    def addcascademenu(self, parentMenuName, menuName, statusHelp='',
-            traverseSpec = None, **kw):
-        self._addmenu(parentMenuName, menuName, None, statusHelp,
-            traverseSpec, kw)
-
-    def _addmenu(self, parentMenuName, menuName, balloonHelp, statusHelp,
-            traverseSpec, kw):
-
-        if (menuName) in self.components():
-            raise ValueError, 'menu "%s" already exists' % menuName
-
-        menukw = {}
-        if kw.has_key('tearoff'):
-            menukw['tearoff'] = kw['tearoff']
-            del kw['tearoff']
-        else:
-            menukw['tearoff'] = 0
-        if kw.has_key('name'):
-            menukw['name'] = kw['name']
-            del kw['name']
-
-        if not kw.has_key('label'):
-            kw['label'] = menuName
-
-        self._addHotkeyToOptions(parentMenuName, kw, traverseSpec)
-
-        if parentMenuName is None:
-            parentMenu = self._menu
-            balloon = self['balloon']
-            # Bug in Tk: balloon help not implemented
-            # if balloon is not None:
-            #     balloon.mainmenubind(parentMenu, balloonHelp, statusHelp)
-        else:
-            parentMenu = self.component(parentMenuName)
-
-        apply(parentMenu.add_cascade, (), kw)
-
-        menu = apply(self.createcomponent, (menuName,
-                (), 'Menu',
-                Tkinter.Menu, (parentMenu,)), menukw)
-        parentMenu.entryconfigure('end', menu = menu)
-
-        self._menuInfo[parentMenuName][1].append(statusHelp)
-        self._menuInfo[menuName] = (parentMenuName, [])
-
-        menu.bind('<Leave>', self._resetHelpmessage)
-        menu.bind('<Motion>', 
-            lambda event=None, self=self, menuName=menuName:
-                    self._menuHelp(event, menuName))
-
-    def addmenuitem(self, menuName, itemType, statusHelp = '',
-            traverseSpec = None, **kw):
-
-        menu = self.component(menuName)
-        if itemType != 'separator':
-            self._addHotkeyToOptions(menuName, kw, traverseSpec)
-
-        if itemType == 'command':
-            command = menu.add_command
-        elif itemType == 'separator':
-            command = menu.add_separator
-        elif itemType == 'checkbutton':
-            command = menu.add_checkbutton
-        elif itemType == 'radiobutton':
-            command = menu.add_radiobutton
-        elif itemType == 'cascade':
-            command = menu.add_cascade
-        else:
-            raise ValueError, 'unknown menuitem type "%s"' % itemType
-
-        self._menuInfo[menuName][1].append(statusHelp)
-        apply(command, (), kw)
-
-    def _addHotkeyToOptions(self, menuName, kw, traverseSpec):
-
-        if (not self['hotkeys'] or kw.has_key('underline') or
-                not kw.has_key('label')):
-            return
-
-        if type(traverseSpec) == types.IntType:
-            kw['underline'] = traverseSpec
-            return
-
-        if menuName is None:
-            menu = self._menu
-        else:
-            menu = self.component(menuName)
-        hotkeyList = []
-        end = menu.index('end')
-        if end is not None:
-            for item in range(end + 1):
-                if menu.type(item) not in ('separator', 'tearoff'):
-                    underline = \
-                            string.atoi(str(menu.entrycget(item, 'underline')))
-                    if underline != -1:
-                        label = str(menu.entrycget(item, 'label'))
-                        if underline < len(label):
-                            hotkey = string.lower(label[underline])
-                            if hotkey not in hotkeyList:
-                                hotkeyList.append(hotkey)
-
-        name = kw['label']
-
-        if type(traverseSpec) == types.StringType:
-            lowerLetter = string.lower(traverseSpec)
-            if traverseSpec in name and lowerLetter not in hotkeyList:
-                kw['underline'] = string.index(name, traverseSpec)
-        else:
-            targets = string.digits + string.letters
-            lowerName = string.lower(name)
-            for letter_index in range(len(name)):
-                letter = lowerName[letter_index]
-                if letter in targets and letter not in hotkeyList:
-                    kw['underline'] = letter_index
-                    break
-
-    def _menuHelp(self, event, menuName):
-        if menuName is None:
-            menu = self._menu
-            index = menu.index('@%d'% event.x)
-        else:
-            menu = self.component(menuName)
-            index = menu.index('@%d'% event.y)
-
-        balloon = self['balloon']
-        if balloon is not None:
-            if index is None:
-                balloon.showstatus('')
-            else:
-                if str(menu.cget('tearoff')) == '1':
-                    index = index - 1
-                if index >= 0:
-                    help = self._menuInfo[menuName][1][index]
-                    balloon.showstatus(help)
-
-    def _resetHelpmessage(self, event=None):
-        balloon = self['balloon']
-        if balloon is not None:
-            balloon.clearstatus()
-
-forwardmethods(MainMenuBar, Tkinter.Menu, '_hull')
-
-######################################################################
-### File: PmwMenuBar.py
-# Manager widget for menus.
-
-import string
-import types
-import Tkinter
-
-
-class MenuBar(MegaWidget):
-
-    def __init__(self, parent = None, **kw):
-
-        # Define the megawidget options.
-        
-        optiondefs = (
-            ('balloon',      None,       None),
-            ('hotkeys',      1,          INITOPT),
-            ('padx',         0,          INITOPT),
-        )
-        self.defineoptions(kw, optiondefs, dynamicGroups = ('Menu', 'Button'))
-
-        # Initialise the base class (after defining the options).
-        MegaWidget.__init__(self, parent)
-
-        self._menuInfo = {}
-        # Map from a menu name to a tuple of information about the menu.
-        # The first item in the tuple is the name of the parent menu (for
-        # toplevel menus this is None). The second item in the tuple is
-        # a list of status help messages for each item in the menu.
-        # The third item in the tuple is the id of the binding used
-        # to detect mouse motion to display status help.
-        # Information for the toplevel menubuttons is not stored here.
-
-        self._mydeletecommand = self.component('hull').tk.deletecommand
-        # Cache this method for use later.
-
-        # Check keywords and initialise options.
-        self.initialiseoptions()
-
-    def deletemenuitems(self, menuName, start, end = None):
-        self.component(menuName + '-menu').delete(start, end)
-        if end is None:
-            del self._menuInfo[menuName][1][start]
-        else:
-            self._menuInfo[menuName][1][start:end+1] = []
-
-    def deletemenu(self, menuName):
-        """Delete should be called for cascaded menus before main menus.
-        """
-
-        # Clean up binding for this menu.
-        parentName = self._menuInfo[menuName][0]
-        bindId = self._menuInfo[menuName][2]
-        _bindtag = 'PmwMenuBar' + str(self) + menuName
-        self.unbind_class(_bindtag, '<Motion>')
-        self._mydeletecommand(bindId) # unbind_class does not clean up
-        del self._menuInfo[menuName]
-
-        if parentName is None:
-            self.destroycomponent(menuName + '-button')
-        else:
-            parentMenu = self.component(parentName + '-menu')
-
-            menu = self.component(menuName + '-menu')
-            menuId = str(menu)
-            for item in range(parentMenu.index('end') + 1):
-                if parentMenu.type(item) == 'cascade':
-                    itemMenu = str(parentMenu.entrycget(item, 'menu'))
-                    if itemMenu == menuId:
-                        parentMenu.delete(item)
-                        del self._menuInfo[parentName][1][item]
-                        break
-
-        self.destroycomponent(menuName + '-menu')
-
-    def disableall(self):
-        for menuName in self._menuInfo.keys():
-            if self._menuInfo[menuName][0] is None:
-                menubutton = self.component(menuName + '-button')
-                menubutton.configure(state = 'disabled')
-
-    def enableall(self):
-        for menuName in self._menuInfo.keys():
-            if self._menuInfo[menuName][0] is None:
-                menubutton = self.component(menuName + '-button')
-                menubutton.configure(state = 'normal')
-
-    def addmenu(self, menuName, balloonHelp, statusHelp = None,
-            side = 'left', traverseSpec = None, **kw):
-
-        self._addmenu(None, menuName, balloonHelp, statusHelp,
-            traverseSpec, side, 'text', kw)
-
-    def addcascademenu(self, parentMenuName, menuName, statusHelp = '',
-            traverseSpec = None, **kw):
-
-        self._addmenu(parentMenuName, menuName, None, statusHelp,
-            traverseSpec, None, 'label', kw)
-
-    def _addmenu(self, parentMenuName, menuName, balloonHelp, statusHelp,
-            traverseSpec, side, textKey, kw):
-
-        if (menuName + '-menu') in self.components():
-            raise ValueError, 'menu "%s" already exists' % menuName
-
-        menukw = {}
-        if kw.has_key('tearoff'):
-            menukw['tearoff'] = kw['tearoff']
-            del kw['tearoff']
-        else:
-            menukw['tearoff'] = 0
-
-        if not kw.has_key(textKey):
-            kw[textKey] = menuName
-
-        self._addHotkeyToOptions(parentMenuName, kw, textKey, traverseSpec)
-
-        if parentMenuName is None:
-            button = apply(self.createcomponent, (menuName + '-button',
-                    (), 'Button',
-                    Tkinter.Menubutton, (self.interior(),)), kw)
-            button.pack(side=side, padx = self['padx'])
-            balloon = self['balloon']
-            if balloon is not None:
-                balloon.bind(button, balloonHelp, statusHelp)
-            parentMenu = button
-        else:
-            parentMenu = self.component(parentMenuName + '-menu')
-            apply(parentMenu.add_cascade, (), kw)
-            self._menuInfo[parentMenuName][1].append(statusHelp)
-
-        menu = apply(self.createcomponent, (menuName + '-menu',
-                (), 'Menu',
-                Tkinter.Menu, (parentMenu,)), menukw)
-        if parentMenuName is None:
-            button.configure(menu = menu)
-        else:
-            parentMenu.entryconfigure('end', menu = menu)
-
-        # Need to put this binding after the class bindings so that
-        # menu.index() does not lag behind.
-        _bindtag = 'PmwMenuBar' + str(self) + menuName
-        bindId = self.bind_class(_bindtag, '<Motion>',
-            lambda event=None, self=self, menuName=menuName:
-                    self._menuHelp(menuName))
-        menu.bindtags(menu.bindtags() + (_bindtag,))
-        menu.bind('<Leave>', self._resetHelpmessage)
-
-        self._menuInfo[menuName] = (parentMenuName, [], bindId)
-
-    def addmenuitem(self, menuName, itemType, statusHelp = '',
-            traverseSpec = None, **kw):
-
-        menu = self.component(menuName + '-menu')
-        if itemType != 'separator':
-            self._addHotkeyToOptions(menuName, kw, 'label', traverseSpec)
-
-        if itemType == 'command':
-            command = menu.add_command
-        elif itemType == 'separator':
-            command = menu.add_separator
-        elif itemType == 'checkbutton':
-            command = menu.add_checkbutton
-        elif itemType == 'radiobutton':
-            command = menu.add_radiobutton
-        elif itemType == 'cascade':
-            command = menu.add_cascade
-        else:
-            raise ValueError, 'unknown menuitem type "%s"' % itemType
-
-        self._menuInfo[menuName][1].append(statusHelp)
-        apply(command, (), kw)
-
-    def _addHotkeyToOptions(self, menuName, kw, textKey, traverseSpec):
-
-        if (not self['hotkeys'] or kw.has_key('underline') or
-                not kw.has_key(textKey)):
-            return
-
-        if type(traverseSpec) == types.IntType:
-            kw['underline'] = traverseSpec
-            return
-
-        hotkeyList = []
-        if menuName is None:
-            for menuName in self._menuInfo.keys():
-                if self._menuInfo[menuName][0] is None:
-                    menubutton = self.component(menuName + '-button')
-                    underline = string.atoi(str(menubutton.cget('underline')))
-                    if underline != -1:
-                        label = str(menubutton.cget(textKey))
-                        if underline < len(label):
-                            hotkey = string.lower(label[underline])
-                            if hotkey not in hotkeyList:
-                                hotkeyList.append(hotkey)
-        else:
-            menu = self.component(menuName + '-menu')
-            end = menu.index('end')
-            if end is not None:
-                for item in range(end + 1):
-                    if menu.type(item) not in ('separator', 'tearoff'):
-                        underline = string.atoi(
-                            str(menu.entrycget(item, 'underline')))
-                        if underline != -1:
-                            label = str(menu.entrycget(item, textKey))
-                            if underline < len(label):
-                                hotkey = string.lower(label[underline])
-                                if hotkey not in hotkeyList:
-                                    hotkeyList.append(hotkey)
-
-        name = kw[textKey]
-
-        if type(traverseSpec) == types.StringType:
-            lowerLetter = string.lower(traverseSpec)
-            if traverseSpec in name and lowerLetter not in hotkeyList:
-                kw['underline'] = string.index(name, traverseSpec)
-        else:
-            targets = string.digits + string.letters
-            lowerName = string.lower(name)
-            for letter_index in range(len(name)):
-                letter = lowerName[letter_index]
-                if letter in targets and letter not in hotkeyList:
-                    kw['underline'] = letter_index
-                    break
-
-    def _menuHelp(self, menuName):
-        menu = self.component(menuName + '-menu')
-        index = menu.index('active')
-
-        balloon = self['balloon']
-        if balloon is not None:
-            if index is None:
-                balloon.showstatus('')
-            else:
-                if str(menu.cget('tearoff')) == '1':
-                    index = index - 1
-                if index >= 0:
-                    help = self._menuInfo[menuName][1][index]
-                    balloon.showstatus(help)
-
-    def _resetHelpmessage(self, event=None):
-        balloon = self['balloon']
-        if balloon is not None:
-            balloon.clearstatus()
-
-######################################################################
-### File: PmwMessageBar.py
-# Class to display messages in an information line.
-
-import string
-import Tkinter
-
-
-class MessageBar(MegaWidget):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	defaultMessageTypes = {
-			   # (priority, showtime, bells, logmessage)
-	    'systemerror'  : (5, 10, 2, 1),
-	    'usererror'    : (4, 5, 1, 0),
-	    'busy'         : (3, 0, 0, 0),
-	    'systemevent'  : (2, 5, 0, 0),
-	    'userevent'    : (2, 5, 0, 0),
-	    'help'         : (1, 5, 0, 0),
-	    'state'        : (0, 0, 0, 0),
-	}
-	optiondefs = (
-	    ('labelmargin',    0,                     INITOPT),
-	    ('labelpos',       None,                  INITOPT),
-	    ('messagetypes',   defaultMessageTypes,   INITOPT),
-	    ('silent',         0,                     None),
-	    ('sticky',         'ew',                  INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-	self._messageBarEntry = self.createcomponent('entry',
-		(), None,
-		Tkinter.Entry, (interior,))
-
-        # Can't always use 'disabled', since this greys out text in Tk 8.4.2
-        try:
-            self._messageBarEntry.configure(state = 'readonly')
-        except Tkinter.TclError:
-            self._messageBarEntry.configure(state = 'disabled')
-
-	self._messageBarEntry.grid(column=2, row=2, sticky=self['sticky'])
-	interior.grid_columnconfigure(2, weight=1)
-	interior.grid_rowconfigure(2, weight=1)
-
-	self.createlabel(interior)
-
-	# Initialise instance variables.
-	self._numPriorities = 0
-	for info in self['messagetypes'].values():
-	    if self._numPriorities < info[0]:
-		self._numPriorities = info[0]
-
-	self._numPriorities = self._numPriorities + 1
-	self._timer = [None] * self._numPriorities
-	self._messagetext = [''] * self._numPriorities
-	self._activemessage = [0] * self._numPriorities
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	for timerId in self._timer:
-	    if timerId is not None:
-		self.after_cancel(timerId)
-	self._timer = [None] * self._numPriorities
-	MegaWidget.destroy(self)
-
-    def message(self, type, text):
-        # Display a message in the message bar.
-
-	(priority, showtime, bells, logmessage) = self['messagetypes'][type]
-
-	if not self['silent']:
-	    for i in range(bells):
-		if i != 0:
-		    self.after(100)
-		self.bell()
-
-	self._activemessage[priority] = 1
-	if text is None:
-	    text = ''
-	self._messagetext[priority] = string.replace(text, '\n', ' ')
-	self._redisplayInfoMessage()
-
-	if logmessage:
-	    # Should log this text to a text widget.
-	    pass
-
-	if showtime > 0:
-	    if self._timer[priority] is not None:
-		self.after_cancel(self._timer[priority])
-
-	    # Define a callback to clear this message after a time.
-	    def _clearmessage(self=self, priority=priority):
-		self._clearActivemessage(priority)
-
-	    mseconds = int(showtime * 1000)
-	    self._timer[priority] = self.after(mseconds, _clearmessage)
-
-    def helpmessage(self, text):
-        if text is None:
-            self.resetmessages('help')
-        else:
-            self.message('help', text)
-
-    def resetmessages(self, type):
-	priority = self['messagetypes'][type][0]
-	self._clearActivemessage(priority)
-	for messagetype, info in self['messagetypes'].items():
-	    thisPriority = info[0]
-	    showtime = info[1]
-	    if thisPriority < priority and showtime != 0:
-		self._clearActivemessage(thisPriority)
-
-    def _clearActivemessage(self, priority):
-	self._activemessage[priority] = 0
-	if self._timer[priority] is not None:
-	    self.after_cancel(self._timer[priority])
-	    self._timer[priority] = None
-	self._redisplayInfoMessage()
-
-    def _redisplayInfoMessage(self):
-	text = ''
-        for priority in range(self._numPriorities - 1, -1, -1):
-	    if self._activemessage[priority]:
-		text = self._messagetext[priority]
-	        break
-	self._messageBarEntry.configure(state = 'normal')
-	self._messageBarEntry.delete(0, 'end')
-	self._messageBarEntry.insert('end', text)
-
-        # Can't always use 'disabled', since this greys out text in Tk 8.4.2
-        try:
-            self._messageBarEntry.configure(state = 'readonly')
-        except Tkinter.TclError:
-            self._messageBarEntry.configure(state = 'disabled')
-
-forwardmethods(MessageBar, Tkinter.Entry, '_messageBarEntry')
-
-######################################################################
-### File: PmwMessageDialog.py
-# Based on iwidgets2.2.0/messagedialog.itk code.
-
-import Tkinter
-
-
-class MessageDialog(Dialog):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderx',       20,    INITOPT),
-	    ('bordery',       20,    INITOPT),
-	    ('iconmargin',    20,    INITOPT),
-	    ('iconpos',       None,  INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	Dialog.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-
-	self._message = self.createcomponent('message',
-		(), None,
-		Tkinter.Label, (interior,))
-
-        iconpos = self['iconpos']
-	iconmargin = self['iconmargin']
-	borderx = self['borderx']
-	bordery = self['bordery']
-	border_right = 2
-	border_bottom = 2
-	if iconpos is None:
-	    self._message.grid(column = 1, row = 1)
-	else:
-	    self._icon = self.createcomponent('icon',
-		    (), None,
-		    Tkinter.Label, (interior,))
-	    if iconpos not in 'nsew':
-		raise ValueError, \
-		    'bad iconpos option "%s":  should be n, s, e, or w' \
-			% iconpos
-
-	    if iconpos in 'nw':
-		icon = 1
-		message = 3
-	    else:
-		icon = 3
-		message = 1
-
-	    if iconpos in 'ns':
-		# vertical layout
-		self._icon.grid(column = 1, row = icon)
-		self._message.grid(column = 1, row = message)
-		interior.grid_rowconfigure(2, minsize = iconmargin)
-		border_bottom = 4
-	    else:
-		# horizontal layout
-		self._icon.grid(column = icon, row = 1)
-		self._message.grid(column = message, row = 1)
-		interior.grid_columnconfigure(2, minsize = iconmargin)
-		border_right = 4
-
-	interior.grid_columnconfigure(0, minsize = borderx)
-	interior.grid_rowconfigure(0, minsize = bordery)
-	interior.grid_columnconfigure(border_right, minsize = borderx)
-	interior.grid_rowconfigure(border_bottom, minsize = bordery)
-
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-######################################################################
-### File: PmwNoteBook.py
-import string
-import types
-import Tkinter
-
-
-class NoteBook(MegaArchetype):
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-        optiondefs = (
-	    ('hull_highlightthickness',  0,           None),
-	    ('hull_borderwidth',         0,           None),
-            ('arrownavigation',          1,           INITOPT),
-            ('borderwidth',              2,           INITOPT),
-            ('createcommand',            None,        None),
-            ('lowercommand',             None,        None),
-            ('pagemargin',               4,           INITOPT),
-            ('raisecommand',             None,        None),
-	    ('tabpos',                   'n',         INITOPT),
-        )
-	self.defineoptions(kw, optiondefs, dynamicGroups = ('Page', 'Tab'))
-
-	# Initialise the base class (after defining the options).
-	MegaArchetype.__init__(self, parent, Tkinter.Canvas)
-
-        self.bind('<Map>', self._handleMap)
-        self.bind('<Configure>', self._handleConfigure)
-
-        tabpos = self['tabpos']
-	if tabpos is not None and tabpos != 'n':
-            raise ValueError, \
-                'bad tabpos option %s:  should be n or None' % repr(tabpos)
-        self._withTabs = (tabpos is not None)
-        self._pageMargin = self['pagemargin']
-        self._borderWidth = self['borderwidth']
-
-        # Use a dictionary as a set of bits indicating what needs to
-        # be redisplayed the next time _layout() is called.  If
-        # dictionary contains 'topPage' key, the value is the new top
-        # page to be displayed.  None indicates that all pages have
-        # been deleted and that _layout() should draw a border under where
-        # the tabs should be.
-        self._pending = {}
-        self._pending['size'] = 1
-        self._pending['borderColor'] = 1
-        self._pending['topPage'] = None
-        if self._withTabs:
-            self._pending['tabs'] = 1
-
-        self._canvasSize = None       # This gets set by <Configure> events
-
-        # Set initial height of space for tabs
-        if self._withTabs:
-            self.tabBottom = 35
-        else:
-            self.tabBottom = 0
-
-        self._lightBorderColor, self._darkBorderColor = \
-                Color.bordercolors(self, self['hull_background'])
-
-	self._pageNames   = []        # List of page names
-
-        # Map from page name to page info.  Each item is itself a
-        # dictionary containing the following items:
-        #   page           the Tkinter.Frame widget for the page
-        #   created        set to true the first time the page is raised
-        #   tabbutton      the Tkinter.Button widget for the button (if any)
-        #   tabreqwidth    requested width of the tab
-        #   tabreqheight   requested height of the tab
-        #   tabitems       the canvas items for the button: the button
-        #                  window item, the lightshadow and the darkshadow
-        #   left           the left and right canvas coordinates of the tab
-        #   right
-	self._pageAttrs   = {}
-
-        # Name of page currently on top (actually displayed, using
-        # create_window, not pending).  Ignored if current top page
-        # has been deleted or new top page is pending.  None indicates
-        # no pages in notebook.
-	self._topPageName = None
-
-        # Canvas items used:
-        #   Per tab: 
-        #       top and left shadow
-        #       right shadow
-        #       button
-        #   Per notebook: 
-        #       page
-        #       top page
-        #       left shadow
-        #       bottom and right shadow
-        #       top (one or two items)
-
-        # Canvas tags used:
-        #   lighttag      - top and left shadows of tabs and page
-        #   darktag       - bottom and right shadows of tabs and page
-        #                   (if no tabs then these are reversed)
-        #                   (used to color the borders by recolorborders)
-
-        # Create page border shadows.
-        if self._withTabs:
-            self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
-                fill = self._lightBorderColor, tags = 'lighttag')
-            self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
-                fill = self._darkBorderColor, tags = 'darktag')
-            self._pageTop1Border = self.create_polygon(0, 0, 0, 0, 0, 0,
-                fill = self._darkBorderColor, tags = 'lighttag')
-            self._pageTop2Border = self.create_polygon(0, 0, 0, 0, 0, 0,
-                fill = self._darkBorderColor, tags = 'lighttag')
-        else:
-            self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
-                fill = self._darkBorderColor, tags = 'darktag')
-            self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
-                fill = self._lightBorderColor, tags = 'lighttag')
-            self._pageTopBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
-                fill = self._darkBorderColor, tags = 'darktag')
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def insert(self, pageName, before = 0, **kw):
-	if self._pageAttrs.has_key(pageName):
-	    msg = 'Page "%s" already exists.' % pageName
-	    raise ValueError, msg
-
-        # Do this early to catch bad <before> spec before creating any items.
-	beforeIndex = self.index(before, 1)
-
-        pageOptions = {}
-        if self._withTabs:
-            # Default tab button options.
-            tabOptions = {
-                'text' : pageName,
-                'borderwidth' : 0,
-            }
-
-        # Divide the keyword options into the 'page_' and 'tab_' options.
-        for key in kw.keys():
-            if key[:5] == 'page_':
-                pageOptions[key[5:]] = kw[key]
-                del kw[key]
-            elif self._withTabs and key[:4] == 'tab_':
-                tabOptions[key[4:]] = kw[key]
-                del kw[key]
-            else:
-		raise KeyError, 'Unknown option "' + key + '"'
-
-        # Create the frame to contain the page.
-	page = apply(self.createcomponent, (pageName,
-		(), 'Page',
-		Tkinter.Frame, self._hull), pageOptions)
-
-        attributes = {}
-        attributes['page'] = page
-        attributes['created'] = 0
-
-        if self._withTabs:
-            # Create the button for the tab.
-            def raiseThisPage(self = self, pageName = pageName):
-                self.selectpage(pageName)
-            tabOptions['command'] = raiseThisPage
-            tab = apply(self.createcomponent, (pageName + '-tab',
-                    (), 'Tab',
-                    Tkinter.Button, self._hull), tabOptions)
-
-            if self['arrownavigation']:
-                # Allow the use of the arrow keys for Tab navigation:
-                def next(event, self = self, pageName = pageName):
-                    self.nextpage(pageName)
-                def prev(event, self = self, pageName = pageName):
-                    self.previouspage(pageName)
-                tab.bind('<Left>', prev)
-                tab.bind('<Right>', next)
-
-            attributes['tabbutton'] = tab
-            attributes['tabreqwidth'] = tab.winfo_reqwidth()
-            attributes['tabreqheight'] = tab.winfo_reqheight()
-
-            # Create the canvas item to manage the tab's button and the items
-            # for the tab's shadow.
-            windowitem = self.create_window(0, 0, window = tab, anchor = 'nw')
-            lightshadow = self.create_polygon(0, 0, 0, 0, 0, 0,
-                tags = 'lighttag', fill = self._lightBorderColor)
-            darkshadow = self.create_polygon(0, 0, 0, 0, 0, 0,
-                tags = 'darktag', fill = self._darkBorderColor)
-            attributes['tabitems'] = (windowitem, lightshadow, darkshadow)
-            self._pending['tabs'] = 1
-
-        self._pageAttrs[pageName] = attributes
-	self._pageNames.insert(beforeIndex, pageName)
-
-        # If this is the first page added, make it the new top page
-        # and call the create and raise callbacks.
-        if self.getcurselection() is None:
-            self._pending['topPage'] = pageName
-            self._raiseNewTop(pageName)
-
-        self._layout()
-        return page
-  		
-    def add(self, pageName, **kw):
-        return apply(self.insert, (pageName, len(self._pageNames)), kw)
-
-    def delete(self, *pageNames):
-        newTopPage = 0
-        for page in pageNames:
-            pageIndex = self.index(page)
-            pageName = self._pageNames[pageIndex]
-            pageInfo = self._pageAttrs[pageName]
-
-            if self.getcurselection() == pageName:
-                if len(self._pageNames) == 1:
-                    newTopPage = 0
-                    self._pending['topPage'] = None
-                elif pageIndex == len(self._pageNames) - 1:
-                    newTopPage = 1
-                    self._pending['topPage'] = self._pageNames[pageIndex - 1]
-                else:
-                    newTopPage = 1
-                    self._pending['topPage'] = self._pageNames[pageIndex + 1]
-
-            if self._topPageName == pageName:
-                self._hull.delete(self._topPageItem)
-                self._topPageName = None
-                                
-            if self._withTabs:
-                self.destroycomponent(pageName + '-tab')
-                apply(self._hull.delete, pageInfo['tabitems'])
-            self.destroycomponent(pageName)
-            del self._pageAttrs[pageName]
-            del self._pageNames[pageIndex]
-
-        # If the old top page was deleted and there are still pages
-        # left in the notebook, call the create and raise callbacks.
-        if newTopPage:
-            pageName = self._pending['topPage']
-            self._raiseNewTop(pageName)
-
-        if self._withTabs:
-            self._pending['tabs'] = 1
-        self._layout()
-
-    def page(self, pageIndex):
-        pageName = self._pageNames[self.index(pageIndex)]
-	return self._pageAttrs[pageName]['page']
-
-    def pagenames(self):
-	return list(self._pageNames)
-
-    def getcurselection(self):
-        if self._pending.has_key('topPage'):
-            return self._pending['topPage']
-        else:
-            return self._topPageName
-
-    def tab(self, pageIndex):
-        if self._withTabs:
-            pageName = self._pageNames[self.index(pageIndex)]
-            return self._pageAttrs[pageName]['tabbutton']
-        else:
-            return None
-
-    def index(self, index, forInsert = 0):
-	listLength = len(self._pageNames)
-	if type(index) == types.IntType:
-	    if forInsert and index <= listLength:
-		return index
-	    elif not forInsert and index < listLength:
-		return index
-	    else:
-		raise ValueError, 'index "%s" is out of range' % index
-	elif index is END:
-	    if forInsert:
-		return listLength
-	    elif listLength > 0:
-		return listLength - 1
-	    else:
-		raise ValueError, 'NoteBook has no pages'
-	elif index is SELECT:
-	    if listLength == 0:
-		raise ValueError, 'NoteBook has no pages'
-            return self._pageNames.index(self.getcurselection())
-	else:
-            if index in self._pageNames:
-                return self._pageNames.index(index)
-	    validValues = 'a name, a number, END or SELECT'
-	    raise ValueError, \
-                'bad index "%s": must be %s' % (index, validValues)
-
-    def selectpage(self, page):
-        pageName = self._pageNames[self.index(page)]
-        oldTopPage = self.getcurselection()
-        if pageName != oldTopPage:
-            self._pending['topPage'] = pageName
-            if oldTopPage == self._topPageName:
-                self._hull.delete(self._topPageItem)
-            cmd = self['lowercommand']
-            if cmd is not None:
-                cmd(oldTopPage)
-            self._raiseNewTop(pageName)
-
-            self._layout()
-
-        # Set focus to the tab of new top page:
-        if self._withTabs and self['arrownavigation']:
-            self._pageAttrs[pageName]['tabbutton'].focus_set()
-
-    def previouspage(self, pageIndex = None):
-        if pageIndex is None:
-            curpage = self.index(SELECT)
-        else:
-            curpage = self.index(pageIndex)
-	if curpage > 0:
-	    self.selectpage(curpage - 1)
-
-    def nextpage(self, pageIndex = None):
-        if pageIndex is None:
-            curpage = self.index(SELECT)
-        else:
-            curpage = self.index(pageIndex)
-	if curpage < len(self._pageNames) - 1:
-	    self.selectpage(curpage + 1)
-
-    def setnaturalsize(self, pageNames = None):
-        self.update_idletasks()
-        maxPageWidth = 1
-        maxPageHeight = 1
-        if pageNames is None:
-            pageNames = self.pagenames()
-        for pageName in pageNames:
-            pageInfo = self._pageAttrs[pageName]
-            page = pageInfo['page']
-            w = page.winfo_reqwidth()
-            h = page.winfo_reqheight()
-            if maxPageWidth < w:
-                maxPageWidth = w
-            if maxPageHeight < h:
-                maxPageHeight = h
-        pageBorder = self._borderWidth + self._pageMargin
-        width = maxPageWidth + pageBorder * 2
-        height = maxPageHeight + pageBorder * 2
-
-        if self._withTabs:
-            maxTabHeight = 0
-            for pageInfo in self._pageAttrs.values():
-                if maxTabHeight < pageInfo['tabreqheight']:
-                    maxTabHeight = pageInfo['tabreqheight']
-            height = height + maxTabHeight + self._borderWidth * 1.5
-
-        # Note that, since the hull is a canvas, the width and height
-        # options specify the geometry *inside* the borderwidth and
-        # highlightthickness.
-        self.configure(hull_width = width, hull_height = height)
-
-    def recolorborders(self):
-        self._pending['borderColor'] = 1
-        self._layout()
-
-    def _handleMap(self, event):
-        self._layout()
-
-    def _handleConfigure(self, event):
-        self._canvasSize = (event.width, event.height)
-        self._pending['size'] = 1
-        self._layout()
-
-    def _raiseNewTop(self, pageName):
-        if not self._pageAttrs[pageName]['created']:
-            self._pageAttrs[pageName]['created'] = 1
-            cmd = self['createcommand']
-            if cmd is not None:
-                cmd(pageName)
-        cmd = self['raisecommand']
-        if cmd is not None:
-            cmd(pageName)
-
-    # This is the vertical layout of the notebook, from top (assuming
-    # tabpos is 'n'):
-    #     hull highlightthickness (top)
-    #     hull borderwidth (top)
-    #     borderwidth (top border of tabs)
-    #     borderwidth * 0.5 (space for bevel)
-    #     tab button (maximum of requested height of all tab buttons)
-    #     borderwidth (border between tabs and page)
-    #     pagemargin (top)
-    #     the page itself
-    #     pagemargin (bottom)
-    #     borderwidth (border below page)
-    #     hull borderwidth (bottom)
-    #     hull highlightthickness (bottom)
-    #
-    # canvasBorder is sum of top two elements.
-    # tabBottom is sum of top five elements.
-    #
-    # Horizontal layout (and also vertical layout when tabpos is None):
-    #     hull highlightthickness
-    #     hull borderwidth
-    #     borderwidth
-    #     pagemargin
-    #     the page itself
-    #     pagemargin
-    #     borderwidth
-    #     hull borderwidth
-    #     hull highlightthickness
-    #
-    def _layout(self):
-        if not self.winfo_ismapped() or self._canvasSize is None:
-            # Don't layout if the window is not displayed, or we
-            # haven't yet received a <Configure> event.
-            return
-
-        hullWidth, hullHeight = self._canvasSize
-        borderWidth = self._borderWidth
-        canvasBorder = string.atoi(self._hull['borderwidth']) + \
-            string.atoi(self._hull['highlightthickness'])
-        if not self._withTabs:
-            self.tabBottom = canvasBorder
-        oldTabBottom = self.tabBottom
-
-        if self._pending.has_key('borderColor'):
-            self._lightBorderColor, self._darkBorderColor = \
-                    Color.bordercolors(self, self['hull_background'])
-
-        # Draw all the tabs.
-        if self._withTabs and (self._pending.has_key('tabs') or
-                self._pending.has_key('size')):
-            # Find total requested width and maximum requested height
-            # of tabs.
-            sumTabReqWidth = 0
-            maxTabHeight = 0
-            for pageInfo in self._pageAttrs.values():
-                sumTabReqWidth = sumTabReqWidth + pageInfo['tabreqwidth']
-                if maxTabHeight < pageInfo['tabreqheight']:
-                    maxTabHeight = pageInfo['tabreqheight']
-            if maxTabHeight != 0:
-                # Add the top tab border plus a bit for the angled corners
-                self.tabBottom = canvasBorder + maxTabHeight + borderWidth * 1.5
-
-            # Prepare for drawing the border around each tab button.
-            tabTop = canvasBorder
-            tabTop2 = tabTop + borderWidth
-            tabTop3 = tabTop + borderWidth * 1.5
-            tabBottom2 = self.tabBottom
-            tabBottom = self.tabBottom + borderWidth
-
-            numTabs = len(self._pageNames)
-            availableWidth = hullWidth - 2 * canvasBorder - \
-                numTabs * 2 * borderWidth
-            x = canvasBorder
-            cumTabReqWidth = 0
-            cumTabWidth = 0
-
-            # Position all the tabs.
-            for pageName in self._pageNames:
-                pageInfo = self._pageAttrs[pageName]
-                (windowitem, lightshadow, darkshadow) = pageInfo['tabitems']
-                if sumTabReqWidth <= availableWidth:
-                    tabwidth = pageInfo['tabreqwidth']
-                else:
-                    # This ugly calculation ensures that, when the
-                    # notebook is not wide enough for the requested
-                    # widths of the tabs, the total width given to
-                    # the tabs exactly equals the available width,
-                    # without rounding errors.
-                    cumTabReqWidth = cumTabReqWidth + pageInfo['tabreqwidth']
-                    tmp = (2*cumTabReqWidth*availableWidth + sumTabReqWidth) \
-                            / (2 * sumTabReqWidth)
-                    tabwidth = tmp - cumTabWidth
-                    cumTabWidth = tmp
-
-                # Position the tab's button canvas item.
-                self.coords(windowitem, x + borderWidth, tabTop3)
-                self.itemconfigure(windowitem,
-                    width = tabwidth, height = maxTabHeight)
-
-                # Make a beautiful border around the tab.
-                left = x
-                left2 = left + borderWidth
-                left3 = left + borderWidth * 1.5
-                right = left + tabwidth + 2 * borderWidth
-                right2 = left + tabwidth + borderWidth
-                right3 = left + tabwidth + borderWidth * 0.5
-
-                self.coords(lightshadow, 
-                    left, tabBottom2, left, tabTop2, left2, tabTop,
-                    right2, tabTop, right3, tabTop2, left3, tabTop2,
-                    left2, tabTop3, left2, tabBottom,
-                    )
-                self.coords(darkshadow, 
-                    right2, tabTop, right, tabTop2, right, tabBottom2,
-                    right2, tabBottom, right2, tabTop3, right3, tabTop2,
-                    )
-                pageInfo['left'] = left
-                pageInfo['right'] = right
-
-                x = x + tabwidth + 2 * borderWidth
-
-        # Redraw shadow under tabs so that it appears that tab for old
-        # top page is lowered and that tab for new top page is raised.
-        if self._withTabs and (self._pending.has_key('topPage') or
-                self._pending.has_key('tabs') or self._pending.has_key('size')):
-
-            if self.getcurselection() is None:
-                # No pages, so draw line across top of page area.
-                self.coords(self._pageTop1Border,
-                    canvasBorder, self.tabBottom,
-                    hullWidth - canvasBorder, self.tabBottom,
-                    hullWidth - canvasBorder - borderWidth,
-                        self.tabBottom + borderWidth,
-                    borderWidth + canvasBorder, self.tabBottom + borderWidth,
-                    )
-
-                # Ignore second top border.
-                self.coords(self._pageTop2Border, 0, 0, 0, 0, 0, 0)
-            else:
-                # Draw two lines, one on each side of the tab for the
-                # top page, so that the tab appears to be raised.
-                pageInfo = self._pageAttrs[self.getcurselection()]
-                left = pageInfo['left']
-                right = pageInfo['right']
-                self.coords(self._pageTop1Border,
-                    canvasBorder, self.tabBottom,
-                    left, self.tabBottom,
-                    left + borderWidth, self.tabBottom + borderWidth,
-                    canvasBorder + borderWidth, self.tabBottom + borderWidth,
-                    )
-
-                self.coords(self._pageTop2Border,
-                    right, self.tabBottom,
-                    hullWidth - canvasBorder, self.tabBottom,
-                    hullWidth - canvasBorder - borderWidth,
-                        self.tabBottom + borderWidth,
-                    right - borderWidth, self.tabBottom + borderWidth,
-                    )
-
-            # Prevent bottom of dark border of tabs appearing over
-            # page top border.
-            self.tag_raise(self._pageTop1Border)
-            self.tag_raise(self._pageTop2Border)
-
-        # Position the page border shadows.
-        if self._pending.has_key('size') or oldTabBottom != self.tabBottom:
-
-            self.coords(self._pageLeftBorder,
-                canvasBorder, self.tabBottom,
-                borderWidth + canvasBorder,
-                    self.tabBottom + borderWidth,
-                borderWidth + canvasBorder,
-                    hullHeight - canvasBorder - borderWidth,
-                canvasBorder, hullHeight - canvasBorder,
-                )
-
-            self.coords(self._pageBottomRightBorder,
-                hullWidth - canvasBorder, self.tabBottom,
-                hullWidth - canvasBorder, hullHeight - canvasBorder,
-                canvasBorder, hullHeight - canvasBorder,
-                borderWidth + canvasBorder,
-                    hullHeight - canvasBorder - borderWidth,
-                hullWidth - canvasBorder - borderWidth,
-                    hullHeight - canvasBorder - borderWidth,
-                hullWidth - canvasBorder - borderWidth,
-                    self.tabBottom + borderWidth,
-                )
-
-            if not self._withTabs:
-                self.coords(self._pageTopBorder,
-                    canvasBorder, self.tabBottom,
-                    hullWidth - canvasBorder, self.tabBottom,
-                    hullWidth - canvasBorder - borderWidth,
-                        self.tabBottom + borderWidth,
-                    borderWidth + canvasBorder, self.tabBottom + borderWidth,
-                    )
-
-        # Color borders.
-        if self._pending.has_key('borderColor'):
-            self.itemconfigure('lighttag', fill = self._lightBorderColor)
-            self.itemconfigure('darktag', fill = self._darkBorderColor)
-
-        newTopPage = self._pending.get('topPage')
-        pageBorder = borderWidth + self._pageMargin
-
-        # Raise new top page.
-        if newTopPage is not None:
-            self._topPageName = newTopPage
-            self._topPageItem = self.create_window(
-                pageBorder + canvasBorder, self.tabBottom + pageBorder,
-                window = self._pageAttrs[newTopPage]['page'],
-                anchor = 'nw',
-                )
-
-        # Change position of top page if tab height has changed.
-        if self._topPageName is not None and oldTabBottom != self.tabBottom:
-            self.coords(self._topPageItem,
-                    pageBorder + canvasBorder, self.tabBottom + pageBorder)
-
-        # Change size of top page if,
-        #   1) there is a new top page.
-        #   2) canvas size has changed, but not if there is no top
-        #      page (eg:  initially or when all pages deleted).
-        #   3) tab height has changed, due to difference in the height of a tab
-        if (newTopPage is not None or \
-                self._pending.has_key('size') and self._topPageName is not None
-                or oldTabBottom != self.tabBottom):
-            self.itemconfigure(self._topPageItem,
-                width = hullWidth - 2 * canvasBorder - pageBorder * 2,
-                height = hullHeight - 2 * canvasBorder - pageBorder * 2 -
-                    (self.tabBottom - canvasBorder),
-                )
-
-        self._pending = {}
-
-# Need to do forwarding to get the pack, grid, etc methods. 
-# Unfortunately this means that all the other canvas methods are also
-# forwarded.
-forwardmethods(NoteBook, Tkinter.Canvas, '_hull')
-
-######################################################################
-### File: PmwOptionMenu.py
-import types
-import Tkinter
-
-
-class OptionMenu(MegaWidget):
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('command',        None,       None),
-            ('items',          (),         INITOPT),
-            ('initialitem',    None,       INITOPT),
-	    ('labelmargin',    0,          INITOPT),
-	    ('labelpos',       None,       INITOPT),
-	    ('sticky',         'ew',       INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-
-	self._menubutton = self.createcomponent('menubutton',
-		(), None,
-		Tkinter.Menubutton, (interior,),
-		borderwidth = 2,
-		indicatoron = 1,
-		relief = 'raised',
-		anchor = 'c',
-		highlightthickness = 2,
-		direction = 'flush',
-                takefocus = 1,
-	)
-	self._menubutton.grid(column = 2, row = 2, sticky = self['sticky'])
-
-	self._menu = self.createcomponent('menu',
-		(), None,
-		Tkinter.Menu, (self._menubutton,),
-		tearoff=0
-	)
-	self._menubutton.configure(menu = self._menu)
-
-	interior.grid_columnconfigure(2, weight = 1)
-	interior.grid_rowconfigure(2, weight = 1)
-
-        # Create the label.
-        self.createlabel(interior)
-
-        # Add the items specified by the initialisation option.
-	self._itemList = []
-        self.setitems(self['items'], self['initialitem'])
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def setitems(self, items, index = None):
-
-        # Clean up old items and callback commands.
-        for oldIndex in range(len(self._itemList)):
-            tclCommandName = str(self._menu.entrycget(oldIndex, 'command'))
-            if tclCommandName != '':   
-                self._menu.deletecommand(tclCommandName)
-        self._menu.delete(0, 'end')
-	self._itemList = list(items)
-
-	# Set the items in the menu component.
-        for item in items:
-            self._menu.add_command(label = item,
-		command = lambda self = self, item = item: self._invoke(item))
-
-	# Set the currently selected value.
-	if index is None:
-            var = str(self._menubutton.cget('textvariable'))
-	    if var != '':
-		# None means do not change text variable.
-		return
-	    if len(items) == 0:
-		text = ''
-	    elif str(self._menubutton.cget('text')) in items:
-                # Do not change selection if it is still valid
-		return
-	    else:
-		text = items[0]
-	else:
-	    index = self.index(index)
-	    text = self._itemList[index]
-
-        self.setvalue(text)
-
-    def getcurselection(self):
-	var = str(self._menubutton.cget('textvariable'))
-	if var == '':
-	    return str(self._menubutton.cget('text'))
-	else:
-	    return self._menu.tk.globalgetvar(var)
-
-    def getvalue(self):
-        return self.getcurselection()
-
-    def setvalue(self, text):
-	var = str(self._menubutton.cget('textvariable'))
-	if var == '':
-	    self._menubutton.configure(text = text)
-	else:
-	    self._menu.tk.globalsetvar(var, text)
-
-    def index(self, index):
-	listLength = len(self._itemList)
-	if type(index) == types.IntType:
-	    if index < listLength:
-		return index
-	    else:
-		raise ValueError, 'index "%s" is out of range' % index
-	elif index is END:
-	    if listLength > 0:
-		return listLength - 1
-	    else:
-		raise ValueError, 'OptionMenu has no items'
-	else:
-	    if index is SELECT:
-		if listLength > 0:
-		    index = self.getcurselection()
-		else:
-		    raise ValueError, 'OptionMenu has no items'
-            if index in self._itemList:
-                return self._itemList.index(index)
-	    raise ValueError, \
-		    'bad index "%s": must be a ' \
-                    'name, a number, END or SELECT' % (index,)
-
-    def invoke(self, index = SELECT):
-	index = self.index(index)
-	text = self._itemList[index]
-
-        return self._invoke(text)
-
-    def _invoke(self, text):
-        self.setvalue(text)
-
-	command = self['command']
-	if callable(command):
-	    return command(text)
-
-######################################################################
-### File: PmwPanedWidget.py
-# PanedWidget
-# a frame which may contain several resizable sub-frames
-
-import string
-import sys
-import types
-import Tkinter
-
-
-class PanedWidget(MegaWidget):
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-            ('command',            None,         None),
-            ('orient',             'vertical',   INITOPT),
-            ('separatorrelief',    'sunken',     INITOPT),
-            ('separatorthickness', 2,            INITOPT),
-            ('handlesize',         8,            INITOPT),
-            ('hull_width',         400,          None),
-            ('hull_height',        400,          None),
-	)
-	self.defineoptions(kw, optiondefs,
-                dynamicGroups = ('Frame', 'Separator', 'Handle'))
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	self.bind('<Configure>', self._handleConfigure)
-
-	if self['orient'] not in ('horizontal', 'vertical'):
-	    raise ValueError, 'bad orient option ' + repr(self['orient']) + \
-		': must be either \'horizontal\' or \'vertical\''
-
-        self._separatorThickness = self['separatorthickness']
-        self._handleSize = self['handlesize']
-	self._paneNames = []            # List of pane names
-	self._paneAttrs = {}            # Map from pane name to pane info
-
-	self._timerId = None
-	self._frame = {}
-	self._separator = []
-	self._button = []
-	self._totalSize = 0
-	self._movePending = 0
-	self._relsize = {}
-	self._relmin = {}
-	self._relmax = {}
-	self._size = {}
-	self._min = {}
-	self._max = {}
-	self._rootp = None
-	self._curSize = None
-	self._beforeLimit = None
-	self._afterLimit = None
-	self._buttonIsDown = 0
-	self._majorSize = 100
-	self._minorSize = 100
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def insert(self, name, before = 0, **kw):
-	# Parse <kw> for options.
-        self._initPaneOptions(name)
-	self._parsePaneOptions(name, kw)
-
-	insertPos = self._nameToIndex(before)
-	atEnd = (insertPos == len(self._paneNames))
-
-	# Add the frame.
-	self._paneNames[insertPos:insertPos] = [name]
-	self._frame[name] = self.createcomponent(name,
-		(), 'Frame',
-		Tkinter.Frame, (self.interior(),))
-
-	# Add separator, if necessary.
-	if len(self._paneNames) > 1:
-	    self._addSeparator()
-	else:
-	    self._separator.append(None)
-	    self._button.append(None)
-
-	# Add the new frame and adjust the PanedWidget
-	if atEnd:
-	    size = self._size[name]
-	    if size > 0 or self._relsize[name] is not None:
-		if self['orient'] == 'vertical':
-		    self._frame[name].place(x=0, relwidth=1,
-					    height=size, y=self._totalSize)
-		else:
-		    self._frame[name].place(y=0, relheight=1,
-					    width=size, x=self._totalSize)
-	    else:
-		if self['orient'] == 'vertical':
-		    self._frame[name].place(x=0, relwidth=1,
-					    y=self._totalSize)
-		else:
-		    self._frame[name].place(y=0, relheight=1,
-					    x=self._totalSize)
-	else:
-	    self._updateSizes()
-
-	self._totalSize = self._totalSize + self._size[name]
-	return self._frame[name]
-
-    def add(self, name, **kw):
-        return apply(self.insert, (name, len(self._paneNames)), kw)
-
-    def delete(self, name):
-	deletePos = self._nameToIndex(name)
-	name = self._paneNames[deletePos]
-	self.destroycomponent(name)
-	del self._paneNames[deletePos]
-	del self._frame[name]
-	del self._size[name]
-	del self._min[name]
-	del self._max[name]
-	del self._relsize[name]
-	del self._relmin[name]
-	del self._relmax[name]
-
-	last = len(self._paneNames)
-	del self._separator[last]
-	del self._button[last]
-        if last > 0:
-            self.destroycomponent(self._sepName(last))
-            self.destroycomponent(self._buttonName(last))
-
-	self._plotHandles()
-
-    def setnaturalsize(self):
-        self.update_idletasks()
-        totalWidth = 0
-        totalHeight = 0
-        maxWidth = 0
-        maxHeight = 0
-	for name in self._paneNames:
-            frame = self._frame[name]
-            w = frame.winfo_reqwidth()
-            h = frame.winfo_reqheight()
-            totalWidth = totalWidth + w
-            totalHeight = totalHeight + h
-            if maxWidth < w:
-                maxWidth = w
-            if maxHeight < h:
-                maxHeight = h
-
-        # Note that, since the hull is a frame, the width and height
-        # options specify the geometry *outside* the borderwidth and
-        # highlightthickness.
-        bw = string.atoi(str(self.cget('hull_borderwidth')))
-        hl = string.atoi(str(self.cget('hull_highlightthickness')))
-        extra = (bw + hl) * 2
-        if str(self.cget('orient')) == 'horizontal':
-            totalWidth = totalWidth + extra
-            maxHeight = maxHeight + extra
-            self.configure(hull_width = totalWidth, hull_height = maxHeight)
-        else:
-            totalHeight = (totalHeight + extra +
-                    (len(self._paneNames) - 1) * self._separatorThickness)
-            maxWidth = maxWidth + extra
-            self.configure(hull_width = maxWidth, hull_height = totalHeight)
-
-    def move(self, name, newPos, newPosOffset = 0):
-
-        # see if we can spare ourselves some work
-        numPanes = len(self._paneNames)
-        if numPanes < 2:
-            return
-
-        newPos = self._nameToIndex(newPos) + newPosOffset
-        if newPos < 0 or newPos >=numPanes:
-            return
-
-        deletePos = self._nameToIndex(name)
-
-        if deletePos == newPos:
-            # inserting over ourself is a no-op
-            return
-
-        # delete name from old position in list
-        name = self._paneNames[deletePos]
-        del self._paneNames[deletePos]
-
-        # place in new position
-        self._paneNames[newPos:newPos] = [name]
-
-        # force everything to redraw
-        self._plotHandles()
-        self._updateSizes()
-
-    def _nameToIndex(self, nameOrIndex):
-	try:
-	    pos = self._paneNames.index(nameOrIndex)
-	except ValueError:
-	    pos = nameOrIndex
-
-	return pos
-
-    def _initPaneOptions(self, name):
-	# Set defaults.
-	self._size[name] = 0
-	self._relsize[name] = None
-	self._min[name] = 0
-	self._relmin[name] = None
-	self._max[name] = 100000
-	self._relmax[name] = None
-
-    def _parsePaneOptions(self, name, args):
-	# Parse <args> for options.
-	for arg, value in args.items():
-	    if type(value) == types.FloatType:
-		relvalue = value
-		value = self._absSize(relvalue)
-	    else:
-		relvalue = None
-
-	    if arg == 'size':
-		self._size[name], self._relsize[name] = value, relvalue
-	    elif arg == 'min':
-		self._min[name], self._relmin[name] = value, relvalue
-	    elif arg == 'max':
-		self._max[name], self._relmax[name] = value, relvalue
-	    else:
-		raise ValueError, 'keyword must be "size", "min", or "max"'
-
-    def _absSize(self, relvalue):
-	return int(round(relvalue * self._majorSize))
-
-    def _sepName(self, n):
-	return 'separator-%d' % n
-
-    def _buttonName(self, n):
-	return 'handle-%d' % n
-
-    def _addSeparator(self):
-	n = len(self._paneNames) - 1
-
-	downFunc = lambda event, s = self, num=n: s._btnDown(event, num)
-	upFunc = lambda event, s = self, num=n: s._btnUp(event, num)
-	moveFunc = lambda event, s = self, num=n: s._btnMove(event, num)
-
-	# Create the line dividing the panes.
-	sep = self.createcomponent(self._sepName(n),
-		(), 'Separator',
-		Tkinter.Frame, (self.interior(),),
-		borderwidth = 1,
-		relief = self['separatorrelief'])
-	self._separator.append(sep)
-
-	sep.bind('<ButtonPress-1>', downFunc)
-	sep.bind('<Any-ButtonRelease-1>', upFunc)
-	sep.bind('<B1-Motion>', moveFunc)
-
-	if self['orient'] == 'vertical':
-	    cursor = 'sb_v_double_arrow'
-	    sep.configure(height = self._separatorThickness,
-                    width = 10000, cursor = cursor)
-	else:
-	    cursor = 'sb_h_double_arrow'
-	    sep.configure(width = self._separatorThickness,
-                    height = 10000, cursor = cursor)
-
-	self._totalSize = self._totalSize + self._separatorThickness
-
-	# Create the handle on the dividing line.
-	handle = self.createcomponent(self._buttonName(n),
-		(), 'Handle',
-		Tkinter.Frame, (self.interior(),),
-		    relief = 'raised',
-		    borderwidth = 1,
-		    width = self._handleSize,
-		    height = self._handleSize,
-		    cursor = cursor,
-		)
-	self._button.append(handle)
-
-	handle.bind('<ButtonPress-1>', downFunc)
-	handle.bind('<Any-ButtonRelease-1>', upFunc)
-	handle.bind('<B1-Motion>', moveFunc)
-
-	self._plotHandles()
-
-	for i in range(1, len(self._paneNames)):
-	    self._separator[i].tkraise()
-	for i in range(1, len(self._paneNames)):
-	    self._button[i].tkraise()
-
-    def _btnUp(self, event, item):
-	self._buttonIsDown = 0
-	self._updateSizes()
-	try:
-	    self._button[item].configure(relief='raised')
-	except:
-	    pass
-
-    def _btnDown(self, event, item):
-	self._button[item].configure(relief='sunken')
-	self._getMotionLimit(item)
-	self._buttonIsDown = 1
-	self._movePending = 0
-
-    def _handleConfigure(self, event = None):
-	self._getNaturalSizes()
-	if self._totalSize == 0:
-	    return
-
-	iterRange = list(self._paneNames)
-	iterRange.reverse()
-	if self._majorSize > self._totalSize:
-	    n = self._majorSize - self._totalSize
-	    self._iterate(iterRange, self._grow, n)
-	elif self._majorSize < self._totalSize:
-	    n = self._totalSize - self._majorSize
-	    self._iterate(iterRange, self._shrink, n)
-
-	self._plotHandles()
-	self._updateSizes()
-
-    def _getNaturalSizes(self):
-	# Must call this in order to get correct winfo_width, winfo_height
-	self.update_idletasks()
-
-	self._totalSize = 0
-
-	if self['orient'] == 'vertical':
-	    self._majorSize = self.winfo_height()
-	    self._minorSize = self.winfo_width()
-	    majorspec = Tkinter.Frame.winfo_reqheight
-	else:
-	    self._majorSize = self.winfo_width()
-	    self._minorSize = self.winfo_height()
-	    majorspec = Tkinter.Frame.winfo_reqwidth
-
-        bw = string.atoi(str(self.cget('hull_borderwidth')))
-        hl = string.atoi(str(self.cget('hull_highlightthickness')))
-        extra = (bw + hl) * 2
-        self._majorSize = self._majorSize - extra
-        self._minorSize = self._minorSize - extra
-
-	if self._majorSize < 0:
-	    self._majorSize = 0
-	if self._minorSize < 0:
-	    self._minorSize = 0
-
-	for name in self._paneNames:
-	    # adjust the absolute sizes first...
-	    if self._relsize[name] is None:
-		#special case
-		if self._size[name] == 0:
-		    self._size[name] = apply(majorspec, (self._frame[name],))
-		    self._setrel(name)
-	    else:
-		self._size[name] = self._absSize(self._relsize[name])
-
-	    if self._relmin[name] is not None:
-		self._min[name] = self._absSize(self._relmin[name])
-	    if self._relmax[name] is not None:
-		self._max[name] = self._absSize(self._relmax[name])
-
-	    # now adjust sizes
-	    if self._size[name] < self._min[name]:
-		self._size[name] = self._min[name]
-		self._setrel(name)
-
-	    if self._size[name] > self._max[name]:
-		self._size[name] = self._max[name]
-		self._setrel(name)
-
-	    self._totalSize = self._totalSize + self._size[name]
-
-	# adjust for separators
-	self._totalSize = (self._totalSize +
-                (len(self._paneNames) - 1) * self._separatorThickness)
-
-    def _setrel(self, name):
-	if self._relsize[name] is not None:
-	    if self._majorSize != 0:
-		self._relsize[name] = round(self._size[name]) / self._majorSize
-
-    def _iterate(self, names, proc, n):
-	for i in names:
-	    n = apply(proc, (i, n))
-	    if n == 0:
-		break
-
-    def _grow(self, name, n):
-	canGrow = self._max[name] - self._size[name]
-
-	if canGrow > n:
-	    self._size[name] = self._size[name] + n
-	    self._setrel(name)
-	    return 0
-	elif canGrow > 0:
-	    self._size[name] = self._max[name]
-	    self._setrel(name)
-	    n = n - canGrow
-
-	return n
-
-    def _shrink(self, name, n):
-	canShrink = self._size[name] - self._min[name]
-
-	if canShrink > n:
-	    self._size[name] = self._size[name] - n
-	    self._setrel(name)
-	    return 0
-	elif canShrink > 0:
-	    self._size[name] = self._min[name]
-	    self._setrel(name)
-	    n = n - canShrink
-
-	return n
-
-    def _updateSizes(self):
-	totalSize = 0
-
-	for name in self._paneNames:
-	    size = self._size[name]
-	    if self['orient'] == 'vertical':
-		self._frame[name].place(x = 0, relwidth = 1,
-					y = totalSize,
-					height = size)
-	    else:
-		self._frame[name].place(y = 0, relheight = 1,
-					x = totalSize,
-					width = size)
-
-	    totalSize = totalSize + size + self._separatorThickness
-
-	# Invoke the callback command
-	cmd = self['command']
-	if callable(cmd):
-	    cmd(map(lambda x, s = self: s._size[x], self._paneNames))
-
-    def _plotHandles(self):
-	if len(self._paneNames) == 0:
-	    return
-
-	if self['orient'] == 'vertical':
-	    btnp = self._minorSize - 13
-	else:
-	    h = self._minorSize
-
-	    if h > 18:
-		btnp = 9
-	    else:
-		btnp = h - 9
-
-	firstPane = self._paneNames[0]
-	totalSize = self._size[firstPane]
-
-	first = 1
-	last = len(self._paneNames) - 1
-
-	# loop from first to last, inclusive
-	for i in range(1, last + 1):
-
-	    handlepos = totalSize - 3
-	    prevSize = self._size[self._paneNames[i - 1]]
-	    nextSize = self._size[self._paneNames[i]]
-
-	    offset1 = 0
-
-	    if i == first:
-		if prevSize < 4:
-		    offset1 = 4 - prevSize
-	    else:
-		if prevSize < 8:
-		    offset1 = (8 - prevSize) / 2
-
-	    offset2 = 0
-
-	    if i == last:
-		if nextSize < 4:
-		    offset2 = nextSize - 4
-	    else:
-		if nextSize < 8:
-		    offset2 = (nextSize - 8) / 2
-
-	    handlepos = handlepos + offset1
-
-	    if self['orient'] == 'vertical':
-		height = 8 - offset1 + offset2
-
-		if height > 1:
-		    self._button[i].configure(height = height)
-		    self._button[i].place(x = btnp, y = handlepos)
-		else:
-		    self._button[i].place_forget()
-
-		self._separator[i].place(x = 0, y = totalSize,
-					 relwidth = 1)
-	    else:
-		width = 8 - offset1 + offset2
-
-		if width > 1:
-		    self._button[i].configure(width = width)
-		    self._button[i].place(y = btnp, x = handlepos)
-		else:
-		    self._button[i].place_forget()
-
-		self._separator[i].place(y = 0, x = totalSize,
-					 relheight = 1)
-
-	    totalSize = totalSize + nextSize + self._separatorThickness
-
-    def pane(self, name):
-	return self._frame[self._paneNames[self._nameToIndex(name)]]
-
-    # Return the name of all panes
-    def panes(self):
-	return list(self._paneNames)
-
-    def configurepane(self, name, **kw):
-	name = self._paneNames[self._nameToIndex(name)]
-	self._parsePaneOptions(name, kw)
-	self._handleConfigure()
-
-    def updatelayout(self):
-	self._handleConfigure()
-
-    def _getMotionLimit(self, item):
-	curBefore = (item - 1) * self._separatorThickness
-	minBefore, maxBefore = curBefore, curBefore
-
-	for name in self._paneNames[:item]:
-	    curBefore = curBefore + self._size[name]
-	    minBefore = minBefore + self._min[name]
-	    maxBefore = maxBefore + self._max[name]
-
-	curAfter = (len(self._paneNames) - item) * self._separatorThickness
-	minAfter, maxAfter = curAfter, curAfter
-	for name in self._paneNames[item:]:
-	    curAfter = curAfter + self._size[name]
-	    minAfter = minAfter + self._min[name]
-	    maxAfter = maxAfter + self._max[name]
-
-	beforeToGo = min(curBefore - minBefore, maxAfter - curAfter)
-	afterToGo = min(curAfter - minAfter, maxBefore - curBefore)
-
-	self._beforeLimit = curBefore - beforeToGo
-	self._afterLimit = curBefore + afterToGo
-	self._curSize = curBefore
-
-	self._plotHandles()
-
-    # Compress the motion so that update is quick even on slow machines
-    #
-    # theRootp = root position (either rootx or rooty)
-    def _btnMove(self, event, item):
-	self._rootp = event
-
-	if self._movePending == 0:
-	    self._timerId = self.after_idle(
-		    lambda s = self, i = item: s._btnMoveCompressed(i))
-	    self._movePending = 1
-
-    def destroy(self):
-        if self._timerId is not None:
-          self.after_cancel(self._timerId)
-	  self._timerId = None
-        MegaWidget.destroy(self)
-
-    def _btnMoveCompressed(self, item):
-	if not self._buttonIsDown:
-	    return
-
-	if self['orient'] == 'vertical':
-	    p = self._rootp.y_root - self.winfo_rooty()
-	else:
-	    p = self._rootp.x_root - self.winfo_rootx()
-
-	if p == self._curSize:
-	    self._movePending = 0
-	    return
-
-	if p < self._beforeLimit:
-	    p = self._beforeLimit
-
-	if p >= self._afterLimit:
-	    p = self._afterLimit
-
-	self._calculateChange(item, p)
-	self.update_idletasks()
-	self._movePending = 0
-
-    # Calculate the change in response to mouse motions
-    def _calculateChange(self, item, p):
-
-	if p < self._curSize:
-	    self._moveBefore(item, p)
-	elif p > self._curSize:
-	    self._moveAfter(item, p)
-
-	self._plotHandles()
-
-    def _moveBefore(self, item, p):
-	n = self._curSize - p
-
-	# Shrink the frames before
-	iterRange = list(self._paneNames[:item])
-	iterRange.reverse()
-	self._iterate(iterRange, self._shrink, n)
-
-	# Adjust the frames after
-	iterRange = self._paneNames[item:]
-	self._iterate(iterRange, self._grow, n)
-
-	self._curSize = p
-
-    def _moveAfter(self, item, p):
-	n = p - self._curSize
-
-	# Shrink the frames after
-	iterRange = self._paneNames[item:]
-	self._iterate(iterRange, self._shrink, n)
-
-	# Adjust the frames before
-	iterRange = list(self._paneNames[:item])
-	iterRange.reverse()
-	self._iterate(iterRange, self._grow, n)
-
-	self._curSize = p
-
-######################################################################
-### File: PmwPromptDialog.py
-# Based on iwidgets2.2.0/promptdialog.itk code.
-
-
-
-# A Dialog with an entryfield
-
-class PromptDialog(Dialog):
-    def __init__(self, parent = None, **kw):
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderx',     20,    INITOPT),
-	    ('bordery',     20,    INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	Dialog.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-	aliases = (
-	    ('entry', 'entryfield_entry'),
-	    ('label', 'entryfield_label'),
-	)
-	self._promptDialogEntry = self.createcomponent('entryfield',
-		aliases, None,
-		EntryField, (interior,))
-	self._promptDialogEntry.pack(fill='x', expand=1,
-		padx = self['borderx'], pady = self['bordery'])
-	
-        if not kw.has_key('activatecommand'):
-            # Whenever this dialog is activated, set the focus to the
-            # EntryField's entry widget.
-            tkentry = self.component('entry')
-            self.configure(activatecommand = tkentry.focus_set)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    # Supply aliases to some of the entry component methods.
-    def insertentry(self, index, text):
-	self._promptDialogEntry.insert(index, text)
-
-    def deleteentry(self, first, last=None):
-	self._promptDialogEntry.delete(first, last)
-
-    def indexentry(self, index):
-	return self._promptDialogEntry.index(index)
-
-forwardmethods(PromptDialog, EntryField, '_promptDialogEntry')
-
-######################################################################
-### File: PmwRadioSelect.py
-import types
-import Tkinter
-
-
-class RadioSelect(MegaWidget):
-    # A collection of several buttons.  In single mode, only one
-    # button may be selected.  In multiple mode, any number of buttons
-    # may be selected.
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('buttontype',    'button',      INITOPT),
-	    ('command',       None,          None),
-	    ('labelmargin',   0,             INITOPT),
-	    ('labelpos',      None,          INITOPT),
-	    ('orient',       'horizontal',   INITOPT),
-	    ('padx',          5,             INITOPT),
-	    ('pady',          5,             INITOPT),
-	    ('selectmode',    'single',      INITOPT),
-	)
-	self.defineoptions(kw, optiondefs, dynamicGroups = ('Button',))
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-	if self['labelpos'] is None:
-	    self._radioSelectFrame = self._hull
-	else:
-	    self._radioSelectFrame = self.createcomponent('frame',
-		    (), None,
-		    Tkinter.Frame, (interior,))
-	    self._radioSelectFrame.grid(column=2, row=2, sticky='nsew')
-	    interior.grid_columnconfigure(2, weight=1)
-	    interior.grid_rowconfigure(2, weight=1)
-
-	    self.createlabel(interior)
-
-	# Initialise instance variables.
-	self._buttonList = []
-	if self['selectmode'] == 'single':
-	    self._singleSelect = 1
-	elif self['selectmode'] == 'multiple':
-	    self._singleSelect = 0
-	else: 
-	    raise ValueError, 'bad selectmode option "' + \
-		    self['selectmode'] + '": should be single or multiple'
-
-	if self['buttontype'] == 'button':
-	    self.buttonClass = Tkinter.Button
-	elif self['buttontype'] == 'radiobutton':
-	    self._singleSelect = 1
-	    self.var = Tkinter.StringVar()
-	    self.buttonClass = Tkinter.Radiobutton
-	elif self['buttontype'] == 'checkbutton':
-	    self._singleSelect = 0
-	    self.buttonClass = Tkinter.Checkbutton
-	else:
-	    raise ValueError, 'bad buttontype option "' + \
-		    self['buttontype'] + \
-		    '": should be button, radiobutton or checkbutton'
-
-	if self._singleSelect:
-	    self.selection = None
-	else:
-	    self.selection = []
-
-	if self['orient'] not in ('horizontal', 'vertical'):
-	    raise ValueError, 'bad orient option ' + repr(self['orient']) + \
-		': must be either \'horizontal\' or \'vertical\''
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def getcurselection(self):
-	if self._singleSelect:
-            return self.selection
-        else:
-            return tuple(self.selection)
-
-    def getvalue(self):
-        return self.getcurselection()
-
-    def setvalue(self, textOrList):
-	if self._singleSelect:
-            self.__setSingleValue(textOrList)
-        else:
-	    # Multiple selections
-            oldselection = self.selection
-            self.selection = textOrList
-            for button in self._buttonList:
-                if button in oldselection:
-                    if button not in self.selection:
-                        # button is currently selected but should not be
-                        widget = self.component(button)
-                        if self['buttontype'] == 'checkbutton':
-                            widget.deselect()
-                        else:  # Button
-                            widget.configure(relief='raised')
-                else:
-                    if button in self.selection:
-                        # button is not currently selected but should be
-                        widget = self.component(button)
-                        if self['buttontype'] == 'checkbutton':
-                            widget.select()
-                        else:  # Button
-                            widget.configure(relief='sunken')
-
-    def numbuttons(self):
-        return len(self._buttonList)
-
-    def index(self, index):
-	# Return the integer index of the button with the given index.
-
-	listLength = len(self._buttonList)
-	if type(index) == types.IntType:
-	    if index < listLength:
-		return index
-	    else:
-		raise ValueError, 'index "%s" is out of range' % index
-	elif index is END:
-	    if listLength > 0:
-		return listLength - 1
-	    else:
-		raise ValueError, 'RadioSelect has no buttons'
-	else:
-	    for count in range(listLength):
-		name = self._buttonList[count]
-		if index == name:
-		    return count
-	    validValues = 'a name, a number or END'
-	    raise ValueError, \
-		    'bad index "%s": must be %s' % (index, validValues)
-
-    def button(self, buttonIndex):
-	name = self._buttonList[self.index(buttonIndex)]
-        return self.component(name)
-
-    def add(self, componentName, **kw):
-	if componentName in self._buttonList:
-	    raise ValueError, 'button "%s" already exists' % componentName
-
-	kw['command'] = \
-                lambda self=self, name=componentName: self.invoke(name)
-	if not kw.has_key('text'):
-	    kw['text'] = componentName
-
-	if self['buttontype'] == 'radiobutton':
-	    if not kw.has_key('anchor'):
-		kw['anchor'] = 'w'
-	    if not kw.has_key('variable'):
-		kw['variable'] = self.var
-	    if not kw.has_key('value'):
-		kw['value'] = kw['text']
-	elif self['buttontype'] == 'checkbutton':
-	    if not kw.has_key('anchor'):
-		kw['anchor'] = 'w'
-
-	button = apply(self.createcomponent, (componentName,
-		(), 'Button',
-		self.buttonClass, (self._radioSelectFrame,)), kw)
-
-	if self['orient'] == 'horizontal':
-	    self._radioSelectFrame.grid_rowconfigure(0, weight=1)
-	    col = len(self._buttonList)
-	    button.grid(column=col, row=0, padx = self['padx'],
-		    pady = self['pady'], sticky='nsew')
-	    self._radioSelectFrame.grid_columnconfigure(col, weight=1)
-	else:
-	    self._radioSelectFrame.grid_columnconfigure(0, weight=1)
-	    row = len(self._buttonList)
-	    button.grid(column=0, row=row, padx = self['padx'],
-		    pady = self['pady'], sticky='ew')
-	    self._radioSelectFrame.grid_rowconfigure(row, weight=1)
-
-	self._buttonList.append(componentName)
-	return button
-
-    def deleteall(self):
-	for name in self._buttonList:
-	    self.destroycomponent(name)
-	self._buttonList = []
-	if self._singleSelect:
-	    self.selection = None
-	else: 
-	    self.selection = []
-
-    def __setSingleValue(self, value):
-            self.selection = value
-            if self['buttontype'] == 'radiobutton':
-                widget = self.component(value)
-                widget.select()
-            else:  # Button
-                for button in self._buttonList:
-                    widget = self.component(button)
-                    if button == value:
-                        widget.configure(relief='sunken')
-                    else:
-                        widget.configure(relief='raised')
-
-    def invoke(self, index):
-	index = self.index(index)
-	name = self._buttonList[index]
-
-	if self._singleSelect:
-            self.__setSingleValue(name)
-	    command = self['command']
-	    if callable(command):
-		return command(name)
-        else:
-	    # Multiple selections
-	    widget = self.component(name)
-	    if name in self.selection:
-		if self['buttontype'] == 'checkbutton':
-		    widget.deselect()
-		else:
-		    widget.configure(relief='raised')
-		self.selection.remove(name)
-		state = 0
-	    else:
-		if self['buttontype'] == 'checkbutton':
-		    widget.select()
-		else:
-		    widget.configure(relief='sunken')
-		self.selection.append(name)
-		state = 1
-
-	    command = self['command']
-	    if callable(command):
-	      return command(name, state)
-
-######################################################################
-### File: PmwScrolledCanvas.py
-import Tkinter
-
-
-class ScrolledCanvas(MegaWidget):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderframe',    0,            INITOPT),
-	    ('canvasmargin',   0,            INITOPT),
-	    ('hscrollmode',    'dynamic',    self._hscrollMode),
-	    ('labelmargin',    0,            INITOPT),
-	    ('labelpos',       None,         INITOPT),
-	    ('scrollmargin',   2,            INITOPT),
-	    ('usehullsize',    0,            INITOPT),
-	    ('vscrollmode',    'dynamic',    self._vscrollMode),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	self.origInterior = MegaWidget.interior(self)
-
-	if self['usehullsize']:
-	    self.origInterior.grid_propagate(0)
-
-	if self['borderframe']:
-	    # Create a frame widget to act as the border of the canvas. 
-	    self._borderframe = self.createcomponent('borderframe',
-		    (), None,
-		    Tkinter.Frame, (self.origInterior,),
-		    relief = 'sunken',
-		    borderwidth = 2,
-	    )
-	    self._borderframe.grid(row = 2, column = 2, sticky = 'news')
-
-	    # Create the canvas widget.
-	    self._canvas = self.createcomponent('canvas',
-		    (), None,
-		    Tkinter.Canvas, (self._borderframe,),
-		    highlightthickness = 0,
-		    borderwidth = 0,
-	    )
-	    self._canvas.pack(fill = 'both', expand = 1)
-	else:
-	    # Create the canvas widget.
-	    self._canvas = self.createcomponent('canvas',
-		    (), None,
-		    Tkinter.Canvas, (self.origInterior,),
-		    relief = 'sunken',
-		    borderwidth = 2,
-	    )
-	    self._canvas.grid(row = 2, column = 2, sticky = 'news')
-
-	self.origInterior.grid_rowconfigure(2, weight = 1, minsize = 0)
-	self.origInterior.grid_columnconfigure(2, weight = 1, minsize = 0)
-	
-	# Create the horizontal scrollbar
-	self._horizScrollbar = self.createcomponent('horizscrollbar',
-		(), 'Scrollbar',
-		Tkinter.Scrollbar, (self.origInterior,),
-	        orient='horizontal',
-		command=self._canvas.xview
-	)
-
-	# Create the vertical scrollbar
-	self._vertScrollbar = self.createcomponent('vertscrollbar',
-		(), 'Scrollbar',
-		Tkinter.Scrollbar, (self.origInterior,),
-		orient='vertical',
-		command=self._canvas.yview
-	)
-
-	self.createlabel(self.origInterior, childCols = 3, childRows = 3)
-
-	# Initialise instance variables.
-	self._horizScrollbarOn = 0
-	self._vertScrollbarOn = 0
-	self.scrollTimer = None
-        self._scrollRecurse = 0
-	self._horizScrollbarNeeded = 0
-	self._vertScrollbarNeeded = 0
-	self.setregionTimer = None
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	if self.scrollTimer is not None:
-	    self.after_cancel(self.scrollTimer)
-	    self.scrollTimer = None
-	if self.setregionTimer is not None:
-	    self.after_cancel(self.setregionTimer)
-	    self.setregionTimer = None
-	MegaWidget.destroy(self)
-
-    # ======================================================================
-
-    # Public methods.
-
-    def interior(self):
-	return self._canvas
-
-    def resizescrollregion(self):
-	if self.setregionTimer is None:
-	    self.setregionTimer = self.after_idle(self._setRegion)
-
-    # ======================================================================
-
-    # Configuration methods.
-
-    def _hscrollMode(self):
-	# The horizontal scroll mode has been configured.
-
-	mode = self['hscrollmode']
-
-	if mode == 'static':
-	    if not self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	elif mode == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	elif mode == 'none':
-	    if self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	else:
-	    message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
-	    raise ValueError, message
-
-        self._configureScrollCommands()
-
-    def _vscrollMode(self):
-	# The vertical scroll mode has been configured.
-
-	mode = self['vscrollmode']
-
-	if mode == 'static':
-	    if not self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	elif mode == 'dynamic':
-	    if self._vertScrollbarNeeded != self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	elif mode == 'none':
-	    if self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	else:
-	    message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
-	    raise ValueError, message
-
-        self._configureScrollCommands()
-
-    # ======================================================================
-
-    # Private methods.
-
-    def _configureScrollCommands(self):
-        # If both scrollmodes are not dynamic we can save a lot of
-        # time by not having to create an idle job to handle the
-        # scroll commands.
-
-        # Clean up previous scroll commands to prevent memory leak.
-        tclCommandName = str(self._canvas.cget('xscrollcommand'))
-        if tclCommandName != '':   
-            self._canvas.deletecommand(tclCommandName)
-        tclCommandName = str(self._canvas.cget('yscrollcommand'))
-        if tclCommandName != '':   
-            self._canvas.deletecommand(tclCommandName)
-
-	if self['hscrollmode'] == self['vscrollmode'] == 'dynamic':
-            self._canvas.configure(
-                    xscrollcommand=self._scrollBothLater,
-                    yscrollcommand=self._scrollBothLater
-            )
-        else:
-            self._canvas.configure(
-                    xscrollcommand=self._scrollXNow,
-                    yscrollcommand=self._scrollYNow
-            )
-
-    def _scrollXNow(self, first, last):
-        self._horizScrollbar.set(first, last)
-        self._horizScrollbarNeeded = ((first, last) != ('0', '1'))
-
-	if self['hscrollmode'] == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-
-    def _scrollYNow(self, first, last):
-        self._vertScrollbar.set(first, last)
-        self._vertScrollbarNeeded = ((first, last) != ('0', '1'))
-
-        if self['vscrollmode'] == 'dynamic':
-            if self._vertScrollbarNeeded != self._vertScrollbarOn:
-                self._toggleVertScrollbar()
-
-    def _scrollBothLater(self, first, last):
-	# Called by the canvas to set the horizontal or vertical
-	# scrollbar when it has scrolled or changed scrollregion.
-
-	if self.scrollTimer is None:
-	    self.scrollTimer = self.after_idle(self._scrollBothNow)
-
-    def _scrollBothNow(self):
-        # This performs the function of _scrollXNow and _scrollYNow.
-        # If one is changed, the other should be updated to match.
-	self.scrollTimer = None
-
-        # Call update_idletasks to make sure that the containing frame
-        # has been resized before we attempt to set the scrollbars. 
-        # Otherwise the scrollbars may be mapped/unmapped continuously.
-        self._scrollRecurse = self._scrollRecurse + 1
-        self.update_idletasks()
-        self._scrollRecurse = self._scrollRecurse - 1
-        if self._scrollRecurse != 0:
-            return
-
-	xview = self._canvas.xview()
-	yview = self._canvas.yview()
-	self._horizScrollbar.set(xview[0], xview[1])
-	self._vertScrollbar.set(yview[0], yview[1])
-
-	self._horizScrollbarNeeded = (xview != (0.0, 1.0))
-	self._vertScrollbarNeeded = (yview != (0.0, 1.0))
-
-	# If both horizontal and vertical scrollmodes are dynamic and
-	# currently only one scrollbar is mapped and both should be
-	# toggled, then unmap the mapped scrollbar.  This prevents a
-	# continuous mapping and unmapping of the scrollbars. 
-	if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
-		self._horizScrollbarNeeded != self._horizScrollbarOn and
-		self._vertScrollbarNeeded != self._vertScrollbarOn and
-		self._vertScrollbarOn != self._horizScrollbarOn):
-	    if self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	    else:
-		self._toggleVertScrollbar()
-	    return
-
-	if self['hscrollmode'] == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-
-	if self['vscrollmode'] == 'dynamic':
-	    if self._vertScrollbarNeeded != self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-
-    def _toggleHorizScrollbar(self):
-
-	self._horizScrollbarOn = not self._horizScrollbarOn
-
-	interior = self.origInterior
-	if self._horizScrollbarOn:
-	    self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news')
-	    interior.grid_rowconfigure(3, minsize = self['scrollmargin'])
-	else:
-	    self._horizScrollbar.grid_forget()
-	    interior.grid_rowconfigure(3, minsize = 0)
-
-    def _toggleVertScrollbar(self):
-
-	self._vertScrollbarOn = not self._vertScrollbarOn
-
-	interior = self.origInterior
-	if self._vertScrollbarOn:
-	    self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news')
-	    interior.grid_columnconfigure(3, minsize = self['scrollmargin'])
-	else:
-	    self._vertScrollbar.grid_forget()
-	    interior.grid_columnconfigure(3, minsize = 0)
-
-    def _setRegion(self):
-	self.setregionTimer = None
-
-	region = self._canvas.bbox('all')
-        if region is not None:
-	    canvasmargin = self['canvasmargin']
-	    region = (region[0] - canvasmargin, region[1] - canvasmargin,
-		region[2] + canvasmargin, region[3] + canvasmargin)
-	    self._canvas.configure(scrollregion = region)
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)bbox method inherited from Tkinter.Frame.Grid.
-    def bbox(self, *args):
-	return apply(self._canvas.bbox, args)
-
-forwardmethods(ScrolledCanvas, Tkinter.Canvas, '_canvas')
-
-######################################################################
-### File: PmwScrolledField.py
-import Tkinter
-
-
-class ScrolledField(MegaWidget):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('labelmargin',   0,      INITOPT),
-	    ('labelpos',      None,   INITOPT),
-	    ('sticky',        'ew',   INITOPT),
-	    ('text',          '',     self._text),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-	self._scrolledFieldEntry = self.createcomponent('entry',
-		(), None,
-		Tkinter.Entry, (interior,))
-
-        # Can't always use 'disabled', since this greys out text in Tk 8.4.2
-        try:
-            self._scrolledFieldEntry.configure(state = 'readonly')
-        except Tkinter.TclError:
-            self._scrolledFieldEntry.configure(state = 'disabled')
-
-	self._scrolledFieldEntry.grid(column=2, row=2, sticky=self['sticky'])
-	interior.grid_columnconfigure(2, weight=1)
-	interior.grid_rowconfigure(2, weight=1)
-
-	self.createlabel(interior)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def _text(self):
-        text = self['text']
-        self._scrolledFieldEntry.configure(state = 'normal')
-        self._scrolledFieldEntry.delete(0, 'end')
-        self._scrolledFieldEntry.insert('end', text)
-
-        # Can't always use 'disabled', since this greys out text in Tk 8.4.2
-        try:
-            self._scrolledFieldEntry.configure(state = 'readonly')
-        except Tkinter.TclError:
-            self._scrolledFieldEntry.configure(state = 'disabled')
-
-forwardmethods(ScrolledField, Tkinter.Entry, '_scrolledFieldEntry')
-
-######################################################################
-### File: PmwScrolledFrame.py
-import string
-import types
-import Tkinter
-
-
-class ScrolledFrame(MegaWidget):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderframe',    1,            INITOPT),
-	    ('horizflex',      'fixed',      self._horizflex),
-	    ('horizfraction',  0.05,         INITOPT),
-	    ('hscrollmode',    'dynamic',    self._hscrollMode),
-	    ('labelmargin',    0,            INITOPT),
-	    ('labelpos',       None,         INITOPT),
-	    ('scrollmargin',   2,            INITOPT),
-	    ('usehullsize',    0,            INITOPT),
-	    ('vertflex',       'fixed',      self._vertflex),
-	    ('vertfraction',   0.05,         INITOPT),
-	    ('vscrollmode',    'dynamic',    self._vscrollMode),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	self.origInterior = MegaWidget.interior(self)
-
-	if self['usehullsize']:
-	    self.origInterior.grid_propagate(0)
-
-	if self['borderframe']:
-	    # Create a frame widget to act as the border of the clipper. 
-	    self._borderframe = self.createcomponent('borderframe',
-		    (), None,
-		    Tkinter.Frame, (self.origInterior,),
-		    relief = 'sunken',
-		    borderwidth = 2,
-	    )
-	    self._borderframe.grid(row = 2, column = 2, sticky = 'news')
-
-	    # Create the clipping window.
-	    self._clipper = self.createcomponent('clipper',
-		    (), None,
-		    Tkinter.Frame, (self._borderframe,),
-		    width = 400,
-		    height = 300,
-		    highlightthickness = 0,
-		    borderwidth = 0,
-	    )
-	    self._clipper.pack(fill = 'both', expand = 1)
-	else:
-	    # Create the clipping window.
-	    self._clipper = self.createcomponent('clipper',
-		    (), None,
-		    Tkinter.Frame, (self.origInterior,),
-		    width = 400,
-		    height = 300,
-		    relief = 'sunken',
-		    borderwidth = 2,
-	    )
-	    self._clipper.grid(row = 2, column = 2, sticky = 'news')
-
-	self.origInterior.grid_rowconfigure(2, weight = 1, minsize = 0)
-	self.origInterior.grid_columnconfigure(2, weight = 1, minsize = 0)
-	
-	# Create the horizontal scrollbar
-	self._horizScrollbar = self.createcomponent('horizscrollbar',
-		(), 'Scrollbar',
-		Tkinter.Scrollbar, (self.origInterior,),
-	        orient='horizontal',
-		command=self.xview
-	)
-
-	# Create the vertical scrollbar
-	self._vertScrollbar = self.createcomponent('vertscrollbar',
-		(), 'Scrollbar',
-		Tkinter.Scrollbar, (self.origInterior,),
-		orient='vertical',
-		command=self.yview
-	)
-
-	self.createlabel(self.origInterior, childCols = 3, childRows = 3)
-
-	# Initialise instance variables.
-	self._horizScrollbarOn = 0
-	self._vertScrollbarOn = 0
-	self.scrollTimer = None
-	self._scrollRecurse = 0
-	self._horizScrollbarNeeded = 0
-	self._vertScrollbarNeeded = 0
-	self.startX = 0
-	self.startY = 0
-	self._flexoptions = ('fixed', 'expand', 'shrink', 'elastic')
-
-	# Create a frame in the clipper to contain the widgets to be
-	# scrolled.
-	self._frame = self.createcomponent('frame',
-		(), None,
-		Tkinter.Frame, (self._clipper,)
-	)
-
-	# Whenever the clipping window or scrolled frame change size,
-	# update the scrollbars.
-	self._frame.bind('<Configure>', self._reposition)
-	self._clipper.bind('<Configure>', self._reposition)
-
-        # Work around a bug in Tk where the value returned by the
-        # scrollbar get() method is (0.0, 0.0, 0.0, 0.0) rather than
-        # the expected 2-tuple.  This occurs if xview() is called soon
-        # after the ScrolledFrame has been created.
-        self._horizScrollbar.set(0.0, 1.0)
-        self._vertScrollbar.set(0.0, 1.0)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	if self.scrollTimer is not None:
-	    self.after_cancel(self.scrollTimer)
-	    self.scrollTimer = None
-	MegaWidget.destroy(self)
-
-    # ======================================================================
-
-    # Public methods.
-
-    def interior(self):
-	return self._frame
-
-    # Set timer to call real reposition method, so that it is not
-    # called multiple times when many things are reconfigured at the
-    # same time.
-    def reposition(self):
-	if self.scrollTimer is None:
-	    self.scrollTimer = self.after_idle(self._scrollBothNow)
-
-    # Called when the user clicks in the horizontal scrollbar. 
-    # Calculates new position of frame then calls reposition() to
-    # update the frame and the scrollbar.
-    def xview(self, mode = None, value = None, units = None):
-
-        if type(value) == types.StringType:
-            value = string.atof(value)
-        if mode is None:
-            return self._horizScrollbar.get()
-	elif mode == 'moveto':
-	    frameWidth = self._frame.winfo_reqwidth()
-	    self.startX = value * float(frameWidth)
-	else: # mode == 'scroll'
-	    clipperWidth = self._clipper.winfo_width()
-	    if units == 'units':
-		jump = int(clipperWidth * self['horizfraction'])
-	    else:
-		jump = clipperWidth
-            self.startX = self.startX + value * jump
-
-	self.reposition()
-
-    # Called when the user clicks in the vertical scrollbar. 
-    # Calculates new position of frame then calls reposition() to
-    # update the frame and the scrollbar.
-    def yview(self, mode = None, value = None, units = None):
-
-        if type(value) == types.StringType:
-            value = string.atof(value)
-        if mode is None:
-            return self._vertScrollbar.get()
-	elif mode == 'moveto':
-	    frameHeight = self._frame.winfo_reqheight()
-	    self.startY = value * float(frameHeight)
-	else: # mode == 'scroll'
-	    clipperHeight = self._clipper.winfo_height()
-	    if units == 'units':
-		jump = int(clipperHeight * self['vertfraction'])
-	    else:
-		jump = clipperHeight
-            self.startY = self.startY + value * jump
-
-	self.reposition()
-
-    # ======================================================================
-
-    # Configuration methods.
-
-    def _hscrollMode(self):
-	# The horizontal scroll mode has been configured.
-
-	mode = self['hscrollmode']
-
-	if mode == 'static':
-	    if not self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	elif mode == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	elif mode == 'none':
-	    if self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	else:
-	    message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
-	    raise ValueError, message
-
-    def _vscrollMode(self):
-	# The vertical scroll mode has been configured.
-
-	mode = self['vscrollmode']
-
-	if mode == 'static':
-	    if not self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	elif mode == 'dynamic':
-	    if self._vertScrollbarNeeded != self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	elif mode == 'none':
-	    if self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	else:
-	    message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
-	    raise ValueError, message
-
-    def _horizflex(self):
-	# The horizontal flex mode has been configured.
-
-	flex = self['horizflex']
-
-	if flex not in self._flexoptions:
-	    message = 'bad horizflex option "%s": should be one of %s' % \
-		    (flex, str(self._flexoptions))
-	    raise ValueError, message
-
-	self.reposition()
-
-    def _vertflex(self):
-	# The vertical flex mode has been configured.
-
-	flex = self['vertflex']
-
-	if flex not in self._flexoptions:
-	    message = 'bad vertflex option "%s": should be one of %s' % \
-		    (flex, str(self._flexoptions))
-	    raise ValueError, message
-
-	self.reposition()
-
-    # ======================================================================
-
-    # Private methods.
-
-    def _reposition(self, event):
-	self.reposition()
-
-    def _getxview(self):
-
-	# Horizontal dimension.
-	clipperWidth = self._clipper.winfo_width()
-	frameWidth = self._frame.winfo_reqwidth()
-	if frameWidth <= clipperWidth:
-	    # The scrolled frame is smaller than the clipping window.
-
-	    self.startX = 0
-	    endScrollX = 1.0
-
-	    if self['horizflex'] in ('expand', 'elastic'):
-		relwidth = 1
-	    else:
-		relwidth = ''
-	else:
-	    # The scrolled frame is larger than the clipping window.
-
-	    if self['horizflex'] in ('shrink', 'elastic'):
-		self.startX = 0
-		endScrollX = 1.0
-		relwidth = 1
-	    else:
-		if self.startX + clipperWidth > frameWidth:
-		    self.startX = frameWidth - clipperWidth
-		    endScrollX = 1.0
-		else:
-		    if self.startX < 0:
-			self.startX = 0
-		    endScrollX = (self.startX + clipperWidth) / float(frameWidth)
-		relwidth = ''
-
-	# Position frame relative to clipper.
-	self._frame.place(x = -self.startX, relwidth = relwidth)
-	return (self.startX / float(frameWidth), endScrollX)
-
-    def _getyview(self):
-
-	# Vertical dimension.
-	clipperHeight = self._clipper.winfo_height()
-	frameHeight = self._frame.winfo_reqheight()
-	if frameHeight <= clipperHeight:
-	    # The scrolled frame is smaller than the clipping window.
-
-	    self.startY = 0
-	    endScrollY = 1.0
-
-	    if self['vertflex'] in ('expand', 'elastic'):
-		relheight = 1
-	    else:
-		relheight = ''
-	else:
-	    # The scrolled frame is larger than the clipping window.
-
-	    if self['vertflex'] in ('shrink', 'elastic'):
-		self.startY = 0
-		endScrollY = 1.0
-		relheight = 1
-	    else:
-		if self.startY + clipperHeight > frameHeight:
-		    self.startY = frameHeight - clipperHeight
-		    endScrollY = 1.0
-		else:
-		    if self.startY < 0:
-			self.startY = 0
-		    endScrollY = (self.startY + clipperHeight) / float(frameHeight)
-		relheight = ''
-
-	# Position frame relative to clipper.
-	self._frame.place(y = -self.startY, relheight = relheight)
-	return (self.startY / float(frameHeight), endScrollY)
-
-    # According to the relative geometries of the frame and the
-    # clipper, reposition the frame within the clipper and reset the
-    # scrollbars.
-    def _scrollBothNow(self):
-	self.scrollTimer = None
-
-        # Call update_idletasks to make sure that the containing frame
-        # has been resized before we attempt to set the scrollbars. 
-        # Otherwise the scrollbars may be mapped/unmapped continuously.
-        self._scrollRecurse = self._scrollRecurse + 1
-        self.update_idletasks()
-        self._scrollRecurse = self._scrollRecurse - 1
-        if self._scrollRecurse != 0:
-            return
-
-	xview = self._getxview()
-	yview = self._getyview()
-	self._horizScrollbar.set(xview[0], xview[1])
-	self._vertScrollbar.set(yview[0], yview[1])
-
-	self._horizScrollbarNeeded = (xview != (0.0, 1.0))
-	self._vertScrollbarNeeded = (yview != (0.0, 1.0))
-
-	# If both horizontal and vertical scrollmodes are dynamic and
-	# currently only one scrollbar is mapped and both should be
-	# toggled, then unmap the mapped scrollbar.  This prevents a
-	# continuous mapping and unmapping of the scrollbars. 
-	if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
-		self._horizScrollbarNeeded != self._horizScrollbarOn and
-		self._vertScrollbarNeeded != self._vertScrollbarOn and
-		self._vertScrollbarOn != self._horizScrollbarOn):
-	    if self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	    else:
-		self._toggleVertScrollbar()
-	    return
-
-	if self['hscrollmode'] == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-
-	if self['vscrollmode'] == 'dynamic':
-	    if self._vertScrollbarNeeded != self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-
-    def _toggleHorizScrollbar(self):
-
-	self._horizScrollbarOn = not self._horizScrollbarOn
-
-	interior = self.origInterior
-	if self._horizScrollbarOn:
-	    self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news')
-	    interior.grid_rowconfigure(3, minsize = self['scrollmargin'])
-	else:
-	    self._horizScrollbar.grid_forget()
-	    interior.grid_rowconfigure(3, minsize = 0)
-
-    def _toggleVertScrollbar(self):
-
-	self._vertScrollbarOn = not self._vertScrollbarOn
-
-	interior = self.origInterior
-	if self._vertScrollbarOn:
-	    self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news')
-	    interior.grid_columnconfigure(3, minsize = self['scrollmargin'])
-	else:
-	    self._vertScrollbar.grid_forget()
-	    interior.grid_columnconfigure(3, minsize = 0)
-
-######################################################################
-### File: PmwScrolledListBox.py
-# Based on iwidgets2.2.0/scrolledlistbox.itk code.
-
-import types
-import Tkinter
-
-
-class ScrolledListBox(MegaWidget):
-    _classBindingsDefinedFor = 0
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('dblclickcommand',    None,            None),
-	    ('hscrollmode',        'dynamic',       self._hscrollMode),
-	    ('items',              (),              INITOPT),
-	    ('labelmargin',        0,               INITOPT),
-	    ('labelpos',           None,            INITOPT),
-	    ('scrollmargin',       2,               INITOPT),
-	    ('selectioncommand',   None,            None),
-	    ('usehullsize',        0,               INITOPT),
-	    ('vscrollmode',        'dynamic',       self._vscrollMode),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-
-	if self['usehullsize']:
-	    interior.grid_propagate(0)
-
-	# Create the listbox widget.
-	self._listbox = self.createcomponent('listbox',
-		(), None,
-		Tkinter.Listbox, (interior,))
-	self._listbox.grid(row = 2, column = 2, sticky = 'news')
-	interior.grid_rowconfigure(2, weight = 1, minsize = 0)
-	interior.grid_columnconfigure(2, weight = 1, minsize = 0)
-
-	# Create the horizontal scrollbar
-	self._horizScrollbar = self.createcomponent('horizscrollbar',
-		(), 'Scrollbar',
-		Tkinter.Scrollbar, (interior,),
-	        orient='horizontal',
-		command=self._listbox.xview
-	)
-
-	# Create the vertical scrollbar
-	self._vertScrollbar = self.createcomponent('vertscrollbar',
-		(), 'Scrollbar',
-		Tkinter.Scrollbar, (interior,),
-		orient='vertical',
-		command=self._listbox.yview
-	)
-
-	self.createlabel(interior, childCols = 3, childRows = 3)
-
-	# Add the items specified by the initialisation option.
-	items = self['items']
-	if type(items) != types.TupleType:
-	    items = tuple(items)
-	if len(items) > 0:
-	    apply(self._listbox.insert, ('end',) + items)
-
-	_registerScrolledList(self._listbox, self)
-
-        # Establish the special class bindings if not already done.
-        # Also create bindings if the Tkinter default interpreter has
-        # changed.  Use Tkinter._default_root to create class
-        # bindings, so that a reference to root is created by
-        # bind_class rather than a reference to self, which would
-        # prevent object cleanup.
-        theTag = 'ScrolledListBoxTag'
-        if ScrolledListBox._classBindingsDefinedFor != Tkinter._default_root:
-            root  = Tkinter._default_root
-	    	    
-            def doubleEvent(event):
-                _handleEvent(event, 'double')
-            def keyEvent(event):
-                _handleEvent(event, 'key')
-            def releaseEvent(event):
-                _handleEvent(event, 'release')
-
-            # Bind space and return keys and button 1 to the selectioncommand.
-            root.bind_class(theTag, '<Key-space>', keyEvent)
-            root.bind_class(theTag, '<Key-Return>', keyEvent)
-            root.bind_class(theTag, '<ButtonRelease-1>', releaseEvent)
-
-            # Bind double button 1 click to the dblclickcommand.
-            root.bind_class(theTag, '<Double-ButtonRelease-1>', doubleEvent)
-
-	    ScrolledListBox._classBindingsDefinedFor = root
-
-	bindtags = self._listbox.bindtags()
-	self._listbox.bindtags(bindtags + (theTag,))
-
-	# Initialise instance variables.
-	self._horizScrollbarOn = 0
-	self._vertScrollbarOn = 0
-	self.scrollTimer = None
-        self._scrollRecurse = 0
-	self._horizScrollbarNeeded = 0
-	self._vertScrollbarNeeded = 0
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	if self.scrollTimer is not None:
-	    self.after_cancel(self.scrollTimer)
-	    self.scrollTimer = None
-	_deregisterScrolledList(self._listbox)
-	MegaWidget.destroy(self)
-
-    # ======================================================================
-
-    # Public methods.
-
-    def clear(self):
-	self.setlist(())
-
-    def getcurselection(self):
-	rtn = []
-	for sel in self.curselection():
-	    rtn.append(self._listbox.get(sel))
-	return tuple(rtn)
-
-    def getvalue(self):
-        return self.getcurselection()
-
-    def setvalue(self, textOrList):
-        self._listbox.selection_clear(0, 'end')
-        listitems = list(self._listbox.get(0, 'end'))
-        if type(textOrList) == types.StringType:
-            if textOrList in listitems:
-                self._listbox.selection_set(listitems.index(textOrList))
-            else:
-                raise ValueError, 'no such item "%s"' % textOrList
-        else:
-            for item in textOrList:
-                if item in listitems:
-                    self._listbox.selection_set(listitems.index(item))
-                else:
-                    raise ValueError, 'no such item "%s"' % item
-
-    def setlist(self, items):
-        self._listbox.delete(0, 'end')
-	if len(items) > 0:
-	    if type(items) != types.TupleType:
-		items = tuple(items)
-	    apply(self._listbox.insert, (0,) + items)
-
-    # Override Tkinter.Listbox get method, so that if it is called with
-    # no arguments, return all list elements (consistent with other widgets).
-    def get(self, first=None, last=None):
-	if first is None:
-	    return self._listbox.get(0, 'end')
-	else:
-	    return self._listbox.get(first, last)
-
-    # ======================================================================
-
-    # Configuration methods.
-
-    def _hscrollMode(self):
-	# The horizontal scroll mode has been configured.
-
-	mode = self['hscrollmode']
-
-	if mode == 'static':
-	    if not self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	elif mode == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	elif mode == 'none':
-	    if self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	else:
-	    message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
-	    raise ValueError, message
-
-        self._configureScrollCommands()
-
-    def _vscrollMode(self):
-	# The vertical scroll mode has been configured.
-
-	mode = self['vscrollmode']
-
-	if mode == 'static':
-	    if not self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	elif mode == 'dynamic':
-	    if self._vertScrollbarNeeded != self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	elif mode == 'none':
-	    if self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	else:
-	    message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
-	    raise ValueError, message
-
-        self._configureScrollCommands()
-
-    # ======================================================================
-
-    # Private methods.
-
-    def _configureScrollCommands(self):
-        # If both scrollmodes are not dynamic we can save a lot of
-        # time by not having to create an idle job to handle the
-        # scroll commands.
-
-        # Clean up previous scroll commands to prevent memory leak.
-        tclCommandName = str(self._listbox.cget('xscrollcommand'))
-        if tclCommandName != '':   
-            self._listbox.deletecommand(tclCommandName)
-        tclCommandName = str(self._listbox.cget('yscrollcommand'))
-        if tclCommandName != '':   
-            self._listbox.deletecommand(tclCommandName)
-
-	if self['hscrollmode'] == self['vscrollmode'] == 'dynamic':
-            self._listbox.configure(
-                    xscrollcommand=self._scrollBothLater,
-                    yscrollcommand=self._scrollBothLater
-            )
-        else:
-            self._listbox.configure(
-                    xscrollcommand=self._scrollXNow,
-                    yscrollcommand=self._scrollYNow
-            )
-
-    def _scrollXNow(self, first, last):
-        self._horizScrollbar.set(first, last)
-        self._horizScrollbarNeeded = ((first, last) != ('0', '1'))
-
-	if self['hscrollmode'] == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-
-    def _scrollYNow(self, first, last):
-        self._vertScrollbar.set(first, last)
-        self._vertScrollbarNeeded = ((first, last) != ('0', '1'))
-
-        if self['vscrollmode'] == 'dynamic':
-            if self._vertScrollbarNeeded != self._vertScrollbarOn:
-                self._toggleVertScrollbar()
-
-    def _scrollBothLater(self, first, last):
-	# Called by the listbox to set the horizontal or vertical
-	# scrollbar when it has scrolled or changed size or contents.
-
-	if self.scrollTimer is None:
-	    self.scrollTimer = self.after_idle(self._scrollBothNow)
-
-    def _scrollBothNow(self):
-        # This performs the function of _scrollXNow and _scrollYNow.
-        # If one is changed, the other should be updated to match.
-	self.scrollTimer = None
-
-        # Call update_idletasks to make sure that the containing frame
-        # has been resized before we attempt to set the scrollbars. 
-        # Otherwise the scrollbars may be mapped/unmapped continuously.
-        self._scrollRecurse = self._scrollRecurse + 1
-        self.update_idletasks()
-        self._scrollRecurse = self._scrollRecurse - 1
-        if self._scrollRecurse != 0:
-            return
-
-	xview = self._listbox.xview()
-	yview = self._listbox.yview()
-	self._horizScrollbar.set(xview[0], xview[1])
-	self._vertScrollbar.set(yview[0], yview[1])
-
-	self._horizScrollbarNeeded = (xview != (0.0, 1.0))
-	self._vertScrollbarNeeded = (yview != (0.0, 1.0))
-
-	# If both horizontal and vertical scrollmodes are dynamic and
-	# currently only one scrollbar is mapped and both should be
-	# toggled, then unmap the mapped scrollbar.  This prevents a
-	# continuous mapping and unmapping of the scrollbars. 
-	if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
-		self._horizScrollbarNeeded != self._horizScrollbarOn and
-		self._vertScrollbarNeeded != self._vertScrollbarOn and
-		self._vertScrollbarOn != self._horizScrollbarOn):
-	    if self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	    else:
-		self._toggleVertScrollbar()
-	    return
-
-	if self['hscrollmode'] == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-
-	if self['vscrollmode'] == 'dynamic':
-	    if self._vertScrollbarNeeded != self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-
-    def _toggleHorizScrollbar(self):
-
-	self._horizScrollbarOn = not self._horizScrollbarOn
-
-	interior = self.interior()
-	if self._horizScrollbarOn:
-	    self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news')
-	    interior.grid_rowconfigure(3, minsize = self['scrollmargin'])
-	else:
-	    self._horizScrollbar.grid_forget()
-	    interior.grid_rowconfigure(3, minsize = 0)
-
-    def _toggleVertScrollbar(self):
-
-	self._vertScrollbarOn = not self._vertScrollbarOn
-
-	interior = self.interior()
-	if self._vertScrollbarOn:
-	    self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news')
-	    interior.grid_columnconfigure(3, minsize = self['scrollmargin'])
-	else:
-	    self._vertScrollbar.grid_forget()
-	    interior.grid_columnconfigure(3, minsize = 0)
-
-    def _handleEvent(self, event, eventType):
-        if eventType == 'double':
-            command = self['dblclickcommand']
-        elif eventType == 'key':
-            command = self['selectioncommand']
-        else: #eventType == 'release'
-            # Do not execute the command if the mouse was released
-            # outside the listbox.
-            if (event.x < 0 or self._listbox.winfo_width() <= event.x or
-                    event.y < 0 or self._listbox.winfo_height() <= event.y):
-                return
-
-            command = self['selectioncommand']
-
-        if callable(command):
-            command()
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)size method inherited from Tkinter.Frame.Grid.
-    def size(self):
-	return self._listbox.size()
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)bbox method inherited from Tkinter.Frame.Grid.
-    def bbox(self, index):
-	return self._listbox.bbox(index)
-
-forwardmethods(ScrolledListBox, Tkinter.Listbox, '_listbox')
-
-# ======================================================================
-
-_listboxCache = {}
-
-def _registerScrolledList(listbox, scrolledList):
-    # Register an ScrolledList widget for a Listbox widget
-
-    _listboxCache[listbox] = scrolledList
-
-def _deregisterScrolledList(listbox):
-    # Deregister a Listbox widget
-    del _listboxCache[listbox]
-
-def _handleEvent(event, eventType):
-    # Forward events for a Listbox to it's ScrolledListBox
-
-    # A binding earlier in the bindtags list may have destroyed the
-    # megawidget, so need to check.
-    if _listboxCache.has_key(event.widget):
-        _listboxCache[event.widget]._handleEvent(event, eventType)
-
-######################################################################
-### File: PmwScrolledText.py
-# Based on iwidgets2.2.0/scrolledtext.itk code.   
-
-import Tkinter
-
-
-class ScrolledText(MegaWidget):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderframe',    0,            INITOPT),
-	    ('columnheader',   0,            INITOPT),
-	    ('hscrollmode',    'dynamic',    self._hscrollMode),
-	    ('labelmargin',    0,            INITOPT),
-	    ('labelpos',       None,         INITOPT),
-	    ('rowcolumnheader',0,            INITOPT),
-	    ('rowheader',      0,            INITOPT),
-	    ('scrollmargin',   2,            INITOPT),
-	    ('usehullsize',    0,            INITOPT),
-	    ('vscrollmode',    'dynamic',    self._vscrollMode),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-
-	if self['usehullsize']:
-	    interior.grid_propagate(0)
-
-	if self['borderframe']:
-	    # Create a frame widget to act as the border of the text 
-	    # widget.  Later, pack the text widget so that it fills
-	    # the frame.  This avoids a problem in Tk, where window
-	    # items in a text widget may overlap the border of the
-	    # text widget.
-	    self._borderframe = self.createcomponent('borderframe',
-		    (), None,
-		    Tkinter.Frame, (interior,),
-		    relief = 'sunken',
-		    borderwidth = 2,
-	    )
-	    self._borderframe.grid(row = 4, column = 4, sticky = 'news')
-
-	    # Create the text widget.
-	    self._textbox = self.createcomponent('text',
-		    (), None,
-		    Tkinter.Text, (self._borderframe,),
-		    highlightthickness = 0,
-		    borderwidth = 0,
-	    )
-	    self._textbox.pack(fill = 'both', expand = 1)
-
-            bw = self._borderframe.cget('borderwidth'),
-            ht = self._borderframe.cget('highlightthickness'),
-	else:
-	    # Create the text widget.
-	    self._textbox = self.createcomponent('text',
-		    (), None,
-		    Tkinter.Text, (interior,),
-	    )
-	    self._textbox.grid(row = 4, column = 4, sticky = 'news')
-
-            bw = self._textbox.cget('borderwidth'),
-            ht = self._textbox.cget('highlightthickness'),
-
-        # Create the header text widgets
-        if self['columnheader']:
-            self._columnheader = self.createcomponent('columnheader',
-                    (), 'Header',
-                    Tkinter.Text, (interior,),
-                    height=1,
-                    wrap='none',
-                    borderwidth = bw,
-                    highlightthickness = ht,
-            )
-            self._columnheader.grid(row = 2, column = 4, sticky = 'ew')
-            self._columnheader.configure(
-                    xscrollcommand = self._columnheaderscrolled)
-
-        if self['rowheader']:
-            self._rowheader = self.createcomponent('rowheader',
-                    (), 'Header',
-                    Tkinter.Text, (interior,),
-                    wrap='none',
-                    borderwidth = bw,
-                    highlightthickness = ht,
-            )
-            self._rowheader.grid(row = 4, column = 2, sticky = 'ns')
-            self._rowheader.configure(
-                    yscrollcommand = self._rowheaderscrolled)
-
-        if self['rowcolumnheader']:
-            self._rowcolumnheader = self.createcomponent('rowcolumnheader',
-                    (), 'Header',
-                    Tkinter.Text, (interior,),
-                    height=1,
-                    wrap='none',
-                    borderwidth = bw,
-                    highlightthickness = ht,
-            )
-            self._rowcolumnheader.grid(row = 2, column = 2, sticky = 'nsew')
-
-	interior.grid_rowconfigure(4, weight = 1, minsize = 0)
-	interior.grid_columnconfigure(4, weight = 1, minsize = 0)
-
-	# Create the horizontal scrollbar
-	self._horizScrollbar = self.createcomponent('horizscrollbar',
-		(), 'Scrollbar',
-		Tkinter.Scrollbar, (interior,),
-	        orient='horizontal',
-		command=self._textbox.xview
-	)
-
-	# Create the vertical scrollbar
-	self._vertScrollbar = self.createcomponent('vertscrollbar',
-		(), 'Scrollbar',
-		Tkinter.Scrollbar, (interior,),
-		orient='vertical',
-		command=self._textbox.yview
-	)
-
-	self.createlabel(interior, childCols = 5, childRows = 5)
-
-	# Initialise instance variables.
-	self._horizScrollbarOn = 0
-	self._vertScrollbarOn = 0
-	self.scrollTimer = None
-        self._scrollRecurse = 0
-	self._horizScrollbarNeeded = 0
-	self._vertScrollbarNeeded = 0
-	self._textWidth = None
-
-        # These four variables avoid an infinite loop caused by the
-        # row or column header's scrollcommand causing the main text
-        # widget's scrollcommand to be called and vice versa.
-	self._textboxLastX = None
-	self._textboxLastY = None
-	self._columnheaderLastX = None
-	self._rowheaderLastY = None
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	if self.scrollTimer is not None:
-	    self.after_cancel(self.scrollTimer)
-	    self.scrollTimer = None
-	MegaWidget.destroy(self)
-
-    # ======================================================================
-
-    # Public methods.
-
-    def clear(self):
-	self.settext('')
-
-    def importfile(self, fileName, where = 'end'):
-	file = open(fileName, 'r')
-	self._textbox.insert(where, file.read())
-	file.close()
-
-    def exportfile(self, fileName):
-	file = open(fileName, 'w')
-	file.write(self._textbox.get('1.0', 'end'))
-	file.close()
-
-    def settext(self, text):
-	disabled = (str(self._textbox.cget('state')) == 'disabled')
-	if disabled:
-	    self._textbox.configure(state='normal')
-	self._textbox.delete('0.0', 'end')
-	self._textbox.insert('end', text)
-	if disabled:
-	    self._textbox.configure(state='disabled')
-
-    # Override Tkinter.Text get method, so that if it is called with
-    # no arguments, return all text (consistent with other widgets).
-    def get(self, first=None, last=None):
-	if first is None:
-	    return self._textbox.get('1.0', 'end')
-	else:
-	    return self._textbox.get(first, last)
-
-    def getvalue(self):
-        return self.get()
-
-    def setvalue(self, text):
-        return self.settext(text)
-
-    def appendtext(self, text):
-        oldTop, oldBottom = self._textbox.yview()
-     
-        disabled = (str(self._textbox.cget('state')) == 'disabled')
-        if disabled:
-            self._textbox.configure(state='normal')
-        self._textbox.insert('end', text)
-        if disabled:
-            self._textbox.configure(state='disabled')
-     
-        if oldBottom == 1.0:
-            self._textbox.yview('moveto', 1.0)
-
-    # ======================================================================
-
-    # Configuration methods.
-
-    def _hscrollMode(self):
-	# The horizontal scroll mode has been configured.
-
-	mode = self['hscrollmode']
-
-	if mode == 'static':
-	    if not self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	elif mode == 'dynamic':
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	elif mode == 'none':
-	    if self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	else:
-	    message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
-	    raise ValueError, message
-
-        self._configureScrollCommands()
-
-    def _vscrollMode(self):
-	# The vertical scroll mode has been configured.
-
-	mode = self['vscrollmode']
-
-	if mode == 'static':
-	    if not self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	elif mode == 'dynamic':
-	    if self._vertScrollbarNeeded != self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	elif mode == 'none':
-	    if self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-	else:
-	    message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
-	    raise ValueError, message
-
-        self._configureScrollCommands()
-
-    # ======================================================================
-
-    # Private methods.
-
-    def _configureScrollCommands(self):
-        # If both scrollmodes are not dynamic we can save a lot of
-        # time by not having to create an idle job to handle the
-        # scroll commands.
-
-        # Clean up previous scroll commands to prevent memory leak.
-        tclCommandName = str(self._textbox.cget('xscrollcommand'))
-        if tclCommandName != '':   
-            self._textbox.deletecommand(tclCommandName)
-        tclCommandName = str(self._textbox.cget('yscrollcommand'))
-        if tclCommandName != '':   
-            self._textbox.deletecommand(tclCommandName)
-
-	if self['hscrollmode'] == self['vscrollmode'] == 'dynamic':
-            self._textbox.configure(
-                    xscrollcommand=self._scrollBothLater,
-                    yscrollcommand=self._scrollBothLater
-            )
-        else:
-            self._textbox.configure(
-                    xscrollcommand=self._scrollXNow,
-                    yscrollcommand=self._scrollYNow
-            )
-
-    def _scrollXNow(self, first, last):
-        self._horizScrollbar.set(first, last)
-        self._horizScrollbarNeeded = ((first, last) != ('0', '1'))
-
-        # This code is the same as in _scrollBothNow.  Keep it that way.
-        if self['hscrollmode'] == 'dynamic':
-            currentWidth = self._textbox.winfo_width()
-            if self._horizScrollbarNeeded != self._horizScrollbarOn:
-                if self._horizScrollbarNeeded or \
-                        self._textWidth != currentWidth:
-                    self._toggleHorizScrollbar()
-            self._textWidth = currentWidth
-
-        if self['columnheader']:
-	    if self._columnheaderLastX != first:
-		self._columnheaderLastX = first
-		self._columnheader.xview('moveto', first)
-
-    def _scrollYNow(self, first, last):
-        if first == '0' and last == '0':
-            return
-        self._vertScrollbar.set(first, last)
-        self._vertScrollbarNeeded = ((first, last) != ('0', '1'))
-
-        if self['vscrollmode'] == 'dynamic':
-            if self._vertScrollbarNeeded != self._vertScrollbarOn:
-                self._toggleVertScrollbar()
-
-        if self['rowheader']:
-	    if self._rowheaderLastY != first:
-		self._rowheaderLastY = first
-		self._rowheader.yview('moveto', first)
-
-    def _scrollBothLater(self, first, last):
-	# Called by the text widget to set the horizontal or vertical
-	# scrollbar when it has scrolled or changed size or contents.
-
-	if self.scrollTimer is None:
-	    self.scrollTimer = self.after_idle(self._scrollBothNow)
-
-    def _scrollBothNow(self):
-        # This performs the function of _scrollXNow and _scrollYNow.
-        # If one is changed, the other should be updated to match.
-	self.scrollTimer = None
-
-        # Call update_idletasks to make sure that the containing frame
-        # has been resized before we attempt to set the scrollbars. 
-        # Otherwise the scrollbars may be mapped/unmapped continuously.
-        self._scrollRecurse = self._scrollRecurse + 1
-        self.update_idletasks()
-        self._scrollRecurse = self._scrollRecurse - 1
-        if self._scrollRecurse != 0:
-            return
-
-	xview = self._textbox.xview()
-	yview = self._textbox.yview()
-
-	# The text widget returns a yview of (0.0, 0.0) just after it
-	# has been created. Ignore this.
-	if yview == (0.0, 0.0):
-	    return
-
-        if self['columnheader']:
-	    if self._columnheaderLastX != xview[0]:
-		self._columnheaderLastX = xview[0]
-		self._columnheader.xview('moveto', xview[0])
-        if self['rowheader']:
-	    if self._rowheaderLastY != yview[0]:
-		self._rowheaderLastY = yview[0]
-		self._rowheader.yview('moveto', yview[0])
-
-	self._horizScrollbar.set(xview[0], xview[1])
-	self._vertScrollbar.set(yview[0], yview[1])
-
-	self._horizScrollbarNeeded = (xview != (0.0, 1.0))
-	self._vertScrollbarNeeded = (yview != (0.0, 1.0))
-
-	# If both horizontal and vertical scrollmodes are dynamic and
-	# currently only one scrollbar is mapped and both should be
-	# toggled, then unmap the mapped scrollbar.  This prevents a
-	# continuous mapping and unmapping of the scrollbars. 
-	if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
-		self._horizScrollbarNeeded != self._horizScrollbarOn and
-		self._vertScrollbarNeeded != self._vertScrollbarOn and
-		self._vertScrollbarOn != self._horizScrollbarOn):
-	    if self._horizScrollbarOn:
-		self._toggleHorizScrollbar()
-	    else:
-		self._toggleVertScrollbar()
-	    return
-
-	if self['hscrollmode'] == 'dynamic':
-
-	    # The following test is done to prevent continuous
-	    # mapping and unmapping of the horizontal scrollbar. 
-	    # This may occur when some event (scrolling, resizing
-	    # or text changes) modifies the displayed text such
-	    # that the bottom line in the window is the longest
-	    # line displayed.  If this causes the horizontal
-	    # scrollbar to be mapped, the scrollbar may "cover up"
-	    # the bottom line, which would mean that the scrollbar
-	    # is no longer required.  If the scrollbar is then
-	    # unmapped, the bottom line will then become visible
-	    # again, which would cause the scrollbar to be mapped
-	    # again, and so on...
-	    #
-	    # The idea is that, if the width of the text widget
-	    # has not changed and the scrollbar is currently
-	    # mapped, then do not unmap the scrollbar even if it
-	    # is no longer required.  This means that, during
-	    # normal scrolling of the text, once the horizontal
-	    # scrollbar has been mapped it will not be unmapped
-	    # (until the width of the text widget changes).
-
-	    currentWidth = self._textbox.winfo_width()
-	    if self._horizScrollbarNeeded != self._horizScrollbarOn:
-		if self._horizScrollbarNeeded or \
-			self._textWidth != currentWidth:
-		    self._toggleHorizScrollbar()
-	    self._textWidth = currentWidth
-
-	if self['vscrollmode'] == 'dynamic':
-	    if self._vertScrollbarNeeded != self._vertScrollbarOn:
-		self._toggleVertScrollbar()
-
-    def _columnheaderscrolled(self, first, last):
-	if self._textboxLastX != first:
-	    self._textboxLastX = first
-	    self._textbox.xview('moveto', first)
-
-    def _rowheaderscrolled(self, first, last):
-	if self._textboxLastY != first:
-	    self._textboxLastY = first
-	    self._textbox.yview('moveto', first)
-
-    def _toggleHorizScrollbar(self):
-
-	self._horizScrollbarOn = not self._horizScrollbarOn
-
-	interior = self.interior()
-	if self._horizScrollbarOn:
-	    self._horizScrollbar.grid(row = 6, column = 4, sticky = 'news')
-	    interior.grid_rowconfigure(5, minsize = self['scrollmargin'])
-	else:
-	    self._horizScrollbar.grid_forget()
-	    interior.grid_rowconfigure(5, minsize = 0)
-
-    def _toggleVertScrollbar(self):
-
-	self._vertScrollbarOn = not self._vertScrollbarOn
-
-	interior = self.interior()
-	if self._vertScrollbarOn:
-	    self._vertScrollbar.grid(row = 4, column = 6, sticky = 'news')
-	    interior.grid_columnconfigure(5, minsize = self['scrollmargin'])
-	else:
-	    self._vertScrollbar.grid_forget()
-	    interior.grid_columnconfigure(5, minsize = 0)
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)bbox method inherited from Tkinter.Frame.Grid.
-    def bbox(self, index):
-	return self._textbox.bbox(index)
-
-forwardmethods(ScrolledText, Tkinter.Text, '_textbox')
-
-######################################################################
-### File: PmwHistoryText.py
-
-
-_ORIGINAL = 0
-_MODIFIED = 1
-_DISPLAY = 2
-
-class HistoryText(ScrolledText):
-
-    def __init__(self, parent = None, **kw):
-
-        # Define the megawidget options.
-        optiondefs = (
-	    ('compressany',         1,          None),
-	    ('compresstail',        1,          None),
-            ('historycommand',      None,       None),
-        )
-        self.defineoptions(kw, optiondefs)
-
-        # Initialise the base class (after defining the options).
-        ScrolledText.__init__(self, parent)
-
-        # Initialise instance variables.
-	self._list = []
-	self._currIndex = 0
-	self._pastIndex = None
-	self._lastIndex = 0          # pointer to end of history list
-
-        # Check keywords and initialise options.
-        self.initialiseoptions()
-
-    def addhistory(self):
-	text = self.get()
-	if text[-1] == '\n':
-	    text = text[:-1]
-
-	if len(self._list) == 0:
-            # This is the first history entry.  Add it.
-	    self._list.append([text, text, _MODIFIED])
-            return
-
-        currentEntry =  self._list[self._currIndex]
-        if text == currentEntry[_ORIGINAL]:
-            # The current history entry has not been modified. Check if
-            # we need to add it again.
-
-            if self['compresstail'] and self._currIndex == self._lastIndex:
-                return
-
-            if self['compressany']:
-                return
-
-        # Undo any changes for the current history entry, since they
-        # will now be available in the new entry.
-        currentEntry[_MODIFIED] = currentEntry[_ORIGINAL]
-
-        historycommand = self['historycommand']
-        if self._currIndex == self._lastIndex:
-            # The last history entry is currently being displayed,
-            # so disable the special meaning of the 'Next' button.
-            self._pastIndex = None
-            nextState = 'disabled'
-        else:
-            # A previous history entry is currently being displayed,
-            # so allow the 'Next' button to go to the entry after this one.
-            self._pastIndex = self._currIndex
-            nextState = 'normal'
-        if callable(historycommand):
-            historycommand('normal', nextState)
-
-        # Create the new history entry.
-        self._list.append([text, text, _MODIFIED])
-
-        # Move the pointer into the history entry list to the end.
-        self._lastIndex = self._lastIndex + 1
-        self._currIndex = self._lastIndex
-
-    def next(self):
-	if self._currIndex == self._lastIndex and self._pastIndex is None:
-	    self.bell()
-        else:
-            self._modifyDisplay('next')
-
-    def prev(self):
-        self._pastIndex = None
-	if self._currIndex == 0:
-	    self.bell()
-        else:
-            self._modifyDisplay('prev')
-
-    def undo(self):
-	if len(self._list) != 0:
-            self._modifyDisplay('undo')
-
-    def redo(self):
-	if len(self._list) != 0:
-            self._modifyDisplay('redo')
-
-    def gethistory(self):
-        return self._list
-
-    def _modifyDisplay(self, command):
-        # Modify the display to show either the next or previous
-        # history entry (next, prev) or the original or modified
-        # version of the current history entry (undo, redo).
-
-        # Save the currently displayed text.
-        currentText = self.get()
-        if currentText[-1] == '\n':
-            currentText = currentText[:-1]
-
-        currentEntry =  self._list[self._currIndex]
-        if currentEntry[_DISPLAY] == _MODIFIED:
-            currentEntry[_MODIFIED] = currentText
-        elif currentEntry[_ORIGINAL] != currentText:
-            currentEntry[_MODIFIED] = currentText
-            if command in ('next', 'prev'):
-                currentEntry[_DISPLAY] = _MODIFIED
-
-        if command in ('next', 'prev'):
-            prevstate = 'normal'
-            nextstate = 'normal'
-            if command == 'next':
-                if self._pastIndex is not None:
-                    self._currIndex = self._pastIndex
-                    self._pastIndex = None
-                self._currIndex = self._currIndex + 1
-                if self._currIndex == self._lastIndex:
-                    nextstate = 'disabled'
-            elif command == 'prev':
-                self._currIndex = self._currIndex - 1
-                if self._currIndex == 0:
-                    prevstate = 'disabled'
-            historycommand = self['historycommand']
-            if callable(historycommand):
-                historycommand(prevstate, nextstate)
-            currentEntry =  self._list[self._currIndex]
-        else:
-            if command == 'undo':
-                currentEntry[_DISPLAY] = _ORIGINAL
-            elif command == 'redo':
-                currentEntry[_DISPLAY] = _MODIFIED
-
-        # Display the new text.
-        self.delete('1.0', 'end')
-        self.insert('end', currentEntry[currentEntry[_DISPLAY]])
-
-######################################################################
-### File: PmwSelectionDialog.py
-# Not Based on iwidgets version.
-
-
-
-class SelectionDialog(Dialog):
-    # Dialog window with selection list.
-    
-    # Dialog window displaying a list and requesting the user to
-    # select one.
-
-    def __init__(self, parent = None, **kw):
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderx',     10,    INITOPT),
-	    ('bordery',     10,    INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	Dialog.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-	aliases = (
-	    ('listbox', 'scrolledlist_listbox'),
-	    ('label', 'scrolledlist_label'),
-	)
-	self._list = self.createcomponent('scrolledlist',
-		aliases, None,
-		ScrolledListBox, (interior,),
-		dblclickcommand = self.invoke)
-	self._list.pack(side='top', expand='true', fill='both',
-		padx = self['borderx'], pady = self['bordery'])
-
-        if not kw.has_key('activatecommand'):
-            # Whenever this dialog is activated, set the focus to the
-            # ScrolledListBox's listbox widget.
-            listbox = self.component('listbox')
-            self.configure(activatecommand = listbox.focus_set)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)size method inherited from Tkinter.Toplevel.Grid.
-    def size(self):
-	return self.component('listbox').size()
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)bbox method inherited from Tkinter.Toplevel.Grid.
-    def bbox(self, index):
-	return self.component('listbox').size(index)
-
-forwardmethods(SelectionDialog, ScrolledListBox, '_list')
-
-######################################################################
-### File: PmwTextDialog.py
-# A Dialog with a ScrolledText widget.
-
-
-
-class TextDialog(Dialog):
-    def __init__(self, parent = None, **kw):
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderx',     10,    INITOPT),
-	    ('bordery',     10,    INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	Dialog.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-	aliases = (
-	    ('text', 'scrolledtext_text'),
-	    ('label', 'scrolledtext_label'),
-	)
-	self._text = self.createcomponent('scrolledtext',
-		aliases, None,
-		ScrolledText, (interior,))
-	self._text.pack(side='top', expand=1, fill='both',
-		padx = self['borderx'], pady = self['bordery'])
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)bbox method inherited from Tkinter.Toplevel.Grid.
-    def bbox(self, index):
-	return self._text.bbox(index)
-
-forwardmethods(TextDialog, ScrolledText, '_text')
-
-######################################################################
-### File: PmwTimeCounter.py
-# Authors: Joe VanAndel and Greg McFarlane
-
-import string
-import sys
-import time
-import Tkinter
-
-
-class TimeCounter(MegaWidget):
-    """Up-down counter
-
-    A TimeCounter is a single-line entry widget with Up and Down arrows
-    which increment and decrement the Time value in the entry.  
-    """
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('autorepeat',    1,    None),
-	    ('buttonaspect',  1.0,  INITOPT),
-	    ('command',       None, None),
-	    ('initwait',      300,  None),
-	    ('labelmargin',   0,    INITOPT),
-	    ('labelpos',      None, INITOPT),
-	    ('max',           None, self._max),
-	    ('min',           None, self._min),
-	    ('padx',          0,    INITOPT),
-	    ('pady',          0,    INITOPT),
-	    ('repeatrate',    50,   None),
-	    ('value',         None, INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-    	self.arrowDirection = {}
-	self._flag = 'stopped'
-	self._timerId = None
-
-	self._createComponents(kw)
-
-	value = self['value']
-	if value is None:
-	    now = time.time()
-	    value = time.strftime('%H:%M:%S', time.localtime(now))
-    	self.setvalue(value)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def _createComponents(self, kw):
-
-	# Create the components.
-	interior = self.interior()
-
-	# If there is no label, put the arrows and the entry directly
-	# into the interior, otherwise create a frame for them.  In
-	# either case the border around the arrows and the entry will
-	# be raised (but not around the label).
-	if self['labelpos'] is None:
-	    frame = interior
-            if not kw.has_key('hull_relief'):
-                frame.configure(relief = 'raised')
-            if not kw.has_key('hull_borderwidth'):
-                frame.configure(borderwidth = 1)
-	else:
-	    frame = self.createcomponent('frame',
-		    (), None,
-		    Tkinter.Frame, (interior,),
-                    relief = 'raised', borderwidth = 1)
-	    frame.grid(column=2, row=2, sticky='nsew')
-	    interior.grid_columnconfigure(2, weight=1)
-	    interior.grid_rowconfigure(2, weight=1)
-
-	# Create the down arrow buttons.
-
-	# Create the hour down arrow.
-	self._downHourArrowBtn = self.createcomponent('downhourarrow',
-		(), 'Arrow',
-		Tkinter.Canvas, (frame,),
-		width = 16, height = 16, relief = 'raised', borderwidth = 2)
-    	self.arrowDirection[self._downHourArrowBtn] = 'down'
-	self._downHourArrowBtn.grid(column = 0, row = 2)
-
-	# Create the minute down arrow.
-	self._downMinuteArrowBtn = self.createcomponent('downminutearrow',
-		(), 'Arrow',
-		Tkinter.Canvas, (frame,),
-		width = 16, height = 16, relief = 'raised', borderwidth = 2)
-    	self.arrowDirection[self._downMinuteArrowBtn] = 'down'
-	self._downMinuteArrowBtn.grid(column = 1, row = 2)
-
-	# Create the second down arrow.
-	self._downSecondArrowBtn = self.createcomponent('downsecondarrow',
-		(), 'Arrow',
-		Tkinter.Canvas, (frame,),
-		width = 16, height = 16, relief = 'raised', borderwidth = 2)
-    	self.arrowDirection[self._downSecondArrowBtn] = 'down'
-	self._downSecondArrowBtn.grid(column = 2, row = 2)
-
-	# Create the entry fields.
-
-	# Create the hour entry field.
-	self._hourCounterEntry = self.createcomponent('hourentryfield',
-		(('hourentry', 'hourentryfield_entry'),), None,
-		EntryField, (frame,), validate='integer', entry_width = 2)
-	self._hourCounterEntry.grid(column = 0, row = 1, sticky = 'news')
-
-	# Create the minute entry field.
-	self._minuteCounterEntry = self.createcomponent('minuteentryfield',
-		(('minuteentry', 'minuteentryfield_entry'),), None,
-		EntryField, (frame,), validate='integer', entry_width = 2)
-	self._minuteCounterEntry.grid(column = 1, row = 1, sticky = 'news')
-
-	# Create the second entry field.
-	self._secondCounterEntry = self.createcomponent('secondentryfield',
-		(('secondentry', 'secondentryfield_entry'),), None,
-		EntryField, (frame,), validate='integer', entry_width = 2)
-	self._secondCounterEntry.grid(column = 2, row = 1, sticky = 'news')
-
-	# Create the up arrow buttons.
-
-	# Create the hour up arrow.
-	self._upHourArrowBtn = self.createcomponent('uphourarrow',
-		(), 'Arrow',
-		Tkinter.Canvas, (frame,),
-		width = 16, height = 16, relief = 'raised', borderwidth = 2)
-    	self.arrowDirection[self._upHourArrowBtn] = 'up'
-	self._upHourArrowBtn.grid(column = 0, row = 0)
-
-	# Create the minute up arrow.
-	self._upMinuteArrowBtn = self.createcomponent('upminutearrow',
-		(), 'Arrow',
-		Tkinter.Canvas, (frame,),
-		width = 16, height = 16, relief = 'raised', borderwidth = 2)
-    	self.arrowDirection[self._upMinuteArrowBtn] = 'up'
-	self._upMinuteArrowBtn.grid(column = 1, row = 0)
-
-	# Create the second up arrow.
-	self._upSecondArrowBtn = self.createcomponent('upsecondarrow',
-		(), 'Arrow',
-		Tkinter.Canvas, (frame,),
-		width = 16, height = 16, relief = 'raised', borderwidth = 2)
-    	self.arrowDirection[self._upSecondArrowBtn] = 'up'
-	self._upSecondArrowBtn.grid(column = 2, row = 0)
-
-	# Make it resize nicely.
-	padx = self['padx']
-	pady = self['pady']
-	for col in range(3):
-	    frame.grid_columnconfigure(col, weight = 1, pad = padx)
-	frame.grid_rowconfigure(0, pad = pady)
-	frame.grid_rowconfigure(2, pad = pady)
-
-	frame.grid_rowconfigure(1, weight = 1)
-
-	# Create the label.
-	self.createlabel(interior)
-
-	# Set bindings.
-
-	# Up hour
-	self._upHourArrowBtn.bind('<Configure>', 
-		lambda  event, s=self,button=self._upHourArrowBtn: 
-		s._drawArrow(button, 'up'))
-
-	self._upHourArrowBtn.bind('<1>', 
-    	    	lambda event, s=self,button=self._upHourArrowBtn: 
-		s._countUp(button, 3600))
-
-	self._upHourArrowBtn.bind('<Any-ButtonRelease-1>', 
-		lambda event, s=self, button=self._upHourArrowBtn:
-		s._stopUpDown(button))
-
-	# Up minute
-	self._upMinuteArrowBtn.bind('<Configure>', 
-		lambda  event, s=self,button=self._upMinuteArrowBtn: 
-		s._drawArrow(button, 'up'))
-	    
-
-	self._upMinuteArrowBtn.bind('<1>', 
-    	    	lambda event, s=self,button=self._upMinuteArrowBtn: 
-		s._countUp(button, 60))
-
-	self._upMinuteArrowBtn.bind('<Any-ButtonRelease-1>', 
-		lambda event, s=self, button=self._upMinuteArrowBtn:
-		s._stopUpDown(button))
-
-	# Up second
-	self._upSecondArrowBtn.bind('<Configure>', 
-		lambda  event, s=self,button=self._upSecondArrowBtn: 
-		s._drawArrow(button, 'up'))
-	    
-
-	self._upSecondArrowBtn.bind('<1>', 
-    	    	lambda event, s=self,button=self._upSecondArrowBtn: 
-		s._countUp(button, 1))
-
-	self._upSecondArrowBtn.bind('<Any-ButtonRelease-1>', 
-		lambda event, s=self, button=self._upSecondArrowBtn:
-		s._stopUpDown(button))
-
-	# Down hour
-	self._downHourArrowBtn.bind('<Configure>', 
-		lambda  event, s=self,button=self._downHourArrowBtn: 
-		s._drawArrow(button, 'down'))
-
-	self._downHourArrowBtn.bind('<1>', 
-    	    	lambda event, s=self,button=self._downHourArrowBtn: 
-		s._countDown(button, 3600))
-	self._downHourArrowBtn.bind('<Any-ButtonRelease-1>', 
-		lambda event, s=self, button=self._downHourArrowBtn:
-		s._stopUpDown(button))
-
-
-	# Down minute
-	self._downMinuteArrowBtn.bind('<Configure>', 
-		lambda  event, s=self,button=self._downMinuteArrowBtn: 
-		s._drawArrow(button, 'down'))
-
-	self._downMinuteArrowBtn.bind('<1>', 
-    	    	lambda event, s=self,button=self._downMinuteArrowBtn:
-                s._countDown(button, 60))
-	self._downMinuteArrowBtn.bind('<Any-ButtonRelease-1>', 
-		lambda event, s=self, button=self._downMinuteArrowBtn:
-		s._stopUpDown(button))
-
-	# Down second
-	self._downSecondArrowBtn.bind('<Configure>', 
-		lambda  event, s=self,button=self._downSecondArrowBtn: 
-		s._drawArrow(button, 'down'))
-
-	self._downSecondArrowBtn.bind('<1>', 
-    	    	lambda event, s=self, button=self._downSecondArrowBtn: 
-		s._countDown(button,1))
-	self._downSecondArrowBtn.bind('<Any-ButtonRelease-1>', 
-		lambda event, s=self, button=self._downSecondArrowBtn:
-		s._stopUpDown(button))
-
-	self._hourCounterEntry.component('entry').bind(
-                '<Return>', self._invoke)
-	self._minuteCounterEntry.component('entry').bind(
-        	'<Return>', self._invoke)
-	self._secondCounterEntry.component('entry').bind(
-        	'<Return>', self._invoke)
-
-	self._hourCounterEntry.bind('<Configure>', self._resizeArrow)
-	self._minuteCounterEntry.bind('<Configure>', self._resizeArrow)
-	self._secondCounterEntry.bind('<Configure>', self._resizeArrow)
-
-    def _drawArrow(self, arrow, direction):
-        drawarrow(arrow, self['hourentry_foreground'], direction, 'arrow')
-
-    def _resizeArrow(self, event = None):
-	for btn in (self._upHourArrowBtn, self._upMinuteArrowBtn,
-		self._upSecondArrowBtn,
-		self._downHourArrowBtn,
-		self._downMinuteArrowBtn, self._downSecondArrowBtn):
-	    bw = (string.atoi(btn['borderwidth']) +
-		    string.atoi(btn['highlightthickness']))
-	    newHeight = self._hourCounterEntry.winfo_reqheight() - 2 * bw
-	    newWidth = int(newHeight * self['buttonaspect'])
-	    btn.configure(width=newWidth, height=newHeight)
-	    self._drawArrow(btn, self.arrowDirection[btn])
-
-    def _min(self):
-	min = self['min']
-        if min is None:
-	    self._minVal = 0
-	else:
-	    self._minVal = timestringtoseconds(min)
-
-    def _max(self):
-	max = self['max']
-	if max is None:
-	    self._maxVal = None
-	else:
-	    self._maxVal = timestringtoseconds(max)
-
-    def getvalue(self):
-        return self.getstring()
-
-    def setvalue(self, text):
-        list = string.split(text, ':')
-	if len(list) != 3:
-	    raise ValueError, 'invalid value: ' + text
-
-	self._hour = string.atoi(list[0])
-	self._minute = string.atoi(list[1])
-	self._second = string.atoi(list[2]) 
-
-    	self._setHMS()
-
-    def getstring(self):
-    	return '%02d:%02d:%02d' % (self._hour, self._minute, self._second)
-
-    def getint(self):
-    	return self._hour * 3600 + self._minute * 60 + self._second
-
-    def _countUp(self, button, increment):
-	self._relief = self._upHourArrowBtn.cget('relief')
-	button.configure(relief='sunken')
-	self._count(1, 'start', increment)
-
-    def _countDown(self, button, increment):
-
-	self._relief = self._downHourArrowBtn.cget('relief')
-	button.configure(relief='sunken')
-	self._count(-1, 'start', increment)
-
-    def increment(self, seconds = 1):
-	self._count(1, 'force', seconds)
-
-    def decrement(self, seconds = 1):
-	self._count(-1, 'force', seconds)
-
-    def _count(self, factor, newFlag = None, increment = 1):
-	if newFlag != 'force':
-	  if newFlag is not None:
-	    self._flag = newFlag
-
-	  if self._flag == 'stopped':
-	    return
-
-	value = (string.atoi(self._hourCounterEntry.get()) *3600) + \
-	      (string.atoi(self._minuteCounterEntry.get()) *60) + \
-	      string.atoi(self._secondCounterEntry.get()) + \
-	      factor * increment
-	min = self._minVal
-	max = self._maxVal
-	if value < min:
-	  value = min
-	if max is not None and value > max:
-	  value = max
-
-	self._hour = value /3600
-	self._minute = (value - (self._hour*3600)) / 60
-	self._second = value - (self._hour*3600) - (self._minute*60)
-	self._setHMS()
-
-	if newFlag != 'force':
-	  if self['autorepeat']:
-	    if self._flag == 'start':
-	      delay = self['initwait']
-	      self._flag = 'running'
-	    else:
-	      delay = self['repeatrate']
-	    self._timerId = self.after(
-		delay, lambda self=self, factor=factor,increment=increment: 
-		  self._count(factor,'running', increment))
-
-    def _setHMS(self):
-        self._hourCounterEntry.setentry('%02d' % self._hour)
-        self._minuteCounterEntry.setentry('%02d' % self._minute)
-        self._secondCounterEntry.setentry('%02d' % self._second)
-
-    def _stopUpDown(self, button):
-        if self._timerId is not None:
-            self.after_cancel(self._timerId)
-	    self._timerId = None
-        button.configure(relief=self._relief)
-        self._flag = 'stopped'
-
-    def _invoke(self, event):
-        cmd = self['command']
-        if callable(cmd):
-	    cmd()
-
-    def invoke(self):
-        cmd = self['command']
-        if callable(cmd):
-	    return cmd()
-
-    def destroy(self):
-        if self._timerId is not None:
-            self.after_cancel(self._timerId)
-	    self._timerId = None
-        MegaWidget.destroy(self)
-
-######################################################################
-### File: PmwAboutDialog.py
-
-
-class AboutDialog(MessageDialog):
-    # Window to display version and contact information.
-
-    # Class members containing resettable 'default' values:
-    _version = ''
-    _copyright = ''
-    _contact = ''
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('applicationname',   '',          INITOPT),
-	    ('iconpos',           'w',         None),
-	    ('icon_bitmap',       'info',      None),
-	    ('buttons',           ('Close',),  None),
-	    ('defaultbutton',     0,           None),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MessageDialog.__init__(self, parent)
-
-	applicationname = self['applicationname']
-        if not kw.has_key('title'):
-            self.configure(title = 'About ' + applicationname)
-
-        if not kw.has_key('message_text'):
-            text = applicationname + '\n\n'
-            if AboutDialog._version != '':
-              text = text + 'Version ' + AboutDialog._version + '\n'
-            if AboutDialog._copyright != '':
-              text = text + AboutDialog._copyright + '\n\n'
-            if AboutDialog._contact != '':
-              text = text + AboutDialog._contact
-
-            self.configure(message_text=text)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-def aboutversion(value):
-    AboutDialog._version = value
-
-def aboutcopyright(value):
-    AboutDialog._copyright = value
-
-def aboutcontact(value):
-    AboutDialog._contact = value
-
-######################################################################
-### File: PmwComboBox.py
-# Based on iwidgets2.2.0/combobox.itk code.
-
-import os
-import string
-import types
-import Tkinter
-
-
-class ComboBox(MegaWidget):
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('autoclear',          0,          INITOPT),
-	    ('buttonaspect',       1.0,        INITOPT),
-	    ('dropdown',           1,          INITOPT),
-	    ('fliparrow',          0,          INITOPT),
-	    ('history',            1,          INITOPT),
-	    ('labelmargin',        0,          INITOPT),
-	    ('labelpos',           None,       INITOPT),
-	    ('listheight',         200,        INITOPT),
-	    ('selectioncommand',   None,       None),
-	    ('sticky',            'ew',        INITOPT),
-	    ('unique',             1,          INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-
-	self._entryfield = self.createcomponent('entryfield',
-		(('entry', 'entryfield_entry'),), None,
-		EntryField, (interior,))
-	self._entryfield.grid(column=2, row=2, sticky=self['sticky'])
-	interior.grid_columnconfigure(2, weight = 1)
-	self._entryWidget = self._entryfield.component('entry')
-
-	if self['dropdown']:
-	    self._isPosted = 0
-            interior.grid_rowconfigure(2, weight = 1)
-
-	    # Create the arrow button.
-	    self._arrowBtn = self.createcomponent('arrowbutton',
-		    (), None,
-		    Tkinter.Canvas, (interior,), borderwidth = 2,
-		    relief = 'raised',
-		    width = 16, height = 16)
-            if 'n' in self['sticky']:
-                sticky = 'n'
-            else:
-                sticky = ''
-            if 's' in self['sticky']:
-                sticky = sticky + 's'
-	    self._arrowBtn.grid(column=3, row=2, sticky = sticky)
-	    self._arrowRelief = self._arrowBtn.cget('relief')
-
-	    # Create the label.
-	    self.createlabel(interior, childCols=2)
-
-	    # Create the dropdown window.
-	    self._popup = self.createcomponent('popup',
-		    (), None,
-		    Tkinter.Toplevel, (interior,))
-	    self._popup.withdraw()
-	    self._popup.overrideredirect(1)
-
-	    # Create the scrolled listbox inside the dropdown window.
-	    self._list = self.createcomponent('scrolledlist',
-		    (('listbox', 'scrolledlist_listbox'),), None,
-		    ScrolledListBox, (self._popup,),
-		    hull_borderwidth = 2,
-		    hull_relief = 'raised',
-		    hull_height = self['listheight'],
-		    usehullsize = 1,
-		    listbox_exportselection = 0)
-	    self._list.pack(expand=1, fill='both')
-	    self.__listbox = self._list.component('listbox')
-
-	    # Bind events to the arrow button.
-	    self._arrowBtn.bind('<1>', self._postList)
-	    self._arrowBtn.bind('<Configure>', self._drawArrow)
-	    self._arrowBtn.bind('<3>', self._next)
-	    self._arrowBtn.bind('<Shift-3>', self._previous)
-	    self._arrowBtn.bind('<Down>', self._next)
-	    self._arrowBtn.bind('<Up>', self._previous)
-	    self._arrowBtn.bind('<Control-n>', self._next)
-	    self._arrowBtn.bind('<Control-p>', self._previous)
-	    self._arrowBtn.bind('<Shift-Down>', self._postList)
-	    self._arrowBtn.bind('<Shift-Up>', self._postList)
-	    self._arrowBtn.bind('<F34>', self._postList)
-	    self._arrowBtn.bind('<F28>', self._postList)
-	    self._arrowBtn.bind('<space>', self._postList)
-
-	    # Bind events to the dropdown window.
-	    self._popup.bind('<Escape>', self._unpostList)
-	    self._popup.bind('<space>', self._selectUnpost)
-	    self._popup.bind('<Return>', self._selectUnpost)
-	    self._popup.bind('<ButtonRelease-1>', self._dropdownBtnRelease)
-	    self._popup.bind('<ButtonPress-1>', self._unpostOnNextRelease)
-
-	    # Bind events to the Tk listbox.
-	    self.__listbox.bind('<Enter>', self._unpostOnNextRelease)
-
-	    # Bind events to the Tk entry widget.
-	    self._entryWidget.bind('<Configure>', self._resizeArrow)
-	    self._entryWidget.bind('<Shift-Down>', self._postList)
-	    self._entryWidget.bind('<Shift-Up>', self._postList)
-	    self._entryWidget.bind('<F34>', self._postList)
-	    self._entryWidget.bind('<F28>', self._postList)
-
-            # Need to unpost the popup if the entryfield is unmapped (eg: 
-            # its toplevel window is withdrawn) while the popup list is
-            # displayed.
-            self._entryWidget.bind('<Unmap>', self._unpostList)
-
-	else:
-	    # Create the scrolled listbox below the entry field.
-	    self._list = self.createcomponent('scrolledlist',
-		    (('listbox', 'scrolledlist_listbox'),), None,
-		    ScrolledListBox, (interior,),
-                    selectioncommand = self._selectCmd)
-	    self._list.grid(column=2, row=3, sticky='nsew')
-	    self.__listbox = self._list.component('listbox')
-
-	    # The scrolled listbox should expand vertically.
-	    interior.grid_rowconfigure(3, weight = 1)
-
-	    # Create the label.
-	    self.createlabel(interior, childRows=2)
-
-	self._entryWidget.bind('<Down>', self._next)
-	self._entryWidget.bind('<Up>', self._previous)
-	self._entryWidget.bind('<Control-n>', self._next)
-	self._entryWidget.bind('<Control-p>', self._previous)
-	self.__listbox.bind('<Control-n>', self._next)
-	self.__listbox.bind('<Control-p>', self._previous)
-
-	if self['history']:
-	    self._entryfield.configure(command=self._addHistory)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def destroy(self):
-	if self['dropdown'] and self._isPosted:
-            popgrab(self._popup)
-        MegaWidget.destroy(self)
-
-    #======================================================================
-
-    # Public methods
-
-    def get(self, first = None, last=None):
-	if first is None:
-	    return self._entryWidget.get()
-	else:
-	    return self._list.get(first, last)
-
-    def invoke(self):
-	if self['dropdown']:
-	    self._postList()
-	else:
-	    return self._selectCmd()
-
-    def selectitem(self, index, setentry=1):
-	if type(index) == types.StringType:
-	    text = index
-	    items = self._list.get(0, 'end')
-	    if text in items:
-		index = list(items).index(text)
-	    else:
-	    	raise IndexError, 'index "%s" not found' % text
-	elif setentry:
-	    text = self._list.get(0, 'end')[index]
-
-	self._list.select_clear(0, 'end')
-	self._list.select_set(index, index)
-	self._list.activate(index)
-	self.see(index)
-	if setentry:
-	    self._entryfield.setentry(text)
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)size method inherited from Tkinter.Frame.Grid.
-    def size(self):
-	return self._list.size()
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)bbox method inherited from Tkinter.Frame.Grid.
-    def bbox(self, index):
-	return self._list.bbox(index)
-
-    def clear(self):
-	self._entryfield.clear()
-	self._list.clear()
-
-    #======================================================================
-
-    # Private methods for both dropdown and simple comboboxes.
-
-    def _addHistory(self):
-	input = self._entryWidget.get()
-
-	if input != '':
-	    index = None
-	    if self['unique']:
-		# If item is already in list, select it and return.
-		items = self._list.get(0, 'end')
-		if input in items:
-		    index = list(items).index(input)
-
-	    if index is None:
-		index = self._list.index('end')
-		self._list.insert('end', input)
-
-	    self.selectitem(index)
-	    if self['autoclear']:
-		self._entryWidget.delete(0, 'end')
-
-	    # Execute the selectioncommand on the new entry.
-	    self._selectCmd()
-
-    def _next(self, event):
-	size = self.size()
-	if size <= 1:
-	    return
-
-	cursels = self.curselection()
-
-	if len(cursels) == 0:
-	    index = 0
-	else:
-	    index = string.atoi(cursels[0])
-	    if index == size - 1:
-		index = 0
-	    else:
-		index = index + 1
-
-	self.selectitem(index)
-
-    def _previous(self, event):
-	size = self.size()
-	if size <= 1:
-	    return
-
-	cursels = self.curselection()
-
-	if len(cursels) == 0:
-	    index = size - 1
-	else:
-	    index = string.atoi(cursels[0])
-	    if index == 0:
-		index = size - 1
-	    else:
-		index = index - 1
-
-	self.selectitem(index)
-
-    def _selectCmd(self, event=None):
-
-	sels = self.getcurselection()
-	if len(sels) == 0:
-	    item = None
-	else:
-	    item = sels[0]
-	    self._entryfield.setentry(item)
-
-	cmd = self['selectioncommand']
-	if callable(cmd):
-            if event is None:
-                # Return result of selectioncommand for invoke() method.
-                return cmd(item)
-            else:
-                cmd(item)
-
-    #======================================================================
-
-    # Private methods for dropdown combobox.
-
-    def _drawArrow(self, event=None, sunken=0):
-        arrow = self._arrowBtn
-	if sunken:
-	    self._arrowRelief = arrow.cget('relief')
-	    arrow.configure(relief = 'sunken')
-	else:
-	    arrow.configure(relief = self._arrowRelief)
-
-	if self._isPosted and self['fliparrow']:
-            direction = 'up'
-        else:
-            direction = 'down'
-        drawarrow(arrow, self['entry_foreground'], direction, 'arrow')
-
-    def _postList(self, event = None):
-        self._isPosted = 1
-        self._drawArrow(sunken=1)
-
-        # Make sure that the arrow is displayed sunken.
-        self.update_idletasks()
-
-        x = self._entryfield.winfo_rootx()
-        y = self._entryfield.winfo_rooty() + \
-            self._entryfield.winfo_height()
-        w = self._entryfield.winfo_width() + self._arrowBtn.winfo_width()
-        h =  self.__listbox.winfo_height()
-        sh = self.winfo_screenheight()
-
-        if y + h > sh and y > sh / 2:
-            y = self._entryfield.winfo_rooty() - h
-
-        self._list.configure(hull_width=w)
-
-        setgeometryanddeiconify(self._popup, '+%d+%d' % (x, y))
-
-        # Grab the popup, so that all events are delivered to it, and
-        # set focus to the listbox, to make keyboard navigation
-        # easier.
-        pushgrab(self._popup, 1, self._unpostList)
-        self.__listbox.focus_set()
-
-        self._drawArrow()
-
-        # Ignore the first release of the mouse button after posting the
-        # dropdown list, unless the mouse enters the dropdown list.
-        self._ignoreRelease = 1
-
-    def _dropdownBtnRelease(self, event):
-	if (event.widget == self._list.component('vertscrollbar') or
-		event.widget == self._list.component('horizscrollbar')):
-	    return
-
-	if self._ignoreRelease:
-	    self._unpostOnNextRelease()
-	    return
-
-        self._unpostList()
-
-	if (event.x >= 0 and event.x < self.__listbox.winfo_width() and
-		event.y >= 0 and event.y < self.__listbox.winfo_height()):
-	    self._selectCmd()
-
-    def _unpostOnNextRelease(self, event = None):
-	self._ignoreRelease = 0
-
-    def _resizeArrow(self, event):
-	bw = (string.atoi(self._arrowBtn['borderwidth']) + 
-		string.atoi(self._arrowBtn['highlightthickness']))
-	newHeight = self._entryfield.winfo_reqheight() - 2 * bw
-	newWidth = int(newHeight * self['buttonaspect'])
-	self._arrowBtn.configure(width=newWidth, height=newHeight)
-	self._drawArrow()
-
-    def _unpostList(self, event=None):
-	if not self._isPosted:
-            # It is possible to get events on an unposted popup.  For
-            # example, by repeatedly pressing the space key to post
-            # and unpost the popup.  The <space> event may be
-            # delivered to the popup window even though
-            # popgrab() has set the focus away from the
-            # popup window.  (Bug in Tk?)
-            return
-
-        # Restore the focus before withdrawing the window, since
-        # otherwise the window manager may take the focus away so we
-        # can't redirect it.  Also, return the grab to the next active
-        # window in the stack, if any.
-        popgrab(self._popup)
-	self._popup.withdraw()
-
-	self._isPosted = 0
-	self._drawArrow()
-
-    def _selectUnpost(self, event):
-        self._unpostList()
-	self._selectCmd()
-
-forwardmethods(ComboBox, ScrolledListBox, '_list')
-forwardmethods(ComboBox, EntryField, '_entryfield')
-
-######################################################################
-### File: PmwComboBoxDialog.py
-# Not Based on iwidgets version.
-
-
-
-class ComboBoxDialog(Dialog):
-    # Dialog window with simple combobox.
-    
-    # Dialog window displaying a list and entry field and requesting
-    # the user to make a selection or enter a value
-
-    def __init__(self, parent = None, **kw):
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderx',    10,              INITOPT),
-	    ('bordery',    10,              INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	Dialog.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-
-	aliases = (
-	    ('listbox', 'combobox_listbox'),
-	    ('scrolledlist', 'combobox_scrolledlist'),
-	    ('entry', 'combobox_entry'),
-	    ('label', 'combobox_label'),
-	)
-	self._combobox = self.createcomponent('combobox',
-		aliases, None,
-		ComboBox, (interior,),
-		scrolledlist_dblclickcommand = self.invoke,
-		dropdown = 0,
-	)
-	self._combobox.pack(side='top', expand='true', fill='both',
-		padx = self['borderx'], pady = self['bordery'])
-
-        if not kw.has_key('activatecommand'):
-            # Whenever this dialog is activated, set the focus to the
-            # ComboBox's listbox widget.
-            listbox = self.component('listbox')
-            self.configure(activatecommand = listbox.focus_set)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)size method inherited from Tkinter.Toplevel.Grid.
-    def size(self):
-	return self._combobox.size()
-
-    # Need to explicitly forward this to override the stupid
-    # (grid_)bbox method inherited from Tkinter.Toplevel.Grid.
-    def bbox(self, index):
-	return self._combobox.bbox(index)
-
-forwardmethods(ComboBoxDialog, ComboBox, '_combobox')
-
-######################################################################
-### File: PmwCounter.py
-import string
-import sys
-import types
-import Tkinter
-
-
-class Counter(MegaWidget):
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('autorepeat',     1,             None),
-	    ('buttonaspect',   1.0,           INITOPT),
-	    ('datatype',       'numeric',     self._datatype),
-	    ('increment',      1,             None),
-	    ('initwait',       300,           None),
-	    ('labelmargin',    0,             INITOPT),
-	    ('labelpos',       None,          INITOPT),
-	    ('orient',         'horizontal',  INITOPT),
-	    ('padx',           0,             INITOPT),
-	    ('pady',           0,             INITOPT),
-	    ('repeatrate',     50,            None),
-	    ('sticky',         'ew',          INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	MegaWidget.__init__(self, parent)
-
-	# Initialise instance variables.
-	self._timerId = None
-	self._normalRelief = None
-
-	# Create the components.
-	interior = self.interior()
-
-	# If there is no label, put the arrows and the entry directly
-	# into the interior, otherwise create a frame for them.  In
-	# either case the border around the arrows and the entry will
-	# be raised (but not around the label).
-	if self['labelpos'] is None:
-	    frame = interior
-            if not kw.has_key('hull_relief'):
-                frame.configure(relief = 'raised')
-            if not kw.has_key('hull_borderwidth'):
-                frame.configure(borderwidth = 1)
-	else:
-	    frame = self.createcomponent('frame',
-		    (), None,
-		    Tkinter.Frame, (interior,),
-                    relief = 'raised', borderwidth = 1)
-	    frame.grid(column=2, row=2, sticky=self['sticky'])
-	    interior.grid_columnconfigure(2, weight=1)
-	    interior.grid_rowconfigure(2, weight=1)
-
-	# Create the down arrow.
-	self._downArrowBtn = self.createcomponent('downarrow',
-		(), 'Arrow',
-		Tkinter.Canvas, (frame,),
-		width = 16, height = 16, relief = 'raised', borderwidth = 2)
-
-	# Create the entry field.
-	self._counterEntry = self.createcomponent('entryfield',
-		(('entry', 'entryfield_entry'),), None,
-		EntryField, (frame,))
-
-	# Create the up arrow.
-	self._upArrowBtn = self.createcomponent('uparrow',
-		(), 'Arrow',
-		Tkinter.Canvas, (frame,),
-		width = 16, height = 16, relief = 'raised', borderwidth = 2)
-
-	padx = self['padx']
-	pady = self['pady']
-	orient = self['orient']
-	if orient == 'horizontal':
-	    self._downArrowBtn.grid(column = 0, row = 0)
-	    self._counterEntry.grid(column = 1, row = 0,
-                    sticky = self['sticky'])
-	    self._upArrowBtn.grid(column = 2, row = 0)
-	    frame.grid_columnconfigure(1, weight = 1)
-	    frame.grid_rowconfigure(0, weight = 1)
-	    if Tkinter.TkVersion >= 4.2:
-		frame.grid_columnconfigure(0, pad = padx)
-		frame.grid_columnconfigure(2, pad = padx)
-		frame.grid_rowconfigure(0, pad = pady)
-	elif orient == 'vertical':
-	    self._upArrowBtn.grid(column = 0, row = 0, sticky = 's')
-	    self._counterEntry.grid(column = 0, row = 1,
-                    sticky = self['sticky'])
-	    self._downArrowBtn.grid(column = 0, row = 2, sticky = 'n')
-	    frame.grid_columnconfigure(0, weight = 1)
-	    frame.grid_rowconfigure(0, weight = 1)
-	    frame.grid_rowconfigure(2, weight = 1)
-	    if Tkinter.TkVersion >= 4.2:
-		frame.grid_rowconfigure(0, pad = pady)
-		frame.grid_rowconfigure(2, pad = pady)
-		frame.grid_columnconfigure(0, pad = padx)
-	else:
-	    raise ValueError, 'bad orient option ' + repr(orient) + \
-		': must be either \'horizontal\' or \'vertical\''
-
-	self.createlabel(interior)
-
-	self._upArrowBtn.bind('<Configure>', self._drawUpArrow)
-	self._upArrowBtn.bind('<1>', self._countUp)
-	self._upArrowBtn.bind('<Any-ButtonRelease-1>', self._stopCounting)
-	self._downArrowBtn.bind('<Configure>', self._drawDownArrow)
-	self._downArrowBtn.bind('<1>', self._countDown)
-	self._downArrowBtn.bind('<Any-ButtonRelease-1>', self._stopCounting)
-	self._counterEntry.bind('<Configure>', self._resizeArrow)
-	entry = self._counterEntry.component('entry')
-	entry.bind('<Down>', lambda event, s = self: s._key_decrement(event))
-	entry.bind('<Up>', lambda event, s = self: s._key_increment(event))
-
-	# Need to cancel the timer if an arrow button is unmapped (eg: 
-	# its toplevel window is withdrawn) while the mouse button is
-	# held down.  The canvas will not get the ButtonRelease event
-	# if it is not mapped, since the implicit grab is cancelled.
-	self._upArrowBtn.bind('<Unmap>', self._stopCounting)
-	self._downArrowBtn.bind('<Unmap>', self._stopCounting)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    def _resizeArrow(self, event):
-	for btn in (self._upArrowBtn, self._downArrowBtn):
-	    bw = (string.atoi(btn['borderwidth']) +
-		    string.atoi(btn['highlightthickness']))
-	    newHeight = self._counterEntry.winfo_reqheight() - 2 * bw
-	    newWidth = int(newHeight * self['buttonaspect'])
-	    btn.configure(width=newWidth, height=newHeight)
-	    self._drawArrow(btn)
-
-    def _drawUpArrow(self, event):
-	self._drawArrow(self._upArrowBtn)
-
-    def _drawDownArrow(self, event):
-	self._drawArrow(self._downArrowBtn)
-
-    def _drawArrow(self, arrow):
-        if self['orient'] == 'vertical':
-            if arrow == self._upArrowBtn:
-                direction = 'up'
-            else:
-                direction = 'down'
-        else:
-            if arrow == self._upArrowBtn:
-                direction = 'right'
-            else:
-                direction = 'left'
-        drawarrow(arrow, self['entry_foreground'], direction, 'arrow')
-
-    def _stopCounting(self, event = None):
-        if self._timerId is not None:
-            self.after_cancel(self._timerId)
-	    self._timerId = None
-	if self._normalRelief is not None:
-	    button, relief = self._normalRelief
-	    button.configure(relief=relief)
-	    self._normalRelief = None
-
-    def _countUp(self, event):
-	self._normalRelief = (self._upArrowBtn, self._upArrowBtn.cget('relief'))
-	self._upArrowBtn.configure(relief='sunken')
-	# Force arrow down (it may come up immediately, if increment fails).
-	self._upArrowBtn.update_idletasks()
-	self._count(1, 1)
-
-    def _countDown(self, event):
-	self._normalRelief = (self._downArrowBtn, self._downArrowBtn.cget('relief'))
-	self._downArrowBtn.configure(relief='sunken')
-	# Force arrow down (it may come up immediately, if increment fails).
-	self._downArrowBtn.update_idletasks()
-	self._count(-1, 1)
-
-    def increment(self):
-	self._forceCount(1)
-
-    def decrement(self):
-	self._forceCount(-1)
-
-    def _key_increment(self, event):
-	self._forceCount(1)
-	self.update_idletasks()
-
-    def _key_decrement(self, event):
-	self._forceCount(-1)
-	self.update_idletasks()
-
-    def _datatype(self):
-	datatype = self['datatype']
-
-	if type(datatype) is types.DictionaryType:
-	    self._counterArgs = datatype.copy()
-	    if self._counterArgs.has_key('counter'):
-		datatype = self._counterArgs['counter']
-		del self._counterArgs['counter']
-	    else:
-		datatype = 'numeric'
-	else:
-	    self._counterArgs = {}
-
-	if _counterCommands.has_key(datatype):
-	    self._counterCommand = _counterCommands[datatype]
-	elif callable(datatype):
-	    self._counterCommand = datatype
-	else:
-	    validValues = _counterCommands.keys()
-	    validValues.sort()
-	    raise ValueError, ('bad datatype value "%s":  must be a' +
-		    ' function or one of %s') % (datatype, validValues)
-
-    def _forceCount(self, factor):
-	if not self.valid():
-	    self.bell()
-	    return
-
-	text = self._counterEntry.get()
-	try:
-	    value = apply(self._counterCommand,
-		    (text, factor, self['increment']), self._counterArgs)
-	except ValueError:
-	    self.bell()
-	    return
-
-        previousICursor = self._counterEntry.index('insert')
-	if self._counterEntry.setentry(value) == OK:
-	    self._counterEntry.xview('end')
-	    self._counterEntry.icursor(previousICursor)
-
-    def _count(self, factor, first):
-	if not self.valid():
-	    self.bell()
-	    return
-
-	self._timerId = None
-	origtext = self._counterEntry.get()
-	try:
-	    value = apply(self._counterCommand,
-		    (origtext, factor, self['increment']), self._counterArgs)
-	except ValueError:
-	    # If text is invalid, stop counting.
-	    self._stopCounting()
-	    self.bell()
-	    return
-
-	# If incrementing produces an invalid value, restore previous
-	# text and stop counting.
-        previousICursor = self._counterEntry.index('insert')
-	valid = self._counterEntry.setentry(value)
-	if valid != OK:
-	    self._stopCounting()
-	    self._counterEntry.setentry(origtext)
-	    if valid == PARTIAL:
-		self.bell()
-	    return
-	self._counterEntry.xview('end')
-	self._counterEntry.icursor(previousICursor)
-
-	if self['autorepeat']:
-	    if first:
-		delay = self['initwait']
-	    else:
-		delay = self['repeatrate']
-	    self._timerId = self.after(delay,
-		    lambda self=self, factor=factor: self._count(factor, 0))
-
-    def destroy(self):
-	self._stopCounting()
-        MegaWidget.destroy(self)
-
-forwardmethods(Counter, EntryField, '_counterEntry')
-
-def _changeNumber(text, factor, increment):
-  value = string.atol(text)
-  if factor > 0:
-    value = (value / increment) * increment + increment
-  else:
-    value = ((value - 1) / increment) * increment
-
-  # Get rid of the 'L' at the end of longs (in python up to 1.5.2).
-  rtn = str(value)
-  if rtn[-1] == 'L':
-      return rtn[:-1]
-  else:
-      return rtn
-
-def _changeReal(text, factor, increment, separator = '.'):
-  value = stringtoreal(text, separator)
-  div = value / increment
-
-  # Compare reals using str() to avoid problems caused by binary
-  # numbers being only approximations to decimal numbers.
-  # For example, if value is -0.3 and increment is 0.1, then
-  # int(value/increment) = -2, not -3 as one would expect.
-  if str(div)[-2:] == '.0':
-    # value is an even multiple of increment.
-    div = round(div) + factor
-  else:
-    # value is not an even multiple of increment.
-    div = int(div) * 1.0
-    if value < 0:
-      div = div - 1
-    if factor > 0:
-      div = (div + 1)
-
-  value = div * increment
-
-  text = str(value)
-  if separator != '.':
-      index = string.find(text, '.')
-      if index >= 0:
-	text = text[:index] + separator + text[index + 1:]
-  return text
-
-def _changeDate(value, factor, increment, format = 'ymd',
-	separator = '/', yyyy = 0):
-
-  jdn = datestringtojdn(value, format, separator) + factor * increment
-
-  y, m, d = jdntoymd(jdn)
-  result = ''
-  for index in range(3):
-    if index > 0:
-      result = result + separator
-    f = format[index]
-    if f == 'y':
-      if yyyy:
-        result = result + '%02d' % y
-      else:
-        result = result + '%02d' % (y % 100)
-    elif f == 'm':
-      result = result + '%02d' % m
-    elif f == 'd':
-      result = result + '%02d' % d
-
-  return result
-
-_SECSPERDAY = 24 * 60 * 60
-def _changeTime(value, factor, increment, separator = ':', time24 = 0):
-  unixTime = timestringtoseconds(value, separator)
-  if factor > 0:
-    chunks = unixTime / increment + 1
-  else:
-    chunks = (unixTime - 1) / increment
-  unixTime = chunks * increment
-  if time24:
-      while unixTime < 0:
-	  unixTime = unixTime + _SECSPERDAY
-      while unixTime >= _SECSPERDAY:
-	  unixTime = unixTime - _SECSPERDAY
-  if unixTime < 0:
-    unixTime = -unixTime
-    sign = '-'
-  else:
-    sign = ''
-  secs = unixTime % 60
-  unixTime = unixTime / 60
-  mins = unixTime % 60
-  hours = unixTime / 60
-  return '%s%02d%s%02d%s%02d' % (sign, hours, separator, mins, separator, secs)
-
-# hexadecimal, alphabetic, alphanumeric not implemented
-_counterCommands = {
-    'numeric'   : _changeNumber,      # } integer
-    'integer'   : _changeNumber,      # } these two use the same function
-    'real'      : _changeReal,        # real number
-    'time'      : _changeTime,
-    'date'      : _changeDate,
-}
-
-######################################################################
-### File: PmwCounterDialog.py
-
-
-# A Dialog with a counter
-
-class CounterDialog(Dialog):
-
-    def __init__(self, parent = None, **kw):
-
-	# Define the megawidget options.
-	
-	optiondefs = (
-	    ('borderx',    20,  INITOPT),
-	    ('bordery',    20,  INITOPT),
-	)
-	self.defineoptions(kw, optiondefs)
-
-	# Initialise the base class (after defining the options).
-	Dialog.__init__(self, parent)
-
-	# Create the components.
-	interior = self.interior()
-
-	# Create the counter.
-	aliases = (
-	    ('entryfield', 'counter_entryfield'),
-	    ('entry', 'counter_entryfield_entry'),
-	    ('label', 'counter_label')
-	)
-	self._cdCounter = self.createcomponent('counter',
-		aliases, None,
-		Counter, (interior,))
-	self._cdCounter.pack(fill='x', expand=1,
-		padx = self['borderx'], pady = self['bordery'])
-	
-        if not kw.has_key('activatecommand'):
-            # Whenever this dialog is activated, set the focus to the
-            # Counter's entry widget.
-            tkentry = self.component('entry')
-            self.configure(activatecommand = tkentry.focus_set)
-
-	# Check keywords and initialise options.
-	self.initialiseoptions()
-
-    # Supply aliases to some of the entry component methods.
-    def insertentry(self, index, text):
-	self._cdCounter.insert(index, text)
-
-    def deleteentry(self, first, last=None):
-	self._cdCounter.delete(first, last)
-
-    def indexentry(self, index):
-	return self._cdCounter.index(index)
-
-forwardmethods(CounterDialog, Counter, '_cdCounter')
-
-######################################################################
-### File: PmwLogicalFont.py
-import os
-import string
-
-def _font_initialise(root, size=None, fontScheme = None):
-    global _fontSize
-    if size is not None:
-        _fontSize = size
-
-    if fontScheme in ('pmw1', 'pmw2'):
-        if os.name == 'posix':
-            defaultFont = logicalfont('Helvetica')
-            menuFont = logicalfont('Helvetica', weight='bold', slant='italic')
-            scaleFont = logicalfont('Helvetica', slant='italic')
-            root.option_add('*Font',            defaultFont,  'userDefault')
-            root.option_add('*Menu*Font',       menuFont,     'userDefault')
-            root.option_add('*Menubutton*Font', menuFont,     'userDefault')
-            root.option_add('*Scale.*Font',     scaleFont,    'userDefault')
-
-            if fontScheme == 'pmw1':
-                balloonFont = logicalfont('Helvetica', -6, pixel = '12')
-            else: # fontScheme == 'pmw2'
-                balloonFont = logicalfont('Helvetica', -2)
-            root.option_add('*Balloon.*Font', balloonFont, 'userDefault')
-        else:
-            defaultFont = logicalfont('Helvetica')
-            root.option_add('*Font', defaultFont,  'userDefault')
-    elif fontScheme == 'default':
-        defaultFont = ('Helvetica', '-%d' % (_fontSize,), 'bold')
-        entryFont = ('Helvetica', '-%d' % (_fontSize,))
-        textFont = ('Courier', '-%d' % (_fontSize,))
-        root.option_add('*Font',            defaultFont,  'userDefault')
-        root.option_add('*Entry*Font',      entryFont,    'userDefault')
-        root.option_add('*Text*Font',       textFont,     'userDefault')
-
-def logicalfont(name='Helvetica', sizeIncr = 0, **kw):
-  if not _fontInfo.has_key(name):
-    raise ValueError, 'font %s does not exist' % name
-
-  rtn = []
-  for field in _fontFields:
-    if kw.has_key(field):
-      logicalValue = kw[field]
-    elif _fontInfo[name].has_key(field):
-      logicalValue = _fontInfo[name][field]
-    else:
-      logicalValue = '*'
-
-    if _propertyAliases[name].has_key((field, logicalValue)):
-      realValue = _propertyAliases[name][(field, logicalValue)]
-    elif _propertyAliases[name].has_key((field, None)):
-      realValue = _propertyAliases[name][(field, None)]
-    elif _propertyAliases[None].has_key((field, logicalValue)):
-      realValue = _propertyAliases[None][(field, logicalValue)]
-    elif _propertyAliases[None].has_key((field, None)):
-      realValue = _propertyAliases[None][(field, None)]
-    else:
-      realValue = logicalValue
-
-    if field == 'size':
-      if realValue == '*':
-	  realValue = _fontSize
-      realValue = str((realValue + sizeIncr) * 10)
-
-    rtn.append(realValue)
-
-  return string.join(rtn, '-')
-
-def logicalfontnames():
-  return _fontInfo.keys()
-
-if os.name == 'nt':
-    _fontSize = 16
-else:
-    _fontSize = 14
-
-_fontFields = (
-  'registry', 'foundry', 'family', 'weight', 'slant', 'width', 'style',
-  'pixel', 'size', 'xres', 'yres', 'spacing', 'avgwidth', 'charset', 'encoding')
-
-# <_propertyAliases> defines other names for which property values may
-# be known by.  This is required because italics in adobe-helvetica
-# are specified by 'o', while other fonts use 'i'.
-
-_propertyAliases = {}
-
-_propertyAliases[None] = {
-  ('slant', 'italic') : 'i',
-  ('slant', 'normal') : 'r',
-  ('weight', 'light') : 'normal',
-  ('width', 'wide') : 'normal',
-  ('width', 'condensed') : 'normal',
-}
-
-# <_fontInfo> describes a 'logical' font, giving the default values of
-# some of its properties.
-
-_fontInfo = {}
-
-_fontInfo['Helvetica'] = {
-  'foundry' : 'adobe',
-  'family' : 'helvetica',
-  'registry' : '',
-  'charset' : 'iso8859',
-  'encoding' : '1',
-  'spacing' : 'p',
-  'slant' : 'normal',
-  'width' : 'normal',
-  'weight' : 'normal',
-}
-
-_propertyAliases['Helvetica'] = {
-  ('slant', 'italic') : 'o',
-  ('weight', 'normal') : 'medium',
-  ('weight', 'light') : 'medium',
-}
-
-_fontInfo['Times'] = {
-  'foundry' : 'adobe',
-  'family' : 'times',
-  'registry' : '',
-  'charset' : 'iso8859',
-  'encoding' : '1',
-  'spacing' : 'p',
-  'slant' : 'normal',
-  'width' : 'normal',
-  'weight' : 'normal',
-}
-
-_propertyAliases['Times'] = {
-  ('weight', 'normal') : 'medium',
-  ('weight', 'light') : 'medium',
-}
-
-_fontInfo['Fixed'] = {
-  'foundry' : 'misc',
-  'family' : 'fixed',
-  'registry' : '',
-  'charset' : 'iso8859',
-  'encoding' : '1',
-  'spacing' : 'c',
-  'slant' : 'normal',
-  'width' : 'normal',
-  'weight' : 'normal',
-}
-
-_propertyAliases['Fixed'] = {
-  ('weight', 'normal') : 'medium',
-  ('weight', 'light') : 'medium',
-  ('style', None) : '',
-  ('width', 'condensed') : 'semicondensed',
-}
-
-_fontInfo['Courier'] = {
-  'foundry' : 'adobe',
-  'family' : 'courier',
-  'registry' : '',
-  'charset' : 'iso8859',
-  'encoding' : '1',
-  'spacing' : 'm',
-  'slant' : 'normal',
-  'width' : 'normal',
-  'weight' : 'normal',
-}
-
-_propertyAliases['Courier'] = {
-  ('weight', 'normal') : 'medium',
-  ('weight', 'light') : 'medium',
-  ('style', None) : '',
-}
-
-_fontInfo['Typewriter'] = {
-  'foundry' : 'b&h',
-  'family' : 'lucidatypewriter',
-  'registry' : '',
-  'charset' : 'iso8859',
-  'encoding' : '1',
-  'spacing' : 'm',
-  'slant' : 'normal',
-  'width' : 'normal',
-  'weight' : 'normal',
-}
-
-_propertyAliases['Typewriter'] = {
-  ('weight', 'normal') : 'medium',
-  ('weight', 'light') : 'medium',
-}
-
-if os.name == 'nt':
-    # For some reason 'fixed' fonts on NT aren't.
-    _fontInfo['Fixed'] = _fontInfo['Courier']
-    _propertyAliases['Fixed'] = _propertyAliases['Courier']
diff -uNr bkchem-0.12.5/setup.py bkchem-0.12.5-nopiddle/setup.py
--- bkchem-0.12.5/setup.py	2008-05-30 04:34:24.000000000 -0300
+++ bkchem-0.12.5-nopiddle/setup.py	2008-12-16 09:54:29.000000000 -0200
@@ -58,7 +58,7 @@
   platforms = ["Unix", "Windows", "hopefully other OSes able to run Python"],
   long_description = "BKchem is a chemical drawing program written in Python",
   
-  packages=[ 'bkchem', 'bkchem/plugins', 'bkchem/oasa', 'bkchem/oasa/oasa', 'bkchem/oasa/oasa/graph', 'bkchem/plugins/piddle'],
+  packages=[ 'bkchem', 'bkchem/plugins'],
 
   data_files=[ ('share/bkchem/templates', glob.glob( 'templates/*.cdml')+glob.glob('templates/*.xml')),
                ('share/bkchem/images', ['images/logo.ppm','images/icon.ico']+glob.glob('images/bkchem*.png')),
diff -uNr bkchem-0.12.5/setup.py~ bkchem-0.12.5-nopiddle/setup.py~
--- bkchem-0.12.5/setup.py~	1969-12-31 21:00:00.000000000 -0300
+++ bkchem-0.12.5-nopiddle/setup.py~	2008-11-26 09:25:30.000000000 -0200
@@ -0,0 +1,128 @@
+#! /usr/bin/python
+
+import sys
+from distutils.core import setup
+import glob
+import os
+import operator
+try:
+  import py2exe
+except:
+  pass
+from bkchem import config
+
+
+
+## A few pre-setup hacks
+
+
+if os.name != 'posix':
+  sys.path.insert( 0, 'bkchem')
+
+# all the apicdoc directories and files
+apidocs = [('share/doc/bkchem/'+path[4:], map( os.path.join, len( filenames)*[path], filenames)) for (path, dirnames, filenames) in os.walk( 'doc/api')]
+
+# available languages to pack
+langs = [l for l in os.listdir( 'locale') if os.path.isdir( 'locale/'+l) and os.path.exists( 'locale/'+l+'/LC_MESSAGES/BKchem.mo')]
+
+print "found languages:", langs
+localizations = [('share/locale/'+lang+'/LC_MESSAGES', ['locale/'+lang+'/LC_MESSAGES/BKchem.mo']) for lang in langs]
+
+
+# should we strip something from in the scripts from the installation path (used in gentoo sandboxing etc.)
+strip = ""
+for arg in sys.argv:
+  if arg.startswith( "--strip="):
+    strip = arg.lstrip( "--strip=")
+    sys.argv.remove( arg)
+    break
+
+def strip_path( path):
+  if path.startswith( strip):
+    new = path.replace( strip, "", 1)
+    return new
+  else:
+    return path
+
+
+# the setup itself
+
+set = setup(
+  name = 'bkchem',
+  version = config.current_BKchem_version,
+  description = "BKchem is a chemical drawing program written in Python",
+  author = "Beda Kosata",
+  author_email = "beda@zirael.org",
+  url = "http://bkchem.zirael.org",
+  license = "GNU GPL",
+  platforms = ["Unix", "Windows", "hopefully other OSes able to run Python"],
+  long_description = "BKchem is a chemical drawing program written in Python",
+  
+  packages=[ 'bkchem', 'bkchem/plugins', 'bkchem/oasa', 'bkchem/oasa/oasa', 'bkchem/oasa/oasa/graph'],
+
+  data_files=[ ('share/bkchem/templates', glob.glob( 'templates/*.cdml')+glob.glob('templates/*.xml')),
+               ('share/bkchem/images', ['images/logo.ppm','images/icon.ico']+glob.glob('images/bkchem*.png')),
+               ('share/bkchem/pixmaps', glob.glob( 'pixmaps/*.gif')),
+               ('share/bkchem/dtd', glob.glob( 'dtd/*.dtd') + glob.glob( 'dtd/*.xsd')),
+               ('share/bkchem/plugins', glob.glob( 'plugins/*.py')+glob.glob( 'plugins/*.xml')),
+               ('share/doc/bkchem', glob.glob( 'doc/*.xml') + glob.glob( 'doc/*.html') + ['README', 'RELEASE', 'INSTALL', 'progress.log']),
+               #('share/doc/bkchem/ps', glob.glob( 'doc/ps/*')),
+               #('share/doc/bkchem/pdf', glob.glob( 'doc/pdf/*')),
+               ('share/doc/bkchem/html', glob.glob( 'doc/html/*')),
+               ('share/doc/bkchem/scripts', glob.glob( 'doc/scripts/*')),
+               ('share/doc/bkchem/img', glob.glob( 'doc/img/*')),
+               ] + localizations + apidocs,
+  windows=['bkchem/bkchem.py'],
+  options = {"py2exe": {"packages": ["encodings"]}}
+  )
+
+
+
+
+if len( sys.argv) > 1 and sys.argv[1] == 'install' and '--help' not in sys.argv:
+  data_dir = set.command_obj['install'].install_data
+  py_dir = set.command_obj['install'].install_lib
+  bin_dir = set.command_obj['install'].install_scripts
+
+  # the configuration file
+  config_name = os.path.join( py_dir, 'bkchem/site_config.py')
+  try:
+    file = open( config_name, 'w')
+  except:
+    print "ERROR: couldn't open the file %s for write" %  config_name
+    sys.exit()
+  file.write( "# the bkchem configuration file, do not edit!\n #(unless you are pretty sure that you know what you are doing, which even I am not)\n")
+  file.write( 'BKCHEM_MODULE_PATH="%s"\n' % strip_path( os.path.join( py_dir, "bkchem")))
+  file.write( 'BKCHEM_TEMPLATE_PATH="%s"\n' % strip_path( os.path.join( data_dir, "share/bkchem/templates")))
+  file.write( 'BKCHEM_PIXMAP_PATH="%s"\n' % strip_path( os.path.join( data_dir, "share/bkchem/pixmaps")))
+  file.write( 'BKCHEM_IMAGE_PATH="%s"\n' % strip_path( os.path.join( data_dir, "share/bkchem/images")))
+  file.write( 'BKCHEM_PLUGIN_PATH="%s"\n' % strip_path( os.path.join( data_dir, "share/bkchem/plugins")))
+  file.close()
+  print "file %s created" % config_name
+
+
+  # the executable
+  if not os.path.isdir( bin_dir):
+    try:
+      os.mkdir( bin_dir)
+    except:
+      print "ERROR: could not create directory %s" % bin_dir
+      sys.exit( 201)      
+  exec_name = os.path.join( bin_dir, 'bkchem')
+  try:
+    file = open( exec_name, 'w')
+  except:
+    print "ERROR: couldn't open the file %s for write" %  exec_name
+    sys.exit( 201)
+  file.write( "#!/bin/sh\n")
+  file.write( 'python %s "$@"\n' % strip_path( os.path.join( py_dir, "bkchem", "bkchem.py")))
+  file.close()
+  print "file %s created" % exec_name
+  try:
+    os.chmod( os.path.join( bin_dir, 'bkchem'), 5+5*8+7*8*8)
+  except:
+    print "ERROR: failed to make %s executable" % exec_name
+    sys.exit( 201)
+  print "file %s made executable" % exec_name
+  
+
