xdot-0.2.4: src/Graphics/XDot/Viewer.hs
{- |
Module : Graphics.XDot.Viewer
Copyright : (c) Dennis Felsing
License : 3-Clause BSD-style
Maintainer : dennis@felsin9.de
This module draws the operations of an xdot graph using Cairo and Pango on a
Gtk canvas.
-}
module Graphics.XDot.Viewer (
drawAll
)
where
import Graphics.XDot.Types hiding (w, h, filled, alignment, text, name, size)
import Graphics.UI.Gtk hiding (Color, Rectangle, descent, Object)
import Graphics.Rendering.Cairo
import Control.Monad.State hiding (State)
import qualified Control.Monad.State as MS
type RGBA = (Double, Double, Double, Double)
data DState = DState
{ fontName :: String
, fontSize :: Double
, lineWidth :: Double
, lineStyle :: [Double]
, filledColor :: RGBA
, strokeColor :: RGBA
}
type DrawState a = MS.StateT DState Render a
-- | Draw an xdot graph, possibly highlighting a node.
drawAll :: Eq t =>
Object t -- ^ id of the node to highlight
-> Rectangle -- ^ dimensions of the graph, as returned by 'Graphics.XDot.Parser.getSize'
-> [(Object t, Operation)] -- ^ operations, as returned by 'Graphics.XDot.Parser.getOperations'
-> Render [(Object t, Rectangle)] -- ^ dimensions of the rendered nodes on the screen
drawAll hover (_,_,sw,sh) ops = do
let scalex = 1
scaley = -1
offsetx = -0.5 * sw
offsety = 0.5 * sh
save
translate offsetx offsety
scale scalex scaley
boundingBoxes <- evalStateT (mapM (draw hover) ops) $ DState "" 1 1 [] (1,1,1,1) (0,0,0,1)
restore
return
$ map (\(o, (x,y,w,h)) -> (o, (x*scalex+offsetx,y*scaley+offsety,w,h)))
$ concat boundingBoxes
stylizedDraw :: Eq t => Bool -> Object t -> Object t -> Render a -> DrawState ()
stylizedDraw filled mn hover renderOps = do
(rf,gf,bf,af) <- gets filledColor
(rs,gs,bs,as) <- gets strokeColor
lWidth <- gets lineWidth
lStyle <- gets lineStyle
lift $ do
when filled $ do
if mn /= None && mn == hover
then setSourceRGBA 1 0.8 0.8 1
else setSourceRGBA rf gf bf af
save
renderOps
restore
fillPreserve
fill
setLineWidth lWidth
setDash lStyle 0
if mn /= None && mn == hover
then setSourceRGBA 1 0 0 1
else setSourceRGBA rs gs bs as
save
renderOps
restore
stroke
draw :: Eq t => Object t -> (Object t, Operation) -> DrawState [(Object t, Rectangle)]
draw hover (mn, Ellipse (x,y) w h filled) = do
stylizedDraw filled hover mn $ do
translate x y
scale w h
moveTo 1 0
arc 0 0 1 0 (2 * pi)
return $ case mn of
None -> []
o -> [(o, (x - w, y + h, 2 * w, 2 * h))]
draw hover (mn, Polygon a@((x,y):xys) filled) = do
stylizedDraw filled hover mn $ do
moveTo x y
mapM_ (uncurry lineTo) xys
closePath
let xs = x : map fst a
let ys = y : map snd a
return $ case mn of
None -> []
o -> [(o, (minimum xs, maximum ys, maximum xs - minimum xs, maximum ys - minimum ys))]
draw _ (_, Polygon [] _) = return []
draw hover (mn, Polyline a@((x,y):xys)) = do
stylizedDraw False hover mn $ do
moveTo x y
mapM_ (uncurry lineTo) xys
let xs = x : map fst a
let ys = y : map snd a
return $ case mn of
None -> []
o -> [(o, (minimum xs, maximum ys, maximum xs - minimum xs, maximum ys - minimum ys))]
draw _ (_, Polyline []) = return []
draw hover (mn, BSpline ((x,y):xys) filled) = do
stylizedDraw filled hover mn $ do
moveTo x y
drawBezier xys
return $ case mn of
None -> []
o -> [ (o, (x - 15, y + 15, 30, 30))
, (o, (xe - 15, ye + 15, 30, 30))
]
where drawBezier ((x1,y1):(x2,y2):(x3,y3):xys2) = do
curveTo x1 y1 x2 y2 x3 y3
drawBezier xys2
drawBezier _ = return ()
(xe,ye) = last xys
draw _ (_, BSpline [] _) = return []
draw hover (mn, Text (x,y) alignment w text) = do
fontName' <- gets fontName
fontSize' <- gets fontSize
layout <- lift $ createLayout text
context <- liftIO $ layoutGetContext layout
fo <- liftIO $ cairoContextGetFontOptions context
fontOptionsSetAntialias fo AntialiasDefault
fontOptionsSetHintStyle fo HintStyleNone
fontOptionsSetHintMetrics fo HintMetricsOff
liftIO $ cairoContextSetFontOptions context fo
liftIO $ layoutContextChanged layout
-- This does not work with "Times Roman", but it works with a font that is
-- installed on the system
--font <- liftIO fontDescriptionNew
--liftIO $ fontDescriptionSetFamily font "Nimbus Roman No9 L, Regular"
--liftIO $ fontDescriptionSetFamily font "Times Roman"
--liftIO $ fontDescriptionSetSize font fontSize'
-- Only fontDescriptionFromString works as expected, choosing a similar
-- alternative font when the selected one is not available
font <- liftIO $ fontDescriptionFromString fontName'
liftIO $ fontDescriptionSetSize font fontSize'
liftIO $ layoutSetFontDescription layout (Just font)
(_, PangoRectangle _ _ w2 h2) <- liftIO $ layoutGetExtents layout
let (f, w3, h3, descent) = if w2 > w
then (w / w2, w, h2 * w / w2, 4 * w / w2)
else (1, w2, h2, 4)
let x3 = case alignment of
LeftAlign -> x
CenterAlign -> x - 0.5 * w3
RightAlign -> x - w3
y3 = y + h3 - descent
stylizedDraw False hover mn $ do
moveTo x3 y3
scale f (-f)
showLayout layout
return $ case mn of
None -> []
o -> [(o, (x3, y3, w3, h3))]
draw _ (_, Color color filled) = do
modify (\s -> if filled
then s{filledColor = color}
else s{strokeColor = color})
return []
draw _ (_, Font size name) = do
modify (\s -> s{fontName = fixedName, fontSize = size})
return []
-- Pango does not like "Times-Roman", but works with "Times Roman".
-- Graphviz handles this in plugin/pango/gvtextlayout_pango.c
where fixedName = map fixName name
fixName '-' = ' '
fixName x = x
draw _ (_, Style x) = do
case x of -- TODO: Some styles missing
"solid" -> modify (\s -> s{lineStyle = []}) -- always on
"dashed" -> modify (\s -> s{lineStyle = [6,6]}) -- 6 pts on, 6 pts off
"dotted" -> modify (\s -> s{lineStyle = [2,4]}) -- 2 pts on, 4 pts off
_ -> return ()
return []
draw _ (_, Image{}) = return [] -- TODO
draw _ (_, FontCharacteristics{}) = return [] -- TODO