packages feed

sifflet-0.1.5: TreeGraph.hs

module TreeGraph (LayoutGraph, 
                  flayoutToGraph, treeLayoutToGraph,
                  -- treeToGraph, 
                  orderedTreeToGraph, 
                  treeGraphNodesTree, graphToTreeOriginal,
                  graphToTreeStructure, 
                  flayoutToGraphRoots,
                  graphToOrderedTree, graphToOrderedTreeFrom,
                  orderedChildren, adjCompareEdge,
                  nextNodes, -- exported for testing; any other reason?
                  grTranslateNode, grTranslateSubtree, grTranslateGraph,

                  -- moved from Workspace.hs:
                  functoidToFunction, graphToExprTree,

                  -- Tree graph rendering
                  graphQuickView, 
                  graphWriteImageFile, graphRender, treeRender,
                  treeWriteImageFile, gtkShowTree
                 )

where

import IO
import Data.IORef
import Data.List (sort, sortBy)
import System.Cmd

import Data.Graph.Inductive as G

import Graphics.UI.Gtk hiding (Function, Style, fill, lineWidth)
import Graphics.UI.Gtk.Gdk.EventM

import Graphics.Rendering.Cairo hiding (translate)

import Geometry
import Tree as T
import TreeLayout
import Expr
import Util
import Workspace.Functoid
import Workspace.WGraph

type LayoutGraph n e = Gr (LayoutNode n) e

flayoutToGraph :: FunctoidLayout -> WGraph
flayoutToGraph tlo = 
    case tlo of 
      FLayoutTree t -> treeLayoutToGraph t
      FLayoutForest ts _bbox -> 
          foldl grAddGraph wgraphNew (map treeLayoutToGraph ts)

treeLayoutToGraph :: TreeLayout ExprNode -> WGraph
treeLayoutToGraph = orderedTreeToGraph . fmap WSimple


-- flayoutToGraphRoots returns a list of graph nodes (Ints)
-- corresponding to the root of the tree, or the roots of
-- the trees in the forest.
-- The list is ordered in the same sense as the graph nodes.

flayoutToGraphRoots :: FunctoidLayout -> [G.Node]
flayoutToGraphRoots (FLayoutTree _t) = [1]
flayoutToGraphRoots (FLayoutForest trees _bbox) =
    let loop _ [] res = reverse res
        loop next (t:ts) res =
            loop (next + treeSize t) ts (next:res)
    in loop 1 trees []


-- {-# DEPRECATED treeToGraph "use ??? instead" #-}

-- treeToGraph :: Tree e -> Gr e ()
-- treeToGraph (T.Node root subtrees)  = 
--     -- Convert an ordered tree to a graph.
--     -- The graph edge labels from parent to child 
--     -- are integers corresponding to the order of the children.
--     -- So, to recover the ordered list of children, 
--     -- sort by the edge label.
--     let g0 = empty :: Gr e ()
--         g1 = insNode (1, root) g0

--         grow :: Gr e () -> [(Tree e, G.Node)] -> G.Node -> Gr e ()
--         -- grow graph [(tree, parent)] node
--         grow g [] _ = g
--         grow g ((t, p):tps) n =
--             -- insert t forming g', insert ts into g'
--             let edge = ((), p) -- predecessors, or both?
--                 g' = ([edge], n, (rootLabel t), []) & g -- assume pred only
--             in grow g' (tps ++ [(s, n) | s <- subForest t])
--                        (succ n)

--     in grow g1 [(s, 1) | s <- subtrees] 2

sprout :: G.Node -> Tree e -> [(G.Node, WEdge, Tree e)]
sprout parent (T.Node _ subtrees) =
    -- create triples (parent graph node, edge, subtree)
    let m = length subtrees - 1
    in [(parent, WEdge e, s) | (e, s) <- zip [0..m] subtrees]


orderedTreeToGraph :: Tree e -> Gr e WEdge
orderedTreeToGraph otree  = 
    -- Convert an ordered tree to a graph.
    -- The graph edge labels from parent to child 
    -- are integers corresponding to the order of the children.
    -- So, to recover the ordered list of children, 
    -- sort by the edge label.
    let g0 = empty :: Gr e WEdge
        g1 = insNode (1, rootLabel otree) g0

        grow :: Gr e WEdge -> [(G.Node, WEdge, Tree e)] -> G.Node -> Gr e WEdge
        -- grow graph [(parent, edgeLabel, subtree), ...] node
        grow g [] _ = g
        grow g ((p, e, t):pets) n =
            -- insert pet forming g', insert pets into g';
            -- n is the node id for the root of this subtree
            let adj = (e, p) -- to parent (priority, node)
                g' = ([adj], n, (rootLabel t), []) & g 
                n' = succ n
            in grow g' (pets ++ 
                        sprout n t
                        -- [(s, n) | s <- subForest t])
                        )
                       n'

    in grow g1 (sprout 1 otree) 2

-- And what about this (e, Node) type?  Is it some sort of monad?

treeGraphNodesTree :: Tree e -> Tree Node
treeGraphNodesTree atree =
    -- returns a tree of Nodes of the Graph (treeToGraph atree)
    let gnTree :: Tree e -> Node -> Node -> (Tree Node, Node)
        gnTree (T.Node _root subtrees) rootNode next =
            -- rootNode = Node (number) for the root,
            -- next = next unused Node
            let (nNodes, next') = nextNodes subtrees next
                (subtrees', next'') = gnSubtrees subtrees nNodes next'
            in (T.Node rootNode subtrees', next'')

        gnSubtrees :: [Tree e] -> [Node] -> Node -> ([Tree Node], Node)
        gnSubtrees [] [] next = ([], next)
        gnSubtrees (t:ts) (n:ns) next = 
            let (t', next') = gnTree t n next
                (ts', next'') = gnSubtrees ts ns next'
            in ((t' : ts'), next'')
        gnSubtrees _ _ _ = error "gnSubtrees: list lengths do not match"

    in fst (gnTree atree 1 2)

nextNodes :: [e] -> Node -> ([Node], Node)
nextNodes items next = 
  -- next is the next unused Node (number).
  -- Returns list of new nodes, and a new "next" node
  -- E.g., nextNodes [a, b] 3 = ([3, 4], 5)
  --       nextNodes [c, d, e] 5 = ([5, 6, 7], 8)
  --       nextNodes [] 8 = ([], 8)
  let n = length items
      next' = next + n
  in ([next .. (next' - 1)], next')

-- When a tree is converted to a graph,
-- each tree node's ordered children get graph node numbers
-- in ascending order.  Therefore, when reconstructing the tree,
-- sorting the graph nodes restores the order of the children
-- as in the tree.

graphToOrderedTree :: Gr e WEdge -> Tree e
-- inverse of orderedTreeToGraph
graphToOrderedTree g = graphToOrderedTreeFrom g 1

graphToOrderedTreeFrom :: Gr e WEdge -> G.Node -> Tree e
graphToOrderedTreeFrom g n =
    case lab g n of
      Just label -> 
          T.Node label (map (graphToOrderedTreeFrom g) (orderedChildren g n))
      Nothing -> 
          error $ "missing label for node " ++ show n

-- | List of the nodes children, ordered by edge number
orderedChildren :: Gr e WEdge -> G.Node -> [G.Node]
orderedChildren g = map fst . sortBy adjCompareEdge . lsuc g

adjCompareEdge :: (Node, WEdge) -> (Node, WEdge) -> Ordering
adjCompareEdge (_n1, e1) (_n2, e2) = compare e1 e2

{-# DEPRECATED graphToTreeOriginal "use ??? instead" #-}

graphToTreeOriginal :: Gr e () -> G.Node -> Tree e
-- (\g -> graphToTreeOriginal g 1) is the inverse of treeToGraph
graphToTreeOriginal g n =
    case lab g n of
      Just label -> T.Node label (map (graphToTreeOriginal g) 
                                      (sort (suc g n)))
      _ -> error ("missing label for node " ++ show n)

{-# DEPRECATED graphToTreeStructure "use ??? instead" #-}

graphToTreeStructure :: Gr n e -> G.Node -> Tree G.Node
-- This is *not* an inverse of treeToGraph.
-- Rather, graphToTreeStructure (treeToGraph t 1) is a tree t' of Nodes
-- (i.e., integer identifiers of nodes in the graph)
-- which parallels the structure of t.

graphToTreeStructure g n = T.Node n (map (graphToTreeStructure g)
                                         (sort (suc g n)))

grTranslateNode :: 
  Node -> Double -> Double -> LayoutGraph n e -> LayoutGraph n e
grTranslateNode node dx dy graph = 
    grUpdateNodeLabel graph node (translate dx dy)

grTranslateSubtree :: 
  Node -> Double -> Double -> LayoutGraph n e -> LayoutGraph n e
grTranslateSubtree root dx dy graph = 
    let trSubtrees :: [Node] -> LayoutGraph n e -> LayoutGraph n e
        trSubtrees [] g = g
        trSubtrees (r:rs) g = trSubtrees (rs ++ suc g r)
                              (grTranslateNode r dx dy g)
    in trSubtrees [root] graph

grTranslateGraph :: Double -> Double -> LayoutGraph n e -> LayoutGraph n e
grTranslateGraph dx dy graph = nmap (translate dx dy) graph

grUpdateNodeLabel :: (DynGraph g) => g a b -> Node -> (a -> a) -> g a b
grUpdateNodeLabel graph node updater =
  case match node graph of
    (Nothing, _) -> error "no such node"
    (Just (preds, jnode, label, succs), graph') ->
        -- jnode == node
        (preds, jnode, updater label, succs) & graph'

functoidToFunction :: 
    Functoid -> WGraph -> G.Node -> Env -> SuccFail Function
functoidToFunction functoid graph frameNode env =
    case functoid of
      FunctoidFunc f -> Succ f
      FunctoidParts {fpName = name, fpArgs = args} ->
          let roots = suc graph frameNode
              expr = treeToExpr $ graphToExprTree graph (head roots)
              impl = Compound args expr
          in 
            -- Check whether the frame contains a single tree
            if length roots /= 1
            then Fail "The graph structure is not a tree!"
            else case decideTypes expr args env of
                   Left errmsg -> Fail errmsg
                   Right (atypes, rtype) -> 
                       Succ (Function (Just name) atypes rtype impl)


graphToExprTree :: WGraph -> G.Node -> Tree ExprNode
graphToExprTree g root =
    let extractExprNode wnode =
            case wnode of
              WSimple layoutNode -> gnodeValue (nodeGNode layoutNode)
              WFrame _ -> error "graphToExprTreeFrom: unexpected WFrame node"
    in fmap extractExprNode (graphToOrderedTreeFrom g root)



-- ============================================================
-- GRAPH VIEWING AND RENDERING
-- ============================================================

-- Quick view of a graph using GraphViz

graphQuickView :: (Graph g, Show a, Show b) => g a b -> IO ()
graphQuickView g = 
    let dot_src = graphviz g "graphQuickView" (6, 4) (1, 1) Portrait
        dot_file = "tmp.dot"
        png_file = "tmp.png"
    in do
      h <- openFile dot_file WriteMode
      hPutStr h dot_src
      hClose h
      system ("dot -Tpng -o" ++ png_file ++ " " ++ dot_file)
      system ("feh " ++ png_file)
      return ()


graphWriteImageFile :: (Repr n) => 
                       Style -> Maybe Node -> Maybe Node ->
                       Double -> Double -> 
                       LayoutGraph n e -> String -> 
                       IO String 
graphWriteImageFile style mactive mselected dwidth dheight graph file = do
    withImageSurface FormatARGB32 (round dwidth) (round dheight) $ \surf -> do
      renderWith surf $ 
        graphRender style mactive mselected graph
      surfaceWriteToPNG surf file
    return file

graphRender :: (Repr n) => 
               Style -> Maybe Node -> Maybe Node -> LayoutGraph n e -> 
               Render ()
graphRender style mactive mselected graph = do
  let renderNode :: Node -> Render ()
      renderNode node = do
        let Just layoutNode = lab graph node 
            -- ^^ is this safe? it can't be Nothing?
            nodeBB = gnodeNodeBB (nodeGNode layoutNode)
            active = (mactive == Just node)
            selected = case mselected of
                         Nothing -> False
                         Just sel -> sel == node
            mode = if active then DrawActive
                   else if selected then DrawSelectedNode -- !!!
                        else DrawNormal
            xcenter = bbXCenter nodeBB
        draw style mode layoutNode
        connectParent node xcenter (bbTop nodeBB)
        return ()

      connectParent node x y = 
        let parents = pre graph node in
            case parents of
              [] -> return ()
              [parent] -> do
                let Just playoutNode = lab graph parent
                    parentBB = gnodeNodeBB (nodeGNode playoutNode)
                    px = bbXCenter parentBB
                    py = bbBottom parentBB
                setColor (styleNormalEdgeColor style)
                moveTo px (py + snd (vtinypad style)) -- bottom of parent
                lineTo x (y - fst (vtinypad style))   -- top of node
                stroke

              _ -> error "Too many parents"
        
  setAntialias AntialiasDefault

  -- canvas background
  setColor (styleNormalFillColor style)
  let Just layoutNode = lab graph 1 -- root node, represents whole tree
      BBox x y bwidth bheight = nodeTreeBB layoutNode
  rectangle x y bwidth bheight
  fill

  -- draw the graph/tree
  setLineWidth (lineWidth style)
  mapM_ renderNode (nodes graph)

treeRender :: (Repr e) => Style -> TreeLayout e -> Render ()
treeRender style = graphRender style Nothing Nothing . orderedTreeToGraph

treeWriteImageFile :: (Repr e) => 
  Style -> IoletCounter e -> Tree e -> String -> IO String

treeWriteImageFile style counter atree filename = do
  let tlo = treeLayout style counter atree
      Size surfWidth surfHeight = treeLayoutPaddedSize style tlo
  withImageSurface FormatARGB32 (round surfWidth) (round surfHeight) $ 
    \ surf -> do
      renderWith surf $ treeRender style tlo
      surfaceWriteToPNG surf filename
  return filename

-- ============================================================
-- Simple tree viewing.
-- gtkShowTree displays a single tree very simply
-- Works for any kind of (Repr e, Show e) => Tree e.

gtkShowTree :: (Repr e, Show e) => 
  Style -> IoletCounter e -> Tree e -> IO ()
gtkShowTree style counter atree = do
  let tlo = treeLayout style counter atree
      Size dwidth dheight = treeLayoutPaddedSize style tlo

  tloRef <- newIORef tlo

  initGUI

  -- init window
  window <- windowNew
  set window [windowTitle := "Test Cairo Tree"]
  onDestroy window mainQuit

  -- init vbox
  vbox <- vBoxNew False 5 -- width not homogeneous; spacing
  set window [containerChild := vbox]

  -- init canvas
  canvas <- layoutNew Nothing Nothing
  onSizeRequest canvas (return (Requisition (round dwidth) (round dheight)))
  widgetSetCanFocus canvas True -- to receive key events

  -- event handlers
  on canvas exposeEvent (updateCanvas style canvas tloRef)
  on canvas keyPressEvent (keyPress window)

  -- pack, show, and run
  boxPackStartDefaults vbox canvas
  widgetShowAll window
  mainGUI

updateCanvas :: (Repr e) => Style -> Layout -> IORef (TreeLayout e) 
             -> EventM EExpose Bool
updateCanvas style canvas tloRef = 
    tryEvent $ liftIO $ do
      {       
        tlo <- readIORef tloRef
      ; win <- layoutGetDrawWindow canvas
      ; renderWithDrawable win (treeRender style tlo)
      }

keyPress :: Window -> EventM EKey Bool
keyPress window =
    tryEvent $ do
      {
        kname <- eventKeyName
      ; case kname of
          "q" -> liftIO $ widgetDestroy window  -- implies mainQuit
          _ -> stopEvent
      }