#------------------------------------------------------------------------------
#  Copyright (c) 2009 Richard W. Lincoln
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to
#  deal in the Software without restriction, including without limitation the
#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
#  sell copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
#  IN THE SOFTWARE.
#------------------------------------------------------------------------------

""" Defines a representation of a graph in Graphviz's dot language.
"""

#------------------------------------------------------------------------------
#  Imports:
#------------------------------------------------------------------------------

import os
import subprocess
import tempfile
import math
import logging

from enthought.traits.api import \
    HasTraits, Any, Instance, Trait, Tuple, Bool, Str, Enum, Float, Color, \
    Either, Range, Int, Font, List, Directory, ListInstance, This, Property, \
    Dict, on_trait_change

from enthought.traits.trait_handlers import TraitListEvent

from enthought.traits.ui.api import View, Group, Item, Tabbed
from enthought.pyface.image_resource import ImageResource
from enthought.enable.api import Canvas, Viewport, Container
from enthought.enable.tools.api import ViewportPanTool, ViewportZoomTool
from enthought.enable.component_editor import ComponentEditor

from common import \
    Alias, color_scheme_trait, rectangle_trait, fontcolor_trait, \
    fontname_trait, fontsize_trait, label_trait, point_trait, pointf_trait, \
    nojustify_trait, root_trait, showboxes_trait, target_trait, margin_trait

from godot.base_graph import BaseGraph
from godot.node import Node
from godot.edge import Edge
from godot.subgraph import Subgraph
from godot.cluster import Cluster

from godot.ui.graph_view import graph_view, tabbed_view

#------------------------------------------------------------------------------
#  Logging:
#------------------------------------------------------------------------------

logger = logging.getLogger(__name__)

#------------------------------------------------------------------------------
#  Trait definitions:
#------------------------------------------------------------------------------

cluster_mode_trait = Enum(
    "local", "global", "none", desc="mode used for handling clusters",
    label="Cluster rank", graphviz=True
)

start_trait = Enum("regular", "self", "random", graphviz=True)

#------------------------------------------------------------------------------
#  "Graph" class:
#------------------------------------------------------------------------------

class Graph(BaseGraph):
    """ Defines a representation of a graph in Graphviz's dot language.
    """

    #--------------------------------------------------------------------------
    #  Trait definitions.
    #--------------------------------------------------------------------------

    # A strict graph is an unweighted, undirected graph containing no
  # graph loops or multiple edges.
    strict = Bool(False, desc="A strict graph is an unweighted, "
        "undirected graph containing no graph loops or multiple edges." )

    # Do edges have direction?
    directed = Bool(False, desc="directed edges.")

    # All graphs, subgraphs and clusters.
    all_graphs = Property( List(Instance(BaseGraph)) )

    #--------------------------------------------------------------------------
    #  Dot trait definitions.
    #--------------------------------------------------------------------------

    # Bounding box of drawing in integer points.
    bb = rectangle_trait

    # When attached to the root graph, this color is used as the background for
    # entire canvas. When a cluster attribute, it is used as the initial
    # background for the cluster. If a cluster has a filled
    # <html:a rel="attr">style</html:a>, the
    # cluster's <html:a rel="attr">fillcolor</html:a> will overlay the
    # background color.
    #
    # If no background color is specified for the root graph, no graphics
    # operation are performed on the background. This works fine for
    # PostScript but for bitmap output, all bits are initialized to something.
    # This means that when the bitmap output is included in some other
    # document, all of the bits within the bitmap's bounding box will be
    # set, overwriting whatever color or graphics where already on the page.
    # If this effect is not desired, and you only want to set bits explicitly
    # assigned in drawing the graph, set <html:a rel="attr">bgcolor</html:a>="transparent".
    bgcolor = Color("white", desc="color used as the background for the "
        "entire canvas", label="Background Color", graphviz=True)

  # If true, the drawing is centered in the output canvas.
    center = Bool(False, desc="is drawing centered in the output canvas",
        graphviz=True)

    # Specifies the character encoding used when interpreting string input
    # as a text label. The default value is <html:span class="val">UTF-8</html:span>.
    # The other legal value is <html:span class="val">iso-8859-1</html:span> or,
    # equivalently,
    # <html:span class="val">Latin1</html:span>. The <html:a rel="attr">charset</html:a> attribute is case-insensitive.
    # Note that if the character encoding used in the input does not
    # match the <html:a rel="attr">charset</html:a> value, the resulting output may be very strange.
    charset = Str("UTF-8", label="Character encoding", graphviz=True)

    # Mode used for handling clusters. If <html:a rel="attr">clusterrank</html:a> is <html:span class="val">local</html:span>, a
    # subgraph whose name begins with "cluster" is given special treatment.
    # The subgraph is laid out separately, and then integrated as a unit into
    # its parent graph, with a bounding rectangle drawn about it.
    # If the cluster has a <html:a rel="attr">label</html:a> parameter, this label
    # is displayed within the rectangle.
    # Note also that there can be clusters within clusters.
    # At present, the modes <html:span class="val">global</html:span> and <html:span class="val">none</html:span>
    # appear to be identical, both turning off the special cluster processing.
    clusterrank = cluster_mode_trait

    # This attribute specifies a color scheme namespace. If defined, it specifies
    # the context for interpreting color names. In particular, if a
    # <html:a rel="type">color</html:a> value has form <html:code>xxx</html:code> or <html:code>//xxx</html:code>,
    # then the color <html:code>xxx</html:code> will be evaluated according to the current color scheme.
    # If no color scheme is set, the standard X11 naming is used.
    # For example, if <html:code>colorscheme=bugn9</html:code>, then <html:code>color=7</html:code>
    # is interpreted as <html:code>/bugn9/7</html:code>.
    colorscheme = color_scheme_trait

  # Comments are inserted into output. Device-dependent.
    comment = Str(desc="comments are inserted into output", graphviz=True)

  # If <html:span class="val">true</html:span>, allow edges between clusters.
    # (See <html:a rel="attr">lhead</html:a> and <html:a rel="attr">ltail</html:a> below.)
    compound = Bool(False, desc="edges allowed between clusters",
        graphviz=True)

  # If <html:span class="val">true</html:span>, use edge concentrators.
    concentrate = Bool(False, desc="edge concentrators", graphviz=True)

    # Factor damping force motions. On each iteration, a nodes movement
    # is limited to this factor of its potential motion. By being less than
    # 1.0, the system tends to "cool", thereby preventing cycling.
    Damping = Float(0.99, desc="factor damping force motions", graphviz=True)

    # This specifies the distance between nodes in separate connected
    # components. If set too small, connected components may overlap.
    # Only applicable if <html:a rel="attr">pack</html:a>=false.
    defaultdist = Float(desc="distance between nodes in separate connected "
        "components", label="Default distance", graphviz=True)

    # Set the number of dimensions used for the layout. The maximum value
    # allowed is 10.
    dim = Range(low=2, high=10, desc="number of dimensions for the layout",
        label="Dimensions", graphviz=True)

    # Only valid when mode="ipsep". If true, constraints are generated for each
    # edge in the largest (heuristic) directed acyclic subgraph such that the
    # edge must point downwards. If "hier", generates level constraints similar
    # to those used with mode="hier". The main difference is that, in the
    # latter case, only these constraints are involved, so a faster solver can
    # be used.
    diredgeconstraints = Enum(True, "heir", label="Edge constraints",
        graphviz=True)

    # This specifies the expected number of pixels per inch on a display device.
    # For bitmap output, this guarantees that text rendering will be
    # done more accurately, both in size and in placement. For SVG output,
    # it is used to guarantee that the dimensions in the output correspond to
    # the correct number of points or inches.
    dpi = Float(96.0, desc="expected number of pixels per inch on a display",
        label="DPI", graphviz=True)

    # Terminating condition. If the length squared of all energy gradients are
    # < <html:a rel="attr">epsilon</html:a>, the algorithm stops.
    epsilon = Float(0.0001, desc="terminating condition", graphviz=True)

    # Fraction to increase polygons (multiply
    # coordinates by 1 + esep) for purposes of spline edge routing.
    # This should normally be strictly less than
    # <html:a rel="attr">sep</html:a>.
    esep = Int(3, desc="Fraction to increase polygons (multiply coordinates "
        "by 1 + esep) for purposes of spline edge routing",
        label="Edge separation", graphviz=True)

  # Color used for text.
    fontcolor = fontcolor_trait

    # Font used for text. This very much depends on the output format and, for
    # non-bitmap output such as PostScript or SVG, the availability of the font
    # when the graph is displayed or printed. As such, it is best to rely on
    # font faces that are generally available, such as Times-Roman, Helvetica or
    # Courier.
    #
    # If Graphviz was built using the
    # <html:a href="http://pdx.freedesktop.org/~fontconfig/fontconfig-user.html">fontconfig library</html:a>, the latter library
    # will be used to search for the font. However, if the <html:a rel="attr">fontname</html:a> string
    # contains a slash character "/", it is treated as a pathname for the font
    # file, though font lookup will append the usual font suffixes.
    #
    # If Graphviz does not use fontconfig, <html:a rel="attr">fontname</html:a> will be
    # considered the name of a Type 1 or True Type font file.
    # If you specify <html:code>fontname=schlbk</html:code>, the tool will look for a
    # file named  <html:code>schlbk.ttf</html:code> or <html:code>schlbk.pfa</html:code> or <html:code>schlbk.pfb</html:code>
    # in one of the directories specified by
    # the <html:a rel="attr">fontpath</html:a> attribute.
    # The lookup does support various aliases for the common fonts.
    fontname = fontname_trait

    # Allows user control of how basic fontnames are represented in SVG output.
    # If <html:a rel="attr">fontnames</html:a> is undefined or <html:span class="val">svg</html:span>,
    # the output will try to use known SVG fontnames. For example, the
    # default font <html:code>Times-Roman</html:code> will be mapped to the
    # basic SVG font <html:code>serif</html:code>. This can be overridden by setting
    # <html:a rel="attr">fontnames</html:a> to <html:span class="val">ps</html:span> or <html:span class="val">gd</html:span>.
    # In the former case, known PostScript font names such as
    # <html:code>Times-Roman</html:code> will be used in the output.
    # In the latter case, the fontconfig font conventions
    # are used. Thus, <html:code>Times-Roman</html:code> would be treated as
    # <html:code>Nimbus Roman No9 L</html:code>. These last two options are useful
    # with SVG viewers that support these richer fontname spaces.
    fontnames = Enum("svg", "ps", "gd", label="Font names",
        desc="how basic fontnames are represented in SVG output",
        graphviz=True)

    # Directory list used by libgd to search for bitmap fonts if Graphviz
    # was not built with the fontconfig library.
    # If <html:a rel="attr">fontpath</html:a> is not set, the environment
    # variable <html:code>DOTFONTPATH</html:code> is checked.
    # If that is not set, <html:code>GDFONTPATH</html:code> is checked.
    # If not set, libgd uses its compiled-in font path.
    # Note that fontpath is an attribute of the root graph.
    fontpath = List(Directory, label="Font path", graphviz=True)

  # Font size, in <html:a rel="note">points</html:a>, used for text.
    fontsize = fontsize_trait

    # Spring constant used in virtual physical model. It roughly corresponds to
    # an ideal edge length (in inches), in that increasing K tends to increase
    # the distance between nodes. Note that the edge attribute len can be used
    # to override this value for adjacent nodes.
    K = Float(0.3, desc="spring constant used in virtual physical model",
        graphviz=True)

    # Text label attached to objects.
    # If a node's <html:a rel="attr">shape</html:a> is record, then the label can
    # have a <html:a href="http://www.graphviz.org/doc/info/shapes.html#record">special format</html:a>
    # which describes the record layout.
    label = label_trait

    # Justification for cluster labels. If <html:span class="val">r</html:span>, the label
    # is right-justified within bounding rectangle; if <html:span class="val">l</html:span>, left-justified;
    # else the label is centered.
    # Note that a subgraph inherits attributes from its parent. Thus, if
    # the root graph sets <html:a rel="attr">labeljust</html:a> to <html:span class="val">l</html:span>, the subgraph inherits
    # this value.
    labeljust = Trait("c", {"Centre": "c", "Right": "r", "Left": "l"},
        desc="justification for cluster labels", label="Label justification",
        graphviz=True)

    # Top/bottom placement of graph and cluster labels.
    # If the attribute is <html:span class="val">t</html:span>, place label at the top;
    # if the attribute is <html:span class="val">b</html:span>, place label at the bottom.
    # By default, root
    # graph labels go on the bottom and cluster labels go on the top.
    # Note that a subgraph inherits attributes from its parent. Thus, if
    # the root graph sets <html:a rel="attr">labelloc</html:a> to <html:span class="val">b</html:span>, the subgraph inherits
    # this value.
    labelloc = Trait("b", {"Bottom": "b", "Top":"t"},
        desc="placement of graph and cluster labels",
        label="Label location", graphviz=True)

    # If true, the graph is rendered in landscape mode. Synonymous with
    # <html:code><html:a rel="attr">rotate</html:a>=90</html:code> or <html:code>
    # <html:a rel="attr">orientation</html:a>=landscape</html:code>.
    landscape = Bool(False, desc="rendering in landscape mode", graphviz=True)

    # Specifies a linearly ordered list of layer names attached to the graph
    # The graph is then output in separate layers. Only those components
    # belonging to the current output layer appear. For more information,
    # see the page <html:a href="http://www.graphviz.org/Documentation/html/layers/">How to use drawing layers (overlays)</html:a>.
    layers = Str(desc="a linearly ordered list of layer names",
        graphviz=True)

    # Specifies the separator characters used to split the
    # <html:a rel="attr">layers </html:a>attribute into a list of layer names.
    layersep = Enum(":\t", "\t", " ", label="Layer separation",
        desc="separator characters used to split layer names", graphviz=True)

    # Specifies strictness of level constraints in neato
    # when <html:code><html:a rel="attr">mode</html:a>="ipsep" or "hier"</html:code>.
    # Larger positive values mean stricter constraints, which demand more
    # separation between levels. On the other hand, negative values will relax
    # the constraints by allowing some overlap between the levels.
    levelsgap = Float(0.0, desc="strictness of level constraints in neato",
        label="Levels gap", graphviz=True)

    # Label position, in points. The position indicates the center of the
    # label.
    lp = point_trait

    # For graphs, this sets x and y margins of canvas, in inches. If the margin
    # is a single double, both margins are set equal to the given value.
    #
    # Note that the margin is not part of the drawing but just empty space
    # left around the drawing. It basically corresponds to a translation of
    # drawing, as would be necessary to center a drawing on a page. Nothing
    # is actually drawn in the margin. To actually extend the background of
    # a drawing, see the <html:a rel="attr">pad</html:a> attribute.
    #
    # For nodes, this attribute specifies space left around the node's label.
    # By default, the value is <html:code>0.11,0.055</html:code>.
    margin = margin_trait#Either(Float, pointf_trait, desc="x and y margins of canvas")

    # Sets the number of iterations used.
    maxiter = Int(200, desc="number of iterations used",
        label="Maximum iterations", graphviz=True)

    # Multiplicative scale factor used to alter the MinQuit (default = 8)
    # and MaxIter (default = 24) parameters used during crossing
    # minimization. These correspond to the
    # number of tries without improvement before quitting and the
    # maximum number of iterations in each pass.
    mclimit = Float(1.0, desc="Multiplicative scale factor used to alter the "
        "MinQuit (default = 8) and MaxIter (default = 24) parameters used "
        "during crossing minimization", label="Multiplicative scale factor",
        graphviz=True)

  # Specifies the minimum separation between all nodes.
    mindist = Float(1.0, desc="minimum separation between all nodes",
        label="Minimum separation", graphviz=True)

    # Technique for optimizing the layout. If <html:a rel="attr">mode</html:a> is <html:span class="val">major</html:span>,
    # neato uses stress majorization. If <html:a rel="attr">mode</html:a> is <html:span class="val">KK</html:span>,
    # neato uses a version of the gradient descent method. The only advantage
    # to the latter technique is that it is sometimes appreciably faster for
    # small (number of nodes < 100) graphs. A significant disadvantage is that
    # it may cycle.
    #
    # There are two new, experimental modes in neato, <html:span class="val">hier</html:span>, which adds a top-down
    # directionality similar to the layout used in dot, and <html:span class="val">ipsep</html:span>, which
    # allows the graph to specify minimum vertical and horizontal distances
    # between nodes. (See the <html:a rel="attr">sep</html:a> attribute.)
    mode = Enum("major", "KK", "heir", "ipsep",
        desc="Technique for optimizing the layout", graphviz=True)

    # This value specifies how the distance matrix is computed for the input
    # graph. The distance matrix specifies the ideal distance between every
    # pair of nodes. neato attemps to find a layout which best achieves
    # these distances. By default, it uses the length of the shortest path,
    # where the length of each edge is given by its <html:a rel="attr">len</html:a>
    # attribute. If <html:a rel="attr">model</html:a> is <html:span class="val">circuit</html:span>, neato uses the
    # circuit resistance
    # model to compute the distances. This tends to emphasize clusters. If
    # <html:a rel="attr">model</html:a> is <html:span class="val">subset</html:span>, neato uses the subset model. This sets the
    # edge length to be the number of nodes that are neighbors of exactly one
    # of the end points, and then calculates the shortest paths. This helps
    # to separate nodes with high degree.
    model = Enum("shortpath", "circuit", "subset",
        desc="how the distance matrix is computed for the input graph",
        graphviz=True)

    # If Graphviz is built with MOSEK defined, mode=ipsep and mosek=true,
    # the Mosek software (www.mosek.com) is use to solve the ipsep constraints.
    mosek = Bool(False, desc="solve the ipsep constraints with MOSEK",
        graphviz=True)

  # Minimum space between two adjacent nodes in the same rank, in inches.
    nodesep = Float(0.25, desc="minimum space between two adjacent nodes in "
        "the same rank", label="Node separation", graphviz=True)

    # By default, the justification of multi-line labels is done within the
    # largest context that makes sense. Thus, in the label of a polygonal node,
    # a left-justified line will align with the left side of the node (shifted
    # by the prescribed margin). In record nodes, left-justified line will line
    # up with the left side of the enclosing column of fields. If nojustify is
    # "true", multi-line labels will be justified in the context of itself. For
    # example, if the attribute is set, the first label line is long, and the
    # second is shorter and left-justified, the second will align with the
    # left-most character in the first line, regardless of how large the node
    # might be.
    nojustify = nojustify_trait

    # If set, normalize coordinates of final
    # layout so that the first point is at the origin, and then rotate the
    # layout so that the first edge is horizontal.
    normalize = Bool(False, desc="If set, normalize coordinates of final "
        "layout so that the first point is at the origin, and then rotate the "
        "layout so that the first edge is horizontal", graphviz=True)

    # Used to set number of iterations in
    # network simplex applications, used in
    # computing node x coordinates.
    # If defined, # iterations =  <html:a rel="attr">nslimit</html:a> * # nodes;
    # otherwise,  # iterations = MAXINT.
    nslimit = Float(desc="iterations in network simplex applications",
        label="x-coordinate limit", graphviz=True)

    # Used to set number of iterations in
    # network simplex applications, used for ranking nodes.
    # If defined, # iterations =  <html:a rel="attr">nslimit1</html:a> * # nodes;
    # otherwise,  # iterations = MAXINT.
    nslimit1 = Float(desc="iterations in network simplex applications",
        label="Ranking limit", graphviz=True)

    # If "out" for a graph G, and n is a node in G, then edges n->* appear
    # left-to-right in the same order in which they are defined.
    # If "in", the edges *->n appear
    # left-to-right in the same order in which they are defined for all
    # nodes n.
    ordering = Enum("out", "in", desc="If 'out' for a graph G, and n is a "
        "node in G, then edges n->* appear left-to-right in the same order in "
        "which they are defined. If 'in', the edges *->n appear left-to-right "
        "in the same order in which they are defined for all nodes n.",
        graphviz=True)

    # These specify the order in which nodes and edges are drawn in concrete
    # output. The default "breadthfirst" is the simplest, but when the graph
    # layout does not avoid edge-node overlap, this mode will sometimes have
    # edges drawn over nodes and sometimes on top of nodes. If the mode
    # "nodesfirst" is chosen, all nodes are drawn first, followed by the
    # edges. This guarantees an edge-node overlap will not be mistaken for
    # an edge ending at a node. On the other hand, usually for aesthetic
    # reasons, it may be desirable that all edges appear beneath nodes,
    # even if the resulting drawing is ambiguous. This can be achieved by
    # choosing "edgesfirst".
    outputorder = Enum("breadthfirst", "nodesfirst", "edgesfirst",
        desc="order in which nodes and edges are drawn", label="Output order",
        graphviz=True)

    # Determines if and how node overlaps should be removed. Nodes are first
    # enlarged using the <html:a rel="attr">sep</html:a> attribute.
    # If <html:span class="val">true</html:span>, overlaps are retained.
    # If the value is <html:span class="val">scale</html:span>, overlaps are removed by uniformly scaling in x and y.
    # If the value converts to <html:span class="val">false</html:span>, node overlaps are removed by a
    # Voronoi-based technique.
    # If the value is <html:span class="val">scalexy</html:span>, x and y are separately
    # scaled to remove overlaps.
    # If the value is <html:span class="val">orthoxy</html:span> or <html:span class="val">orthoyx</html:span>, overlaps
    # are moved by optimizing two constraint problems, one for the x axis and
    # one for the y. The suffix indicates which axis is processed first.
    # If the value is <html:span class="val">ortho</html:span>, the technique is similar to <html:span class="val">orthoxy</html:span> except a
    # heuristic is used to reduce the bias between the two passes.
    # If the value is <html:span class="val">ortho_yx</html:span>, the technique is the same as <html:span class="val">ortho</html:span>, except
    # the roles of x and y are reversed.
    # The values <html:span class="val">portho</html:span>, <html:span class="val">porthoxy</html:span>, <html:span class="val">porthoxy</html:span>, and <html:span class="val">portho_yx</html:span> are similar
    # to the previous four, except only pseudo-orthogonal ordering is
    # enforced.
    #
    # If the value is <html:span class="val">compress</html:span>, the layout will be scaled down as much as
    # possible without introducing any overlaps, obviously assuming there are
    # none to begin with.
    #
    # If the value is <html:span class="val">ipsep</html:span>, and the layout is done by neato with
    # <html:a rel="attr">mode</html:a>="ipsep", the overlap removal constraints are
    # incorporated into the layout algorithm itself.
    # N.B. At present, this only supports one level of clustering.
    #
    # If the value is <html:span class="val">vpsc</html:span>, overlap removal is similarly to <html:span class="val">ortho</html:span>, except
    # quadratic optimization is used to minimize node displacement.
    # N.B. At present, this mode only works when <html:a rel="attr">mode</html:a>="ipsep".
    #
    # Except for fdp, the layouts assume <html:code>overlap="true"</html:code> as the default.
    # Fdp first uses a number of passes using built-in, force-directed technique
    # to remove overlaps. Thus, fdp accepts <html:a rel="attr">overlap</html:a> with an integer
    # prefix followed by a colon, specifying the number of tries. If there is
    # no prefix, no initial tries will be performed. If there is nothing following
    # a colon, none of the above methods will be attempted. By default, fdp
    # uses <html:code>overlap="9:portho"</html:code>. Note that <html:code>overlap="true"</html:code>,
    # <html:code>overlap="0:true"</html:code> and <html:code>overlap="0:"</html:code> all turn off all overlap
    # removal.
    #
    # Except for the Voronoi method, all of these transforms preserve the
    # orthogonal ordering of the original layout. That is, if the x coordinates
    # of two nodes are originally the same, they will remain the same, and if
    # the x coordinate of one node is originally less than the x coordinate of
    # another, this relation will still hold in the transformed layout. The
    # similar properties hold for the y coordinates.
    # This is not quite true for the "porth*" cases. For these, orthogonal
    # ordering is only preserved among nodes related by an edge.
    #
    # <html:b>NOTE</html:b>The methods <html:span class="val">orthoxy</html:span> and <html:span class="val">orthoyx</html:span> are still evolving. The semantics of these may change, or these methods may disappear altogether.
    overlap = Enum("True", "False", "scale", "scalexy", "prism", "compress",
        "vpsc", "ipsep", desc="determines if and how node overlaps should be "
        "removed", graphviz=True)

    # This is true if the value of pack is "true" or a non-negative integer.
    # If true, each connected component of the graph is laid out separately,
    # and then the graphs are packed tightly. If pack has an integral value,
    # this is used as the size, in points, of a margin around each part;
    # otherwise, a default margin of 8 is used. If pack is interpreted as
    # false, the entire graph is laid out together. The granularity and method
    # of packing is influenced by the packmode attribute.
    #
    # For layouts which always do packing, such a twopi, the pack attribute is
    # just used to set the margin.
    pack = Bool(graphviz=True) #Either(
#        Bool, Int, desc="If true, each connected component of the graph is "
#        "laid out separately, and then the graphs are packed tightly"
#    )

    # This indicates the granularity and method used for packing
    # (cf. <html:a rel="type">packMode</html:a>). Note that defining
    # <html:a rel="attr">packmode</html:a> will automatically turn on packing as though one had
    # set <html:code>pack=true</html:code>.
    #
    # "node","clust","graph" These specify the granularity of packing connected
    # components when the pack attribute is true. A value of "node" causes
    # packing at the node and edge label, with no overlapping of these objects.
    # This produces a layout with the least area, but it also allows
    # interleaving, where a node of one component may lie between two nodes in
    # another component. A value of "graph" does a packing using the bounding
    # box of the component. Thus, there will be a rectangular region around a
    # component free of elements of any other component. A value of "clust"
    # guarantees that top-level clusters are kept intact. What effect a value
    # has also depends on the layout algorithm. For example, neato does not
    # support clusters, so a value of "clust" will have the same effect as the
    # default "node" value.
    packmode = Enum("node", "cluster", "graph", label="Pack mode",
        desc="granularity and method used for packing", graphviz=True)

    # The pad attribute specifies how much, in inches, to extend the
    # drawing area around the minimal area needed to draw the graph.
    # If the pad is a single double, both the x and y pad values are set
    # equal to the given value. This area is part of the
    # drawing and will be filled with the background color, if appropriate.
    #
    # Normally, a small pad is used for aesthetic reasons, especially when
    # a background color is used, to avoid having nodes and edges abutting
    # the boundary of the drawn region.
    pad = Float(0.0555, desc="how much to extend the drawing area around the "
        "minimal area needed to draw the graph", graphviz=True)

    # Width and height of output pages, in inches. If this is set and is
    # smaller than the size of the layout, a rectangular array of pages of the
    # specified page size is overlaid on the layout, with origins aligned in
    # the lower-left corner, thereby partitioning the layout into pages. The
    # pages are then produced one at a time, in pagedir order.
    #
    # At present, this only works for PostScript output. For other types of
    # output, one should use another tool to split the output into multiple
    # output files. Or use the viewport to generate multiple files.
    page = pointf_trait

    # If the <html:a rel="attr">page</html:a> attribute is set and applicable,
    # this attribute specifies the order in which the pages are emitted.
    # This is limited to one of the 8 row or column major orders.
    pagedir = Enum("BL", "BR", "TL", "TR", "RB", "RT", "LB", "LT",
        desc="If the page attribute is set and applicable, this attribute "
        "specifies the order in which the pages are emitted",
        label="Page direction", graphviz=True)

    # If <html:a rel="attr">quantum</html:a> > 0.0, node label dimensions
    # will be rounded to integral multiples of the quantum.
    quantum = Float(0.0, desc="If quantum > 0.0, node label dimensions will "
        "be rounded to integral multiples of the quantum.", graphviz=True)

    # Sets direction of graph layout. For example, if <html:a rel="attr">rankdir</html:a>="LR",
    # and barring cycles, an edge <html:code>T -> H;</html:code> will go
    # from left to right. By default, graphs are laid out from top to bottom.
    rankdir = Enum("TB", "LR", "BT", "RL", desc="direction of graph layout",
        label="Rank direction", graphviz=True)

    # In dot, this gives the desired rank separation, in inches. This is
    # the minimum vertical distance between the bottom of the nodes in one
    # rank and the tops of nodes in the next. If the value
    # contains "equally", the centers of all ranks are spaced equally apart.
    # Note that both
    # settings are possible, e.g., ranksep = "1.2 equally".
    # In twopi, specifies radial separation of concentric circles.
    ranksep = Float(0.5, desc="In dot, this gives the desired rank "
        "separation.  In twopi, specifies radial separation of concentric "
        "circles", label="Rank separation", graphviz=True)

    # Sets the aspect ratio (drawing height/drawing width) for the drawing.
    # Note that this is adjusted before the size attribute constraints are
    # enforced.
    #
    # If ratio is numeric, it is taken as the desired aspect ratio. Then, if
    # the actual aspect ratio is less than the desired ratio, the drawing
    # height is scaled up to achieve the desired ratio; if the actual ratio is
    # greater than that desired ratio, the drawing width is scaled up.
    #
    # If ratio = "fill" and the size attribute is set, node positions are
    # scaled, separately in both x and y, so that the final drawing exactly
    # fills the specified size.
    #
    # If ratio = "compress" and the size attribute is set, dot attempts to
    # compress the initial layout to fit in the given size. This achieves a
    # tighter packing of nodes but reduces the balance and symmetry. This
    # feature only works in dot.
    #
    # If ratio = "expand", the size attribute is set, and both the width and
    # the height of the graph are less than the value in size, node positions
    # are scaled uniformly until at least one dimension fits size exactly.
    # Note that this is distinct from using size as the desired size, as here
    # the drawing is expanded before edges are generated and all node and text
    # sizes remain unchanged.
    #
    # If ratio = "auto", the page attribute is set and the graph cannot be
    # drawn on a single page, then size is set to an ``ideal'' value. In
    # particular, the size in a given dimension will be the smallest integral
    # multiple of the page size in that dimension which is at least half the
    # current size. The two dimensions are then scaled independently to the new
    # size. This feature only works in dot.
#    ratio = Either(Float, Enum("fill", "compress", "expand", "auto"),
    ratio = Enum("fill", "compress", "expand", "auto",
        desc="aspect ratio (drawing height/drawing width) for the drawing",
        graphviz=True)

    # If true and there are multiple clusters, run cross minimization a second
    # time.
    remincross = Bool(False, desc="If true and there are multiple clusters, "
        "run cross minimization a second", label="Re-cross minimization",
        graphviz=True)

  # This is a synonym for the <html:a rel="attr">dpi</html:a> attribute.
    resolution = Alias("dpi", desc="a synonym for the dpi attribute",
        graphviz=True)

    # This specifies nodes to be used as the center of the
    # layout and the root of the generated spanning tree. As a graph attribute,
    # this gives the name of the node. As a node attribute (circo only), it
    # specifies that the node should be used as a central node. In twopi,
    # this will actually be the central node. In circo, the block containing
    # the node will be central in the drawing of its connected component.
    # If not defined,
    # twopi will pick a most central node, and circo will pick a random node.
    root = root_trait

  # If 90, set drawing orientation to landscape.
    rotate = Range(0, 360, desc="drawing orientation", graphviz=True)

    # During network simplex, maximum number of edges with negative cut values
    # to search when looking for one with minimum cut value.
    searchsize = Int(30, desc="maximum number of edges with negative cut "
        "values to search when looking for one with minimum cut value",
        label="Search size", graphviz=True)

    # Fraction to increase polygons (multiply
    # coordinates by 1 + sep) for purposes of determining overlap. Guarantees
    # a minimal non-zero distance between nodes.
    # If unset but <html:a rel="attr">esep</html:a> is defined, <html:a rel="attr">sep</html:a> will be
    # set to <html:code>esep/0.8</html:code>. If <html:a rel="attr">esep</html:a> is unset, the default value
    # is used.
    #
    # When <html:a rel="attr">overlap</html:a>="ipsep" or "vpsc",
    # <html:a rel="attr">sep</html:a> gives a minimum distance, in inches, to be left between nodes.
    # In this case, if <html:a rel="attr">sep</html:a> is a pointf, the x and y separations can be
    # specified separately.
    sep = Int(4, desc="Fraction to increase polygons (multiply coordinates by "
        "1 + sep) for purposes of determining overlap", label="Separation",
        graphviz=True)

  # Print guide boxes in PostScript at the beginning of
  # routesplines if 1, or at the end if 2. (Debugging)
    showboxes = showboxes_trait

    # Maximum width and height of drawing, in inches.
    # If defined and the drawing is too large, the drawing is uniformly
    # scaled down so that it fits within the given size.
    #
    # If <html:a rel="attr">size</html:a> ends in an exclamation point (<html:tt>!</html:tt>),
    # then it is taken to be
    # the desired size. In this case, if both dimensions of the drawing are
    # less than <html:a rel="attr">size</html:a>, the drawing is scaled up uniformly until at
    # least one dimension equals its dimension in <html:a rel="attr">size</html:a>.
    #
    # Note that there is some interaction between the <html:a rel="attr">size</html:a> and
    # <html:a rel="attr">ratio</html:a> attributes.
    size = pointf_trait

    # Controls how, and if, edges are represented. If true, edges are drawn as
    # splines routed around nodes; if false, edges are drawn as line segments.
    # If set to "", no edges are drawn at all.
    #
    # (1 March 2007) The values <html:span class="val">line</html:span> and <html:span class="val">spline</html:span> can be
    # used as synonyms for <html:span class="val">false</html:span> and <html:span class="val">true</html:span>, respectively.
    # In addition, the value <html:span class="val">polyline</html:span> specifies that edges should be
    # drawn as polylines.
    #
    # By default, the attribute is unset. How this is interpreted depends on
    # the layout. For dot, the default is to draw edges as splines. For all
    # other layouts, the default is to draw edges as line segments. Note that
    # for these latter layouts, if <html:code>splines="true"</html:code>, this
    # requires non-overlapping nodes (cf. <html:a rel="attr">overlap</html:a>).
    # If fdp is used for layout and <html:tt>splines="compound"</html:tt>, then the edges are
    # drawn to avoid clusters as well as nodes.
    splines = Enum("True", "False", "",
        desc="how, and if, edges are represented", graphviz=True)

    # Parameter used to determine the initial layout of nodes. If unset, the
    # nodes are randomly placed in a unit square with
    # the same seed is always used for the random number generator, so the
    # initial placement is repeatable.
    #
    #
    # has the syntax [style][seed].
    #
    # If style is present, it must be one of the strings "regular", "self", or
    # "random". In the first case, the nodes are placed regularly about a
    # circle. In the second case, an abbreviated version of neato is run to
    # obtain the initial layout. In the last case, the nodes are placed
    # randomly in a unit square.
    #
    # If seed is present, it specifies a seed for the random number generator.
    # If seed is a positive number, this is used as the seed. If it is anything
    # else, the current time, and possibly the process id, is used to pick a
    # seed, thereby making the choice more random. In this case, the seed value
    # is stored in the graph.
    #
    # If the value is just "random", a time-based seed is chosen.
    #
    # Note that input positions, specified by a node's pos attribute, are only
    # used when the style is "random".
    start = start_trait

  # A URL or pathname specifying an XML style sheet, used in SVG output.
    stylesheet = Str(desc="URL or pathname specifying an XML style sheet",
        label="Style sheet", graphviz=True)

    # If the object has a URL, this attribute determines which window
    # of the browser is used for the URL.
    # See <html:a href="http://www.w3.org/TR/html401/present/frames.html#adef-target">W3C documentation</html:a>.
    target = target_trait

    # If set explicitly to true or false, the value determines whether or not
    # internal bitmap rendering relies on a truecolor color model or uses
    # a color palette.
    # If the attribute is unset, truecolor is not used
    # unless there is a <html:a rel="attr">shapefile</html:a> property
    # for some node in the graph.
    # The output model will use the input model when possible.
    #
    # Use of color palettes results in less memory usage during creation of the
    # bitmaps and smaller output files.
    #
    # Usually, the only time it is necessary to specify the truetype model
    # is if the graph uses more than 256 colors.
    # However, if one uses <html:a rel="attr">bgcolor</html:a>=transparent with
    # a color palette, font
    # antialiasing can show up as a fuzzy white area around characters.
    # Using <html:a rel="attr">truecolor</html:a>=true avoids this problem.
    truecolor = Bool(True, desc="bitmap rendering relies on a truecolor color "
        "model or uses a color palette", graphviz=True)

    # Hyperlinks incorporated into device-dependent output.
    # At present, used in ps2, cmap, i*map and svg formats.
    # For all these formats, URLs can be attached to nodes, edges and
    # clusters. URL attributes can also be attached to the root graph in ps2,
    # cmap and i*map formats. This serves as the base URL for relative URLs in the
    # former, and as the default image map file in the latter.
    #
    # For svg, cmapx and imap output, the active area for a node is its
    # visible image.
    # For example, an unfilled node with no drawn boundary will only be active on its label.
    # For other output, the active area is its bounding box.
    # The active area for a cluster is its bounding box.
    # For edges, the active areas are small circles where the edge contacts its head
    # and tail nodes. In addition, for svg, cmapx and imap, the active area
    # includes a thin polygon approximating the edge. The circles may
    # overlap the related node, and the edge URL dominates.
    # If the edge has a label, this will also be active.
    # Finally, if the edge has a head or tail label, this will also be active.
    #
    # Note that, for edges, the attributes <html:a rel="attr">headURL</html:a>,
    # <html:a rel="attr">tailURL</html:a>, <html:a rel="attr">labelURL</html:a> and
    # <html:a rel="attr">edgeURL</html:a> allow control of various parts of an
    # edge. Also note that, if active areas of two edges overlap, it is unspecified
    # which area dominates.
    URL = Str(desc="hyperlinks incorporated into device-dependent output",
        label="URL", graphviz=True)

    # Clipping window on final drawing.
    #
    # "%lf,%lf,%lf,%lf,%lf" or "%lf,%lf,%lf,"%s""
    #
    # The viewPort W,H,Z,x,y or W,H,Z,N specifies a viewport for the final
    # image. The pair (W,H) gives the dimensions (width and height) of the
    # final image, in points. The optional Z is the zoom factor, i.e., the
    # image in the original layout will be W/Z by H/Z points in size. By
    # default, Z is 1. The optional last part is either a pair (x,y) giving a
    # position in the original layout of the graph, in points, of the center of
    # the viewport, or the name N of a node whose center should used as the
    # focus. By default, the focus is the center of the graph bounding box,
    # i.e., (bbx/2,bby/2), where "bbx,bby" is the value of the bounding box
    # attribute bb.
    #
    # Sample values: 50,50,.5,'2.8 BSD' or 100,100,2,450,300. The first will
    # take the 100x100 point square centered on the node 2.8 BSD and scale it
    # down by 0.5, yielding a 50x50 point final image.
    viewport = Tuple(Float, Float, Float, Float, Float, graphviz=True)
#    Either(
#        Tuple(Float, Float, Float, Float, Float),
#        Tuple(Float, Float, Float, Str),
#        desc="clipping window on final drawing"
#    )

    voro_margin = Float(0.05, desc="Factor to scale up drawing to allow "
        "margin for expansion in Voronoi technique. dim' = (1+2*margin)*dim.",
        label="Voronoi margin", graphviz=True)

#    def _defaultdist_default(self):
#        """ Trait initialiser """
#
#        return 1+(avg. len)*math.sqrt(|V|)

    #--------------------------------------------------------------------------
    #  Views:
    #--------------------------------------------------------------------------

    traits_view = tabbed_view
#    traits_view = graph_view

    #--------------------------------------------------------------------------
    #  "object" interface:
    #--------------------------------------------------------------------------

    def __init__(self, *args, **kw_args):
        """ Initialises the graph.
        """
        super(Graph, self).__init__(*args, **kw_args)

        # Listen for the addition or removal of nodes and update each branch's
        # list of available nodes so that they can move themselves.
        self.on_trait_change(self._on_nodes, "subgraphs*.nodes")
        self.on_trait_change(self._on_nodes, "subgraphs*.nodes_items")

        # Listen for the addition of edges and check that the heado_node and
        # tail_node instances exist in the graph or any subgraphs.
        self.on_trait_change(self._on_edges, "subgraphs*.edges")
        self.on_trait_change(self._on_edges, "subgraphs*.edges_items")

    def __str__(self):
        """ Returns a string representation of the graph in dot language. It
            will return the graph and all its subelements in string form.
        """
        s = ""
        if self.strict:
            s += "strict "

        if self.directed:
            s += "digraph"
        else:
            s += "graph"

        return "%s %s" % ( s, super(Graph, self).__str__() )

    #--------------------------------------------------------------------------
    #  Public interface:
    #--------------------------------------------------------------------------

    @on_trait_change("arrange")
    def arrange_all(self):
        """ Sets for the _draw_ and _ldraw_ attributes for each of the graph
            sub-elements by processing the xdot format of the graph.
        """
        import godot.dot_data_parser

        parser = godot.dot_data_parser.GodotDataParser()

        xdot_data = self.create( format = "xdot" )
#        print "GRAPH DOT:\n", str( self )
#        print "XDOT DATA:\n", xdot_data

        parser.dotparser.parseWithTabs()
        ndata = xdot_data.replace( "\\\n", "" )
        tokens = parser.dotparser.parseString( ndata )[0]
        parser.build_graph( graph=self, tokens=tokens[3] )

        self.redraw_canvas()

    @on_trait_change("redraw")
    def redraw_canvas(self):
        """ Parses the Xdot attributes of all graph components and adds
            the components to a new canvas.
        """
        from xdot_parser import XdotAttrParser

        xdot_parser = XdotAttrParser()
        canvas = self._component_default()

        for node in self.nodes:
            components = xdot_parser.parse_xdot_data( node._draw_ )
            canvas.add( *components )

            components = xdot_parser.parse_xdot_data( node._ldraw_ )
            canvas.add( *components )

        for edge in self.edges:
            components = xdot_parser.parse_xdot_data( edge._draw_ )
            canvas.add( *components )
            components = xdot_parser.parse_xdot_data( edge._ldraw_ )
            canvas.add( *components )
            components = xdot_parser.parse_xdot_data( edge._hdraw_ )
            canvas.add( *components )
            components = xdot_parser.parse_xdot_data( edge._tdraw_ )
            canvas.add( *components )
            components = xdot_parser.parse_xdot_data( edge._hldraw_ )
            canvas.add( *components )
            components = xdot_parser.parse_xdot_data( edge._tldraw_ )
            canvas.add( *components )

        self.component = canvas
        self.vp.request_redraw()

    def get_node(self, ID):
        """ Returns a node given an ID or None if no such node exists.
        """
        node = super(Graph, self).get_node(ID)
        if node is not None:
            return node

        for graph in self.all_graphs:
            for each_node in graph.nodes:
                if each_node.ID == ID:
                    return each_node
        else:
            return None

#    def write(self, path, prog=None, format=None):
#        """ Writes a graph to a file.
#
#            Given a filename 'path' it will open/create and truncate
#            such file and write on it a representation of the graph
#            defined by the dot object and in the format specified by
#            'format'.
#            The format 'raw' is used to dump the string representation
#            of the Dot object, without further processing.
#            The output can be processed by any of graphviz tools, defined
#            in 'prog', which defaults to 'dot'
#            Returns True or False according to the success of the write
#            operation.
#        """
#
#        if prog is None:
#            prog = self.program
#
#        if format is None:
#            format = self.format
#
#
#        dot_fd = None
#        try:
#            dot_fd = open( path, "wb" )
##            dot_fd = file( path, "w+b" )
#            if format == 'raw':
#                dot_fd.write( self.to_string() )
#            else:
#                dot_fd.write( self.create( prog, format ) )
#        finally:
#            if dot_fd is not None:
#                dot_fd.close()
#
#        return True

    #--------------------------------------------------------------------------
    #  Trait initialisers:
    #--------------------------------------------------------------------------

    def _component_default(self):
        """ Trait initialiser.  Overrides the base class to use a Canvas
            for the root Graph.
        """
        return Canvas( draw_axes=True, bgcolor="lightsteelblue")

    def _epsilon_default(self):
        """ Trait initialiser.
        """
        if self.mode == "KK":
            return 0.0001 * len(self.nodes)
        else:
            return 0.0001

    def _maxiter_default(self):
        """ Trait initialiser.
        """
        mode = self.mode
        if mode == "KK":
            return 100 * len(self.nodes)
        elif mode == "major":
            return 200
        else:
            return 600

    #--------------------------------------------------------------------------
    #  Property getters:
    #--------------------------------------------------------------------------

    def _get_all_graphs(self):
        """ Property getter.
        """
        top_graph = self

        def get_subgraphs(graph):
            assert isinstance(graph, BaseGraph)
            subgraphs = graph.subgraphs[:]
            for subgraph in graph.subgraphs:
                subsubgraphs = get_subgraphs(subgraph)
                subgraphs.extend(subsubgraphs)
            return subgraphs

        subgraphs = get_subgraphs(top_graph)
        return [top_graph] + subgraphs

    #--------------------------------------------------------------------------
    #  Event handlers:
    #--------------------------------------------------------------------------

    def _directed_changed(self, new):
        """ Sets the connection string for all edges.
        """
        if new:
            conn = "->"
        else:
            conn = "--"

        for edge in [e for g in self.all_graphs for e in g.edges]:
            edge.conn = conn

    def _on_nodes(self):
        """ Maintains each branch's list of available nodes in order that they
            may move themselves (InstanceEditor values).
        """
        all_graphs = self.all_graphs
        all_nodes = [n for g in all_graphs for n in g.nodes]

        for graph in all_graphs:
            for edge in graph.edges:
                edge._nodes = all_nodes

    def _on_edges(self, object, name, old, new):
        """ Handles the list of edges for any graph changing.
        """
        if name == "edges_items":
            edges = new.added
        elif name == "edges":
            edges = new
        else:
            edges = []

        all_nodes = [n for g in self.all_graphs for n in g.nodes]

        for each_edge in edges:
            # Ensure the edge's nodes exist in the graph.
            if each_edge.tail_node not in all_nodes:
                object.nodes.append( each_edge.tail_node )

            if each_edge.head_node not in all_nodes:
                object.nodes.append( each_edge.head_node )

            # Initialise the edge's list of available nodes.
            each_edge._nodes = all_nodes

#    def _bgcolor_changed(self, new):
#        """ Handles the canvas background colour.
#        """
#        self.canvas.bgcolor = new

#------------------------------------------------------------------------------
#  Stand-alone call:
#------------------------------------------------------------------------------

if __name__ == "__main__":
    import sys
    logger = logging.getLogger()
    logger.addHandler(logging.StreamHandler(sys.stdout))
    logger.setLevel(logging.DEBUG)

#    from godot.graph import Graph

    graph = Graph(ID="Foo", directed=True, label="Foo Graph")

    node1 = Node("node1", label="Node 1", shape="rectangle")
    graph.add_node( node1 )

    node2 = Node("node2", label="Node 2")
    graph.add_node( node2 )

    graph.add_edge(node1, node2)

#    subgraph1 = Subgraph(ID="subgraph1", rank="min")
#    subgraph1.nodes.append(Node("node3", label="Node 3"))
#    graph.subgraphs.append(subgraph1)
#
#    subgraph2 = Subgraph(ID="subgraph2", rank="max")
#    subgraph2.nodes.append(Node("node4", label="Node 4"))
#    subgraph1.subgraphs.append(subgraph2)

#    from godot.component.component_viewer import ComponentViewer
#    viewer = ComponentViewer(component=graph.component)
#    viewer.configure_traits()

#    graph.write("/tmp/graph.xdot", "dot", "xdot")

#    graph.arrange_all()

#    print write_dot_graph(graph)

    graph.configure_traits()

# EOF +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


from jinja2 import nodes
from jinja2.ext import Extension

import markdown
from markdown.extensions.codehilite import CodeHiliteExtension

class MarkdownExtension(Extension):
    tags = set(["markdown"])

    def __init__(self, environment):
        super().__init__(environment)

    def parse(self, parser):
        lineno = next(parser.stream).lineno
        body = parser.parse_statements(["name:endmarkdown"], drop_needle=True)
        return nodes.CallBlock(self.call_method("_support"), [], [], body).set_lineno(
            lineno
        )

    def _support(self, caller):
        """Helper callback."""
        markdown_content = caller()
        html_content = markdown.markdown(
            markdown_content,
            extensions=[
                "markdown.extensions.fenced_code",
                CodeHiliteExtension(css_class="highlight"),
                "markdown.extensions.tables",
            ],
        )
        return html_content


#------------------------------------------------------------------------------
# Copyright (C) 2007 Richard W. Lincoln
#
# 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; version 2 dated June, 1991.
#
# This software is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANDABILITY 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#------------------------------------------------------------------------------

""" Defines a canvas with a viewport for testing purposes """

#------------------------------------------------------------------------------
#  Imports:
#------------------------------------------------------------------------------

from enthought.traits.api import HasTraits, Instance
from enthought.traits.ui.api import View, Item, HGroup, HSplit, Group
from enthought.enable.api import Component, Canvas, Viewport
from enthought.enable.tools.api import ViewportPanTool
from enthought.enable.component_editor import ComponentEditor

#------------------------------------------------------------------------------
#  "ComponentViewer" class:
#------------------------------------------------------------------------------

class ComponentViewer(HasTraits):
    """ A viewer of components for testing purposes """

    # The component being viewed
    component = Instance(Component)

    # The canvas to which the component is added
    canvas = Instance(Canvas)

    # A view into a subsection of the canvas
    viewport = Instance(Viewport)

    # Default view
    traits_view = View(
        HSplit(
            Group(
                Item("viewport", editor=ComponentEditor(), show_label=False)
            ),
            Group(
                Item(name="component", style="custom", show_label=False),
                scrollable=True
            )
        ),
        resizable=True, id="canvas_viewer", width=.6, height=.4,
        title="Viewer"
    )

    def _canvas_default(self):
        """ Trait initialiser """

        canvas = Canvas(draw_axes=True, bgcolor="honeydew")
        return canvas

    def _viewport_default(self):
        """ Trait initialiser """

        viewport = Viewport(component=self.canvas, enable_zoom=True)
        viewport.tools.append(ViewportPanTool(viewport))
        return viewport

    def _component_changed(self, old, new):
        """ Handles the component being changed.
        """
        canvas = self.canvas
        if old is not None:
            canvas.remove(old)
        if new is not None:
            canvas.add(new)

    def _anytrait_changed_for_component(self, new):
        """ Handles redrawing of the canvas. """

        self.canvas.request_redraw()

# EOF -------------------------------------------------------------------------


from jinja2 import nodes
from jinja2.ext import Extension

from pygments import highlight
from pygments.lexers import get_lexer_by_name, guess_lexer
from pygments.formatters import HtmlFormatter

class CodeHighlightExtension(Extension):
    tags = set(["code"])

    def __init__(self, environment):
        super().__init__(environment)

    def parse(self, parser):
        lineno = next(parser.stream).lineno
        args = [parser.parse_expression()]
        body = parser.parse_statements(["name:endcode"], drop_needle=True)
        return nodes.CallBlock(
            self.call_method("_code_support", args), [], [], body
        ).set_lineno(lineno)

    def _code_support(self, language, caller):
        """Helper callback."""
        code = caller()

        # remove the leading whitespace so the whole block can be indented more easily with flow of page
        lines = code.splitlines()

        first_nonempty_line_index = 0
        while not lines[first_nonempty_line_index]:
            first_nonempty_line_index += 1

        len_to_trim = len(lines[first_nonempty_line_index]) - len(
            lines[first_nonempty_line_index].lstrip()
        )
        lines = [x[len_to_trim:] for x in lines]
        code = "\n".join(lines)

        if language:
            lexer = get_lexer_by_name(language, stripall=True)
        else:
            lexer = guess_lexer(code)

        highlighted = highlight(code, lexer, HtmlFormatter())

        return highlighted


from enthought.enable.api import BaseTool
from enthought.enable.tools.traits_tool import Fifo, get_nested_components

class ElementTool(BaseTool):
    """ Tool to edit the traits of a HasTraits element referenced an  Enable
    component when double clicked. Handles containers and canvases so that they
    get edited only if their background regions are clicked.

    """

    # This tool does not have a visual representation (overrides BaseTool).
    draw_mode = "none"
    # This tool is not visible (overrides BaseTool).
    visible = False

    def normal_left_dclick(self, event):
        """ Handles the left mouse button being double-clicked when the tool
        is in the 'normal' state.

        If the event occurred on this tool's component (or any contained
        component of that component), the method opens a Traits UI view on the
        object referenced by the 'element' trait of the component that was
        double-clicked, setting the tool as the active tool for the duration
        of the view.

        """
        x = event.x
        y = event.y

        # First determine what component or components we are going to hittest
        # on.  If our component is a container, then we add its non-container
        # components to the list of candidates.
#        candidates = []
        component = self.component
#        if isinstance(component, Container):
#            candidates = get_nested_components(self.component)
#        else:
#            # We don't support clicking on unrecognized components
#            return
#
#        # Hittest against all the candidate and take the first one
#        item = None
#        for candidate, offset in candidates:
#            if candidate.is_in(x-offset[0], y-offset[1]):
#                item = candidate
#                break

        if hasattr(component, "element"):
            if component.element is not None:
                component.active_tool = self
                component.element.edit_traits(kind="livemodal")
                event.handled = True
                component.active_tool = None
                component.request_redraw()
        return

# EOF -------------------------------------------------------------------------


#------------------------------------------------------------------------------
# Copyright (C) 2008 Richard W. Lincoln
#
# 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; version 2 dated June, 1991.
#
# This software is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANDABILITY 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#------------------------------------------------------------------------------

import sys
import logging

from enthought.traits.api import \
    HasTraits, Instance, List, ListStr, Enum, Callable, Str, Dict, \
    Event, Button, on_trait_change

from enthought.traits.ui.api import View, Group, Item, HGroup

from enthought.enable.api import Canvas, Viewport, Container
from enthought.enable.tools.api import ViewportPanTool, MoveTool, ResizeTool
from enthought.enable.base_tool import BaseTool
from enthought.enable.component_editor import ComponentEditor

#from pylon.ui.graph.component.node import DiagramNode
#from pylon.ui.graph.pydot.pydot import Dot, Node, Edge, graph_from_dot_data
#from pylon.ui.graph.xdot_parser import XDotParser
#from pylon.ui.graph.network_dot import rgba2hex

from godot.node import Node as GodotNode
from godot.edge import Edge as GodotEdge

from godot.tool.element_tool import ElementTool
from godot.tool.context_menu_tool import ContextMenuTool

logger = logging.getLogger(__name__)

class CanvasMapping(HasTraits):

    # A list of the names of the traits of the domain model that correspond to
    # the nodes on the canvas.
    node_lists = ListStr

    # The canvas on which the graph diagram is drawn
    diagram_canvas = Instance(Canvas)

    domain_model = Instance(HasTraits)

    tools = List(Callable)#Instance(BaseTool))

#    palette

#    node_mappings = List(Instance(NodeMapping))

    # A view into a sub-region of the canvas
    viewport = Instance(Viewport)

    traits_view=View(
        Item(
            name="viewport", editor=ComponentEditor(),
            show_label=False, id='.diagram_viewport'
        ),
        Item(name="domain_model", show_label=False, style="custom"),
        id="pyramid.canvas_mapping", resizable=True
    )

    def _diagram_canvas_default(self):
        """ Trait initialiser """

        canvas = Canvas()

        for tool in self.tools:
            canvas.tools.append(tool(canvas))

        return canvas

    def _viewport_default(self):
        """ Trait initialiser """

        vp = Viewport(component=self.diagram_canvas, enable_zoom=True)
        vp.view_position = [0,0]
        vp.tools.append(ViewportPanTool(vp))
        return vp

    @on_trait_change("tools")
    def _diagram_canvas_changed(self, new):
        """ Handles the diagram canvas being set """

        logger.debug("Diagram canvas changed!")
        canvas = self.diagram_canvas

        for tool in self.tools:
            if canvas is not None:
                print "Adding tool: %s" % tool
                canvas.tools.append(tool(canvas))

#    def _domain_model_changed(self, new):
#        """ Handles change of the domain model """
#
#        self.clear_canvas()

    def clear_canvas(self):
        """ Removes all components from the canvas """

        logger.debug("Clearing the diagram canvas!")
        old_canvas = self.diagram_canvas

#        logger.debug("Canvas components: %s" % canvas.components)
#        for component in canvas.components:
#            canvas.remove(component)
#        logger.debug("Canvas components: %s" % canvas.components)
#        for component in canvas.components:
#            canvas.remove(component)
#        logger.debug("Canvas components: %s" % canvas.components)
#        canvas.request_redraw()

        new_canvas = Canvas()
        new_canvas.copy_traits(old_canvas, ["bgcolor", "draw_axes"])
        self.diagram_canvas = new_canvas

        self.viewport.component=new_canvas
        self.viewport.request_redraw()

        return

class LabelMapping(HasTraits):

    label_trait = Str("name")

    diagram_label = Instance(HasTraits)

    node_mapping = Instance(HasTraits)

class NodeMapping(HasTraits):

    # TopNodeReference trait
    containment_trait = Str

    element = Callable #Instance(HasTraits, allow_none=False)

#    diagram_node = Instance(Container)

    dot_node = Instance(GodotNode, ())

    tools = List(Callable)#Instance(BaseTool))

    label = Instance(LabelMapping, ())

class LinkMapping(HasTraits):

    containment_trait = Str

    element = Instance(HasTraits)

    diagram_link = Instance(HasTraits)

    tool = Instance(HasTraits)

    label = Instance(LabelMapping)

class Mapping(HasTraits):

    diagram = Instance(CanvasMapping, ())

    nodes = List(Instance(NodeMapping))

    links = List(Instance(LinkMapping))

    # Graphviz layout program
    program = Enum("dot", "circo", "fdp", "neato", "twopi")

    # XDot code parser
#    parser = Instance(XDotParser, XDotParser())

#    _map = Dict(Instance(HasTraits), Instance(Container))

    refresh = Button

    traits_view = View(
        HGroup(
            Item(name="program"),
            Item(name="refresh", show_label=False),
        ),
        Item(name="diagram", style="custom", show_label=False),
        id="pyramid.mapping", resizable=True
    )

    @on_trait_change("refresh,program")
    def refresh_diagram(self):
        """ Refresh the map of the domain model """

        self.map_model(self.diagram.domain_model)

    def _domain_model_changed_for_diagram(self, obj, name, old, new):
        """ Handles the domain model changing """

        if old is not None:
            self.unmap_model(old)
        if new is not None:
            self.map_model(new)

    def map_model(self, new):
        """ Maps a domain model to the diagram """

        logger.debug("Mapping the domain model!")
        dot = Dot()

        self.diagram.clear_canvas()

        for node_mapping in self.nodes:
            ct = node_mapping.containment_trait
            logger.debug("Mapping elements contained by the '%s' trait" % ct)
            if hasattr(new, ct):
                elements = getattr(new, ct)
                logger.debug("%d element(s) found" % len(elements))
                for element in elements:
                    pydot_node = Node(str(id(element)))
                    dot_attrs = node_mapping.dot_node
                    if dot_attrs is not None:
                        self._style_node(pydot_node, dot_attrs)
                    dot.add_node(pydot_node)

                    new.on_trait_change(self.map_element, ct+"_items")

        logger.debug("Retrieving xdot data and forming pydot graph!")
        xdot = graph_from_dot_data(dot.create(self.program, "xdot"))
        parser = XDotParser()

        for node in xdot.get_node_list():
            diagram_node = parser.parse_node(node)
            logger.debug(
                "Parsed node [%s] and received diagram node [%s]" %
                (node, diagram_node)
            )
            if diagram_node is not None:
                for node_mapping in self.nodes: # FIXME: slow
                    ct = node_mapping.containment_trait
                    for element in getattr(new, ct):
                        if str(id(element)) == diagram_node.dot_node.get_name():
                            logger.debug(
                                "Referencing element [%s] from diagram node [%s]" %
                                (element, diagram_node)
                            )
                            diagram_node.element = element
                            break

                    # Tools
                    if isinstance(diagram_node.element, node_mapping.element):
                        for tool in node_mapping.tools:
                            logger.debug(
                                "Adding tool [%s] to diagram node [%s]" %
                                (tool, diagram_node)
                            )
                            diagram_node.tools.append(tool(diagram_node))

                else:
                    if diagram_node.element is None:
                        logger.warning("Diagram node not referenced to element")

                self.diagram.diagram_canvas.add(diagram_node)

        del parser

    def unmap_model(self, old):
        """ Removes listeners from a domain model """

        for node_mapping in self.nodes:
            ct = node_mapping.containment_trait
            if hasattr(old, ct):
                old_elements = getattr(old, ct)
                for old_element in old_elements:
                    old.on_trait_change(
                        self.map_element, ct+"_items", remove=True
                    )

    def map_element(self, obj, name, event):
        """ Handles mapping elements to diagram components """

        canvas = self.diagram.diagram_canvas
        parser = XDotParser()

        for element in event.added:
            logger.debug("Mapping new element [%s] to diagram node" % element)
            for node_mapping in self.nodes:
                ct = name[:-6] #strip '_items'
                if node_mapping.containment_trait == ct:
                    dot_attrs = node_mapping.dot_node
                    dot = Dot()
                    graph_node = Node(str(id(element)))
                    self._style_node(graph_node, dot_attrs)
                    dot.add_node(graph_node)
                    xdot = graph_from_dot_data(dot.create(self.program,"xdot"))
                    diagram_nodes = parser.parse_nodes(xdot)#.get_node_list())
                    for dn in diagram_nodes:
                        if dn is not None:
                            dn.element = element
                            # Tools
                            for tool in node_mapping.tools:
                                dn.tools.append(tool(dn))

                            canvas.add(dn)
                            canvas.request_redraw()

        for element in event.removed:
            logger.debug("Unmapping element [%s] from diagram" % element)
            for component in canvas.components:
                if element == component.element:
                    canvas.remove(component)
                    canvas.request_redraw()
                    break

    def _label_node(self, node, element):
        """ Adds a label to a node """

    def _style_node(self, pydot_node, dot_attrs):
        """ Styles a node """

        pydot_node.set_shape(dot_attrs.shape)
        pydot_node.set_fixedsize(str(dot_attrs.fixed_size))
        pydot_node.set_width(str(dot_attrs.width))
        pydot_node.set_height(str(dot_attrs.height))
        pydot_node.set_color(rgba2hex(dot_attrs.color_))
        pydot_node.set_fillcolor(
            rgba2hex(dot_attrs.fill_color_)
        )
        for sty in dot_attrs.style:
            logger.debug("Setting node style: %s" % sty)
            pydot_node.set_style(sty)

        return pydot_node

if __name__ == "__main__":
    from pylon.dss.common.api import Circuit, Bus
    from pylon.dss.delivery.api import Line, Transformer, Fault, Capacitor
    from pylon.dss.conversion.api import \
        Generator, Load, VoltageSource, CurrentSource
    from pylon.dss.control.api import CapacitorControl, RegulatorControl

    logger = logging.getLogger()
    logger.addHandler(logging.StreamHandler(sys.stdout))
    logger.setLevel(logging.DEBUG)

    diagram = CanvasMapping(
        domain_model=Circuit(buses=[Bus(), Bus()], generators=[Generator()]),
        diagram_canvas=Canvas(bgcolor="lightslategrey", draw_axes=True),
        tools=[ContextMenuTool]
    )

    node_mappings = [
#        NodeMapping(containment_trait="buses", diagram_node=DiagramNode),
        NodeMapping(
            containment_trait="buses", element=Bus,
            dot_node=GodotNode(
                shape="rectangle", fixed_size=True, width=1.5, height=0.5,
                fill_color="white", color="orange", style=["filled"]
            ),
            tools=[MoveTool, ElementTool], label=LabelMapping()
        ),
        NodeMapping(
            containment_trait="generators", element=Generator,
            dot_node=GodotNode(
                shape="circle", color="red", style=["filled"],
                fill_color="blue"
            ),
            tools=[MoveTool, ElementTool]
        ),
        NodeMapping(
            containment_trait="loads", element=Load,
            dot_node=GodotNode(shape="invtriangle"),
            tools=[MoveTool, ElementTool]
        ),
        NodeMapping(
            containment_trait="voltage_sources", element=VoltageSource,
            dot_node=GodotNode(shape="doublecircle"),
            tools=[MoveTool, ElementTool]
        ),
        NodeMapping(
            containment_trait="current_sources", element=CurrentSource,
            dot_node=GodotNode(shape="pentagon"),
            tools=[MoveTool, ElementTool]
        ),
        NodeMapping(
            containment_trait="cap_controls", element=CapacitorControl,
            dot_node=GodotNode(shape="invhouse"),
            tools=[MoveTool, ElementTool]
        ),
        NodeMapping(
            containment_trait="reg_controls", element=RegulatorControl,
            dot_node=GodotNode(shape="egg"),
            tools=[MoveTool, ElementTool]
        )
    ]
    link_mappings = [
        LinkMapping(containment_trait="lines"),
        LinkMapping(containment_trait="transformers"),
        NodeMapping(containment_trait="faults"),
        NodeMapping(containment_trait="shunt_capacitors"),
    ]
    mapping = Mapping(diagram=diagram, nodes=node_mappings)
#    mapping.refresh_diagram()
    mapping.map_model(diagram.domain_model)
    mapping.configure_traits()

#diagram_editor = DiagramEditor(
#    diagram = CanvasMapping(domain_model=Circuit()),
#    nodes = [
#        NodeMapping(
#            containment_trait="buses", element=Bus,
#            dot_node=GodotNode(
#                shape="rectangle", fixedsize=True, width=0.5, height=0.1,
#                color="black", stroke_color="orange"
#            )
#        )
#    ],
#    edges = [
#        EdgeMapping(
#            containment_trait="generators", element=Line,
#            dot_edge=GodotEdge(width=4, arrowhead="none")
#        )
#    ],
#    show_palette=False, editable=True,
#    deletable=False
#)

# EOF -------------------------------------------------------------------------


#------------------------------------------------------------------------------
#  Copyright (c) 2008 Richard W. Lincoln
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to
#  deal in the Software without restriction, including without limitation the
#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
#  sell copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
#  IN THE SOFTWARE.
#------------------------------------------------------------------------------

""" Defines a Graphviz xdot language parser.

References:
    XDot by Jose.R.Fonseca (http://code.google.com/p/jrfonseca/wiki/XDot)

"""

#------------------------------------------------------------------------------
#  Imports:
#------------------------------------------------------------------------------

from enthought.enable.api import Component

from pyparsing import __version__ as pyparsing_version

from colorsys import hsv_to_rgb

from pyparsing import \
    Literal, CaselessLiteral, Word, Upcase, OneOrMore, ZeroOrMore, Forward, \
    NotAny, delimitedList, oneOf, Group, Optional, Combine, alphas, nums, \
    restOfLine, cppStyleComment, nums, alphanums, printables, empty, \
    quotedString, CharsNotIn, _noncomma, Suppress, nestedExpr, Suppress, Or

from godot.parsing_util import \
    real, integer, minus, quote, equals, colour, nsplit, ToInteger

from godot.component.api import \
    Pen, Ellipse, Polygon, Polyline, BSpline, Text

#------------------------------------------------------------------------------
#  "XdotAttrParser" class:
#------------------------------------------------------------------------------

class XdotAttrParser:
    """ Defines a Graphviz Xdot language parser. """

    parser = None

    pen = None

    #--------------------------------------------------------------------------
    #  "object" interface:
    #--------------------------------------------------------------------------

    def __init__(self):
        """ Initialises the Xdot output parser. """

        self.parser = self.define_parser()
        self.pen = Pen()

    #--------------------------------------------------------------------------
    #  Public interface:
    #--------------------------------------------------------------------------

    def parse_xdot_data(self, data):
        """ Parses xdot data and returns the associated components. """

        parser = self.parser
#        if pyparsing_version >= "1.2":
#            parser.parseWithTabs()
        if data:
            return parser.parseString(data)
        else:
            return []

    #--------------------------------------------------------------------------
    #  Define the dot parser
    #--------------------------------------------------------------------------

    def define_parser(self):
        """ Defines xdot grammar.

        @see: http://graphviz.org/doc/info/output.html#d:xdot """

        # Common constructs.
        point = Group(integer.setResultsName("x") +
                      integer.setResultsName("y"))
        n_points = (integer.setResultsName("n") +
            OneOrMore(point).setResultsName("points"))
        n_bytes = Suppress(integer) + Suppress(minus) + \
            Word(printables).setResultsName("b")
        justify = ToInteger(
            Literal("-1") | Literal("0") | Literal("1")
        ).setResultsName("j")

        # Attributes ----------------------------------------------------------

        # Set fill color. The color value consists of the n bytes following
        # the '-'.
        fill = (Literal("C").suppress() + Suppress(integer) + Suppress(minus) +
            colour.setResultsName("color")).setResultsName("fill")

        # Set pen color. The color value consists of the n bytes following '-'.
        stroke = (Literal("c").suppress() + Suppress(integer) +
            Suppress(minus) + colour.setResultsName("color")
        ).setResultsName("stroke")

        # Set font. The font size is s points. The font name consists of the
        # n bytes following '-'.
        font = (Literal("F").suppress() + real.setResultsName("s") +
            n_bytes).setResultsName("font")

        # Set style attribute. The style value consists of the n bytes
        # following '-'. The syntax of the value is the same as specified for
        # a styleItem in style.
        style = (Literal("S").suppress() + n_bytes).setResultsName("style")

        # Shapes --------------------------------------------------------------

        # Filled ellipse ((x-x0)/w)^2 + ((y-y0)/h)^2 = 1
        filled_ellipse = (Literal("E").suppress() +
            integer.setResultsName("x0") + integer.setResultsName("y0") +
            integer.setResultsName("w") + integer.setResultsName("h")
        ).setResultsName("filled_ellipse")

        # Unfilled ellipse ((x-x0)/w)^2 + ((y-y0)/h)^2 = 1
        ellipse = (Literal("e").suppress() +
            integer.setResultsName("x0") + integer.setResultsName("y0") +
            integer.setResultsName("w") + integer.setResultsName("h")
        ).setResultsName("ellipse")

        # Filled polygon using the given n points.
        filled_polygon = (Literal("P").suppress() +
            n_points).setResultsName("filled_polygon")

        # Unfilled polygon using the given n points.
        polygon = (Literal("p").suppress() +
            n_points).setResultsName("polygon")

        # Polyline using the given n points.
        polyline = (Literal("L").suppress() +
            n_points).setResultsName("polyline")

        # B-spline using the given n control points.
        bspline = (Literal("B").suppress() +
            n_points).setResultsName("bspline")

        # Filled B-spline using the given n control points.
        filled_bspline = (Literal("b").suppress() +
            n_points).setResultsName("filled_bspline")

        # Text drawn using the baseline point (x,y). The text consists of the
        # n bytes following '-'. The text should be left-aligned (centered,
        # right-aligned) on the point if j is -1 (0, 1), respectively. The
        # value w gives the width of the text as computed by the library.
        text = (Literal("T").suppress() + integer.setResultsName("x") +
            integer.setResultsName("y") + justify +
            integer.setResultsName("w") + n_bytes).setResultsName("text")

        # Externally-specified image drawn in the box with lower left corner
        # (x,y) and upper right corner (x+w,y+h). The name of the image
        # consists of the n bytes following '-'. This is usually a bitmap
        # image. Note that the image size, even when converted from pixels to
        # points, might be different from the required size (w,h). It is
        # assumed the renderer will perform the necessary scaling.
        image = (Literal("I").suppress() + integer.setResultsName("x") +
            integer.setResultsName("y") + integer.setResultsName("w") +
            integer.setResultsName("h") + n_bytes).setResultsName("image")

        # The value of the drawing attributes consists of the concatenation of
        # some (multi-)set of the 13 rendering or attribute operations.
        value = (Optional(quote).suppress() + OneOrMore(filled_ellipse |
            ellipse | filled_polygon | polygon | polyline | bspline |
            filled_bspline | text | fill | stroke | font | style | image) +
            Optional(quote).suppress()).setResultsName("value")

        # Drawing operation.
#        draw_ = Literal("_draw_") + Suppress(equals) + value
#        # Label drawing.
#        ldraw_ = Literal("_ldraw_") + Suppress(equals) + value
#        # Edge head arrowhead drawing.
#        hdraw_ = Literal("_hdraw_") + Suppress(equals) + value
#        # Edge tail arrowhead drawing.
#        tdraw_ = Literal("_tdraw_") + Suppress(equals) + value
#        # Edge head label drawing.
#        hldraw_ = Literal("_hldraw_") + Suppress(equals) + value
#        # Edge tail label drawing.
#        tldraw_ = Literal("_tldraw_") + Suppress(equals) + value

        # Parse actions.
#        n_points.setParseAction(self.proc_points)

        # Attribute parse actions.
        fill.setParseAction(self.proc_fill_color)
        stroke.setParseAction(self.proc_stroke_color)
        font.setParseAction(self.proc_font)
        style.setParseAction(self.proc_style)

        # Shape parse actions.
        filled_ellipse.setParseAction(self.proc_filled_ellipse)
        ellipse.setParseAction(self.proc_unfilled_ellipse)
        filled_polygon.setParseAction(self.proc_filled_polygon)
        polygon.setParseAction(self.proc_unfilled_polygon)
        polyline.setParseAction(self.proc_polyline)
        bspline.setParseAction(self.proc_unfilled_bspline)
        filled_bspline.setParseAction(self.proc_filled_bspline)
        text.setParseAction(self.proc_text)
        image.setParseAction(self.proc_image)

        return value

    #--------------------------------------------------------------------------
    #  Parse actions:
    #--------------------------------------------------------------------------

#    def proc_points(self, tokens):
#        """ Returns a list of tuples of the form (x, y). """
#
#        print "N POINTS:", tokens, tokens.keys()
#
#        return tokens["points"]#[(p["x"], p["y"]) for p in tokens["points"]]

    #--------------------------------------------------------------------------
    #  Attribute parse actions:
    #--------------------------------------------------------------------------

    def proc_fill_color(self, tokens):
        """ Sets the pen fill color. """

        self.pen.fill_color = self._proc_color(tokens)
        return []

    def proc_stroke_color(self, tokens):
        """ Sets the pen stroke color. """

        self.pen.color = self._proc_color(tokens)
        return []

    def _proc_color(self, tokens):
        """ The color traits of a Pen instance must be a string of the form
        (r,g,b) or (r,g,b,a) where r, g, b, and a are integers from 0 to 255,
        a wx.Colour instance, an integer which in hex is of the form 0xRRGGBB,
        where RR is red, GG is green, and BB is blue or a valid color name. """

        keys = tokens.keys()
        if "red" in keys: # RGB(A)
            rr, gg, bb = tokens["red"], tokens["green"], tokens["blue"]
            hex2int = lambda h: int(h, 16)
            if "alpha" in keys:
                a = tokens["alpha"]
                c = str((hex2int(rr), hex2int(gg), hex2int(bb), hex2int(a)))
            else:
                c = str((hex2int(rr), hex2int(gg), hex2int(bb)))
        elif "hue" in keys: # HSV
            r, g, b = hsv_to_rgb(tokens["hue"],
                                 tokens["saturation"],
                                 tokens["value"])
            c = str((int(r*255), int(g*255), int(b*255)))
        else:
            c = tokens["color"]

        return c

    def proc_font(self, tokens):
        """ Sets the font. """

        size = int(tokens["s"])
        self.pen.font = "%s %d" % (tokens["b"], size)
        return []

    def proc_style(self, tokens):
        """ Sets the style.  At present, the recognized style names are
        "dashed", "dotted", "solid", "invis" and "bold" for nodes and edges,
        and "filled", "diagonals" and "rounded" for nodes only. The styles
        "filled" and "rounded" are recognized for clusters. Additional styles
        are available in device-dependent form.
        """
        # FIXME: Implement style attributes.
        return []

    #--------------------------------------------------------------------------
    #  Ellipse:
    #--------------------------------------------------------------------------

    def proc_filled_ellipse(self, tokens):
        """ Returns the components of a filled ellipse. """

        return self._proc_ellipse(tokens, filled=True)

    def proc_unfilled_ellipse(self, tokens):
        """ Returns the components of an unfilled ellipse. """

        return self._proc_ellipse(tokens, filled=False)

    def _proc_ellipse(self, tokens, filled):
        """ Returns the components of an ellipse. """

        component = Ellipse(pen=self.pen,
                            x_origin=tokens["x0"],
                            y_origin=tokens["y0"],
                            e_width=tokens["w"],
                            e_height=tokens["h"],
                            filled=filled)

        return component

    #--------------------------------------------------------------------------
    #  Polygon:
    #--------------------------------------------------------------------------

    def proc_filled_polygon(self, tokens):
        """ Returns the components of a filled polygon. """

        return self._proc_polygon(tokens, filled=True)

    def proc_unfilled_polygon(self, tokens):
        """ Returns the components of an unfilled polygon. """

        return self._proc_polygon(tokens, filled=False)

    def _proc_polygon(self, tokens, filled):
        """ Returns the components of a polygon. """

        pts = [(p["x"], p["y"]) for p in tokens["points"]]
        component = Polygon(pen=self.pen, points=pts, filled=filled)

        return component

    #--------------------------------------------------------------------------
    #  Polyline:
    #--------------------------------------------------------------------------

    def proc_polyline(self, tokens):
        """ Returns the components of a polyline. """

        pts = [(p["x"], p["y"]) for p in tokens["points"]]
        component = Polyline(pen=self.pen, points=pts)

        return component

    #--------------------------------------------------------------------------
    #  B-spline (Bezier curve):
    #--------------------------------------------------------------------------

    def proc_filled_bspline(self, tokens):
        """ Returns the components of a filled B-spline. """

        return self._proc_bspline(tokens, filled=True)

    def proc_unfilled_bspline(self, tokens):
        """ Returns the components of an unfilled B-spline. """

        return self._proc_bspline(tokens, filled=False)

    def _proc_bspline(self, tokens, filled):
        """ Returns the components of a B-spline (Bezier curve). """

        pts = [(p["x"], p["y"]) for p in tokens["points"]]
        component = BSpline(pen=self.pen, points=pts, filled=filled)

        return component

    #--------------------------------------------------------------------------
    #  Text:
    #--------------------------------------------------------------------------

    def proc_text(self, tokens):
        """ Returns text components. """

        component = Text(pen=self.pen,
                         text_x=tokens["x"],
                         text_y=tokens["y"],
                         justify=tokens["j"],
                         text_w=tokens["w"],
                         text=tokens["b"])

        return component

    #--------------------------------------------------------------------------
    #  Image:
    #--------------------------------------------------------------------------

    def proc_image(self, tokens):
        """ Returns the components of an image. """

        print "IMAGE:", tokens, tokens.asList(), tokens.keys()

        raise NotImplementedError

# EOF -------------------------------------------------------------------------


# encoding: utf-8

"""MongoDB GridFS file serving support."""

if __debug__:
	log = __import__('logging').getLogger(__name__)

def render_grid_file(context, f):
	"""Allow direct use of GridOut GridFS file wrappers as endpoint responses."""

	f.seek(0)  # Ensure we are reading from the beginning.
	response = context.response  # Frequently accessed, so made local.  Useless optimization on Pypy.

	if __debug__:  # We add some useful diagnostic information in development, omitting from production due to sec.
		response.headers['Grid-ID'] = str(f._id)  # The GridFS file ID.
		log.debug("Serving GridFS file.", extra=dict(
				identifier = str(f._id),
				filename = f.filename,
				length = f.length,
				mimetype = f.content_type
			))

	response.conditional_response = True
	response.accept_ranges = 'bytes'  # We allow returns of partial content, if requested.
	response.content_type = f.content_type  # Direct transfer of GridFS-stored MIME type.
	response.content_length = f.length  # The length was pre-computed when the file was uploaded.
	response.content_md5 = response.etag = f.md5  # As was the MD5, used for simple integrity testing.
	response.last_modified = f.metadata.get('modified', None)  # Optional additional metadata.
	response.content_disposition = 'attachment; filename=' + f.name  # Preserve the filename through to the client.

	# Being asked for a range or not determines the streaming style used.
	if context.request.if_range.match_response(response):
		response.body_file = f  # Support seek + limited read.
	else:
		response.app_iter = iter(f)  # Assign the body as a streaming, chunked iterator.

	return True


#------------------------------------------------------------------------------
# Copyright (C) 2009 Richard W. Lincoln
#
# 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; version 2 dated June, 1991.
#
# This software is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANDABILITY 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#------------------------------------------------------------------------------

""" Defines a graph editor.
"""

#------------------------------------------------------------------------------
#  Imports:
#------------------------------------------------------------------------------

from os.path import exists, basename, join, dirname

from enthought.traits.api import HasTraits, Instance, Str, Adapter, adapts
from enthought.io.api import File

from puddle.resource.i_resource import IResource

from godot.api import parse_dot_file

#------------------------------------------------------------------------------
#  "DotFileIResourceAdapter" class:
#------------------------------------------------------------------------------

class DotFileIResourceAdapter(HasTraits):
    """ Adapts a "File" with Dot language content to 'IResource'.
    """
    # Declare the interfaces this adapter implements for its client:
    adapts(File, to=IResource, when="adaptee.ext=='.dot'")

    # The object that is being adapted.
#    adaptee = Instance(File)
    dot_file = Instance(File)

    #--------------------------------------------------------------------------
    #  object interface:
    #--------------------------------------------------------------------------

    def __init__ (self, dot_file):
        self.dot_file = dot_file
        super(HasTraits, self).__init__(dot_file=dot_file)

    #--------------------------------------------------------------------------
    #  "IResource" interface:
    #--------------------------------------------------------------------------

    def save(self, obj):
        """ Save to file.
        """
        fd = None
        try:
            fd = open(self.dot_file.absolute_path, "wb")
            obj.save_dot(fd)
        finally:
            if fd is not None:
                fd.close()
#        self.m_time = getmtime(self.adaptee.absolute_path)
        return

    def load(self):
        """ Load the file.
        """
        fd = None
        try:
            obj = parse_dot_file( self.dot_file.absolute_path )
        finally:
            if fd is not None:
                fd.close()
        return obj

# EOF -------------------------------------------------------------------------