packages feed

apelsin-1.0: src/Preferences.hs

module Preferences where
import Graphics.UI.Gtk
import Control.Monad
import Control.Applicative hiding (empty)
import Control.Concurrent.STM
import Data.Char
import Data.Array
import Text.Printf
import Network.Tremulous.Protocol

import Types
import Config
import Constants
import GtkUtils
import TremFormatting


newPreferences :: Bundle -> IO ScrolledWindow
newPreferences Bundle{..} = do
	-- Default filters
	
	(tbl, [filterBrowser', filterPlayers'], filterEmpty') <-
		configTable ["_Browser:", "Find _players:"]
	filters <- newLabeledFrame "Default filters"
	set filters [ containerChild := tbl]


	-- Tremulous path
	(pathstbl, [tremPath', tremGppPath']) <- pathTable parent ["_Tremulous 1.1:", "Tremulous _GPP:"]
	paths <- newLabeledFrame "Tremulous path or command"
	set paths [ containerChild := pathstbl]

	-- Startup

	startupMaster	<- checkButtonNewWithMnemonic "Re_fresh all servers"
	startupClan	<- checkButtonNewWithMnemonic "S_ync clan list"
	startupGeometry	<- checkButtonNewWithMnemonic "Restore _window geometry from previous session"
	
	(startup, startupBox) <- framedVBox ("On startup")
	boxPackStartDefaults startupBox startupMaster	
	boxPackStartDefaults startupBox startupClan
	boxPackStartDefaults startupBox startupGeometry
			

	-- Colors

	(colorTbl, colorList)	<- numberedColors 
	(colors', colorBox)	<- framedVBox "Color theme"
	colorWarning		<- labelNew (Just "Note: Requires a restart to take effect")
	
	miscSetAlignment colorWarning 0 0
	boxPackStart colorBox colorTbl PackNatural 0
	boxPackStart colorBox colorWarning PackNatural 0


	-- Internals
	(itbl, [itimeout, iresend, ibuf]) <- mkInternals 
	(internals, ibox) <- framedVBox "Polling Internals"
	ilbl <- labelNew $ Just "Tip: Hover the cursor over each option for a description"
	miscSetAlignment ilbl 0 0
	--labelSetLineWrap ilbl True
	boxPackStart ibox itbl PackNatural 0
	boxPackStart ibox ilbl PackNatural 0


	-- Apply
	
	apply	<- buttonNewFromStock stockApply
	bbox	<- hBoxNew False 0
	boxPackStartDefaults bbox apply
	balign	<- alignmentNew 0.5 1 0 0
	set balign [ containerChild := bbox ]

	on apply buttonActivated $ do
		filterBrowser	<- get filterBrowser' entryText
		filterPlayers	<- get filterPlayers' entryText
		filterEmpty	<- get filterEmpty' toggleButtonActive
		tremPath	<- get tremPath' entryText
		tremGppPath	<- get tremGppPath' entryText
		autoMaster	<- get startupMaster toggleButtonActive
		autoClan	<- get startupClan toggleButtonActive
		autoGeometry	<- get startupGeometry toggleButtonActive
		packetTimeout	<- (*1000) <$> spinButtonGetValueAsInt itimeout
		packetDuplication	<- spinButtonGetValueAsInt iresend
		throughputDelay	<- (*1000) <$> spinButtonGetValueAsInt ibuf
		
		rawcolors	<- forM colorList $ \(colb, cb) -> do
					bool <- get cb toggleButtonActive
					if bool then
						TFColor . colorToHex <$> colorButtonGetColor colb
					else
						return TFNone
		old		<- atomically $ takeTMVar mconfig
		let new		= old {filterBrowser, filterPlayers, autoMaster
				, autoClan, autoGeometry, tremPath, tremGppPath
				, colors = makeColorsFromList rawcolors
				, delays = Delay{..}, filterEmpty}
		atomically $ putTMVar mconfig new
		configToFile new
		return ()
	
	-- Main box

	box <- vBoxNew False spacing
	set box [ containerBorderWidth := spacingBig ]
	boxPackStart box filters PackNatural 0
	boxPackStart box paths PackNatural 0
	boxPackStart box startup PackNatural 0
	boxPackStart box colors' PackNatural 0
	boxPackStart box internals PackNatural 0
	boxPackStart box balign PackGrow 0


	-- Set values from Config
	let updateF = do
		Config {..} 		<- atomically $ readTMVar mconfig
		set filterBrowser'	[ entryText := filterBrowser ]
		set filterPlayers'	[ entryText := filterPlayers ]
		set filterEmpty'	[ toggleButtonActive := filterEmpty]
		set tremPath'		[ entryText := tremPath ]
		set tremGppPath'	[ entryText := tremGppPath ]
		set startupMaster	[ toggleButtonActive := autoMaster ]
		set startupClan		[ toggleButtonActive := autoClan ]
		set startupGeometry	[ toggleButtonActive := autoGeometry ]
		set itimeout		[ spinButtonValue := fromIntegral (packetTimeout delays `div` 1000) ]
		set iresend		[ spinButtonValue := fromIntegral (packetDuplication delays) ]
		set ibuf		[ spinButtonValue := fromIntegral (throughputDelay delays `div` 1000) ]
		sequence_ $ zipWith f colorList (elems colors)
		where	f (a, b) (TFColor c) = do
				colorButtonSetColor a (hexToColor c)
				toggleButtonSetActive b True
			f (_,b) _ = do
				toggleButtonSetActive b False
				-- Apparently this is needed too
				toggleButtonToggled b
	updateF
		
	scrollItV box PolicyNever PolicyAutomatic

configTable :: [String] -> IO (Table, [Entry], CheckButton)
configTable ys = do
	tbl <- tableNew 0 0 False
	empty <- checkButtonNewWithMnemonic "_empty"
	let easyAttach pos lbl  = do
		a <- labelNewWithMnemonic lbl
		b <- entryNew
		set a [ labelMnemonicWidget := b ]
		miscSetAlignment a 0 0.5
		tableAttach tbl a 0 1 pos (pos+1) [Fill] [] spacing spacingHalf
		tableAttach tbl b 1 2 pos (pos+1) [Expand, Fill] [] spacing spacingHalf
		when (pos == 0) $ 
			tableAttach tbl empty 2 3 pos (pos+1) [Fill] [] spacing spacingHalf
				
		return b
		
	let mkTable xs = mapM (uncurry easyAttach) (zip [0..] xs)
	rt	<- mkTable ys
	return (tbl, rt, empty)

pathTable :: Window -> [String] -> IO (Table, [Entry])
pathTable parent ys = do
	tbl <- tableNew 0 0 False
	let easyAttach pos lbl  = do
		a <- labelNewWithMnemonic lbl
		(box, ent) <- pathSelectionEntryNew parent
		set a [ labelMnemonicWidget := ent ]
		miscSetAlignment a 0 0.5
		tableAttach tbl a 0 1 pos (pos+1) [Fill] [] spacing spacingHalf
		tableAttach tbl box 1 2 pos (pos+1) [Expand, Fill] [] spacing spacingHalf
		return ent
		
	let mkTable xs = mapM (uncurry easyAttach) (zip [0..] xs)
	rt	<- mkTable ys
	return (tbl, rt)

mkInternals :: IO (Table, [SpinButton])
mkInternals = do
	tbl <- tableNew 0 0 False
	tips <- tooltipsNew

	let easyAttach pos (lbl, lblafter, tip)  = do
		a <- labelNewWithMnemonic lbl
		tooltipsSetTip tips a tip ""
		b <- spinButtonNewWithRange 0 10000 1
		c <- labelNew (Just lblafter)
		set a [ labelMnemonicWidget := b ]
		miscSetAlignment a 0 0.5
		miscSetAlignment c 0 0
		tableAttach tbl a 0 1 pos (pos+1) [Fill] [] spacing spacingHalf
		tableAttach tbl b 1 2 pos (pos+1) [Fill] [] spacing spacingHalf
		tableAttach tbl c 2 3 pos (pos+1) [Fill] [] spacing spacingHalf
		return b
		
	let mkTable xs = mapM (uncurry easyAttach) (zip [0..] xs)
	rt <- mkTable	[ ("Respo_nse Timeout:", "ms", "How long Apelsin should wait before sending a new request to a server possibly not responding")
			, ("Maximum packet _duplication:", "times", "Maximum number of extra requests to send beoynd the initial one if a server does not respond" )
			, ("Throughput _limit:", "ms", "Should be set as low as possible as long as pings from \"Refresh all servers\" remains the same as \"Refresh current\"") ]
	return (tbl, rt)


framedVBox :: String -> IO (Frame, VBox)
framedVBox title = do
	box	<- vBoxNew False 0
	frame	<- newLabeledFrame title
	set box [ containerBorderWidth := spacing ]
	set frame	[ containerChild := box ]
	return (frame, box)
	
numberedColors :: IO (Table, [(ColorButton, CheckButton)])
numberedColors = do
	tbl <- tableNew 0 0 False
	let easyAttach pos lbl  = do
		a <- labelNew (Just lbl)
		b <- colorButtonNew
		c <- checkButtonNew
		on c toggled $ do
			bool <- get c toggleButtonActive
			set b [ widgetSensitive := if bool then True else False ]
		miscSetAlignment a 0.5 0
		tableAttach tbl a pos (pos+1) 0 1 [Fill] [] spacingHalf spacingHalf
		tableAttach tbl b pos (pos+1) 1 2 [Fill] [] spacingHalf spacingHalf
		tableAttach tbl c pos (pos+1) 2 3 [] [] spacingHalf spacingHalf
		return (b, c)
	let mkTable xs = mapM (uncurry easyAttach) (zip (iterate (+1) 0) xs)

	xs <- mkTable ["^0", "^1", "^2", "^3", "^4", "^5", "^6", "^7"]
	return (tbl, xs)


-- Gtk fails yet again and doesn't offer something like this by default
pathSelectionEntryNew :: Window -> IO (HBox, Entry)
pathSelectionEntryNew parent = do
	box	<- hBoxNew False 0
	img	<- imageNewFromStock stockOpen (IconSizeUser 1)
	button	<- buttonNew
	set button [ buttonImage := img ]
	ent	<- entryNew

	boxPackStart box ent PackGrow 0
	boxPackStart box button PackNatural 0
	
		
	on button buttonActivated $ do
		fc	<- fileChooserDialogNew (Just "Select path") (Just parent) FileChooserActionOpen
			[ (stockCancel, ResponseCancel)
			, (stockOpen, ResponseAccept) ]
		widgetShow fc
		resp <- dialogRun fc
		case resp of
			ResponseAccept -> do
					tst <- fileChooserGetFilename fc
					whenJust tst $ \path ->
						set ent [ entryText := path ]
			_-> return ()
		widgetDestroy fc
		
	
	return (box, ent)


-- And more fail by gtk to not include something like this by default

colorToHex :: Color -> String
colorToHex (Color a b c) = printf "#%02x%02x%02x" (f a) (f b) (f c)
	where f = (`div` 0x100)


hexToColor :: String -> Color
hexToColor ('#':a:b:c:d:e:g:_)	= Color (f a b) (f c d) (f e g)
	where f x y = fromIntegral $ (digitToInt x * 0x10 + digitToInt y) * 0x100
hexToColor ('#':a:b:c:_)	= Color (f a) (f b) (f c)
	where f x = fromIntegral $ digitToInt x * 0x1000
hexToColor _			= Color 0 0 0