hbro-1.3.0.0: library/Hbro/Gui/PromptBar.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Designed to be imported as @qualified@.
module Hbro.Gui.PromptBar (
-- * Types
PromptBar
, boxL
, closedL
, buildFrom
, labelName
, entryName
, boxName
-- * Functions
, initialize
, close
, Hbro.Gui.PromptBar.clean
-- * Prompts
, prompt
, uriPrompt
, iprompt
, getPromptValue
-- * Monadic versions
, promptM
, uriPromptM
, ipromptM
, getPromptValueM
) where
-- {{{ Imports
import Hbro.Error
import Hbro.Event
import Hbro.Gdk.KeyVal
import Hbro.Gui.Builder
import Hbro.Logger
import Hbro.Prelude hiding (on)
import Control.Concurrent.Async.Lifted
import Control.Lens.Getter
import Control.Lens.TH
import Control.Monad.Trans.Resource
import Graphics.Rendering.Pango.Extended
import Graphics.UI.Gtk.Abstract.Widget
import qualified Graphics.UI.Gtk.Builder as Gtk
import Graphics.UI.Gtk.Display.Label
import Graphics.UI.Gtk.Entry.Editable
import Graphics.UI.Gtk.Entry.Entry
import Graphics.UI.Gtk.Gdk.EventM as Gdk
import Graphics.UI.Gtk.General.General.Extended
import Graphics.UI.Gtk.Layout.HBox
import Network.URI.Extended
import System.Glib.Attributes.Extended
import System.Glib.Signals hiding (Signal)
-- }}}
-- {{{ Types
data Closed = Closed deriving(Show)
instance Event Closed where
describeInput _ _ = Just "Prompt closed."
data Changed = Changed deriving(Show)
instance Event Changed where
type Input Changed = Text
describeInput _ = Just . (++) "Prompt value changed to: "
data Validated = Validated deriving(Show)
instance Event Validated where
type Input Validated = Text
describeInput _ = Just . (++) "Prompt validated with value: "
-- | No exported constructor, please use 'buildFrom'
declareLenses [d|
data PromptBar = PromptBar
{ boxL :: HBox
, descriptionL :: Label
, entryL :: Entry
, changedL :: Signal Changed
, closedL :: Signal Closed
, validatedL :: Signal Validated
}
|]
-- }}}
-- | A 'PromptBar' can be built from an XML file.
buildFrom :: (ControlIO m, MonadLogger m, Applicative m) => Gtk.Builder -> m PromptBar
buildFrom builder = do
entry <- getWidget builder entryName
closedSignal <- newSignal Closed
validated <- newSignal Validated
promptBar <- PromptBar <$> getWidget builder boxName
<*> getWidget builder labelName
<*> pure entry
<*> newSignal Changed
<*> pure closedSignal
<*> pure validated
onEntryChanged entry $ emit (promptBar^.changedL)
onEntryCanceled entry . async $ close promptBar
onEntryValidated entry $ emit validated
return promptBar
-- | Widget name used in the XML file that describes the UI
labelName, entryName, boxName :: Text
labelName = "promptDescription"
entryName = "promptEntry"
boxName = "promptBox"
-- | Error message
promptInterrupted :: Text
promptInterrupted = "Prompt interrupted."
initialize :: (MonadIO m) => PromptBar -> m PromptBar
initialize =
withM_ descriptionL (gAsync . (`labelSetAttributes` [allItalic, allBold]))
>=> withM_ descriptionL (gAsync . (`labelSetAttributes` [AttrForeground {paStart = 0, paEnd = -1, paColor = gray}]))
>=> withM_ entryL (gAsync . (\e -> widgetModifyBase e StateNormal black))
>=> withM_ entryL (gAsync . (\e -> widgetModifyText e StateNormal gray))
open :: (MonadIO m) => Text -> Text -> PromptBar -> m PromptBar
open description defaultText =
withM_ descriptionL (gAsync . (`labelSetText` description))
>=> withM_ entryL (gAsync . (`entrySetText` defaultText))
>=> withM_ boxL (gAsync . widgetShow)
>=> withM_ entryL (gAsync . widgetGrabFocus)
>=> withM_ entryL (gAsync . (`editableSetPosition` (-1)))
close :: (ControlIO m, MonadLogger m) => PromptBar -> m PromptBar
close promptBar = do
runFailT $ do
guard =<< get (promptBar^.boxL) widgetVisible
emit (promptBar^.closedL) ()
gAsync . widgetHide $ promptBar^.boxL
void $ clean promptBar
return promptBar
-- | Close prompt, that is: clean its content, signals and callbacks
clean :: (ControlIO m) => PromptBar -> m PromptBar
clean = withM_ entryL (gAsync . (`widgetRestoreText` StateNormal))
>=> withM_ entryL (gAsync . (\e -> widgetModifyText e StateNormal gray))
-- {{{ Prompts
-- | Open prompt bar with given description and default value,
-- register a callback to trigger when value is changed, and another one when value is validated.
prompt :: (ControlIO m, MonadLogger m, MonadError Text m)
=> Text -- ^ Prompt description
-> Text -- ^ Pre-fill value
-> PromptBar
-> m Text
prompt description startValue promptBar = do
clean promptBar
open description startValue promptBar
cancelation <- listenTo $ promptBar^.closedL
validation <- listenTo $ promptBar^.validatedL
result <- io $ waitEitherCancel cancelation validation
close promptBar
maybe (throwError promptInterrupted) return . join $ hush result
promptM :: (ControlIO m, MonadReader r m, Has PromptBar r, MonadLogger m, MonadError Text m) => Text -> Text -> m Text
promptM a b = prompt a b =<< ask
iprompt :: (ControlIO m, MonadLogger m, MonadError Text m, MonadResource m)
=> Text
-> Text
-> (Text -> m ())
-> PromptBar
-> m ()
iprompt description startValue f promptBar = do
clean promptBar
update <- addHandler (promptBar^.changedL) f
open description startValue promptBar
io . wait =<< listenTo (promptBar^.closedL)
close promptBar
release update
ipromptM :: (ControlIO m, MonadResource m, MonadReader r m, Has PromptBar r, MonadLogger m, MonadError Text m) => Text -> Text -> (Text -> m ()) -> m ()
ipromptM a b c = iprompt a b c =<< ask
-- | Same as 'prompt' for URI values
uriPrompt :: (ControlIO m, MonadLogger m, MonadError Text m, MonadResource m)
=> Text
-> Text
-> PromptBar
-> m URI
uriPrompt description startValue promptBar = do
clean promptBar
update <- addHandler (promptBar^.changedL) $ checkURI promptBar
open description startValue promptBar
validation <- listenTo $ promptBar^.validatedL
cancelation <- listenTo $ promptBar^.closedL
result <- io $ waitEitherCancel cancelation validation
release update
close promptBar
parseURIReferenceM =<< maybe (throwError promptInterrupted) return (join $ hush result)
uriPromptM :: (ControlIO m, MonadReader r m, Has PromptBar r, MonadLogger m, MonadError Text m, MonadResource m)
=> Text -> Text -> m URI
uriPromptM a b = uriPrompt a b =<< ask
checkURI :: (MonadIO m, MonadLogger m) => PromptBar -> Text -> m ()
checkURI promptBar v = do
debug $ "Is URI ? " ++ tshow (isURIReference $ unpack v)
gAsync $ widgetModifyText (promptBar^.entryL) StateNormal (green <| isURIReference (unpack v) |> red)
getPromptValue :: (MonadIO m) => PromptBar -> m Text
getPromptValue = gSync . entryGetText . view entryL
getPromptValueM :: (MonadIO m, MonadReader r m, Has PromptBar r) => m Text
getPromptValueM = getPromptValue =<< ask
onEntryCanceled :: (ControlIO m, MonadLogger m, EntryClass t) => t -> m a -> m ()
onEntryCanceled entry f = liftBaseWith $ \runInIO -> gAsync . on entry keyPressEvent $ do
key <- KeyVal <$> eventKeyVal
io . when (key == _Escape) . void . runInIO $ void f
return False
onEntryChanged :: (ControlIO m, MonadLogger m, EditableClass t, EntryClass t) => t -> (Text -> m ()) -> m ()
onEntryChanged entry f = liftBaseWith $ \runInIO -> gAsync . on entry editableChanged $ do
value <- entryGetText entry
void . runInIO $ f value
onEntryValidated :: (ControlIO m, MonadLogger m, EntryClass t) => t -> (Text -> m ()) -> m ()
onEntryValidated entry f = liftBaseWith $ \runInIO -> gAsync . on entry entryActivated $ do
value <- entryGetText entry
void . runInIO $ f value