apelsin-1.2.1: src/Preferences.hs
{-# LANGUAGE CPP #-}
-- This file is horribly coded :( A good example why the gtk code should be separate
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]
, showEmpty
) <- configTable
[ "_Browser:"
, "_Find players:"
]
filters <- framed "Default filters" tbl
-- Tremulous path
path_table <- paddedTableNew
tremulousPath <- pathInsertTable parent path_table 0 "_Tremulous 1.1:"
tremulousGppPath <- pathInsertTable parent path_table 1 "Tremulous _GPP:"
unvanquishedPath <- pathInsertTable parent path_table 2 "_Unvanquished:"
paths <- framed "Path or command" path_table
-- Startup
autoClan <- checkButtonNewWithMnemonic "_Sync clan list"
restoreGeometry <- checkButtonNewWithMnemonic "Restore _window geometry from previous session"
startupBox <- vBoxNew False 0
boxPackStartDefaults startupBox autoClan
boxPackStartDefaults startupBox restoreGeometry
startup <- framed "On startup" startupBox
--Refresh mode
rb1 <- radioButtonNewWithMnemonic "_Once at startup"
rb2 <- radioButtonNewWithMnemonicFromWidget rb1 "_Periodically"
rb3 <- radioButtonNewWithMnemonicFromWidget rb1 "_Manually"
ar <- labelNewWithMnemonic "_Auto Refresh period:"
autoRefreshDelay <- spinButtonNewWithRange 0 3600 1
set ar [labelMnemonicWidget := autoRefreshDelay]
seconds <- labelNew (Just "s")
delaybox <- hBoxNew False spacing
boxPackStart delaybox ar PackNatural 0
boxPackStart delaybox autoRefreshDelay PackNatural 0
boxPackStart delaybox seconds PackNatural 0
rbBox <- vBoxNew False 0
boxPackStartDefaults rbBox rb1
boxPackStartDefaults rbBox rb2
boxPackStartDefaults rbBox rb3
boxPackStartDefaults rbBox delaybox
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 showEmpty [ toggleButtonActive := C.showEmpty c]
set tremulousPath [ entryText := C.tremulousPath c ]
set tremulousGppPath [ entryText := C.tremulousGppPath c]
set unvanquishedPath [ entryText := C.unvanquishedPath c]
set autoClan [ toggleButtonActive := C.autoClan c]
set restoreGeometry [ toggleButtonActive := C.restoreGeometry c]
set autoRefreshDelay [ spinButtonValue := fromIntegral (C.autoRefreshDelay 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) (TremFmt active color) = do
colorButtonSetColor a (hexToColor color)
toggleButtonSetActive b active
toggleButtonToggled b
updateF
_CONNECT(filterBrowser, editableChanged, entryText, C.filterBrowser)
_CONNECT(showEmpty, toggled, toggleButtonActive, C.showEmpty)
_CONNECT(filterPlayers, editableChanged, entryText, C.filterPlayers)
_CONNECT(tremulousPath, editableChanged, entryText, C.tremulousPath)
_CONNECT(tremulousGppPath, editableChanged, entryText, C.tremulousGppPath)
_CONNECT(unvanquishedPath, editableChanged, entryText, C.unvanquishedPath)
_CONNECT(autoClan, toggled, toggleButtonActive, C.autoClan)
_CONNECT(restoreGeometry, toggled, toggleButtonActive, C.restoreGeometry)
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 autoRefreshDelay $ do
value <- (*1000000) <$> spinButtonGetValueAsInt autoRefreshDelay
update (\x -> x { C.autoRefreshDelay = 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) ->
TremFmt <$> get cb toggleButtonActive
<*> (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)
pathInsertTable :: Window -> Table -> Int -> String -> IO Entry
pathInsertTable parent tbl 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
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 extra _requests:", "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