packages feed

apelsin-1.1: src/Clanlist.hs

module Clanlist (newClanList, newClanSync, newOnlineClans) where

import Graphics.UI.Gtk

import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import qualified Data.ByteString.Char8 as B
import Data.Ord
import Data.Tree
import Data.Maybe
import Data.List (sortBy)
import Data.IORef
import Network.Tremulous.NameInsensitive
import Network.Tremulous.ByteStringUtils
import qualified Network.Tremulous.Protocol as P
import Network.Tremulous.Util

import Types
import ClanFetcher
import Constants
import Config
import FilterBar
import InfoBox
import GtkUtils
import TremFormatting


newClanList :: Bundle -> [Clan] -> SetCurrent -> IO (VBox, ClanPolledHook, Entry)
newClanList Bundle{..} cache setCurrent = do
	raw			<- listStoreNew []
	filtered		<- treeModelFilterNew raw []
	sorted			<- treeModelSortNewWithModel filtered
	view			<- treeViewNewWithModel sorted
	scrollview		<- scrollIt view PolicyAutomatic PolicyAlways

	(infobox, statNow, statTot) <- newInfobox "clans"

	(filterbar, current, ent)	<- newFilterBar filtered statNow ""

	let updateF newraw P.PollResult{..} = do
		let new = (`map` newraw) $ \c -> case clanserver c of
			Nothing -> (c, False)
			Just a -> (c, a `elemByAddress` polled)
		listStoreClear raw
		treeViewColumnsAutosize view
		mapM_ (listStoreAppend raw) new
		treeModelFilterRefilter filtered
		set statTot [ labelText := show (length new) ]
		n <- treeModelIterNChildren filtered Nothing
		set statNow [ labelText := show n ]

	addColumnsFilterSort raw filtered sorted view 1 SortAscending 
		[ (""		, False	, RendPixbuf haveServer
			, Just (comparing (isJust . clanserver . fst)))
		, ("_Name"	, False	, RendText (simpleColumn (unpackorig . name))
			, Just (comparing (name . fst)))
		, ("_Tag"	, False	, RendText (markupColumn (prettyTagExpr . tagexpr))
			, Just (comparing (tagexpr . fst)))
		, ("Website"	, False	, RendText (simpleColumn (B.unpack . showURL . website))
			, Nothing)
		, ("IRC"	, False , RendText (simpleColumn (B.unpack . irc))
			, Nothing)
		]

	treeModelFilterSetVisibleFunc filtered $ \iter -> do
		(Clan{..}, _)	<- treeModelGetRow raw iter
		s		<- readIORef current
		let cmplist	= [ cleanedCase name, tagExprGet tagexpr ]
		return $ B.null s || smartFilter s cmplist

	on view cursorChanged $ do
		(path, _)		<- treeViewGetCursor view
		(Clan{..}, active)	<- getElementFS raw sorted filtered path
		
		when active $ whenJust clanserver $ \server -> do
			P.PollResult{..} <- atomically $ readTMVar mpolled
			whenJust (serverByAddress server polled) (setCurrent False)

	on view rowActivated $ \path _ -> do
		(Clan{..}, _) <- getElementFS raw sorted filtered path
		unless (B.null website) $
			openInBrowser (B.unpack website)

	box <- vBoxNew False 0
	boxPackStart box filterbar PackNatural spacing
	boxPackStart box scrollview PackGrow 0
	boxPackStart box infobox PackNatural 0

	
	updateF cache =<< atomically (readTMVar mpolled)

	return (box, updateF, ent)
	where
	showURL x = fromMaybe x (stripPrefix "http://" x)
	markupColumn f (item, _) = [ cellTextMarkup := Just (f item) ]
	simpleColumn f (item, _) = [ cellText := f item ]
	haveServer (Clan{..}, active) = case clanserver of
		Just _	-> [cellPixbufStockId := stockNetwork, cellSensitive := active]
		Nothing	-> [cellPixbufStockId := ""]


newOnlineClans :: Bundle-> SetCurrent -> IO (ScrolledWindow, ClanPolledHook)
newOnlineClans Bundle{..} setServer = do
	Config {colors} <- atomically $ readTMVar mconfig

	let showName c =  case c of
		Left Clan{..}		-> "<b>" ++ htmlEscape (unpackorig name) ++ "</b>"
		Right (name, _)		-> pangoPretty colors name

	let showServer c =  case c of
		Left _ -> ""
		Right (_, P.GameServer{hostname}) -> pangoPretty colors hostname

	raw	<- treeStoreNew []
	view	<- treeViewNewWithModel raw

	treeViewExpandAll view
	addColumns raw view [
		  ("Name"	, 0	, True	, True	, True	, showName )
		, ("Server"	, 0	, True	, True	, True	, showServer )
		]

	let updateF clans P.PollResult{..} = do
		let players = buildTree $ sortByPlayers $
			associatePlayerToClans (makePlayerNameList polled) clans
		treeStoreClear raw
		treeViewColumnsAutosize view
		mapM_ (treeStoreInsertTree raw [] 0) players
		treeViewExpandAll view

	on view cursorChanged $ do
		(path, _)	<- treeViewGetCursor view
		item		<- getElement raw path
		case item of
			Left _ -> return ()
			Right (_, a) -> setServer False a

	on view rowActivated $ \path _ -> do
		gs		<- getElement raw path
		case gs of
			Left _ -> return ()
			Right (_, gameserver) -> setServer True gameserver

	scroll <- scrollIt view PolicyAutomatic PolicyAutomatic

	return (scroll, updateF)


-- /// Utility functions ///////////////////////////////////////////////////////////////////////////

type PlayerList = [(TI, P.GameServer)]

type OnlineView = Forest (Either Clan (TI, P.GameServer))

associatePlayerToClans :: PlayerList -> [Clan] -> [(Clan, PlayerList)]
associatePlayerToClans players clans = map f clans
	where
	f c@Clan{tagexpr} = (c, filter (cmp tagexpr) players)
	cmp e = matchTagExpr e . fst

buildTree :: [(Clan, PlayerList)] -> OnlineView
buildTree = filter notEmpty . foldr f [] where
	f (clan, pls) xs = Node (Left clan) (map rightNode pls) : xs
	rightNode x = Node (Right x) []
	notEmpty (Node _ [])	= False
	notEmpty _		= True

sortByPlayers :: [(Clan, [b])] -> [(Clan, [b])]
sortByPlayers = sortBy (flip (comparing (\(a, b) -> (-length b, name a))))

newClanSync :: Bundle -> Button -> [ClanHook] -> [ClanPolledHook] -> IO ()
newClanSync Bundle{..} button clanHook bothHook = do
	set button [ widgetSensitive := False ]
	Config {clanSyncURL} <- atomically $ readTMVar mconfig
	forkIO $ do
		new <- getClanList clanSyncURL
		case new of
			Nothing	-> postGUISync $ do
				gtkError "Unable to download clanlist"
				set button [ widgetSensitive := True ]
			Just a	-> do
				result <- atomically $ do
					swapTMVar mclans a
					readTMVar mpolled
				postGUISync $ do
					mapM_ ($ a) clanHook
					mapM_ (\f -> f a result) bothHook
					set button [ widgetSensitive := True ]
	return ()