hbro-1.4.0.1: 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
, box_
, closed_
, buildFrom
, labelName
, entryName
, boxName
, PromptException(..)
-- * 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.Maybe
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
{ box_ :: HBox
, description_ :: Label
, entry_ :: Entry
, changed_ :: Signal Changed
, closed_ :: Signal Closed
, validated_ :: Signal Validated
}
|]
data PromptException = PromptInterrupted deriving(Eq)
instance Exception PromptException
instance Show PromptException where
show PromptInterrupted = "Prompt interrupted."
-- }}}
-- | 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^.changed_)
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"
initialize :: (MonadIO m) => PromptBar -> m PromptBar
initialize =
withM_ description_ (gAsync . (`labelSetAttributes` [allItalic, allBold]))
>=> withM_ description_ (gAsync . (`labelSetAttributes` [AttrForeground {paStart = 0, paEnd = -1, paColor = gray}]))
>=> withM_ entry_ (gAsync . (\e -> widgetModifyBase e StateNormal black))
>=> withM_ entry_ (gAsync . (\e -> widgetModifyText e StateNormal gray))
open :: (MonadIO m) => Text -> Text -> PromptBar -> m PromptBar
open description defaultText =
withM_ description_ (gAsync . (`labelSetText` description))
>=> withM_ entry_ (gAsync . (`entrySetText` defaultText))
>=> withM_ box_ (gAsync . widgetShow)
>=> withM_ entry_ (gAsync . widgetGrabFocus)
>=> withM_ entry_ (gAsync . (`editableSetPosition` (-1)))
close :: (ControlIO m, MonadLogger m) => PromptBar -> m PromptBar
close promptBar = do
runMaybeT $ do
guard =<< get (promptBar^.box_) widgetVisible
emit (promptBar^.closed_) ()
gAsync . widgetHide $ promptBar^.box_
void $ clean promptBar
return promptBar
-- | Close prompt, that is: clean its content, signals and callbacks
clean :: (ControlIO m) => PromptBar -> m PromptBar
clean = withM_ entry_ (gAsync . (`widgetRestoreText` StateNormal))
>=> withM_ entry_ (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, MonadThrow 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^.closed_
validation <- listenTo $ promptBar^.validated_
result <- io $ waitEitherCancel cancelation validation
close promptBar
maybe (throwM PromptInterrupted) return . join $ hush result
promptM :: (ControlIO m, MonadReader r m, Has PromptBar r, MonadLogger m, MonadThrow m)
=> Text -> Text -> m Text
promptM a b = prompt a b =<< ask
iprompt :: (ControlIO m, MonadLogger m, MonadThrow m, MonadResource m)
=> Text
-> Text
-> (Text -> m ())
-> PromptBar
-> m ()
iprompt description startValue f promptBar = do
clean promptBar
update <- addHandler (promptBar^.changed_) f
open description startValue promptBar
io . wait =<< listenTo (promptBar^.closed_)
close promptBar
release update
ipromptM :: (ControlIO m, MonadResource m, MonadReader r m, Has PromptBar r, MonadLogger m, MonadThrow 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, MonadThrow m, MonadResource m)
=> Text
-> Text
-> PromptBar
-> m URI
uriPrompt description startValue promptBar = do
clean promptBar
update <- addHandler (promptBar^.changed_) $ checkURI promptBar
open description startValue promptBar
validation <- listenTo $ promptBar^.validated_
cancelation <- listenTo $ promptBar^.closed_
result <- io $ waitEitherCancel cancelation validation
release update
close promptBar
parseURIReference =<< maybe (throwM PromptInterrupted) return (join $ hush result)
uriPromptM :: (ControlIO m, MonadReader r m, Has PromptBar r, MonadLogger m, MonadThrow 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^.entry_) StateNormal (if isURIReference (unpack v) then green else red)
getPromptValue :: (MonadIO m) => PromptBar -> m Text
getPromptValue = gSync . entryGetText . view entry_
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