packages feed

frpnow-gtk3-0.2.0: Control/FRPNow/GTK/Misc.hs

{-# LANGUAGE RecursiveDo #-}

{- |
Module      :  Control.FRPNow.GTK.Misc
Copyright   :  (c) George Steel 2017
License     :  BSD3
Maintainer  :  george.steel@gmail.org

Utility Functions which do not fit anywhere else in the package.

-}
module Control.FRPNow.GTK.Misc where

import Control.FRPNow.GTK.Core
import Graphics.UI.Gtk
import Graphics.UI.Gtk.General.StyleContext
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
import Control.FRPNow
import qualified Data.Text as T
import Data.Text (Text)
import System.IO
import Control.Exception
import Control.Concurrent
import Control.Concurrent.MVar

-- | Map an expensive computation over a 'Behavior' to WHNF in a background thread ('force' is useful to get deep evaluation). The output behavior will lag behind the input behavior and thus requires an initial value.
-- To control the running of the computation, this function also takes an event after which all changes are disregarded (can be provided by 'getUnrealize' to tie this ti widget lifetimes) and returns a boolean Behavior which indicated if a new result is currently pending.
mapBAsync :: (Eq a) => Event () -> b -> (a -> b) -> Behavior a -> Now (Behavior b, Behavior Bool)
mapBAsync stop yinit f xb = do
    xinit <- sample xb
    let xchanged = toChanges xb `beforeEs` stop
    (pendingSet, setPending) <- callbackStream
    (yset, sety) <- callbackStream
    pending <- sample $ fromChanges True (merge (False <$ yset) pendingSet)
    q <- sync $ newMVar xinit
    flip callStream xchanged $ \xs -> let
        x = last xs
        in sync $ tryTakeMVar q >> putMVar q x
    sync . forkIO . forever $ do
        x <- takeMVar q
        setPending True
        y <- evaluate (f x)
        sety y
        setPending False
    yb <- sample $ fromChanges yinit yset
    return (yb, pending)

-- | Filter an event so that it only resolves if it does so before a cutoff event.
beforeE :: Event a -> Event () -> Behavior (Event a)
beforeE ev cutoff = fmap join $ first (never <$ cutoff) (pure <$> ev)

-- | Disable a widget when the condition is true. Does not check the initial state iof the condition and assumes it to be False initially. This isso it can be used inside 'mfix' where the condition has not been defined yet..
setLockedFuturistic :: (WidgetClass w) => w -> Behavior Bool -> Now ()
setLockedFuturistic w lock = do
    done <- getUnrealize w
    let lockchange = toChanges lock `beforeEs` done
    callIOStream (widgetSetSensitive w . not) lockchange

-- | Run a FileChooserDialog without blocking and return an Evemnt containing the resulting selected path.
runFileChooserDialog :: FileChooserDialog -> Now (Event (Maybe FilePath))
runFileChooserDialog dialog = do
    (retev, cb) <- callback
    sync $ mdo
        conn <- on dialog response $ \resp -> do
            widgetHide dialog
            case resp of
                ResponseAccept -> do
                    mfn <- fileChooserGetFilename dialog
                    cb mfn
                _ -> cb Nothing
            signalDisconnect conn
        widgetShow dialog
    return retev

-- | Check any 'IOError's returned by the action so that they result in a 'Nothing' value.
checkIOError :: IO a -> IO (Maybe a)
checkIOError action = catch (fmap Just action) handler where
    handler :: IOError -> IO (Maybe a)
    handler _ = return Nothing

-- | Add CSS classes to a widget.
widgetAddClasses :: (MonadIO m, WidgetClass widget) => [Text] -> widget -> m widget
widgetAddClasses cs w = liftIO $ do
    sc <- widgetGetStyleContext w
    forM_ cs $ \c -> styleContextAddClass sc c
    return w