packages feed

ltk-0.16.1.0: src/Graphics/UI/Editor/MakeEditor.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
-----------------------------------------------------------------------------
--group_Test
-- Module      :  Graphics.UI.Editor.MakeEditor
-- Copyright   :  (c) Juergen Nicklisch-Franken, Hamish Mackenzie
-- License     :  GNU-GPL
--
-- Maintainer  :  <maintainer at leksah.org>
-- Stability   :  provisional
-- Portability :  portable
--
-- | Module for making editors out of descriptions
--
-----------------------------------------------------------------------------------

module Graphics.UI.Editor.MakeEditor (

    buildEditor
,   FieldDescription(..)
,   mkField
,   extractAndValidate
,   extract
,   mkEditor
,   parameters

,   flattenFieldDescription
,   getRealWidget
,   MkFieldDescription

) where

import Prelude ()
import Prelude.Compat
import Control.Monad (foldM, when)
import Data.List (unzip4, intersperse)
import Data.Text (Text)
import Data.Monoid ((<>), mconcat)

import Control.Event
import Graphics.UI.Utils
import Graphics.UI.Editor.Parameters
import Graphics.UI.Editor.Basics
--import Graphics.UI.Frame.ViewFrame
import Data.Maybe (fromMaybe, isNothing, fromJust)
import Data.IORef (newIORef)
import GI.Gtk.Objects.Widget
       (toWidget, widgetSetName, widgetSetSizeRequest, Widget(..))
import GI.Gtk.Objects.Notebook
       (setNotebookEnablePopup, notebookSetCurrentPage,
        notebookAppendPage, notebookSetScrollable, notebookSetShowTabs,
        notebookSetTabPos, notebookNew, Notebook(..))
import GI.Gtk.Objects.ScrolledWindow
       (scrolledWindowSetPolicy, scrolledWindowNew)
import GI.Gtk.Objects.TreeView
       (treeViewSetHeadersVisible, treeViewAppendColumn,
        treeViewGetSelection, treeViewNewWithModel)
import GI.Gtk.Objects.TreeSelection
       (onTreeSelectionChanged,
        treeSelectionSelectPath, treeSelectionSetMode)
import GI.Gtk.Objects.CellRendererText
       (setCellRendererTextText, cellRendererTextNew)
import GI.Gtk.Objects.TreeViewColumn (treeViewColumnNew)
import GI.Gtk.Interfaces.CellLayout
       (cellLayoutSetCellDataFunc, cellLayoutPackStart)
import Data.GI.Base.Signals (on)
import GI.Gtk.Objects.HBox (hBoxNew)
import GI.Gtk.Objects.Container
       (toContainer, Container(..), containerAdd)
import GI.Gtk.Objects.Box (Box(..), boxPackEnd, boxPackStart)
import GI.Gtk.Objects.VBox (vBoxNew)
import GI.Gtk.Objects.Alignment (alignmentSetPadding, alignmentNew)
import GI.Gtk.Objects.Frame
       (frameSetLabel, frameSetShadowType, frameNew)
import GI.Gtk.Objects.Bin (Bin(..), binGetChild)
import GI.Gtk.Enums
       (Orientation(..), Align(..), SelectionMode(..), PolicyType(..),
        PositionType(..))
import Data.GI.Base.ManagedPtr (castTo, unsafeCastTo)
import Data.GI.Gtk.ModelView.CustomStore (customStoreGetRow)
import GI.Gtk.Objects.Adjustment (noAdjustment)
import Data.GI.Gtk.ModelView.SeqStore (SeqStore(..), seqStoreNew)
import GI.Gtk.Objects.Label (labelNew)
import Control.Exception (catch)
import Data.GI.Base.BasicTypes (UnexpectedNullPointerReturn(..), nullToNothing)
import GI.Gtk.Structs.TreePath (treePathNewFirst)
import Data.GI.Gtk.ModelView.Types
       (treeSelectionGetSelectedRows', treePathGetIndices')
import Control.Monad.IO.Class (MonadIO(..), MonadIO)
import GI.Gtk
       (orientableSetOrientation, Orientation, widgetSetVexpand,
        widgetSetHexpand, widgetSetMarginEnd, widgetSetMarginStart,
        widgetSetMarginBottom, widgetSetMarginTop, noWidget,
        widgetSetValign, widgetSetHalign, gridNew)
import Data.Foldable (forM_)

--
-- | A constructor type for a field desciption
--
type MkFieldDescription alpha beta =
    Parameters ->
    Getter alpha beta ->
    Setter alpha beta ->
    Editor beta ->
    FieldDescription alpha

--
-- | A type to describe a field of a record, which can be edited
-- | alpha is the type of the individual field of the record
data FieldDescription alpha =  FD Parameters (alpha -> IO (Widget, Injector alpha ,
                                    alpha -> Extractor alpha , Notifier))
    | VFD Parameters [FieldDescription alpha]
    | HFD Parameters [FieldDescription alpha]
    | NFD [(Text,FieldDescription alpha)]

parameters :: FieldDescription alpha -> Parameters
parameters (FD p _) = p
parameters (VFD p _) = p
parameters (HFD p _) = p
parameters (NFD _) = emptyParams

--
-- | Construct a new notebook
--
newNotebook :: MonadIO m => m Notebook
newNotebook = do
    nb <- notebookNew
    notebookSetTabPos nb PositionTypeTop
    notebookSetShowTabs nb True
    notebookSetScrollable nb True
    setNotebookEnablePopup nb True
    return nb

buildEditor :: (Applicative m, MonadIO m) => FieldDescription alpha -> alpha -> m (Widget, Injector alpha , alpha -> Extractor alpha , Notifier)
buildEditor (FD paras editorf) v  =  liftIO $ editorf v
buildEditor (HFD paras descrs) v =   buildBoxEditor descrs OrientationHorizontal v
buildEditor (VFD paras descrs) v =   buildBoxEditor descrs OrientationVertical v
buildEditor (NFD pairList)     v =   do
    nb <- newNotebook
    notebookSetShowTabs nb False
    resList <- mapM ((`buildEditor` v) . snd) pairList
    let (widgets, setInjs, getExts, notifiers) = unzip4 resList
    notifier <- emptyNotifier
    mapM_ (\ (labelString, widget) -> do
        sw <- scrolledWindowNew noAdjustment noAdjustment
        containerAdd sw widget
        scrolledWindowSetPolicy sw PolicyTypeAutomatic PolicyTypeAutomatic
        notebookAppendPage nb sw . Just =<< labelNew (Just labelString))
         (zip (map fst pairList) widgets)
    seqStore   <- seqStoreNew (map fst pairList)
    listView    <- treeViewNewWithModel seqStore
    widgetSetSizeRequest listView 100 (-1)
    sel         <- treeViewGetSelection listView
    treeSelectionSetMode sel SelectionModeSingle
    renderer    <- cellRendererTextNew
    col         <- treeViewColumnNew
    treeViewAppendColumn listView col
    cellLayoutPackStart col renderer True
    cellLayoutSetCellDataFunc col renderer . Just $ \c r m i -> do
        row <- customStoreGetRow seqStore i
        setCellRendererTextText renderer row
    treeViewSetHeadersVisible listView False
    treeSelectionSelectPath sel =<< treePathNewFirst
    notebookSetCurrentPage nb 0
    onTreeSelectionChanged sel $ do
        selections <- treeSelectionGetSelectedRows' sel >>= mapM treePathGetIndices'
        case selections of
            [[i]] -> notebookSetCurrentPage nb i
            _ -> return ()

    grid        <- gridNew
    sw          <- scrolledWindowNew noAdjustment noAdjustment
    containerAdd sw listView
    scrolledWindowSetPolicy sw PolicyTypeNever PolicyTypeAutomatic
    widgetSetHalign sw AlignStart
    widgetSetVexpand sw True
    containerAdd grid sw
    widgetSetHexpand nb True
    widgetSetVexpand nb True
    containerAdd grid nb
    let newInj v = mapM_ (\ setInj -> setInj v) setInjs
    let newExt v = extract v getExts
    mapM_ (propagateEvent notifier notifiers) allGUIEvents
    widget <- toWidget grid
    return (widget, newInj, newExt, notifier)

buildBoxEditor :: (Applicative m, MonadIO m) => [FieldDescription alpha] -> Orientation -> alpha
    -> m (Widget, Injector alpha , alpha -> Extractor alpha , Notifier)
buildBoxEditor descrs orientation v = do
    resList <- mapM (`buildEditor` v) descrs
    notifier <- emptyNotifier
    let (widgets, setInjs, getExts, notifiers) = unzip4 resList
    grid <- gridNew
    orientableSetOrientation grid orientation
    let newInj v = mapM_ (\ setInj -> setInj v) setInjs
    let fieldNames = map (fromMaybe "Unnamed" . getParameterPrim paraName . parameters) descrs
    let packParas = map (getParameter paraPack . parameters) descrs
    mapM_ (propagateEvent notifier notifiers) allGUIEvents
    let newExt v = extractAndValidate v getExts fieldNames notifier
    forM_ (zip widgets packParas) $ \(w, p) -> do
        case p of
            PackRepel   -> setPrimaryAlign grid w AlignEnd >> setPrimaryExpand grid w True
            PackNatural -> setPrimaryAlign grid w AlignStart
            PackGrow    -> setPrimaryExpand grid w True
        setSecondaryExpand grid w True
        containerAdd grid w
    hbWidget <- liftIO $ toWidget grid
    return (hbWidget, newInj, newExt, notifier)


flattenFieldDescription :: FieldDescription alpha -> [FieldDescription alpha]
flattenFieldDescription (VFD paras descrs)  =   concatMap flattenFieldDescription descrs
flattenFieldDescription (HFD paras descrs)  =   concatMap flattenFieldDescription descrs
flattenFieldDescription (NFD descrp)        =   concatMap (flattenFieldDescription.snd) descrp
flattenFieldDescription fd                  =   [fd]

-- ------------------------------------------------------------
-- * Implementation of editing
-- ------------------------------------------------------------

--
-- | Function to construct a field description
--
mkField :: Eq beta => MkFieldDescription alpha beta
mkField parameters getter setter editor =
    FD parameters
        (\ dat -> do
            noti <- emptyNotifier
            (widget,inj,ext) <- editor parameters noti
            let pext a = do
                            b <- ext
                            case b of
                                Just b -> return (Just (setter b a))
                                Nothing -> return Nothing
            inj (getter dat)
            return (widget,
                    inj . getter,
                    pext,
                    noti))

-- | Function to construct an editor
--
mkEditor :: (Container -> Injector alpha) -> Extractor alpha -> Editor alpha
mkEditor injectorC extractor parameters notifier = do
    frame   <-  frameNew Nothing
    widgetSetHalign frame $ getParameter paraHAlign parameters
    widgetSetValign frame $ getParameter paraVAlign parameters
    let (top, bottom, start, end) = getParameter paraMargin parameters
    widgetSetMarginTop frame top
    widgetSetMarginBottom frame bottom
    widgetSetMarginStart frame start
    widgetSetMarginEnd frame end
    frameSetShadowType frame (getParameter paraShadow parameters)
    case getParameter paraName parameters of
        "" -> return ()
        str -> when (getParameter paraShowLabel parameters) $
                  frameSetLabel frame (Just str)
    let (x,y) = getParameter paraMinSize parameters
    widgetSetSizeRequest frame x y
    let name  =  getParameter paraName parameters
    widgetSetName frame name
    build <- injectorC <$> toContainer frame
    w <- toWidget frame
    return (w, build, extractor)

-- | Convenience method to validate and extract fields
--
extractAndValidate :: MonadIO m => alpha -> [alpha -> Extractor alpha] -> [Text] -> Notifier -> m (Maybe alpha)
extractAndValidate val getExts fieldNames notifier = do
    (newVal,errors) <- foldM (\ (val,errs) (ext,fn) -> do
        extVal <- liftIO $ ext val
        case extVal of
            Just nval -> return (nval,errs)
            Nothing -> return (val, (" " <> fn) : errs))
                (val,[]) (zip getExts fieldNames)
    if null errors
        then return (Just newVal)
        else do
            liftIO $ triggerEvent notifier GUIEvent {
                    selector = ValidationError,
                    eventText = mconcat (intersperse ", " errors),
                    gtkReturn = True}
            return Nothing

extract :: MonadIO m => alpha -> [alpha -> Extractor alpha] -> m (Maybe alpha)
extract val  =
    foldM (\ mbVal ext ->
        case mbVal of
            Nothing -> return Nothing
            Just val -> liftIO $ ext val)
            (Just val)

-- | get through outerAlignment, frame, innerAlignment
getRealWidget :: MonadIO m => Widget -> m (Maybe Widget)
getRealWidget w = liftIO $ (
    castTo Bin w >>= \case
        Nothing -> return Nothing
        Just b  ->
            (fromJust <$> nullToNothing (binGetChild b)) >>= castTo Bin >>= \case
                Nothing -> return Nothing
                Just f  ->
                    (fromJust <$> nullToNothing (binGetChild f)) >>= castTo Bin >>= \case
                        Nothing -> return Nothing
                        Just ia -> nullToNothing (binGetChild ia)
  ) `catch` (\UnexpectedNullPointerReturn {} -> return Nothing)