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