spike (empty) → 0.1
raw patch · 11 files changed
+920/−0 lines, 11 filesdep +basedep +containersdep +directorysetup-changed
Dependencies added: base, containers, directory, filepath, gtk, mtl, process, rosezipper, stm, webkit
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- cbits/cbits.c +20/−0
- spike.cabal +26/−0
- src/CFunctions.hs +12/−0
- src/Datatypes.hs +32/−0
- src/Graphs.hs +28/−0
- src/NotebookSimple.hs +133/−0
- src/Utils.hs +85/−0
- src/VisualBrowseTree.hs +134/−0
- src/spike.hs +418/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Krzysztof Skrzętnicki++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Krzysztof Skrzętnicki nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cbits/cbits.c view
@@ -0,0 +1,20 @@+#include <libsoup/soup.h>+#include <libsoup/soup-gnome.h>+#include <webkit/webkit.h>++#include <gtk/gtk.h>+#include <glib/gi18n.h>+#include <libsoup/soup.h>+#include <webkit/webkit.h>++void spike_setup_webkit_globals(char * web_database_path, char * cookie_jar_path)+{+ //eg. /tmp/spike.webkit.db+ webkit_set_web_database_directory_path(web_database_path);++ + SoupSession * ses = webkit_get_default_session();+ //eg. /tmp/spike.webkit.cookie.db+ SoupCookieJar * jar = soup_cookie_jar_sqlite_new(cookie_jar_path, 0);+ soup_session_add_feature( ses, SOUP_SESSION_FEATURE (jar));+}
+ spike.cabal view
@@ -0,0 +1,26 @@+Name: spike+Version: 0.1+Synopsis: Experimental web browser+Description: Experimental web browser based on WebKit-Gtk++License: BSD3+License-file: LICENSE+Author: Krzysztof Skrzętnicki+Maintainer: gtener@gmail.com+Category: Web+Build-type: Simple+Cabal-version: >=1.6+Homepage: http://github.com/Tener/spike++Source-repository head+ type: git+ location: git://github.com/Tener/spike.git+++Executable spike+ Main-is: spike.hs+ Other-modules: Utils, VisualBrowseTree, Datatypes, Graphs, CFunctions, NotebookSimple+ Hs-source-dirs: src+ Build-depends: webkit == 0.12.*, containers, gtk, base == 4.*, stm, mtl > 2, rosezipper, process, directory, filepath+ Ghc-options: -rtsopts+ C-sources: cbits/cbits.c+ pkgconfig-depends: libsoup-gnome-2.4
+ src/CFunctions.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE PackageImports, FlexibleInstances, DoRec, ForeignFunctionInterface #-}++module CFunctions(spikeSetupWebkitGlobals) where++import Foreign+import Foreign.C++foreign import ccall "spike_setup_webkit_globals" spike_setup_webkit_globals :: CString -> CString -> IO ()++spikeSetupWebkitGlobals arg1 arg2 =+ withCString arg1 $ \ arg1' -> + withCString arg2 $ \ arg2' -> spike_setup_webkit_globals arg1' arg2'
+ src/Datatypes.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE PackageImports, FlexibleInstances, DoRec, ForeignFunctionInterface #-}+module Datatypes where++import Graphics.UI.Gtk.WebKit.WebView+import Graphics.UI.Gtk.WebKit.WebNavigationAction+import System.IO.Unsafe+import Graphics.UI.Gtk+import Control.Concurrent.STM+import Data.Tree++data History = Hist { hiNow :: String, hiPrev :: [String], hiNext :: [String] } deriving Show++data Page = Page { pgWeb :: WebView, pgWidget :: Widget, pgHistory :: TVar History }++instance Show Page where+ show pg = "Page { pgWeb=?, pgWidget=?, pgHistory=" ++ show (unsafeDupablePerformIO (readTVarIO (pgHistory pg))) ++ "}"++instance Eq Page where+ p1 == p2 = pgWidget p1 == pgWidget p2++type BrowseTree = Forest Page+type BrowseTreeState = TVar BrowseTree++-- wrapper newtype to avoid orphan instances+newtype MyShow a = MyShow a+instance Show (MyShow NavigationReason) where+ show (MyShow WebNavigationReasonLinkClicked ) = "WebNavigationReasonLinkClicked"+ show (MyShow WebNavigationReasonFormSubmitted ) = "WebNavigationReasonFormSubmitted"+ show (MyShow WebNavigationReasonBackForward ) = "WebNavigationReasonBackForward"+ show (MyShow WebNavigationReasonReload ) = "WebNavigationReasonReload"+ show (MyShow WebNavigationReasonFormResubmitted) = "WebNavigationReasonFormResubmitted"+ show (MyShow WebNavigationReasonOther ) = "WebNavigationReasonOther"
+ src/Graphs.hs view
@@ -0,0 +1,28 @@+module Graphs where++import Data.Tree+import Data.Tree.Zipper+import Data.Graph.Inductive++treeToGraph :: (Graph gr) => Tree a -> gr a ()+treeToGraph tr = labelTree tr [1..]++labelTree tr = let tr1 = fmap tr (\x -> (x,0))+ tp1 = fromTree tr1+ tp2 = go 0 tp1+ tr2 = toTree tp2+ ++ go ix tp = let tp' = modifyLabel (\(l,_) -> (l,ix)) tp in+ ++ go tp = case next tp of+ Nothing -> firstChild tp+ Just tpN -> go tpN++labelTree (Node a xs) (y:ys) = let (xs',ys') = labelTree' xs ys+ n' = Node (a,y) xs'+ in+ (n',xs')++labelTree' lst labs =
+ src/NotebookSimple.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE ViewPatterns #-}++-- this module is a simple reimplementation of GTK Notebook widget.+-- I needed to be able to change the list of pages without receving a signal from it.+module NotebookSimple where++import Graphics.UI.Gtk+import Control.Concurrent+import Control.Concurrent.STM++import Data.List (elemIndex)++import Utils+import Datatypes++data NotebookSimple = NotebookSimple { ns_tabs :: HBox+ , ns_widget :: Widget+ , ns_pages :: TVar [Page]+ , ns_currentPage :: TVar (Maybe Int)+ , ns_refresh :: IO ()+ }++newScrolledWindowWithViewPort :: WidgetClass child => child -> IO ScrolledWindow+newScrolledWindowWithViewPort child = do+ sw <- scrolledWindowNew Nothing Nothing+ scrolledWindowSetPolicy sw PolicyAutomatic PolicyAutomatic+ scrolledWindowAddWithViewport sw child+ (wx,wy) <- widgetGetSizeRequest child+ widgetSetSizeRequest sw (wx+5) (wy+5)+ return sw++notebookSimpleNew :: (Page -> IO ()) -> IO NotebookSimple+notebookSimpleNew focusOnPage = do+ buttonsBox <- hBoxNew False 1+ contentBox <- hBoxNew False 1+ vbox <- vBoxNew False 1++ widgetSetSizeRequest buttonsBox (-1) 40++ (\sw -> boxPackStart vbox sw PackNatural 1) =<< newScrolledWindowWithViewPort buttonsBox+ boxPackStart vbox contentBox PackGrow 1++ widgetShowAll vbox++ pgs <- newTVarIO [] :: IO (TVar [Page])+ curr <- newTVarIO Nothing++ let updateButtons = postGUIAsync $ do+ pages <- readTVarIO pgs+ listPagesBox focusOnPage pages buttonsBox ++ updatePage = do + wg <- getCurrentWidget+ case wg of+ Just wg' -> postGUIAsync (do+ containerForeach contentBox (containerRemove contentBox)+ set contentBox [ containerChild := (pgWidget wg') ] >> widgetShowAll vbox)+ Nothing -> print "strange stuff."++ getCurrentWidget = do+ c <- readTVarIO curr+ p <- readTVarIO pgs+ case c of+ Nothing -> return Nothing+ Just c' -> return (if c' < length p then Just (p !! c') else Nothing)++ let watchdog page = do+ page' <- waitTVarChangeFrom page curr+ updatePage+ watchdog page'++ let watchdog2 pages = do+ pages' <- waitTVarChangeFrom pages pgs+ updateButtons+ watchdog2 pages'++ forkIO (watchdog2 =<< readTVarIO pgs)+ forkIO (watchdog =<< readTVarIO curr)++ let updateAll = updateButtons >> updatePage+ return (NotebookSimple { ns_tabs = contentBox+ , ns_widget = (toWidget vbox)+ , ns_pages = pgs+ , ns_currentPage = curr+ , ns_refresh = updateAll })++notebookSimpleAddPage :: NotebookSimple -> Page -> IO ()+notebookSimpleAddPage ns@(ns_pages -> pages) page = do+ atomically $ do+ pages' <- readTVar pages+ writeTVar pages (pages' ++ [page])+ notebookSimpleSelectPageIfNone ns++notebookSimpleSelectPage :: NotebookSimple -> Page -> IO ()+notebookSimpleSelectPage ns page = do+ atomically $ do+ current <- readTVar (ns_currentPage ns)+ pages <- readTVar (ns_pages ns)+ writeTVar (ns_currentPage ns) (elemIndex page pages)+ +notebookSimpleSelectPageIfNone :: NotebookSimple -> IO ()+notebookSimpleSelectPageIfNone ns = do+ atomically $ do+ current <- readTVar (ns_currentPage ns)+ case current of+ Nothing -> writeTVar (ns_currentPage ns) (Just 0)+ Just _ -> return ()+++-- | list pages in a box+-- listPagesBox :: (BoxClass box) => [Page] -> box -> IO ()+listPagesBox :: BoxClass box => (Page -> IO a) -> [Page] -> box -> IO ()+listPagesBox focusOnPage pages box = do+ containerForeach box (containerRemove box)+ mapM_ (\p -> do+ title <- getPageTitle p+ l <- labelNew (Just title) -- TODO: use AccelLabel and shortcuts for specific pages+ labelSetEllipsize l EllipsizeEnd+ labelSetWidthChars l 20+ labelSetSingleLineMode l True++ b <- buttonNew+ containerAdd b l++ boxPackStart box b PackNatural 1++ on b buttonActivated $ do+ print ("SIGNAL: on focus", title)+ focusOnPage p+ return ()+ ) pages+ widgetShowAll box+ return ()
+ src/Utils.hs view
@@ -0,0 +1,85 @@+module Utils where++import Control.Concurrent.STM+import Graphics.UI.Gtk.WebKit.WebView+import Graphics.UI.Gtk+import Data.Tree+import Data.Maybe+import Data.Tree.Zipper++import Datatypes++-- | wait until the TVar changes it's value from specified outer value+waitTVarChangeFrom :: (Eq a) => a -> (TVar a) -> IO a+waitTVarChangeFrom t tv = do+ tNew <- atomically $ do+ t' <- readTVar tv+ if (t' /= t) then return t' else retry+ return tNew++-- | wait until the TVar changes it's value from it's current value+waitTVarChange :: (Eq a) => (TVar a) -> IO a+waitTVarChange tv = readTVarIO tv >>= flip waitTVarChangeFrom tv++-- | block for two TVars to change their value from specified tuple value+waitTVarChangePairFrom :: (Eq a,Eq b) => (a,b) -> ((TVar a),(TVar b)) -> IO (a,b)+waitTVarChangePairFrom (ta,tb) (tva,tvb) = do+ tt <- atomically $ do+ ta' <- readTVar tva+ tb' <- readTVar tvb+ let tt' = (ta',tb')+ if tt' /= (ta,tb) then return tt' else retry+ return tt++-- | block for two TVars to change their value from their current values+waitTVarChangePair :: (Eq a,Eq b) => ((TVar a),(TVar b)) -> IO (a,b)+waitTVarChangePair tt@(tva,tvb) = do+ ta <- readTVarIO tva+ tb <- readTVarIO tvb+ waitTVarChangePairFrom (ta,tb) tt++++dontReenter :: TVar Bool -> IO a -> IO ()+dontReenter var act = do+ a' <- atomically $ do+ v <- readTVar var+ if v + then return (return ()) + else writeTVar var True >> return (act >> atomically (writeTVar var False))+ a'++callDepthCount :: TVar Int -> (Int -> IO a) -> IO ()+callDepthCount var act = do+ v <- atomically $ do+ v' <- readTVar var+ writeTVar var (v'+1)+ return v'+ act v+ atomically $ writeTVar var v++----------------+++isWidgetPage :: Widget -> Page -> IO Bool+isWidgetPage w p = do+ return (pgWidget p == w)++isSamePage :: Page -> Page -> Bool+isSamePage p1 p2 = pgWidget p1 == pgWidget p2++getPageTitle :: Page -> IO String+getPageTitle p = do+ mtit <- webViewGetTitle (pgWeb p)+ h <- readTVarIO (pgHistory p)+ case mtit of+ Nothing -> return (hiNow h)+ Just t -> return t++flattenToZipper :: Tree a -> [TreePos Full a]+flattenToZipper n@(Node _ sub) = fromTree n : concatMap flattenToZipper sub+++flattenToZipper' :: Tree a -> [TreePos Full a]+flattenToZipper' n = go (fromTree n) where+ go z = [z] ++ (fromMaybe [] (fmap go (firstChild z))) ++ (fromMaybe [] (fmap go (next z)))
+ src/VisualBrowseTree.hs view
@@ -0,0 +1,134 @@+module VisualBrowseTree where++++import Graphics.UI.Gtk.WebKit.WebFrame+import Graphics.UI.Gtk.WebKit.WebView+import Graphics.UI.Gtk.WebKit.Download+import Graphics.UI.Gtk.WebKit.WebSettings+import Graphics.UI.Gtk.WebKit.NetworkRequest+import Graphics.UI.Gtk.WebKit.WebNavigationAction+-- import Graphics.UI.Gtk.WebKit.WebWindowFeatures++import System.IO.Unsafe+import System.Process+import System.Exit++import Graphics.UI.Gtk+import Text.Printf+import Control.Monad+import Control.Concurrent.STM+import Control.Concurrent++import qualified Data.Foldable as F+import qualified Data.Traversable as T+import Data.Tree.Zipper+import Data.Maybe+import qualified Data.List++import Utils+import NotebookSimple+import Datatypes++import Data.Tree as Tree+++browseTreeToSVG :: [Tree Page] -> IO String+browseTreeToSVG btree = do+ let ellipsis t n | length t < n = t+ | otherwise = take n t ++ "..."++ nodeID <- newTVarIO (0::Int)+ btree' <- mapM (T.mapM (\p -> do+ i <- atomically $ do+ iden <- readTVar nodeID+ writeTVar nodeID (iden+1)+ return iden+ t <- getPageTitle p+ return (i,t))) btree++ let prelude = unlines ["digraph \"Browse tree\" {",+ "graph [",+ "fontname = \"Helvetica-Oblique\",",+ "page = 10",+ "size = 30",+ " ];"]+ -- labels = unlines [ printf "d%d [label=\"%s\"];" i t | (i,t) <- concatMap flatten btree' ]+ footer = "}"++ btreeZip = concatMap flattenToZipper' btree' -- ellipsis t 15+ labels = unlines [ printf "d%d [URL=\"http://google.com\", shape=polygon, fixedsize=true, fontsize=8, width=1.25, height=0.25, tooltip=\"%s\", label=\"%s\"];"+ i t (ellipsis t 10) | (i,t) <- map label btreeZip ]+ edges = unlines [ printf "d%d -> d%d;" (fst . label . fromJust . parent $ z ) (fst . label $ z)+ | z <- btreeZip,+ parent z /= Nothing]++ edges2 = [ ((fst . label . fromJust . parent $ z ),(fst . label $ z))+ | z <- btreeZip,+ parent z /= Nothing]+++ everything = prelude ++ edges ++ labels ++ footer++ print everything+ print edges2+ tot@(code,svg,dotErr) <- readProcessWithExitCode "dot" ["-Tsvg"] everything+ -- _ <- readProcessWithExitCode "dot" ["-Tsvg","-ograph.svg"] everything+ -- _ <- readProcessWithExitCode "dot" ["-ograph.dot"] everything+ print tot+ case code of+ ExitSuccess -> return svg+ ExitFailure c -> return $ printf "<text>Error running 'dot' command. Exit code: %s\n%s</text>" (show c) dotErr+++-- visualBrowseTreeWidget :: t -> IO Widget+visualBrowseTreeWidget :: TVar [Tree Page] -> IO Widget+visualBrowseTreeWidget btreeVar = do+ -- webkit widget+ web <- webViewNew+ webViewSetTransparent web True+ webViewSetFullContentZoom web True++ -- scrolled window to enclose the webkit+ scrollWeb <- scrolledWindowNew Nothing Nothing+ containerAdd scrollWeb web++ settings <- webViewGetWebSettings web+ set settings [webSettingsEnablePlugins := False]++ let refreshSVG = do+ svg <- browseTreeToSVG =<< readTVarIO btreeVar+ webViewLoadString web svg (Just "image/svg+xml") Nothing ""++ on web navigationPolicyDecisionRequested $ \ webframe networkReq webNavAct webPolDec -> do+ print "[navigationPolicyDecisionRequested]"+ muri <- networkRequestGetUri networkReq+ case muri of+ Nothing -> return ()+ Just uri -> print ("visualBrowseTreeWidget",uri)++ return True++ -- watch btreeVar for changes, update+ let watchdog page = do+ page' <- waitTVarChangeFrom page btreeVar+ postGUIAsync refreshSVG+ watchdog page'+ forkIO (watchdog =<< readTVarIO btreeVar)+ forkIO (forever $ do+ threadDelay (10^6)+ postGUIAsync refreshSVG)++ refreshSVG+ return (toWidget scrollWeb)++visualBrowseTreeWindow btreeVar = do+ window <- windowNew+ visualBT <- visualBrowseTreeWidget btreeVar+ set window [ containerBorderWidth := 10,+ windowTitle := "Spike browser - visual browse tree",+ containerChild := visualBT,+ windowAllowGrow := True ]+ widgetShowAll window++ return window
+ src/spike.hs view
@@ -0,0 +1,418 @@+{-# LANGUAGE PackageImports, FlexibleInstances, DoRec, ForeignFunctionInterface #-}+module Main where++import Graphics.UI.Gtk.WebKit.WebFrame+import Graphics.UI.Gtk.WebKit.WebView+import Graphics.UI.Gtk.WebKit.Download+import Graphics.UI.Gtk.WebKit.WebSettings+import Graphics.UI.Gtk.WebKit.NetworkRequest+import Graphics.UI.Gtk.WebKit.WebNavigationAction++import System.IO.Unsafe+import System.Process+import System.Directory+import System.FilePath+import System.Exit++import Graphics.UI.Gtk+import Text.Printf+import Control.Monad+import Control.Concurrent.STM+import Control.Concurrent+import Control.Applicative+import Data.IORef++import qualified Data.Foldable as F+import qualified Data.Traversable as T+import Data.Tree.Zipper+import Data.Maybe+import qualified Data.List++import Utils+import NotebookSimple+import Datatypes+import VisualBrowseTree+import CFunctions++import Data.Tree as Tree++debugBTree :: [Tree Page] -> IO ()+debugBTree btree = do+ btree' <- mapM (T.mapM (\p -> fmap show $ getPageTitle p)) btree+ putStrLn (drawForest btree')+++-- operations on single page+-- | install listeners for various signals+hookupWebView :: WebViewClass object => object -> IO WebView -> IO ()+hookupWebView web createSubPage = do+ on web downloadRequested $ \ down -> do+ uri <- downloadGetUri down+ print ("Download uri",uri)+ return True++ let foo str webFr netReq webNavAct _webPolDec = do+ t0 <- return str+ t1 <- webFrameGetName webFr+ t2 <- webFrameGetUri webFr+ t3 <- networkRequestGetUri netReq+ t4 <- webNavigationActionGetReason webNavAct+ print (t0,t1,t2,t3,MyShow t4)+ return False++ on web newWindowPolicyDecisionRequested $ foo "newWindowPolicyDecisionRequested"+-- on web navigationPolicyDecisionRequested ...++ on web createWebView $ \ frame -> do+ print "createWebView"+ newWebView <- createSubPage+ newUri <- webFrameGetUri frame+ case newUri of+ Just uri -> webViewLoadUri newWebView uri+ Nothing -> return ()+ return newWebView++ -- on web downloadRequested $ \ _ -> print "downloadRequested" >> return False++ -- JavaScript stuff: TODO+ -- scriptAlert+ -- scriptConfirm+ -- scriptPrompt+ -- printRequested+ -- statusBarTextChanged+ on web consoleMessage $ \ s1 s2 i s3 -> print ("[JavaScript/Console message]: ",s1,s2,i,s3) >> return False+ -- closeWebView+ -- titleChanged++ hoveredLink <- newTVarIO Nothing++ on web hoveringOverLink $ \ title uri -> do+ -- print ("[hoveringOverLink]: ",title,uri)+ atomically $ writeTVar hoveredLink uri++ -- navigation+ on web navigationPolicyDecisionRequested $ \ webframe networkReq webNavAct webPolDec -> do+ print "[navigationPolicyDecisionRequested]"+ navReason <- webNavigationActionGetReason webNavAct+ case navReason of+ WebNavigationReasonLinkClicked -> do+ print (MyShow navReason)+ muri <- networkRequestGetUri networkReq+ kmod <- webNavigationActionGetModifierState webNavAct+ mbut <- webNavigationActionGetButton webNavAct+ print (muri,kmod,mbut)+ -- midle click and ctrl+click will spawn child+ -- shift+click spawn toplevel window+ case (mbut,kmod,muri) of+ -- middle click+ (2,_,Just uri) -> do+ print ("loading uri in sub [1]",uri)+ wv <- createSubPage+ webViewLoadRequest wv networkReq+ return True+ -- ctrl+click+ (_,4,Just uri) -> do+ print ("loading uri in sub [2]",uri)+ wv <- createSubPage+ webViewLoadRequest wv networkReq+ return True+ -- shift+click+ (_,1,Just uri) -> do+ print ("loading uri in sub [3]",uri)+ wv <- createSubPage+ webViewLoadRequest wv networkReq+ return True+ -- otherwise+ _ -> return False++-- print =<< webNavigationActionGetTargetFrame webNavAct+-- return False+ _ -> do+ print (MyShow navReason)+ return False++ -- new window via JavaScript+ on web newWindowPolicyDecisionRequested $ \ webframe networkReq webNavAct webPolDec -> do+ return False++-- on web Graphics.UI.Gtk.WebKit.WebView.populatePopup $ \ menu -> do+-- print ("[populate popup]")+-- hovered <- readTVarIO hoveredLink+-- case hovered of+-- Nothing -> return ()+-- Just uri -> do+-- menuShellAppend menu =<< menuItemNewWithLabel ("1: " ++ uri)+-- menuShellAppend menu =<< menuItemNewWithLabel ("2: " ++ uri)+-- widgetShowAll menu++ -- statusBarTextChanged++ return ()++addressBarNew :: WebViewClass self => self -> IO Entry+addressBarNew web = do+ ec <- entryNew+ entrySetActivatesDefault ec True+ on ec entryActivate $ do+ text <- entryGetText ec+ webViewLoadUri web text+ on web loadCommitted $ \ frame -> do+ uri <- webFrameGetUri frame+ uri2 <- webViewGetUri web+ when (uri /= uri2) (print $ "INFO: addressBar: uri/uri2 mismatch: " ++ show (uri,uri2))+ case catMaybes [uri,uri2] of+ (x:_) -> entrySetText ec x+ _ -> print ("INFO: addressBar: no text to set")+ return ()+ return ec++newWeb :: BrowseTreeState -> IO () -> String -> IO (Widget, WebView)+newWeb btreeSt refreshLayout url = do+ -- webkit widget+ web <- webViewNew+ webViewSetTransparent web False+ let loadHome = webViewLoadUri web url+ loadHome+ -- webViewSetMaintainsBackForwardList web False -- TODO: or maybe True?+ on web titleChanged $ \ _ _ -> refreshLayout++ -- plugins are causing trouble. disable them.+ settings <- webViewGetWebSettings web+ set settings [webSettingsEnablePlugins := False]++ -- scrolled window to enclose the webkit+ scrollWeb <- scrolledWindowNew Nothing Nothing+ containerAdd scrollWeb web++ -- menu+ addressBar <- addressBarNew web+ menu <- hBoxNew False 1+ quit <- buttonNewWithLabel "Quit"+ reload <- buttonNewWithLabel "Reload"+ on reload buttonActivated $ webViewReload web+ goHome <- buttonNewWithLabel "Home"+ on goHome buttonActivated $ loadHome+ boxPackEnd menu quit PackNatural 1+ boxPackStart menu reload PackNatural 1+ boxPackStart menu goHome PackNatural 1+ boxPackStart menu addressBar PackGrow 1++ -- fill the page+ page <- vPanedNew+ containerAdd page menu+ containerAdd page scrollWeb++ widgetShowAll page++ let widget = toWidget page+ ww = (widget,web)+ newChildPage = do+ print "[newChildPage called]"+ btree <- readTVarIO btreeSt+ debugBTree btree+ case findPageWidget btree widget of+ Just p -> do+ ww' <- newWeb btreeSt refreshLayout "about:blank"+ p' <- newPage ww' "about:blank" -- TODO: win the battle over the power to navigate the web view.+ let btree' = addChild btree p p'+ atomically $ writeTVar btreeSt btree'+ refreshLayout+ return (pgWeb p')+ Nothing -> do+ error "findPageWidget returned Nothing, can't provide a new window"++ hookupWebView web newChildPage++ return ww++-- move to new page, discard any hiNext out there+navigateToPage :: Page -> String -> IO ()+navigateToPage page url = do+ atomically $ do+ hist <- readTVar (pgHistory page)+ let h' = Hist url (hiNow hist : hiPrev hist) []+ writeTVar (pgHistory page) h'++ webViewLoadUri (pgWeb page) url++-- operations on tree+newTopPage :: BrowseTreeState -> IO () -> String -> IO Page+newTopPage btvar refreshLayout url = do+ ww <- newWeb btvar refreshLayout url+ tp <- newLeafURL ww url+ atomically $ do+ bt <- readTVar btvar+ writeTVar btvar (bt ++ [tp])+ return (rootLabel tp)++newLeafURL :: (Widget, WebView) -> String -> IO (Tree Page)+newLeafURL ww url = do+ page <- (newPage ww url)+ return (newLeaf page)++newPage :: (Widget, WebView) -> String -> IO Page+newPage (widget,webv) url = do+ hist <- newTVarIO (Hist url [] [])+ return (Page { pgWidget=widget, pgWeb=webv, pgHistory=hist})++newLeaf :: Page -> Tree Page+newLeaf page = Node page []++addChild :: BrowseTree -> Page -> Page -> BrowseTree+addChild btree parent child = let+ aux (Node page sub) | isSamePage page parent = Node page (sub ++ [newLeaf child])+ | otherwise = Node page (map aux sub)+ in map aux btree++-- query functions++findPageWidget :: BrowseTree -> Widget -> Maybe Page+findPageWidget btree w = let fun p = pgWidget p == w+ flat = concatMap flatten btree+ filt = filter fun flat+ in+ case filt of+ [] -> Nothing+ (x:_) -> Just x++getPageSurrounds :: Eq a => [Tree a] -> a -> ([a], [a], [a])+getPageSurrounds btree p | not (any (F.elem p) btree) = ([],[p],[])+ | otherwise =+ let parent = getPageParent btree p+ parents = case parent of+ Nothing -> []+ Just x -> map rootLabel (getPageSiblings btree (rootLabel x))+ siblings = map rootLabel (getPageSiblings btree p)+ children = map rootLabel (getPageChildren btree p)+ in (parents,siblings,children)++-- returns node's siblings+getPageSiblings :: Eq b => [Tree b] -> b -> [Tree b]+getPageSiblings btree p = case getPageParent btree p of+ Nothing -> btree+ Just x -> subForest x++getPageChildren :: Eq b => [Tree b] -> b -> Forest b+getPageChildren btree p = case filter ((==p) . rootLabel) (concatMap subtrees btree) of+ [] -> error "Element (page) not found in Forest"+ (Node _ sub:_) -> sub++getPageParent :: Eq b => [Tree b] -> b -> Maybe (Tree b)+getPageParent btree p = case (filter (any ((==p) . rootLabel) . subForest) (subtrees' btree)) of+ [] -> Nothing+ (x:_xs) -> Just x++subtrees :: Tree t -> [Tree t]+subtrees t@(Node _ sub) = t : subtrees' sub++subtrees' :: [Tree t] -> [Tree t]+subtrees' = concatMap subtrees++-- notebook synchronization++viewPagesSimpleNotebook :: [Page] -> NotebookSimple -> IO ()+viewPagesSimpleNotebook pages nb = do+ atomically $ writeTVar (ns_pages nb) pages+ ns_refresh nb++noEntriesInBox :: ContainerClass self => self -> IO ()+noEntriesInBox box = do+ containerForeach box (containerRemove box)+ label <- labelNew Nothing+ labelSetMarkup label "<span color=\"#909090\">(no elements here)</span>"+ containerAdd box label+ widgetShowAll box++--------------++setupGlobals = do+ appDir <- getAppUserDataDirectory "Spike"+ createDirectoryIfMissing False appDir++ spikeSetupWebkitGlobals (appDir </> "spike.webkit.db") (appDir </> "spike.webkit.cookie.db")++main :: IO ()+main = do+ initGUI++ setupGlobals+ + -- glue together gui. yuck.+ parentsBox <- hBoxNew False 1 :: IO HBox++ viewPageRef <- newIORef undefined+ siblingsNotebookSimple <- notebookSimpleNew (\p -> do { fun <- readIORef viewPageRef; fun p}) :: IO NotebookSimple+ childrenBox <- hBoxNew False 1 :: IO HBox++ widgetSetSizeRequest parentsBox (-1) 40+ widgetSetSizeRequest childrenBox (-1) 40++ inside <- vBoxNew False 1+ (\sw -> boxPackStart inside sw PackNatural 1) =<< newScrolledWindowWithViewPort parentsBox+ boxPackStart inside (ns_widget siblingsNotebookSimple) PackGrow 1+ (\sw -> boxPackStart inside sw PackNatural 1) =<< newScrolledWindowWithViewPort childrenBox+++ -- global state++ currentPage <- newTVarIO (error "current page is undefined for now...")+ btreeVar <- newTVarIO []++ -- define refresh layout and others++ let viewPage :: Page -> IO ()+ viewPage page = do+ print "CALL: viewPage"+ atomically $ writeTVar currentPage page+ refreshLayout+ + refreshLayout = do+ print "CALL: refreshLayout"++ btree <- readTVarIO btreeVar+ page <- readTVarIO currentPage+ let (parents,siblings,children) = getPageSurrounds btree page++ viewPagesSimpleNotebook siblings siblingsNotebookSimple+ notebookSimpleSelectPage siblingsNotebookSimple page++ case parents of+ [] -> noEntriesInBox parentsBox+ parents' -> listPagesBox viewPage parents' parentsBox -- update parents+ case children of+ [] -> noEntriesInBox childrenBox+ children' -> listPagesBox viewPage children' childrenBox -- update children+++ spawnHomepage = do+ let homepage = "https://google.com"+ page <- newTopPage btreeVar refreshLayout homepage+ atomically $ writeTVar currentPage page+ notebookSimpleSelectPage siblingsNotebookSimple page+ viewPage page+ return ()++ writeIORef viewPageRef viewPage++ -- create root page++ spawnHomepage+ newTopPage btreeVar refreshLayout "http://news.google.com"++ -- show main window+ window <- windowNew+ onDestroy window mainQuit+ set window [ containerBorderWidth := 10,+ windowTitle := "Spike browser",+ containerChild := inside,+ windowAllowGrow := True ]+ widgetShowAll window++ -- tree view window+ visualBrowseTreeWindow btreeVar++ -- refresh layout once and run GTK loop+ refreshLayout+ mainGUI++ return ()