packages feed

hbro-1.7.0.0: library/Hbro/Gui/MainView.hs

{-# LANGUAGE ConstraintKinds   #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}
module Hbro.Gui.MainView
  ( MainView
  , scrollWindow_
  , webView_
  , downloadHandler_
  , keyPressedHandler_
  , linkClickedHandler_
  , linkHoveredHandler_
  , linkUnhoveredHandler_
  , loadCommittedHandler_
  , loadFailedHandler_
  , loadFinishedHandler_
  , loadRequestedHandler_
  , loadStartedHandler_
  , newWindowHandler_
  , progressChangedHandler_
  , scrolledHandler_
  , titleChangedHandler_
  , uriChangedHandler_
  , zoomLevelChangedHandler_
  , Axis(..)
  , Position(..)
  , getWebView
  , getWebSettings
  , getDOM
  , getAdjustment
  , Scrolled(..)
  , buildFrom
  , initialize
  , canRender
  , render
  , zoomIn
  , zoomOut
  , scrollH
  , scrollV
  ) where

-- {{{ Imports
import           Hbro.Error
import           Hbro.Event
import           Hbro.Gui.Builder
import           Hbro.Keys                                as Keys
import           Hbro.Logger
import           Hbro.Prelude
import           Hbro.WebView.Signals

import qualified Graphics.UI.Gtk.Abstract.Container       as Gtk
import           Graphics.UI.Gtk.Abstract.Widget
import qualified Graphics.UI.Gtk.Builder                  as Gtk
import           Graphics.UI.Gtk.General.General.Extended
import qualified Graphics.UI.Gtk.Misc.Adjustment          as Gtk
import           Graphics.UI.Gtk.Scrolling.ScrolledWindow
import           Graphics.UI.Gtk.WebKit.DOM.Document      hiding
                                                           (scroll)
import           Graphics.UI.Gtk.WebKit.Extended          hiding
                                                           (LoadStatus (..),
                                                           networkRequestGetUri)
import           Graphics.UI.Gtk.WebKit.NetworkRequest
import           Graphics.UI.Gtk.WebKit.WebPolicyDecision
import           Graphics.UI.Gtk.WebKit.WebSettings

import           Lens.Micro.Platform hiding(set)

import           Network.URI

import           System.Glib.Attributes.Extended
import           System.Glib.Signals                      hiding (Signal)
-- }}}

-- * Types
data Scrolled = Scrolled deriving(Show)
instance Event Scrolled where
  describeInput _ _ = Just "Scrolled"

data MainView = MainView
  { _scrollWindow            :: ScrolledWindow  -- ^ 'ScrolledWindow' containing the webview
  , _webView                 :: WebView
  , _downloadHandler         :: Signal Download
  , _keyPressedHandler       :: Signal KeyPressed
  , _linkClickedHandler      :: Signal LinkClicked
  , _linkHoveredHandler      :: Signal LinkHovered
  , _linkUnhoveredHandler    :: Signal LinkUnhovered
  , _loadCommittedHandler    :: Signal LoadCommitted
  , _loadFailedHandler       :: Signal LoadFailed
  , _loadFinishedHandler     :: Signal LoadFinished
  , _loadRequestedHandler    :: Signal LoadRequested
  , _loadStartedHandler      :: Signal LoadStarted
  , _newWindowHandler        :: Signal NewWindow
  , _progressChangedHandler  :: Signal ProgressChanged
  -- , resourceOpenedHandler   :: Signal ResourceOpened
  , _scrolledHandler         :: Signal Scrolled
  , _titleChangedHandler     :: Signal TitleChanged
  , _uriChangedHandler       :: Signal URIChanged
  , _zoomLevelChangedHandler :: Signal ZoomLevelChanged
  }


makeLensesWith (lensRules & lensField .~ lensGen) ''MainView


-- * Commonly used getters
getWebView :: (MonadReader r m, Has MainView r) => m WebView
getWebView = asks $ view webView_

getWebSettings :: (MonadIO m, MonadReader r m, Has MainView r) => m WebSettings
getWebSettings = gSync . webViewGetWebSettings =<< asks (view webView_)

getDOM :: (MonadIO m, MonadReader r m, Has MainView r) => m (Maybe Document)
getDOM = gSync . webViewGetDomDocument =<< asks (view webView_)

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


-- * Others
data Axis     = Horizontal | Vertical deriving(Show)
data Position = Absolute Double | Relative Double deriving(Show)

buildFrom :: (BaseIO m) => Gtk.Builder -> m MainView
buildFrom builder = do
    sWindow <- getWidget builder "webViewParent"
    webView <- gSync webViewNew

    gAsync $ Gtk.containerAdd sWindow webView
    MainView <$> pure sWindow
             <*> pure webView
             <*> newSignal Download
             <*> newSignal KeyPressed
             <*> newSignal LinkClicked
             <*> newSignal LinkHovered
             <*> newSignal LinkUnhovered
             <*> newSignal LoadCommitted
             <*> newSignal LoadFailed
             <*> newSignal LoadFinished
             <*> newSignal LoadRequested
             <*> newSignal LoadStarted
             <*> newSignal NewWindow
             <*> newSignal ProgressChanged
             -- <*> newSignal ResourceOpened
             <*> newSignal Scrolled
             <*> newSignal TitleChanged
             <*> newSignal URIChanged
             <*> newSignal ZoomLevelChanged


initialize :: (ControlIO m, MonadCatch m, MonadLogger m) => MainView -> m MainView
initialize mainView = do
  set webView widgetCanDefault True
  -- set webView webViewSetMaintainsBackForwardList False
  gAsync . on webView closeWebView $ gAsync mainQuit >> return False
  gAsync . on webView consoleMessage $ \a b n c -> do
      putStrLn "console message"
      putStrLn $ unlines [a, b, show n, c]
      return True

  gAsync . on webView mimeTypePolicyDecisionRequested $ \_frame request mimetype decision -> io $ do
    uri <- networkRequestGetUri request :: IO (Maybe Text)
    -- debug $ "Opening resource [MIME type=" ++ mimetype ++ "] at <" ++ tshow uri ++ ">"
    renderable <- webViewCanShowMimeType webView (mimetype :: Text)
    case (uri, renderable) of
      (Just _, True) -> webPolicyDecisionUse decision
      (Just _, _) -> webPolicyDecisionDownload decision
      _ -> webPolicyDecisionIgnore decision
    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)

  attachDownload          webView  $ mainView^.downloadHandler_
  attachKeyPressed        webView  $ mainView^.keyPressedHandler_
  attachLinkHovered       webView (mainView^.linkHoveredHandler_) (mainView^.linkUnhoveredHandler_)
  attachLoadCommitted     webView  $ mainView^.loadCommittedHandler_
  attachLoadFailed        webView  $ mainView^.loadFailedHandler_
  attachLoadFinished      webView  $ mainView^.loadFinishedHandler_
  attachLoadStarted       webView  $ mainView^.loadStartedHandler_
  attachNavigationRequest webView (mainView^.linkClickedHandler_) (mainView^.loadRequestedHandler_)
  attachNewWebView        webView  $ mainView^.newWindowHandler_
  attachNewWindow         webView  $ mainView^.newWindowHandler_
  attachProgressChanged   webView  $ mainView^.progressChangedHandler_
  -- attachResourceOpened    webView (mainView^.resourceOpenedHandler)
  attachScrolled          mainView $ mainView^.scrolledHandler_
  attachTitleChanged      webView  $ mainView^.titleChangedHandler_
  attachUriChanged        webView  $ mainView^.uriChangedHandler_
  attachZoomLevelChanged  webView  $ mainView^.zoomLevelChangedHandler_

  initSettings webView

  return mainView
  where webView = mainView^.webView_

canRender :: (MonadIO m, MonadReader r m, Has MainView r) => Text -> m Bool
canRender mimetype = gSync . (`webViewCanShowMimeType` mimetype) =<< asks (view webView_)


render :: (MonadReader r m, Has MainView r, MonadIO m, MonadLogger m) => Text -> URI -> m ()
render page uri = do
    debug $ "Rendering <" <> show uri <> ">"
    -- loadString page uri =<< get' webView_

    -- debug $ "Base URI: " ++ show (baseOf uri)

    loadString page (baseOf uri) =<< asks (view webView_)
  where
    baseOf uri' = uri' {
      uriPath = (`snoc` '/') . ointercalate "/" . initSafe . splitElem '/' $ uriPath uri'
    }


-- | Set default settings
initSettings :: (MonadIO m, MonadLogger m) => WebView -> m WebView
initSettings webView = do
    s <- gSync $ webViewGetWebSettings webView

    set s webSettingsAutoLoadImages                    True
    set s webSettingsAutoShrinkImages                  True
    set s webSettingsEnableDefaultContextMenu          True
    set s webSettingsDefaultEncoding                   ("utf8" :: Text)
    set s webSettingsEnableDeveloperExtras             False
    set s webSettingsEnableDomPaste                    False
    set s webSettingsEnableHtml5Database               False
    set s webSettingsEnableHtml5LocalStorage           False
    set s webSettingsEnableOfflineWebApplicationCache  False
    set s webSettingsEnablePageCache                   True
    set s webSettingsEnablePlugins                     False
    set s webSettingsEnablePrivateBrowsing             False
    set s webSettingsEnableScripts                     True
    set s webSettingsEnableSpellChecking               False
    set s webSettingsEnableSpatialNavigation           False
    set s webSettingsEnableUniversalAccessFromFileUris True
    set s webSettingsEnableSiteSpecificQuirks          False
    set s webSettingsEnableXssAuditor                  False
    set s webSettingsJSCanOpenWindowAuto               False
    set s webSettingsMonospaceFontFamily               ("inconsolata" :: Text)
    set s webSettingsPrintBackgrounds                  True
    set s webSettingsResizableTextAreas                True
    set s webSettingsSpellCheckingLang                 (Nothing :: Maybe Text)
    set s webSettingsTabKeyCyclesThroughElements       True
    set s webSettingsUserStylesheetUri                 (Nothing :: Maybe Text)
    set s webSettingsZoomStep                          0.1

    return webView


zoomIn, zoomOut :: (MonadIO m, MonadReader r m, Has MainView r) => m ()
zoomIn  = getWebView >>= gAsync . webViewZoomIn
zoomOut = getWebView >>= gAsync . webViewZoomOut

-- | Shortcut to 'scroll' horizontally or vertically.
scrollH, scrollV :: (MonadIO m, MonadLogger m, MonadReader r m, Has MainView r) => Position -> m ()
scrollH p = void . scroll Horizontal p =<< ask
scrollV p = void . scroll Vertical p =<< ask

-- | General scrolling command
scroll :: (MonadIO m, MonadLogger m) => Axis -> Position -> MainView -> m MainView
scroll axis percentage mainView = do
     debug $ "Set scroll " <> show axis <> " = " <> show percentage

     adj     <- getAdjustment axis $ mainView^.scrollWindow_
     page    <- get adj Gtk.adjustmentPageSize
     current <- get adj Gtk.adjustmentValue
     lower   <- get adj Gtk.adjustmentLower
     upper   <- get adj Gtk.adjustmentUpper

     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)

     set adj Gtk.adjustmentValue $ limit (shift percentage)
     return mainView


attachScrolled :: (ControlIO m, MonadLogger m) => MainView -> Signal Scrolled -> m (ConnectId Gtk.Adjustment)
attachScrolled mainView signal = do
  adjustment <- getAdjustment Vertical $ mainView^.scrollWindow_
  liftBaseWith $ \runInIO -> gSync . Gtk.onValueChanged adjustment . void . runInIO $ emit signal ()