packages feed

billeksah-forms-1.0.0: src/Graphics/Forms/FormPane.hs

{-# Language RankNTypes #-}
-----------------------------------------------------------------------------
--
-- Module      :  Graphics.Forms.FormPane
-- Copyright   :  (c) Juergen Nicklisch-Franken, Hamish Mackenzie
-- License     :  GNU-GPL
--
-- Maintainer  :  <maintainer at leksah.org>
-- Stability   :  provisional
-- Portability :  portable
--
-- | A standard builder for a pane with a form
--
-----------------------------------------------------------------------------------

module Graphics.Forms.FormPane (
    FormPaneDescr(..),
    buildFormsPane,
    buildGenericFormsPane,
    SimpleFormPaneDescr(..),
    buildSimpleFormsPane,
) where

import Base

import Graphics.Forms.Build
       (GenFieldDescriptionG(..), GenFieldDescriptionG,
        buildGenericEditor, FieldDescriptionG, buildEditor)
import Graphics.Forms.GUIEvent
       (registerGUIEvent, dummyGUIEvent, triggerGUIEvent)
import Graphics.Forms.Basics
       (GEvent, GenValue(..), Extractor, Injector, GUIEvent(..),
        GUIEventSelector(..))
import Graphics.Pane

import Graphics.UI.Gtk
import Control.Monad.IO.Class (MonadIO(..))
import Data.IORef (newIORef, readIORef, writeIORef)
import Control.Monad (when)


-- | A description for a forms pane, which is targeted as an editor,
-- with save and cancel buttons

data FormPaneDescr alpha beta  =  FormPaneDescr {
    fpGetPane      :: Pane beta  => VBox -> Injector alpha -> Extractor alpha -> GEvent -> beta , -- ^ Construct the pane data type
    fpSaveAction   :: alpha -> StateM (),                                   -- ^ Called when the save button is hit
    fpHasChanged   :: alpha -> alpha -> Bool,                                   -- ^ Judge if this qualify as a change
    fpGuiHandlers  :: [([GUIEventSelector],Handler GUIEvent)],          -- ^ Handle GUI Events triggered
    fpExtraButtons :: [(String,StateM ())]}                             -- ^ Add extra buttons with handlers

--
-- | Returns a builder for a pane
-- Requires a forms description, an initial value and a FormPaneDescr
buildFormsPane :: Pane beta  => FieldDescriptionG alpha  ->  alpha  -> FormPaneDescr alpha beta
                        -> (PanePath -> Notebook -> Window -> StateM (Maybe beta, Connections))
buildFormsPane descr val formDescr =
    buildFormsPanePrim descr (buildEditor descr val) formDescr val

--
-- | Returns a builder for a pane
-- Requires a forms description, an initial value and a FormPaneDescr
buildGenericFormsPane :: Pane beta  => [(String,GenFieldDescriptionG)]  -> FormPaneDescr [GenValue] beta
    -> (PanePath -> Notebook -> Window -> StateM (Maybe beta, Connections))
buildGenericFormsPane descr formDescr =
    buildFormsPanePrim descr (buildGenericEditor descr) formDescr
        (map (\ (_s,GenFG _ v) -> GenV v) descr)

-- | A description for a forms pane, which has no standard buttons and no change detection

data SimpleFormPaneDescr alpha beta  =  SimpleFormPaneDescr {
    sfpGetPane      :: Pane beta  => ScrolledWindow -> Injector alpha -> Extractor alpha -> GEvent -> beta}
            -- ^ Construct the pane data type

--
-- | Returns a builder for a pane
-- Requires a forms description, an initial value and a FormPaneDescr
buildSimpleFormsPane :: Pane beta  => FieldDescriptionG alpha  ->  alpha  -> SimpleFormPaneDescr alpha beta
                        -> (PanePath -> Notebook -> Window -> StateM (Maybe beta, Connections))
buildSimpleFormsPane descr val formDescr =
    buildSimpleFormsPanePrim descr (buildEditor descr val) formDescr val


buildFormsPanePrim
  :: (WidgetClass child, Pane a) =>
     t
     -> StateM
          (child,
           Injector alpha,
           alpha -> Extractor alpha,
           GEvent)
     -> FormPaneDescr alpha a
     -> alpha
     -> t1
     -> t2
     -> Window
     -> StateM (Maybe a, [ConnectId Widget])
buildFormsPanePrim _descr editor formDescr val = \ _panePath _notebook window -> do
    reifyState (\ stateR -> do
        ------------------------------------------
        -- Plugin editor
        lastSaved           <- liftIO $ newIORef val
        (widget,inj,ext,notifier) <- reflectState editor stateR
        sw <- scrolledWindowNew Nothing Nothing
        scrolledWindowAddWithViewport sw widget
        scrolledWindowSetPolicy sw PolicyAutomatic PolicyAutomatic

        -- Buttons
        bb                  <-  hButtonBoxNew
        saveB               <-  buttonNewFromStock "gtk-save"
        revertB             <-  buttonNewFromStock "gtk-revert-to-saved"
        closeB              <-  buttonNewFromStock "gtk-close"

        extraButtons        <-  mapM buttonNewFromStock (map fst (fpExtraButtons formDescr))

        widgetSetSensitive saveB False
        widgetSetSensitive revertB False

        mapM (boxPackStartDefaults bb) extraButtons

        boxPackStartDefaults bb revertB
        boxPackStartDefaults bb saveB
        boxPackStartDefaults bb closeB

        validationLabel <- labelNew Nothing

        --Frame
        vb                  <-  vBoxNew False 0
        boxPackStart vb sw PackGrow 7
        boxPackStart vb validationLabel PackNatural 0
        boxPackEnd vb bb PackNatural 7

        let inj2 = do
            (\b -> do
                    liftIO $ writeIORef lastSaved b
                    inj b)

        let pane  = (fpGetPane formDescr) vb inj2 (ext val) notifier

        --Events
        cid1 <- saveB `onClicked` (do
            mbV <- reflectState (ext val) stateR
            case mbV of
                Nothing -> return ()
                Just v -> do
                    reflectState (do
                        (fpSaveAction formDescr) v
                        liftIO $ writeIORef lastSaved v
                        triggerGUIEvent notifier dummyGUIEvent{geSelector= MayHaveChanged}) stateR
                    return ())

        cid2 <- revertB `onClicked` (do
            old <- readIORef lastSaved
            reflectState (inj old) stateR)

        cid3 <- closeB `onClicked` do
            (hasChanged',_) <- reflectState (hasChanged ext lastSaved) stateR
            if not hasChanged'
                then reflectState (closePane pane >> return ()) stateR
                else do
                    md <- messageDialogNew (Just window) []
                        MessageQuestion
                        ButtonsYesNo
                        "Unsaved changes. Close anyway?"
                    set md [ windowWindowPosition := WinPosCenterOnParent ]
                    resp <- dialogRun md
                    widgetDestroy md
                    case resp of
                        ResponseYes ->   do
                            reflectState (closePane pane >> return ()) stateR
                        _  ->   return ()

        cids <- mapM (\ (button,handler) -> button `onClicked` (reflectState handler stateR))
            (zip extraButtons (map snd (fpExtraButtons formDescr)))

        reflectState (do
            registerGUIEvent notifier [MayHaveChanged] (\ e -> do
                (hasChanged',canExtract) <-  hasChanged ext lastSaved
                when canExtract $ liftIO $ labelSetMarkup validationLabel ""
                setChanged pane hasChanged'
                liftIO $ widgetSetSensitive saveB hasChanged'
                liftIO $ widgetSetSensitive revertB hasChanged'
                return (e{geGtkReturn=False}))

            registerGUIEvent notifier [ValidationError] (\e -> do
                liftIO $ labelSetMarkup validationLabel
                    ("<span foreground=\"red\" size=\"large\">" ++
                        "The following fields have invalid values: "
                        ++ geText e ++ "</span>")
                return e)

            mapM_ (\ (selectors,handler) -> registerGUIEvent notifier selectors handler)
                (fpGuiHandlers formDescr)) stateR

        return (Just pane, map castCID ([cid1, cid2, cid3] ++ cids)))
  where
    hasChanged ext lastSavedRef = do
        old <- liftIO $ readIORef lastSavedRef
        mbP <- ext old
        return $ case mbP of
                    Nothing -> (False,False)
                    Just p -> (not ((fpHasChanged formDescr) p old), True)


buildSimpleFormsPanePrim
  :: (WidgetClass child, Pane a) =>
     t
     -> StateM
          (child,
           Injector alpha,
           alpha -> Extractor alpha,
           GEvent)
     -> SimpleFormPaneDescr alpha a
     -> alpha
     -> t1
     -> t2
     -> Window
     -> StateM (Maybe a, [ConnectId Widget])
buildSimpleFormsPanePrim _descr editor formDescr val = \ _panePath _notebook _ -> do
    reifyState (\ stateR -> do
        ------------------------------------------
        (widget,inj,ext,notifier) <- reflectState editor stateR
        sw <- scrolledWindowNew Nothing Nothing
        scrolledWindowAddWithViewport sw widget
        scrolledWindowSetPolicy sw PolicyAutomatic PolicyAutomatic

        --Frame

        let pane  = (sfpGetPane formDescr) sw inj (ext val) notifier

        return (Just pane,[]))
  where