packages feed

hbro-1.2.0.0: library/Hbro/Gui.hs

{-# LANGUAGE TemplateHaskell, ViewPatterns #-}
module Hbro.Gui (
-- * Types
      GUI
    , HasGUI(..)
-- * Basic
    , get
    , initialize
-- * Getter
    , canRender
    , isSourceMode
    , getDOM
-- * Actions
    , toggle
    , render
    , scroll
    , scrollH
    , scrollV
    , setSourceMode
    , toggleSourceMode
    , zoomIn
    , zoomOut
-- * Misc
    , Axis(..)
    , Position(..)
    , getObject
) where

-- {{{ Imports
import Graphics.UI.Gtk.WebKit.Lifted.WebView

import Hbro.Error as Hbro
import Hbro.Gui.Buildable
import Hbro.Gui.NotificationBar (HasNotificationBar(..))
import qualified Hbro.Gui.NotificationBar as NotifBar
import Hbro.Gui.PromptBar (PromptBar, HasPromptBar(..))
import qualified Hbro.Gui.PromptBar as Prompt
import Hbro.Gui.PromptBar.Signals as Prompt
import Hbro.Gui.StatusBar
import Hbro.Logger hiding(initialize)
import Hbro.Prelude hiding(on)

import Control.Lens.Getter
import Control.Lens.Lens
import Control.Lens.Setter
import Control.Lens.TH
import Control.Monad.Reader hiding(join, mapM_, when)

import Data.Text (splitOn)

import Graphics.Rendering.Pango.Enums
import Graphics.UI.Gtk.Abstract.Container
import Graphics.UI.Gtk.Abstract.Widget
import qualified Graphics.UI.Gtk.Builder as Gtk
import Graphics.UI.Gtk.General.General as GTK
import Graphics.UI.Gtk.Misc.Adjustment
import Graphics.UI.Gtk.Scrolling.ScrolledWindow
import Graphics.UI.Gtk.WebKit.DOM.Document
import Graphics.UI.Gtk.Windows.Window

import Network.URI as N

import System.Glib.Attributes hiding(get, set)
import qualified System.Glib.Attributes as G (get, set)
-- import System.Glib.GError
import System.Glib.Signals
import System.Glib.Types
-- }}}

-- {{{ Types
data Axis     = Horizontal | Vertical deriving(Show)
data Position = Absolute Double | Relative Double deriving(Show)

data GUI = GUI
    { _mainWindow      :: Window
    , _scrollWindow    :: ScrolledWindow  -- ^ 'ScrolledWindow' containing the webview
    , _webView         :: WebView
    , _promptBar       :: PromptBar
    , _statusBar       :: StatusBar
    , _notificationBar :: NotifBar.NotificationBar
    , _builder         :: Gtk.Builder          -- ^ Builder object created from XML file
    }

makeLensesWith ?? ''GUI $ classyRules
    & lensField .~ (\name -> Just (tail name ++ "L"))
    & lensClass .~ (\name -> Just ("Has" ++ name, "_" ++ toLower name))

-- | A 'GUI' can be built from an XML file.
instance Buildable GUI where
    buildWith b = do
        sWindow <- gSync $ Gtk.builderGetObject b castToScrolledWindow (asText "webViewParent")
        webView <- gSync webViewNew

        gAsync $ containerAdd sWindow webView

        GUI <$> gSync (Gtk.builderGetObject b castToWindow (asText "mainWindow"))
            <*> pure sWindow
            <*> pure webView
            <*> buildWith b
            <*> buildWith b
            <*> buildWith b
            <*> pure b

instance HasNotificationBar GUI where _notificationbar = notificationBarL
instance HasPromptBar GUI       where _promptbar       = promptBarL
-- }}}

get :: (MonadReader r m, BaseIO m, HasGUI r) => Lens' GUI a -> m a
get l = askL (_gui.l)


-- {{{ Initialization
initialize :: (BaseIO m, MonadError Text m, MonadPlus m) => FilePath -> m GUI
initialize file = do
    gui <- buildFrom file

    let webView = gui^.webViewL

    initializeWindow    $ gui^.mainWindowL
    initializeWebView   webView
    Prompt.initialize   $ gui^.promptBarL
    NotifBar.initialize $ gui^.notificationBarL

    gAsync . widgetShowAll $ gui^.mainWindowL
    runReaderT Prompt.hide (gui^.promptBarL)

    gAsync $ windowSetDefault (gui^.mainWindowL) (Just $ gui^.webViewL)

    let closePrompt = widgetHide (gui^.promptBarL.boxL) >> widgetGrabFocus webView >> return ()
    onEntryCancelled (gui^.promptBarL.entryL) $ const closePrompt
    onEntryActivated (gui^.promptBarL.entryL) $ const closePrompt

    -- io $ scrolledWindowSetPolicy (gui^.scrollWindowL) PolicyNever PolicyNever
    -- io $ G.set (gui^.scrollWindowL) [ scrolledWindowHscrollbarPolicy := PolicyNever, scrolledWindowVscrollbarPolicy := PolicyNever]

    return gui

buildFrom :: (BaseIO m, MonadError Text m, Buildable t) => FilePath -> m t
buildFrom (fpToText -> uiFile) = do
    infoM "hbro.gui" $ "Building UI from: " ++ uiFile

    builder <- gSync Gtk.builderNew

    {-result <- -}
    gSync $ Gtk.builderAddFromFile builder (unpack uiFile)
    -- leftM throwError result

    buildWith builder

-- TODO: catch IOException
-- builderAddFromFile ::
-- builderAddFromFile builder file = catchGErrorJustDomain (Right <$> Gtk.builderAddFromFile builder file) handler
--   where
--     handler :: Gtk.BuilderError -> Text -> IO (Either Text a)
--     handler e message = return . Left $ "Error while building GUI from [" ++ file ++ "]: " ++ message

initializeWindow :: (BaseIO m) => Window -> m ()
initializeWindow window = gAsync $ do
    widgetModifyBg window StateNormal (Color 0 0 5000)
    void . on window deleteEvent $ gAsync GTK.mainQuit >> return False

initializeWebView :: (BaseIO m) => WebView -> m ()
initializeWebView webView = gAsync $ do
    G.set webView [ widgetCanDefault := True ]
    -- webViewSetMaintainsBackForwardList webView False
    on webView closeWebView $ gAsync GTK.mainQuit >> return False
    void . on webView consoleMessage $ \a b n c -> do
        putStrLn "console message"
        mapM_ putStrLn [a, b, tshow n, c]
        return True
    -- void . on webView resourceRequestStarting $ \frame resource request response -> do
    --     uri <- webResourceGetUri resource
    --     putStrLn $ "resource request starting: " ++ uri
    --     -- print =<< webResourceGetData resource
    --     putStrLn =<< (maybe (return "No request") (return . ("Request URI: " ++) . show <=< W.networkRequestGetUri) request)
    --     putStrLn =<< (maybe (return "No response") (return . ("Response URI: " ++) . show <=< networkResponseGetUri) response)

    --     -- case (endswith ".css" uri || uri `endswith` ".png" || uri `endswith` ".ico") of
    --        -- True -> (putStrLn "OK")
    --     (maybe (return ()) (`networkRequestSetUri` "about:blank") request)
-- }}}

-- {{{ Actions
-- | Toggle a widget's visibility
toggle :: (BaseIO m, WidgetClass a) => a -> m ()
toggle widget = do
    visibility <- gSync $ G.get widget widgetVisible
    gAsync $ (widgetHide <| visibility |> widgetShow) widget

canRender :: (BaseIO m, MonadReader t m, HasGUI t) => Text -> m Bool
canRender mimetype = gSync . (`webViewCanShowMimeType` mimetype) =<< get webViewL


render :: (MonadReader t m, HasGUI t, BaseIO m) => Text -> URI -> m ()
render page uri = do
    debugM "hbro.gui" $ "Rendering <" ++ tshow uri ++ ">"
    -- loadString page uri =<< get' webViewL

    -- debugM "hbro.gui" $ "Base URI: " ++ show (baseOf uri)

    loadString page (baseOf uri) =<< get webViewL
  where
    baseOf uri' = uri' {
        uriPath = unpack . (`snoc` '/') . intercalate "/" . initSafe . splitOn "/" . pack $ uriPath uri'
    }

-- | Shortcut to 'scroll' horizontally or vertically.
scrollH, scrollV :: (BaseIO m, MonadReader t m, HasGUI t) => Position -> m ()
scrollH = scroll Horizontal
scrollV = scroll Vertical

-- | Scroll WebView.
scroll :: (BaseIO m, MonadReader t m, HasGUI t) => Axis -> Position -> m ()
scroll axis percentage = scroll' axis percentage =<< get scrollWindowL

-- General scrolling command
scroll' :: (BaseIO m) => Axis -> Position -> ScrolledWindow -> m ()
scroll' axis percentage scrollWindow = do
     logDebug $ "Set scroll " ++ tshow axis ++ " = " ++ tshow percentage

     adj     <- gSync . getAdjustment axis $ scrollWindow
     page    <- gSync $ adjustmentGetPageSize adj
     current <- gSync $ adjustmentGetValue adj
     lower   <- gSync $ adjustmentGetLower adj
     upper   <- gSync $ adjustmentGetUpper adj

     let shift (Absolute x) = lower   + x/100 * (upper - page - lower)
         shift (Relative x) = current + x/100 * page
         limit x            = (x `max` lower) `min` (upper - page)

     gAsync . adjustmentSetValue adj $ limit (shift percentage)


getAdjustment :: (BaseIO m) => Axis -> ScrolledWindow -> m Adjustment
getAdjustment Horizontal = gSync . scrolledWindowGetHAdjustment
getAdjustment Vertical   = gSync . scrolledWindowGetVAdjustment


-- TODO: see if the lens system can be leveraged to get/set this property
-- sourceMode :: Lens

isSourceMode :: (BaseIO m, MonadReader t m, HasGUI t) => m Bool
isSourceMode = gSync . webViewGetViewSourceMode =<< get webViewL

setSourceMode :: (BaseIO m, MonadReader t m, HasGUI t) => Bool -> m ()
setSourceMode value = get webViewL >>= gAsync . (`webViewSetViewSourceMode` value) >> logDebug ("Set source mode = " ++ tshow value)

-- | Toggle source display. This needs to be done *before* loading an URI.
toggleSourceMode :: (BaseIO m, MonadReader t m, HasGUI t) => m ()
toggleSourceMode = setSourceMode . not =<< isSourceMode


zoomIn, zoomOut :: (BaseIO m, MonadReader t m, HasGUI t) => m ()
zoomIn  = get webViewL >>= gAsync . webViewZoomIn >> logDebug "Zooming in."
zoomOut = get webViewL >>= gAsync . webViewZoomOut >> logDebug "Zooming out."

getDOM :: (BaseIO m, MonadReader t m, HasGUI t) => m (Maybe Document)
getDOM = gSync . webViewGetDomDocument =<< get webViewL

-- | Return the casted 'GObject' corresponding to the given name (set in the builder's XML file)
getObject :: (BaseIO m, MonadReader t m, HasGUI t, GObjectClass a)
          => (GObject -> a)   -- ^ @castTo@ function
          -> Text           -- ^ Widget name
          -> m a
getObject cast name = do
    b <- get builderL
    gSync $ Gtk.builderGetObject b cast name
-- }}}


-- {{{ Util
logDebug{-, logInfo-} :: (BaseIO m) => Text -> m ()
logDebug = debugM "hbro.gui"
-- logInfo  = infoM  "hbro.gui"
-- }}}