packages feed

apelsin-1.2.1: src/Toolbar.hs

module Toolbar(newToolbar, getDNS) where
import Graphics.UI.Gtk

import Control.Applicative
import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Class
import Data.Maybe
import Data.Char (toLower)

import Network.Tremulous.Protocol
import Network.Tremulous.Polling
import Network.Tremulous.MicroTime

import GtkUtils
import Types
import Constants
import Monad2
import Config
import About
import ClanFetcher
import AutoRefresh


newToolbar :: Bundle -> [ClanHook] -> [PolledHook] -> [ClanPolledHook] -> IO HBox
newToolbar bundle@Bundle{..} clanHook polledHook bothHook = do
	c		<- readMVar mconfig
	pb		<- progressBarNew
	set pb		[ widgetNoShowAll := True ]

	refresh		<- mkToolButton "Refresh all" stockRefresh "Ctrl+R or F5"
	clansync	<- mkToolButton "Sync clans" stockSave "Ctrl+S or F6"
	about		<- mkToolButton "About" stockAbout "F7"
	auto		<- mkAuto (refreshMode c)

	doSync		<- mLock clansync (newClanSync bundle clanHook bothHook)
	doRefresh	<- mLock refresh (newRefresh bundle polledHook bothHook pb)
	autoRunner mauto mconfig doRefresh

	on about buttonActivated (newAbout parent)
	on clansync buttonActivated doSync
	on refresh buttonActivated doRefresh

	on auto toggled $ do
		act <- toggleButtonGetActive auto
		autoSignal mauto (if act then AutoStart else AutoStop)

	toolbar	<- hBoxNew False 0 -- should be spacing?
	boxPackStartDefaults toolbar refresh
	boxPackStartDefaults toolbar auto
	boxPackStartDefaults toolbar clansync
	boxPackStartDefaults toolbar about

	align <- alignmentNew 0 0 0 0
	set align [ containerChild := toolbar ]

	pbrbox <- hBoxNew False spacing
	set pbrbox [ containerBorderWidth := spacing ]
	boxPackStart pbrbox align PackNatural 0
	boxPackStart pbrbox pb PackGrow 0

	on parent keyPressEvent $  do
		kmod <- eventModifier
		k <- map toLower <$> eventKeyName
		case kmod of
			[Control]
				| k == "r" -> liftIO doRefresh		>> return True
				| k == "s" -> liftIO doSync		>> return True
			[]	| k == "f5" -> liftIO doRefresh		>> return True
			[]	| k == "f6" -> liftIO doSync		>> return True
			[]	| k == "f7" -> liftIO (newAbout parent)	>> return True
			[]	| k == "f8" -> liftIO (toggleButton auto)	>> return True
			_ -> return False

	case refreshMode c of
		Startup	-> doRefresh
		Auto	-> autoSignal mauto AutoStart
		Manual	-> return ()
	when (autoClan c) doSync

	return pbrbox

toggleButton :: ToggleButton -> IO ()
toggleButton b = toggleButtonSetActive b . not =<< toggleButtonGetActive b

-- The reason a hbox is used is so the icons always gets displayed regardless of the gtk setting
mkToolButton :: String -> StockId -> String -> IO Button
mkToolButton text icon tip = do
	button <- buttonNew
	img <- imageNewFromStock icon IconSizeButton
	lbl <- labelNew (Just text)
	box <- hBoxNew False 2
	boxPackStart box img PackNatural 0
	boxPackStart box lbl PackNatural 0
	containerAdd button box
	set button	[ buttonRelief		:= ReliefNone
			, buttonFocusOnClick	:= False
			, widgetTooltipText	:= Just tip ]
	return button

mkAuto :: RefreshMode -> IO ToggleButton
mkAuto refreshMode = do
	button <- toggleButtonNew
	set button [ toggleButtonActive := refreshMode == Auto ]
	img <- imageNewFromStock stockMediaPlay IconSizeButton
	containerAdd button img
	set button	[ buttonRelief		:= ReliefNone
			, buttonFocusOnClick	:= False
			, widgetTooltipText	:= Just "Refresh all servers periodically (F8)" ]
	return button

mLock :: WidgetClass w => w -> (IO () -> IO ()) -> IO (IO ())
mLock widget f = do
	m <- newEmptyMVar
	let lock = do
		putMVar m ()
		set widget [ widgetSensitive := False ]
	let unlock = do
		takeMVar m
		postGUISync (set widget [ widgetSensitive := True ])

	return $ whenM (isEmptyMVar m) (lock >> f unlock)


newClanSync :: Bundle -> [ClanHook] -> [ClanPolledHook] -> IO () -> IO ()
newClanSync Bundle{..} clanHook bothHook unlock = do
	Config {clanlistURL} <- readMVar mconfig
	forkIO $ do
		new <- getClanList clanlistURL
		case new of
			Nothing	-> postGUISync $ gtkError parent "Unable to download clanlist"
			Just a	-> do
				swapMVar mclans a
				result <- readMVar mpolled
				postGUISync $ do
					mapM_ ($ a) clanHook
					mapM_ (\f -> f a result) bothHook
		unlock
	return ()

newRefresh :: Bundle -> [PolledHook] -> [ClanPolledHook] -> ProgressBar -> IO () -> IO ()
newRefresh Bundle{..} polledHook bothHook pb unlock = do
	progressBarSetFraction pb 0
	widgetShow pb
	Config {masterServers, delays=delays@Delay{..}} <- readMVar mconfig

	-- This is a stupid guess based on that about 110 servers will respond and the master
	-- will take about 200ms to respond
	PollResult { serversResponded } <- readMVar mpolled
	let serversGuess = if serversResponded == 0 then 110 else serversResponded
	let tremTime = (packetDuplication + 1) * packetTimeout
		+ serversGuess * throughputDelay + 200 * 1000
	-- Unmask in case this is called from a masked state
	forkIOWithUnmask $ \un -> un $ do
		hosts <- catMaybes <$> mapM
			(\(host, port, proto) -> fmap (`MasterServer` proto) <$> getDNS host (show port))
			masterServers
		start <- getMicroTime
		pbth <- forkIO $ whileTrue $ do
				threadDelay 10000 --10 ms, 100 fps
				now <- getMicroTime
				let diff = now - start
				if now-start > fromIntegral tremTime then do
					postGUISync $ progressBarSetFraction pb 1
					return False
				else do
					postGUISync $ progressBarSetFraction pb
						(fromIntegral diff / fromIntegral tremTime)
					return True

		result <- pollMasters delays hosts
		swapMVar mpolled result
		killThread pbth
		clans <- readMVar mclans
		autoSignal mauto AutoUpdate
		postGUISync $ do
			mapM_ ($ result) polledHook
			mapM_ (\f -> f clans result) bothHook
			widgetHide pb
		unlock
	return ()