DataTreeView (empty) → 0.1
raw patch · 10 files changed
+897/−0 lines, 10 filesdep +MissingHdep +basedep +bytestringsetup-changed
Dependencies added: MissingH, base, bytestring, containers, deepseq, glib, gtk, monad-control, mtl, syb
Files
- DataTreeView.cabal +39/−0
- DataTreeView.hs +11/−0
- DataTreeView/CustomHandlers.hs +24/−0
- DataTreeView/DataToTree.hs +299/−0
- DataTreeView/Row.hs +96/−0
- DataTreeView/SimpleView.hs +29/−0
- DataTreeView/StrictTypes.hs +145/−0
- DataTreeView/Widget.hs +222/−0
- LICENSE +30/−0
- Setup.hs +2/−0
+ DataTreeView.cabal view
@@ -0,0 +1,39 @@+Name: DataTreeView+Version: 0.1+Synopsis: A GTK widget for displaying arbitrary Data.Data.Data instances+License: BSD3+License-file: LICENSE+Author: Daniel Schüssler+Maintainer: anotheraddress@gmx.de+Category: Development,Debug,GUI+Build-type: Simple+Cabal-version: >=1.6+Description:+ Screenshot: <http://i.imgur.com/5JlhG.png>+ .+ Example use:+ .+ > import DataTreeView+ > main = simpleView [(["hello","world",error "this works too"],25)]+++Source-Repository head+ type: darcs+ location: http://patch-tag.com/r/DanielSchuessler/DataTreeView +++Library+ Build-depends: bytestring, monad-control >= 0.2, mtl >= 2, + containers, glib >= 0.12, + base >= 4 && < 5, syb >= 0.3, MissingH >= 1.1.0.3,+ gtk>=0.12, deepseq ++ Exposed-modules: DataTreeView+ DataTreeView.CustomHandlers+ DataTreeView.StrictTypes+ Other-modules: DataTreeView.DataToTree+ DataTreeView.Widget+ DataTreeView.SimpleView+ DataTreeView.Row+ +
+ DataTreeView.hs view
@@ -0,0 +1,11 @@+{-# OPTIONS -Wall #-}+module DataTreeView(+ DataTreeView(..),+ -- * Construction+ dtvNew,dtvNewWithCH,+ -- * Launching simple GTK windows+ simpleView,simpleViewWithCH,+ ) where++import DataTreeView.Widget+import DataTreeView.SimpleView
+ DataTreeView/CustomHandlers.hs view
@@ -0,0 +1,24 @@+{-# OPTIONS -Wall #-}+module DataTreeView.CustomHandlers(+ module DataTreeView,+ -- * Custom handlers+ CustomHandler(..), showType, showTypeOf,+ -- ** The monad in which custom handlers run+ MCH,self,+ -- ** Specialized constructors+ simpleCH,monoCH,monoPureCH,monoPureCH',poly1CH,poly2CH,+ -- ** Data that custom handlers must produce+ module DataTreeView.StrictTypes,+ CellData,Row(..),newRow,addToAll,+ -- *** Cell attributes+ CellAttr,ColorName,txt,bgcolor,bgcolor',fgcolor,fgcolor',scale,+ -- * Internal+ dataToTree+ ) where++import DataTreeView+import DataTreeView.DataToTree+import DataTreeView.Row+import Data.Monoid+import Data.Tree+import DataTreeView.StrictTypes
+ DataTreeView/DataToTree.hs view
@@ -0,0 +1,299 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# OPTIONS -Wall #-}+module DataTreeView.DataToTree where+import Data.Data+import Data.Monoid+import Data.String.Utils(replace)+import Control.Monad.IO.Control+import Control.Exception.Control+import Control.Applicative+import Control.Monad.Reader+import Prelude hiding(catch)+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.IntMap as IntMap+import Data.List(intercalate)+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Internal as BSI+import qualified Data.ByteString.Lazy.Char8 as BL+import DataTreeView.Row+import DataTreeView.StrictTypes++++++-- | Functionality a @CustomHandler@ may use.+data CustomHandlerServices = CHS {+ chsSelf :: forall d. Data d => d -> MCH (StrictTree Row) +}+ deriving(Typeable)++++newtype MCH a = MCH (ReaderT CustomHandlerServices IO a) deriving(Functor,Applicative,Monad, MonadIO, MonadControlIO, Typeable) ++runMCH :: MCH a -> CustomHandlerServices -> IO a+runMCH (MCH x) = runReaderT x++-- | Invokes the /final/ Data-to-Tree conversion function, which includes the generic handler, the 'CustomHandler' being defined, and any 'CustomHandler's 'mappend'ed to the one being defined.+-- +-- Thus, invoking @'self' x@ from your implementation of @'runCH' x@ will usually amount to an infinite loop, but invoking @'self' y@ on some child @y@ of @x@ is fine.+self :: Data d => d -> MCH (StrictTree Row)+self x = do+ f <- MCH (asks chsSelf)+ f x+++++++-- | Custom handlers are used for overriding the generic formatting behaviour at some values (for example, printing a @String@ directly into the /Constructor or value/ column rather than as a list of chars). +--+-- If a custom handler matches (returns a 'Just'), no recursion into its children is performed by default. You can recurse manually using 'self'. +newtype CustomHandler = CH { runCH :: forall d. Data d => d -> MCH (Maybe (StrictTree Row)) }+++-- | Creates a custom handler that only applies at a fixed monomorphic type @a@.+monoCH :: Typeable a => (a -> MCH (Maybe (StrictTree Row))) -> CustomHandler+monoCH f = CH (case cast f of+ Nothing -> \_ -> return Nothing+ Just f' -> f')++-- | Creates a pure custom handler that only applies at a fixed monomorphic type @a@.+monoPureCH :: Typeable a => (a -> (Maybe (StrictTree Row))) -> CustomHandler+monoPureCH f = CH (case cast f of+ Nothing -> \_ -> return Nothing+ Just f' -> return . f')++-- | Creates a pure custom handler that only and always applies at a fixed monomorphic type @a@.+monoPureCH' :: Typeable a => (a -> StrictTree Row) -> CustomHandler+monoPureCH' f = CH (case cast f of+ Nothing -> \_ -> return Nothing+ Just f' -> return . Just . f')++-- | Creates a custom handler that applies at @f a@ for a fixed type constructor @f@ and for any @a@.+poly1CH :: Typeable1 f => (forall a. Data a => f a -> MCH (Maybe (StrictTree Row))) -> CustomHandler+poly1CH f = CH (case dataCast1 (Q f) of+ Nothing -> \_ -> return Nothing+ Just (Q f') -> f')++-- | Creates a custom handler that applies at @f a b@ for a fixed binary type constructor @f@ and for any @a@ and @b@.+poly2CH :: Typeable2 f => (forall a b. (Data a, Data b) => f a b -> MCH (Maybe (StrictTree Row))) -> CustomHandler+poly2CH f = CH (case dataCast2 (Q f) of+ Nothing -> \_ -> return Nothing+ Just (Q f') -> f')++-- from syb+newtype Q q x = Q { unQ :: x -> q }+++-- | The 'mempty' 'CustomHandler' handles nothing. @'mappend' ch1 ch2@ tries @ch1@ first, falling back to @ch2@ if @ch1@ doesn't handle the argument.+instance Monoid CustomHandler where+ mempty = CH (\_ -> return Nothing)+ mappend ch1 ch2 = CH (\x -> do+ y1 <- runCH ch1 x+ case y1 of+ Just _ -> return y1+ Nothing -> runCH ch2 x)++-- | Construct a 'Row' with empty 'rowFieldName'. +--+-- You usually can't know the field name for a node @x@ during a recursive call with @x@ at its root - the field name will be added /afterwards/ by the generic handler for the parent node of @x@ if that parent node is a constructor with named fields.+newRow :: [CellAttr] -- ^ 'rowCV'+ -> [CellAttr] -- ^ 'rowCustomInfo'+ -> [CellAttr] -- ^ 'rowTypeName'+ -> Row+newRow (cellData -> rowCV) (cellData -> rowCustomInfo) (cellData -> rowTypeName) = + Row {rowCV,rowTypeName,rowCustomInfo,rowFieldName = mempty}++dataToTree :: forall d. Data d => CustomHandler -> d -> IO (StrictTree Row)+dataToTree ch = \x -> runMCH (chsSelf x) customHandlerServices+ where+ customHandlerServices = CHS {chsSelf} ++ ch' = ch `mappend` builtinCH ++ chsSelf :: forall e. Data e => e -> MCH (StrictTree Row)+ chsSelf x = chsSelf1 x `catch` excHandler+ + chsSelf1 x = do + y <- {-# SCC "dataToTree/runCH" #-} runCH ch' x + z <- case y of+ Just y' -> return y'+ Nothing -> defaultHandler x+ evaluate z++excHandler :: SomeException -> MCH (StrictTree Row)+excHandler e = return (strictTree (formatExc e))++++formatExc :: SomeException -> Row+formatExc (SomeException e) = + newRow + (colored [txt $ show e]) + (colored [txt $ showAttrList [("exception type",show (typeOf e))]]) + mempty+ where+ colored = ([ bgcolor $ "black", fgcolor $ "white" ] ++)+++defaultHandler :: forall e. Data e => e -> MCH (StrictTree Row)+defaultHandler x = do+ rec <- {-# SCC "defaultHandler/rec" #-} sequence (gmapQ self x)++ ctorRow <- (do + let constr = toConstr x+ ctorText <- (evaluate . showConstr) constr+ return $ case constrRep constr of+ AlgConstr _ -> newRow [ txt $ ctorText ] [] [txt $ showTypeOf x]+ _ -> formatLit ctorText "" (typeOf x)+ )+ `catch` (return . formatExc)++ -- Add record field names+ let rec' = {-# SCC "defaultHandler/rec'" #-}+ case safeConstrFields x of + Nothing -> rec + Just fieldNames -> + zipWith + (\n b -> + let nodeValue' = (nodeValue n) { rowFieldName = cellData [ txt $ b ] }+ in strictTree (nodeValue', nodeChildren n)) + rec + (fieldNames ++ repeat "") +++ return $ strictTree (ctorRow, rec')++safeConstrFields :: Data a => a -> Maybe [String]+safeConstrFields x = + case dataTypeRep (dataTypeOf x) of+ AlgRep _ -> case constrFields (toConstr x) of+ [] -> Nothing+ xs -> Just xs+ _ -> Nothing++-- safeConstrFields :: Constr -> Maybe [String]+-- safeConstrFields c = unsafePerformIO (+-- (Just <$> evaluate (constrFields c)) `Control.Exception.catch` (\(SomeException e) -> return Nothing) +-- )+++builtinCH :: CustomHandler+builtinCH = mconcat [strCH, lstCH, mapCH, setCH, intMapCH, bsCH, blCH, funCH]++lstCH :: CustomHandler+lstCH = container1CH (\x -> ("List",[("length",(show . length) x)],fmap AnyData x))++mapCH :: CustomHandler+mapCH = container2CH (\x -> ("Map",[("size",(show . Map.size) x)], (fmap AnyData . Map.toList) x))++setCH :: CustomHandler+setCH = container1CH (\x -> ("Set",[("size",(show . Set.size) x)],(fmap AnyData . Set.toList) x))++intMapCH :: CustomHandler+intMapCH = container1CH (\x -> ("IntMap",[("size",(show . IntMap.size) x)],(fmap AnyData . IntMap.toList) x))++data AnyData = forall a. Data a => AnyData a++container0CH :: Typeable a =>+ (a -> (String,[(String,String)],[AnyData])) -> CustomHandler+container0CH convert = monoCH (\x -> containerCH_common (convert x) (typeOf x))++container1CH :: Typeable1 f =>+ (forall a. (Data a) => f a -> (String,[(String,String)],[AnyData])) -> CustomHandler+container1CH convert = poly1CH (\x -> containerCH_common (convert x) (typeOf x))++container2CH :: Typeable2 f => + (forall a b. (Data a, Data b) => f a b -> (String,[(String,String)],[AnyData])) -> CustomHandler+container2CH convert = poly2CH (\x -> containerCH_common (convert x) (typeOf x))+++containerCH_common :: (String,[(String,String)],[AnyData]) -> TypeRep -> MCH (Maybe (StrictTree Row))+containerCH_common (name,attrs,asList) typeRep = do+ cs <- mapM (\(AnyData y) -> self y) asList+ let row = + newRow [ txt $ concat ["{",name,"}"] ]+ [ txt $ showAttrList attrs]+ [ txt $ showType typeRep ] ++ `addToAll` [ fgcolor' 0 0 0xA000 ] ++ (return . Just) (strictTree (row,cs))+++++showAttrList :: [(String, String)] -> String+showAttrList = intercalate ", " . (fmap (\(k,v) -> k ++ "=" ++ v)) +++strCH :: CustomHandler+strCH = monoPureCH' (\x -> {-# SCC "strCH/body" #-}+ (strictTree (formatLit (show (x::String)) "" (typeOf x))+ ))++-- | Strict ByteString+bsCH :: CustomHandler+bsCH = monoPureCH' (\x@(BSI.PS addr offset length_) -> + strictTree (formatLit + ((show . BS.unpack) x) + (showAttrList [("addr",show addr) ,("offset",show offset) ,("length",show length_)])+ (typeOf x))+ )++-- | Lazy ByteString+blCH :: CustomHandler+blCH = container0CH (\x -> + let+ chunks = BL.toChunks x+ in+ ("Lazy ByteString", [("nchunks",(show . length) chunks)], fmap AnyData chunks)) ++++funCH :: CustomHandler+funCH = poly2CH (\x -> + let+ _types = x undefined+ color = fgcolor "darkorange"+ row = newRow [txt "{function}", color ] + [] [txt $ showTypeOf x, color]+ in+ (return . Just . strictTree) row)+++formatLit :: String -> String -> TypeRep -> Row+formatLit x info t = newRow [ txt $ x ] [ txt $ info ] [ txt $ showType t ] + + `addToAll` [ fgcolor' 0 0x8000 0 ]+++++-- | Show a type with some prettification like replacing @[Char]@ with @String@.+showType :: TypeRep -> String+showType = replace "[Char]" "String" . show++showTypeOf :: Typeable a => a -> String+showTypeOf = showType . typeOf++-- | A 'CustomHandler' that only works at a fixed type (and always works there), and generates a node with no children and with the default text in the /type/ column. +-- The function should return the 'rowCV' in the first component and the 'rowCustomInfo' in the second. +simpleCH :: Typeable a => (a -> MCH ([CellAttr],[CellAttr])) -> CustomHandler+simpleCH f = monoCH (\x -> do+ (y1,y2) <- f x + (return . Just) $ strictTree (newRow y1 y2 [txt $ showTypeOf x]))++
+ DataTreeView/Row.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE DeriveDataTypeable, BangPatterns, GeneralizedNewtypeDeriving, DeriveDataTypeable,+ ViewPatterns, DeriveFunctor, StandaloneDeriving #-}+{-# OPTIONS -Wall #-}+module DataTreeView.Row(CellData,cellData,unCellData,+ ColorName,CellAttr,txt,bgcolor,bgcolor',fgcolor,fgcolor',scale,Row(..),+ addToAll,convertAttrs,seqListSpine, standardScale) where+import Control.DeepSeq+import Graphics.UI.Gtk+import Data.Typeable+import Data.Monoid+import DataTreeView.StrictTypes+import Data.Data+import Data.Word++seqListSpine :: [t1] -> t -> t+seqListSpine [] x = x+seqListSpine (_:xs) x = xs `seqListSpine` x +++++type CellData = StrictList CellAttr+++cellData :: [CellAttr] -> CellData+cellData = strictList++unCellData :: CellData -> [CellAttr]+unCellData = fromStrictList++type ColorName = String+++++-- | Cell attributes+data CellAttr =+ Txt !String + -- yes, the !String isn't strict enough; thus the smart constructors+ | Bgcolor !ColorName+ | Bgcolor' !Word16 !Word16 !Word16 -- ^ Expanded because 'Color' doesn't have a 'Data' instances and I don't want the code to break once it does+ | Fgcolor !String+ | Fgcolor' !Word16 !Word16 !Word16+ | Scale !Double+ deriving(Show,Typeable,Data,Eq,Ord)++-- | The cell's text. If this attribute occurs multiple times for a single cell, the occurences are concatenated.+txt :: String -> CellAttr+txt x = rnf x `seq` Txt x+-- | Background color, by name+bgcolor :: ColorName -> CellAttr+bgcolor x = rnf x `seq` Bgcolor x+-- | Background color, red\/green\/blue+bgcolor' :: Word16 -> Word16 -> Word16 -> CellAttr+bgcolor' = Bgcolor'+-- | Foreground color, by name+fgcolor :: ColorName -> CellAttr+fgcolor x = rnf x `seq` Fgcolor x+-- | Foreground color, red\/green\/blue+fgcolor' :: Word16 -> Word16 -> Word16 -> CellAttr+fgcolor' = Fgcolor'+-- | Font scaling factor+scale :: Double -> CellAttr+scale = Scale -- . (*standardScale)++standardScale :: Double+standardScale = 0.75++-- | Data for a row of the tree widget.+data Row = Row { + rowCV :: !CellData -- ^ Constructor name, literal value, or a placeholder like @\"{List}\"@ for custom things + , rowFieldName :: !CellData -- ^ Record field name. You can mostly ignore this, see the remark in 'newRow'.+ , rowCustomInfo :: !CellData -- ^ Arbitrary information (left empty by the generic handler)+ , rowTypeName :: !CellData +} deriving(Show,Typeable,Data)++-- | Adds the given attribute to each cell of the given row.+addToAll :: Row -> [CellAttr] -> Row+addToAll r (cellData -> x) + = r { rowCV = rowCV r `mappend` x+ , rowFieldName = rowFieldName r `mappend` x+ , rowCustomInfo = rowCustomInfo r `mappend` x+ , rowTypeName = rowTypeName r `mappend` x+ }++convertAttrs :: CellRendererTextClass c => CellData -> [AttrOp c]+convertAttrs = fmap convertAttr . fromStrictList + where+ convertAttr (Txt x) = cellText :~ (++x)+ convertAttr (Fgcolor x) = cellTextForeground := x+ convertAttr (Fgcolor' r g b) = cellTextForegroundColor := Color r g b+ convertAttr (Bgcolor x) = cellTextBackground := x+ convertAttr (Bgcolor' r g b) = cellTextBackgroundColor := Color r g b+ convertAttr (Scale x) = cellTextScale :~ (*x)++
+ DataTreeView/SimpleView.hs view
@@ -0,0 +1,29 @@+{-# OPTIONS -Wall #-}+module DataTreeView.SimpleView where+import Graphics.UI.Gtk+import Data.Data+import Data.Monoid+import Control.Monad.Trans++import DataTreeView.Widget+import DataTreeView.DataToTree++simpleView :: Data d => [d] -> IO ()+simpleView = simpleViewWithCH mempty++simpleViewWithCH :: Data d => CustomHandler -> [d] -> IO ()+simpleViewWithCH ch xs = do+ _ <- initGUI+ window <- windowNew+ _ <- window `on` deleteEvent $ liftIO (mainQuit >> return False)+ + dtv <- dtvNewWithCH ch xs++ set window [ windowTypeHint := WindowTypeHintNormal+ , containerChild := dtv ]++ -- i.e., on window deleteEvent (liftIO mainQuit >> return False)+ widgetShowAll window+ mainGUI++
+ DataTreeView/StrictTypes.hs view
@@ -0,0 +1,145 @@+{-# LANGUAGE DeriveDataTypeable,DeriveFunctor,BangPatterns,DeriveFoldable #-}+{-# LANGUAGE DeriveTraversable,NoMonomorphismRestriction, FlexibleContexts,FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS -Wall #-}+-- | Remark: the heavy use of strictness here is for correctly catching exceptions, not for performance reasons+module DataTreeView.StrictTypes(+ module Data.Tree,+ module Data.Monoid,+ -- * Strict lists+ StrictList,+ -- ** Construction+ ToStrictList(..),+ -- ** Modification+ drop,+ -- ** Destruction+ fromStrictList,foldl',+ -- * Strict trees and forests+ StrictTree,StrictForest,+ -- ** Construction+ ToStrictTree(..),ToStrictForest(..),+ -- ** Destruction+ nodeValue,nodeChildren,fromStrictTree,fromStrictForest) where +import Data.Typeable+import Data.Monoid+import Prelude hiding(drop)+import Data.Tree+import Data.Foldable(Foldable)+import Data.Traversable(Traversable)+import Data.Data+import Data.List(unfoldr)++-- Strict lists (in both the head and tail)+data StrictList a = SNil | SCons !a !(StrictList a) + deriving(Show,Typeable,Data,Functor,Foldable,Traversable)++class ToStrictList x a where+ strictList :: x -> StrictList a++-- | Singleton+instance ToStrictList a a where+ strictList x = SCons x SNil++-- | From lazy list+instance ToStrictList [a] a where+ strictList = foldr SCons SNil++-- | Cons+instance ToStrictList x a => ToStrictList (a, x) a where+ strictList (a,x) = SCons a (strictList x)++-- | Identity+instance ToStrictList (StrictList a) a where+ strictList = id++++fromStrictList :: StrictList a -> [a]+fromStrictList = unfoldr f + where+ f SNil = Nothing+ f (SCons x xs) = Just (x,xs)+++-- copied from Data.List+foldl' :: (r -> a -> r) -> r -> StrictList a -> r+foldl' f z0 xs0 = lgo z0 xs0+ where lgo z SNil = z+ lgo z (SCons x xs) = let z' = f z x in z' `seq` lgo z' xs++ ++drop :: (Integral a) => a -> StrictList t -> StrictList t+drop n xs | n < 0 = error "Strict.drop: n<0" + | otherwise = go n xs+ where+ go 0 xs' = xs'+ go n' (SCons _ xs'') = go (pred n') xs''+ go _ SNil = SNil++++-- | Empty list and appending+instance Monoid (StrictList a) where+ mempty = SNil+ mappend SNil !b = b+ mappend (SCons a1 a2) !b = SCons a1 (mappend a2 b) ++data StrictTree a = SNode !a !(StrictForest a) deriving(Show,Typeable,Data,Functor,Foldable,Traversable) ++type StrictForest a = StrictList (StrictTree a)++class ToStrictTree x a where+ strictTree :: x -> StrictTree a++-- | Leaf+instance ToStrictTree a a where+ strictTree = flip SNode SNil++-- | From lazy tree+instance ToStrictTree (Tree a) a where+ strictTree (Node a ts) = SNode a (strictForest ts :: StrictForest a)++-- | From node value and subforest+instance ToStrictForest y a => ToStrictTree (a, y) a where+ strictTree (a,y) = SNode a (strictForest y)++-- | Identity+instance ToStrictTree (StrictTree a) a where+ strictTree = id++class ToStrictForest y a where+ strictForest :: y -> StrictForest a++instance (ToStrictTree x a) => ToStrictForest [x] a where+ strictForest = strictList . fmap (strictTree :: x -> StrictTree a)++instance (ToStrictTree x a) => ToStrictForest (StrictList x) a where+ strictForest = strictList . fmap (strictTree :: x -> StrictTree a)++-- | Identity+instance ToStrictForest (StrictForest a) a where+ strictForest = id++++nodeValue :: StrictTree a -> a+nodeValue (SNode a _) = a++nodeChildren :: StrictTree t -> [StrictTree t]+nodeChildren (SNode _ ts) = fromStrictList ts+++ ++++fromStrictForest :: StrictForest a -> Forest a+fromStrictForest = fmap fromStrictTree . fromStrictList++fromStrictTree :: StrictTree a -> Tree a+fromStrictTree = unfoldTree f+ where+ f (SNode a ts) = (a, fromStrictList ts) +
+ DataTreeView/Widget.hs view
@@ -0,0 +1,222 @@+{-# OPTIONS -Wall #-}+module DataTreeView.Widget where++--import Control.DeepSeq+import Control.Exception+import Control.Monad+import Control.Monad.Trans(liftIO)+import Data.Data+import Data.List+import Data.Maybe+import Data.Monoid+import Data.Tree+import Graphics.UI.Gtk+import System.Glib++import DataTreeView.DataToTree+import DataTreeView.Row+import DataTreeView.StrictTypes+import Control.Applicative+++dbgChangeCursor :: String -> IO ()+dbgChangeCursor = const (return ())+dbgGuardedAttrSetter :: String -> IO ()+dbgGuardedAttrSetter = const (return ())++dbg :: String -> IO ()+dbg = putStrLn+++data DataTreeView = DataTreeView {+ dtvTreeView :: TreeView+}++++ +instance TreeViewClass DataTreeView+instance ContainerClass DataTreeView+instance WidgetClass DataTreeView+instance ObjectClass DataTreeView+instance GObjectClass DataTreeView where+ toGObject = toGObject . dtvTreeView+ unsafeCastGObject = DataTreeView . unsafeCastGObject+++dtvNew :: Data d => [d] -> IO DataTreeView+dtvNew = dtvNewWithCH mempty+ +dtvNewWithCH :: Data d => CustomHandler -> [d] -> IO DataTreeView+dtvNewWithCH ch xs = do+ forest <- let f = fmap fromStrictTree . fromStrictList . strictList + in f <$> mapM (dataToTree ch) xs+ treeModel <- treeStoreNew forest+ treeView <- treeViewNewWithModel treeModel++ cols <- replicateM 4 treeViewColumnNew++ mapM_ (\c -> set c [ treeViewColumnResizable := True+ , treeViewColumnReorderable := True ]) cols ++ zipWithM_ treeViewColumnSetTitle cols ["Constructor or value", "Field name", "Custom info", "Type"]++ -- Set (the same) renderer for all columns+ renderer <- cellRendererTextNew+ mapM_ (\c -> cellLayoutPackEnd c renderer False) cols++ -- Set Row -> attributes functions+ zipWithM_ (\c -> cellLayoutSetAttributesIO c renderer treeModel) + cols+ [simpleAttrSetter rowCV+ ,simpleAttrSetter rowFieldName+ ,simpleAttrSetter rowCustomInfo+ ,simpleAttrSetter rowTypeName+ ]++ -- Append all the columns to the TreeView+ mapM_ (treeViewAppendColumn treeView) cols++ _ <- on treeView keyPressEvent (theOnKeyPress forest treeView)++ treeViewExpandAll treeView+++ return DataTreeView { dtvTreeView = treeView } +++theOnKeyPress+ :: TreeViewClass self =>+ [Tree t]+ -> self+ -> EventM EKey Bool+theOnKeyPress forest treeView = do+ kn <- eventKeyName+ case kn of + "j" -> changeCursor (modifyLast succ)+ "k" -> changeCursor (modifyLast pred)+ "l" -> changeCursor (++[0])+ "h" -> changeCursor dropLast+ _ -> return False++ where+-- changeCursorByIter :: (TreeIter -> IO TreeIter) -> IO Bool+-- changeCursorByIter f = do+-- (path,_) <- treeViewGetCursor treeView+-- mbIter <- treeModelGetIter treeModel path+-- case mbIter of+-- Nothing -> putStrLn "Warning: treeViewGetCursor returned an invalid TreePath"+-- Just iter ->+-- f iter >>= maybe (return ()) +-- ((\path' -> treeViewSetCursor treeView path' Nothing) +-- <=< treeModelGetPath treeModel )+-- +-- return True++ changeCursor f = liftIO $ do+ dbgChangeCursor "Entering changeCursor"+ (path,_) <- treeViewGetCursor treeView+ dbgChangeCursor ("\tpath = "++ show path)+ let path' = f path + dbgChangeCursor ("\tpath' = "++ show path')+ dbgChangeCursor "\tgetting node"+ --node <- treeStoreLookup treeModel path'+ let node_ = forestLookupPath forest path'+ dbgChangeCursor ("\tisJust node = "++show (isJust node_))+ when (isJust node_) $ do+ treeViewExpandToPath treeView path'+ treeViewSetCursor treeView path' Nothing+ (path'',_) <- treeViewGetCursor treeView+ dbgChangeCursor ("\tpath'' = "++ show path'')+ dbgChangeCursor "Leaving changeCursor"+ return True++++forestLookupPath :: [Tree t] -> [Int] -> Maybe (Tree t)+forestLookupPath _ [] = Nothing+forestLookupPath ts (i:is) = flip treeLookupPath is =<< maybeIth ts i++treeLookupPath :: Tree t -> [Int] -> Maybe (Tree t)+treeLookupPath t [] = Just t+treeLookupPath (Node _ f) is = forestLookupPath f is++-- | Zero-based.+maybeIth :: [a] -> Int -> Maybe a+maybeIth lst i | i < 0 = Nothing+ | otherwise =+ case Prelude.drop i lst of+ [] -> Nothing+ (t:_) -> Just t+ +modifyLast :: (a -> a) -> [a] -> [a]+modifyLast f = unfoldr g+ where+ g [] = Nothing+ g [x] = Just (f x,[])+ g (x:xs) = Just (x,xs)++dropLast :: [a] -> [a]+dropLast = unfoldr g+ where+ g [] = Nothing+ g [_] = Nothing+ g (x:xs) = Just (x,xs)+ +showGError :: GError -> String+showGError (GError a b c) = show (a,b,c)+++simpleAttrSetter+ :: CellRendererTextClass self =>+ (Row -> CellData) -> Row -> IO [AttrOp self]+simpleAttrSetter f = guardedAttrSetter (\x -> do+ y <- evaluate ( f x )+ -- putStrLn ("Attrs = "++show y)+ return (convertAttrs y))++++guardedAttrSetter :: CellRendererTextClass cr => (Row -> IO [AttrOp cr]) -> Row -> IO [AttrOp cr]+guardedAttrSetter f x = + (do+ dbgGuardedAttrSetter "Entering guardedAttrSetter"+ attrs <- f x+ dbgGuardedAttrSetter "Leaving guardedAttrSetter"+ return attrs)+ `catchGError` gErrorHandler+ `Control.Exception.catch` handler ++ where+ gErrorHandler e = do+ putStrLn ("GError caught: "++showGError e)+ return [cellText := "GERROR: " ++ showGError e]++ handler (SomeException e) = do+ --putStrLn ("Exception caught: "++show e)+ return [cellText := "EXCEPTION: " ++ show e]+++cellLayoutSetAttributesIO :: + ( TypedTreeModelClass model,+ CellLayoutClass lay,+ CellRendererTextClass cell,+ TreeModelClass (model row)) => + + lay -> cell -> model row -> (row -> IO [AttrOp cell]) -> IO ()++cellLayoutSetAttributesIO self_ cell model attributes =+ cellLayoutSetAttributeFunc self_ cell model $ \iter -> do+ set cell attrResets+ row <- treeModelGetRow model iter+ attrs <- attributes row+ set cell attrs++ where+ attrResets = (cellText := "") : ( cellTextScale := standardScale ) : + (fmap (\a -> a := False)+ [ cellBackgroundSet, cellTextBackgroundSet, cellTextForegroundSet, cellTextSizeSet + , {- cellTextScaleSet, -} cellTextStyleSet, cellTextVariantSet ]) +++
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Daniel Schüssler++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Daniel Schüssler nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain