happlets-0.1.0.0: src/Happlets/Config.hs
-- | Methods and data types for working with the configuration. The Happlets configuration was
-- originally designed to be loaded from an INI file, but this functionality has since been moved
-- into a separate package. However many of the primitives for loading a configuration file from
-- disk are still provided here so that they are available universally to all Happlets back-end
-- providers.
module Happlets.Config
( Config(..),
ConfigException(..), StrictFilePath,
sanitizeName, configErrorsOnLoad, clearConfigErrors, configFilePath,
registeredAppName, windowTitleBar, backgroundTransparency, backgroundGreyValue,
recommendWindowPosition, recommendWindowSize, animationFrameRate,
deleteWindowOnClose, quitOnWindowClose, decorateWindow,
)
where
import Control.Arrow
import Control.Exception
import Control.Lens
import Data.Char (isPrint)
import Data.List (groupBy)
import qualified Data.Text as Strict
----------------------------------------------------------------------------------------------------
type StrictFilePath = Strict.Text
data ConfigException
= ConfigErrorIO StrictFilePath IOException
| ConfigErrorIniParseFail StrictFilePath Strict.Text
| ConfigErrorIniStructure StrictFilePath [Strict.Text]
| ConfigError Strict.Text
| ConfigMultipleErrors Strict.Text [ConfigException]
deriving (Eq, Show)
instance Exception ConfigException where {}
----------------------------------------------------------------------------------------------------
-- | This is the data structure used to configure various parameters in the Happlet back-end
-- provider.
data Config
= Config
{ theConfigErrorsOnLoad :: [ConfigException]
, theConfigFilePath :: StrictFilePath
, theRegisteredAppName :: Strict.Text
, theWindowTitleBar :: Strict.Text
, theBackgroundTransparency :: (Maybe Double)
, theBackgroundGreyValue :: Double
, theRecommendWindowPosition :: (Int, Int)
, theRecommendWindowSize :: (Int, Int)
, theAnimationFrameRate :: Double
, willDecorateWindow :: Bool
, willQuitOnWindowClose :: Bool
, willDeleteWindowOnClose :: Bool
}
deriving (Eq, Show)
-- | A string sanitizer for applied to strings assigned to 'registeredAppName' and 'windowTitleBar'.
sanitizeName :: Strict.Text -> Strict.Text
sanitizeName = let sp = ' ' in Strict.unpack >>>
takeWhile (\ c -> c /= '\n' && c /= '\r') >>> -- Take only the first line of text.
fmap (\ c -> if c == '\t' then sp else c) >>> -- Convert tabs to single-spaces.
filter isPrint >>> -- Filter out non-printing characters.
dropWhile (== sp) >>> -- Cut out leading spaces.
groupBy (\ a b -> a == sp && b == sp) >>> -- Group together spaces.
( let loop stk = \ case
[] -> stk ""
[' ':_] -> stk "" -- Cut out trailing spaces.
(' ':_):more -> loop (stk . (sp : )) more -- Replace multi spaces with single spaces.
txt :more -> loop (stk . (txt ++)) more
in loop id ) >>>
Strict.pack
-- | This is a list of errors that occurred while trying to load this configuration from the
-- filesystem.
configErrorsOnLoad :: Config -> [ConfigException]
configErrorsOnLoad = theConfigErrorsOnLoad
-- | The configuration errors are stored into the 'Config' data structure. This function erases the
-- errors.
clearConfigErrors :: Config -> Config
clearConfigErrors c = c{ theConfigErrorsOnLoad = [] }
-- | Reports the file path from which this config file was loaded, or a null string if this has not
-- been set.
configFilePath :: Config -> StrictFilePath
configFilePath = theConfigFilePath
-- | Gtk+ apps can register your GHCi/Happlet process with the window manager under a specific name,
-- which you can specify here. Using this lens to set a null name will not result in an error but
-- the update will not occur.
registeredAppName :: Lens' Config Strict.Text
registeredAppName = lens theRegisteredAppName $ \ b -> sanitizeName >>> \ a ->
if Strict.null a then b else b{ theRegisteredAppName = sanitizeName a }
-- | Most window managers depict a title bar above every window that displays a descriptive name of
-- the content of that window. Specify that name here. Using this lens to set a title name will not
-- result in an error but the update will not occur.
windowTitleBar :: Lens' Config Strict.Text
windowTitleBar = lens theWindowTitleBar $ \ b -> sanitizeName >>> \ a ->
if Strict.null a then b else b{ theWindowTitleBar = sanitizeName a }
-- | When the window is created, setting this parameter to a non-'Prelude.Nothing' value determines
-- whether an image buffer in memory is allocated with enough memory for an alpha channel, so that
-- the window background can be made partially or fully transparent, and also determines what the
-- default transparency value is, with 0.0 being fully transparent, and 1.0 being fully
-- opaque. However not all window managers will allow for windows with alpha channels, so this may
-- not work depending on how the GUI of your opreating system has been configured.
backgroundTransparency :: Lens' Config (Maybe Double)
backgroundTransparency = lens theBackgroundTransparency $ \ b a -> b{ theBackgroundTransparency=a }
-- | The default background "color", actually a gray value since no one in their right mind would
-- choose a non-grey solid color background for their applet. 0 is black, 1 is white, and everything
-- in between 0 and 1 are valid grey values.
backgroundGreyValue :: Lens' Config Double
backgroundGreyValue = lens theBackgroundGreyValue $ \ b a -> b{ theBackgroundGreyValue=a }
-- | Recommend to the window manager where to place the window. Pass @(0,0)@ to let the window
-- manager choose.
recommendWindowPosition :: Lens' Config (Int, Int)
recommendWindowPosition = lens theRecommendWindowPosition $ \ b a -> b{ theRecommendWindowPosition=a }
-- | Recommend to the window manager how large to make the window. Pass @(0,0)@ to let the
-- window manager choose.
recommendWindowSize :: Lens' Config (Int, Int)
recommendWindowSize = lens theRecommendWindowSize $ \ b a -> b{ theRecommendWindowSize=a }
-- | Recommend the frame rate to use when installing animation event handlers.
animationFrameRate :: Lens' Config Double
animationFrameRate = lens theAnimationFrameRate $ \ b a -> b{ theAnimationFrameRate=a }
-- | For window managers that allow windows to be created without a titlebar, close\/minimize\/hide
-- buttons, or other visible controls (on Linux systems, "reparenting" window managers will do
-- this), setting this value to 'Prelude.False' will tell the window manager to not draw these
-- decorations. Non-reparenting window managers like Xmonad are unable to draw a title bar, in which
-- case this parameter will have no effect, the Happlet window will behave as if this parameter is
-- always set to 'Prelude.True'. Other reparenting window managers may simply ignore this option, in
-- which case the Happlet window will behave as if this parameters is always set to 'Prelude.False'.
decorateWindow :: Lens' Config Bool
decorateWindow = lens willDecorateWindow $ \ b a -> b{ willDecorateWindow = a }
-- | If the Happlet window is closed, halt the Gtk+ event loop, returning control to the @main@
-- function. If the 'gtkLaunchEventLoop' is the final function called in @main@ the Haskell program
-- will end.
quitOnWindowClose :: Lens' Config Bool
quitOnWindowClose = lens willQuitOnWindowClose $ \ b a -> b{ willQuitOnWindowClose = a }
-- | If the Happlet window is closed, the image buffer allocated for it should be
-- deleted. Otherwise, the Happlet window is simply made invisible when it is closed, and the image
-- buffer is not deleted. Of course, if 'quitOnWindowClose' is set to 'Prelude.True', the entire
-- program may quit even if this parameter is set to 'Prelude.False'.
deleteWindowOnClose :: Lens' Config Bool
deleteWindowOnClose = lens willDeleteWindowOnClose $ \ b a -> b{ willDeleteWindowOnClose = a }