apelsin-1.2: src/Preferences.hs
{-# LANGUAGE CPP #-}
module Preferences (newPreferences) where
import Graphics.UI.Gtk
import Control.Monad
import Control.Applicative hiding (empty)
import Data.Char
import Data.Array
import Text.Printf
import Network.Tremulous.Protocol
import System.FilePath
import Types
import qualified Config as C
import Constants
import GtkUtils
import Monad2
import TremFormatting
#define _CONNECT(WID, SIG, GET, SET) on WID SIG (do {new <- get WID GET; update (\x -> x { SET = new})})
#define _CONNECT_WITH(WID, SIG, GET, SET, F) on WID SIG (do {new <- get WID GET; update (\x -> x { SET = F new})})
newPreferences :: Bundle -> IO ScrolledWindow
newPreferences Bundle{..} = do
let update f = do
new <- modifyMVar mconfig $ \old -> do
let new = f old
return (new, new)
C.configToFile parent new
-- Default filters
( tbl
, [filterBrowser, filterPlayers]
, filterEmpty
) <- configTable
[ "_Browser:"
, "_Find players:"
]
filters <- framed "Default filters" tbl
-- Tremulous path
(pathstbl, [tremPath, tremGppPath]) <- pathTable parent ["_Tremulous 1.1:", "Tremulous _GPP:"]
paths <- framed "Tremulous path or command" pathstbl
-- Startup
autoClan <- checkButtonNewWithMnemonic "_Sync clan list"
autoGeometry <- checkButtonNewWithMnemonic "Restore _window geometry from previous session"
startupBox <- vBoxNew False 0
boxPackStartDefaults startupBox autoClan
boxPackStartDefaults startupBox autoGeometry
startup <- framed "On startup" startupBox
--Refresh mode
rb1 <- radioButtonNewWithMnemonic "_Once at startup"
rb2 <- radioButtonNewWithMnemonicFromWidget rb1 "_Periodically each"
rb3 <- radioButtonNewWithMnemonicFromWidget rb1 "_Manually"
autoDelay <- spinButtonNewWithRange 0 3600 1
seconds <- labelNew (Just "s")
delaybox <- hBoxNew False spacing
boxPackStart delaybox autoDelay PackNatural 0
boxPackStart delaybox seconds PackNatural 0
perbox <- hBoxNew False spacingBig
boxPackStart perbox rb2 PackNatural 0
boxPackStart perbox delaybox PackNatural 0
rbBox <- vBoxNew False 0
boxPackStartDefaults rbBox rb1
boxPackStartDefaults rbBox perbox
boxPackStartDefaults rbBox rb3
refreshmode <- framed "Refresh all" rbBox
-- Colors
(colorTbl, colorList) <- numberedColors
colorWarning <- labelNew (Just "Note: Requires a restart to take effect")
miscSetAlignment colorWarning 0 0
colorBox <- vBoxNew False spacing
boxPackStart colorBox colorTbl PackNatural 0
boxPackStart colorBox colorWarning PackNatural 0
colors' <- framed "Color theme" colorBox
-- Internals
(itbl, [packetTimeout', packetDuplication', throughputDelay']) <- mkInternals
ilbl <- labelNew $ Just "Tip: Hover the cursor over each option for a description"
miscSetAlignment ilbl 0 0
ibox <- vBoxNew False spacing
boxPackStart ibox itbl PackNatural 0
boxPackStart ibox ilbl PackNatural 0
internals <- framed "Polling Internals" ibox
-- Main box
box <- vBoxNew False spacingHuge
containerSetBorderWidth box spacing
boxPackStart box filters PackNatural 0
boxPackStart box paths PackNatural 0
boxPackStart box refreshmode PackNatural 0
boxPackStart box startup PackNatural 0
boxPackStart box colors' PackNatural 0
boxPackStart box internals PackNatural 0
-- Set values from Config
let updateF = do
c <- readMVar mconfig
let Delay{..} = C.delays c
set filterBrowser [ entryText := C.filterBrowser c ]
set filterPlayers [ entryText := C.filterPlayers c ]
set filterEmpty [ toggleButtonActive := C.filterEmpty c]
set tremPath [ entryText := C.tremPath c ]
set tremGppPath [ entryText := C.tremGppPath c]
set autoClan [ toggleButtonActive := C.autoClan c]
set autoGeometry [ toggleButtonActive := C.autoGeometry c]
set autoDelay [ spinButtonValue := fromIntegral (C.autoDelay c `quot` 1000000) ]
set packetTimeout' [ spinButtonValue := fromIntegral (packetTimeout `quot` 1000) ]
set packetDuplication' [ spinButtonValue := fromIntegral packetDuplication ]
set throughputDelay' [ spinButtonValue := fromIntegral (throughputDelay `quot` 1000) ]
set rb1 [ toggleButtonActive := C.refreshMode c == C.Startup ]
set rb2 [ toggleButtonActive := C.refreshMode c == C.Auto ]
set rb3 [ toggleButtonActive := C.refreshMode c == C.Manual ]
zipWithM_ f colorList (elems (C.colors c))
where f (a, b) (TFColor c) = do
colorButtonSetColor a (hexToColor c)
toggleButtonSetActive b True
f (a ,b) (TFNone c) = do
colorButtonSetColor a (hexToColor c)
toggleButtonSetActive b False
-- Apparently this is needed too
toggleButtonToggled b
updateF
_CONNECT(filterBrowser, editableChanged, entryText, C.filterBrowser)
_CONNECT(filterEmpty, toggled, toggleButtonActive, C.filterEmpty)
_CONNECT(filterPlayers, editableChanged, entryText, C.filterPlayers)
_CONNECT(tremPath, editableChanged, entryText, C.tremPath)
_CONNECT(tremGppPath, editableChanged, entryText, C.tremGppPath)
--_CONNECT(autoMaster, toggled, toggleButtonActive, C.autoMaster)
_CONNECT(autoClan, toggled, toggleButtonActive, C.autoClan)
_CONNECT(autoGeometry, toggled, toggleButtonActive, C.autoGeometry)
let radiofunc value = update (\x -> x { C.refreshMode = value})
on rb1 toggled $ do b <- toggleButtonGetActive rb1
when b (radiofunc C.Startup)
on rb2 toggled $ do b <- toggleButtonGetActive rb2
when b (radiofunc C.Auto)
on rb3 toggled $ do b <- toggleButtonGetActive rb3
when b (radiofunc C.Manual)
onValueSpinned autoDelay $ do
value <- (*1000000) <$> spinButtonGetValueAsInt autoDelay
update (\x -> x { C.autoDelay = fromIntegral value})
onValueSpinned packetTimeout' $ do
packetTimeout <- (*1000) <$> spinButtonGetValueAsInt packetTimeout'
update (\x -> let delays = C.delays x in x {C.delays = delays {packetTimeout}})
onValueSpinned packetDuplication' $ do
packetDuplication <- spinButtonGetValueAsInt packetDuplication'
update (\x -> let delays = C.delays x in x {C.delays = delays {packetDuplication}})
onValueSpinned throughputDelay' $ do
throughputDelay <- (*1000) <$> spinButtonGetValueAsInt throughputDelay'
update (\x -> let delays = C.delays x in x {C.delays = delays {throughputDelay}})
let updateColors = do
rawcolors <- forM colorList $ \(colb, cb) -> do
bool <- get cb toggleButtonActive
(if bool then TFColor else TFNone)
. colorToHex <$> colorButtonGetColor colb
update $ \x -> x {C.colors = C.makeColorsFromList rawcolors}
forM colorList $ \(colb, cb) -> do
on cb toggled updateColors
afterColorSet colb updateColors
scrollItV box PolicyNever PolicyAutomatic
configTable :: [String] -> IO (Table, [Entry], CheckButton)
configTable ys = do
tbl <- paddedTableNew
empty <- checkButtonNewWithMnemonic "_empty"
let easyAttach pos lbl = do
a <- labelNewWithMnemonic lbl
ent <- entryNew
b <- hBoxNew False spacingHalf
boxPackStart b ent PackGrow 0
when (pos == 0) $
boxPackStart b empty PackNatural 0
set a [ labelMnemonicWidget := ent ]
miscSetAlignment a 0 0.5
tableAttach tbl a 0 1 pos (pos+1) [Fill] [] 0 0
tableAttach tbl b 1 2 pos (pos+1) [Expand, Fill] [] 0 0
return ent
rt <- zipWithM easyAttach [0..] ys
return (tbl, rt, empty)
pathTable :: Window -> [String] -> IO (Table, [Entry])
pathTable parent ys = do
tbl <- paddedTableNew
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] [] 0 0
tableAttach tbl box 1 2 pos (pos+1) [Expand, Fill] [] 0 0
return ent
rt <- zipWithM easyAttach [0..] ys
return (tbl, rt)
mkInternals :: IO (Table, [SpinButton])
mkInternals = do
tbl <- paddedTableNew
let easyAttach pos (lbl, lblafter, tip) = do
a <- labelNewWithMnemonic lbl
b <- spinButtonNewWithRange 0 10000 1
c <- labelNew (Just lblafter)
set a [ labelMnemonicWidget := b
, widgetTooltipText := Just tip
, miscXalign := 0 ]
set c [ miscXalign := 0 ]
tableAttach tbl a 0 1 pos (pos+1) [Fill] [] 0 0
tableAttach tbl b 1 2 pos (pos+1) [Fill] [] 0 0
tableAttach tbl c 2 3 pos (pos+1) [Fill] [] 0 0
return b
let mkTable = zipWithM easyAttach [0..]
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)
framed :: ContainerClass w => String -> w -> IO VBox
framed title box = do
l <- labelNew (Just title)
miscSetAlignment l 0 0
labelSetAttributes l [AttrWeight 0 (-1) WeightBold]
align <- alignmentNew 0.5 0.5 1 1
alignmentSetPadding align 0 0 spacingBig 0
containerAdd align box
vb <- vBoxNew False spacing
boxPackStart vb l PackNatural 0
boxPackStart vb align PackNatural 0
return vb
numberedColors :: IO (Table, [(ColorButton, CheckButton)])
numberedColors = do
tbl <- paddedTableNew
let easyAttach pos lbl = do
a <- labelNew (Just lbl)
b <- colorButtonNew
c <- checkButtonNew
on c toggled $
widgetSetSensitive b =<< toggleButtonGetActive c
miscSetAlignment a 0.5 0
tableAttach tbl a pos (pos+1) 0 1 [Fill] [] 0 0
tableAttach tbl b pos (pos+1) 1 2 [Fill] [] 0 0
tableAttach tbl c pos (pos+1) 2 3 [] [] 0 0
return (b, c)
xs <- zipWithM easyAttach [0..] ["^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
button <- buttonNew
set button [ buttonImage :=> imageNewFromStock stockOpen (IconSizeUser 1) ]
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) ]
current <- takeDirectory <$> get ent entryText
fileChooserSetCurrentFolder fc current
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)
paddedTableNew :: IO Table
paddedTableNew = do
tbl <- tableNew 0 0 False
set tbl [ tableRowSpacing := spacingHalf
, tableColumnSpacing := spacing ]
return tbl
colorToHex :: Color -> String
colorToHex (Color a b c) = printf "#%02x%02x%02x" (f a) (f b) (f c)
where f = (`quot` 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