2014-10-06 21:14:23 +00:00
|
|
|
{-# OPTIONS_HADDOCK ignore-exports #-}
|
|
|
|
|
2014-10-10 15:40:08 +00:00
|
|
|
module GUI.Gtk (makeGUI) where
|
2014-10-01 18:26:57 +00:00
|
|
|
|
2014-10-17 12:04:37 +00:00
|
|
|
import Control.Applicative
|
2014-11-15 23:10:57 +00:00
|
|
|
import Control.Monad(unless)
|
2014-10-01 18:26:57 +00:00
|
|
|
import Control.Monad.IO.Class
|
2014-11-21 03:49:17 +00:00
|
|
|
import qualified Data.ByteString.Char8 as B
|
2014-11-29 22:45:53 +00:00
|
|
|
import Data.Maybe
|
2014-10-01 18:26:57 +00:00
|
|
|
import Diagrams.Prelude
|
|
|
|
import Diagrams.Backend.Cairo
|
|
|
|
import Diagrams.Backend.Cairo.Internal
|
2014-12-03 21:02:42 +00:00
|
|
|
import Graphics.Diagram.Core (DiagProp(..))
|
2014-10-10 15:40:08 +00:00
|
|
|
import Graphics.Diagram.Gtk
|
2014-10-01 18:26:57 +00:00
|
|
|
import Graphics.UI.Gtk
|
2014-10-02 12:29:56 +00:00
|
|
|
import Graphics.UI.Gtk.Glade
|
2014-10-10 15:40:08 +00:00
|
|
|
import MyPrelude
|
2014-10-01 18:26:57 +00:00
|
|
|
import System.Directory
|
2014-11-15 23:25:17 +00:00
|
|
|
import System.FilePath.Posix
|
2014-10-05 18:08:58 +00:00
|
|
|
import Text.Read
|
2014-10-01 18:26:57 +00:00
|
|
|
|
2014-10-10 13:03:12 +00:00
|
|
|
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |Monolithic object passed to various GUI functions in order
|
|
|
|
-- to keep the API stable and not alter the parameters too much.
|
|
|
|
-- This only holds GUI widgets that are needed to be read during
|
|
|
|
-- runtime.
|
2014-10-05 17:32:36 +00:00
|
|
|
data MyGUI = MkMyGUI {
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |main Window
|
2014-11-15 23:10:57 +00:00
|
|
|
rootWin :: Window,
|
2014-11-15 02:58:38 +00:00
|
|
|
-- |Tree Window
|
2014-11-15 23:10:57 +00:00
|
|
|
treeWin :: Window,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |delete Button
|
2014-11-16 15:45:51 +00:00
|
|
|
delButton :: Button,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |save Button
|
2014-11-16 15:45:51 +00:00
|
|
|
saveButton :: Button,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |quit Button
|
2014-11-16 15:45:51 +00:00
|
|
|
quitButton :: Button,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |file chooser button
|
2014-11-16 15:45:51 +00:00
|
|
|
fileButton :: FileChooserButton,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |drawing area
|
2014-11-16 15:45:51 +00:00
|
|
|
mainDraw :: DrawingArea,
|
2014-11-15 02:58:38 +00:00
|
|
|
-- |drawing area for the tree
|
2014-11-15 23:10:57 +00:00
|
|
|
treeDraw :: DrawingArea,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |scaler for point thickness
|
2014-11-16 15:45:51 +00:00
|
|
|
ptScale :: HScale,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |entry widget for lower x bound
|
2014-11-16 15:45:51 +00:00
|
|
|
xminEntry :: Entry,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |entry widget for upper x bound
|
2014-11-16 15:45:51 +00:00
|
|
|
xmaxEntry :: Entry,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |entry widget for lower y bound
|
2014-11-16 15:45:51 +00:00
|
|
|
yminEntry :: Entry,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |entry widget for upper y bound
|
2014-11-16 15:45:51 +00:00
|
|
|
ymaxEntry :: Entry,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |about dialog
|
2014-11-16 15:45:51 +00:00
|
|
|
aboutDialog :: AboutDialog,
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |combo box for choosing the algorithm
|
2014-11-16 15:45:51 +00:00
|
|
|
algoBox :: ComboBox,
|
2014-10-09 16:45:37 +00:00
|
|
|
-- |grid check button
|
2014-11-16 15:45:51 +00:00
|
|
|
gridCheckBox :: CheckButton,
|
2014-11-14 20:28:56 +00:00
|
|
|
-- |coord check button
|
2014-11-16 15:45:51 +00:00
|
|
|
coordCheckBox :: CheckButton,
|
2014-11-14 20:28:56 +00:00
|
|
|
-- |Path entry widget for the quad tree.
|
2014-11-16 15:45:51 +00:00
|
|
|
quadPathEntry :: Entry,
|
2014-11-14 23:32:16 +00:00
|
|
|
-- |Horizontal box containing the path entry widget.
|
2014-11-29 22:45:53 +00:00
|
|
|
vbox7 :: Box,
|
|
|
|
-- |Horizontal box containing the Rang search entry widgets.
|
|
|
|
vbox10 :: Box,
|
|
|
|
-- |Range entry widget for lower x bound
|
|
|
|
rangeXminEntry :: Entry,
|
|
|
|
-- |Range entry widget for upper x bound
|
|
|
|
rangeXmaxEntry :: Entry,
|
|
|
|
-- |Range entry widget for lower y bound
|
|
|
|
rangeYminEntry :: Entry,
|
|
|
|
-- |Range entry widget for upper y bound
|
|
|
|
rangeYmaxEntry :: Entry
|
2014-10-05 17:32:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |The glade file to load the UI from.
|
2014-10-02 12:29:56 +00:00
|
|
|
gladeFile :: FilePath
|
2014-10-10 15:40:08 +00:00
|
|
|
gladeFile = "GUI/gtk2.glade"
|
2014-10-02 12:29:56 +00:00
|
|
|
|
2014-10-05 17:32:36 +00:00
|
|
|
|
|
|
|
-- |Loads the glade file and creates the MyGUI object.
|
|
|
|
makeMyGladeGUI :: IO MyGUI
|
|
|
|
makeMyGladeGUI = do
|
|
|
|
-- load glade file
|
|
|
|
Just xml <- xmlNew gladeFile
|
|
|
|
|
2014-10-17 12:04:37 +00:00
|
|
|
MkMyGUI
|
|
|
|
<$> xmlGetWidget xml castToWindow "window1"
|
2014-11-15 02:58:38 +00:00
|
|
|
<*> xmlGetWidget xml castToWindow "window2"
|
2014-10-17 12:04:37 +00:00
|
|
|
<*> xmlGetWidget xml castToButton "drawButton"
|
|
|
|
<*> xmlGetWidget xml castToButton "saveButton"
|
|
|
|
<*> xmlGetWidget xml castToButton "quitButton"
|
2014-11-16 15:45:51 +00:00
|
|
|
<*> xmlGetWidget xml castToFileChooserButton "filechooserButton"
|
2014-10-17 12:04:37 +00:00
|
|
|
<*> xmlGetWidget xml castToDrawingArea "drawingarea"
|
2014-11-15 02:58:38 +00:00
|
|
|
<*> xmlGetWidget xml castToDrawingArea "treedrawingarea"
|
2014-10-17 12:04:37 +00:00
|
|
|
<*> xmlGetWidget xml castToHScale "hscale"
|
|
|
|
<*> xmlGetWidget xml castToEntry "xlD"
|
|
|
|
<*> xmlGetWidget xml castToEntry "xuD"
|
|
|
|
<*> xmlGetWidget xml castToEntry "ylD"
|
|
|
|
<*> xmlGetWidget xml castToEntry "yuD"
|
|
|
|
<*> xmlGetWidget xml castToAboutDialog "aboutdialog"
|
|
|
|
<*> xmlGetWidget xml castToComboBox "comboalgo"
|
|
|
|
<*> xmlGetWidget xml castToCheckButton "gridcheckbutton"
|
|
|
|
<*> xmlGetWidget xml castToCheckButton "coordcheckbutton"
|
2014-11-14 20:28:56 +00:00
|
|
|
<*> xmlGetWidget xml castToEntry "path"
|
2014-11-14 23:32:16 +00:00
|
|
|
<*> xmlGetWidget xml castToBox "vbox7"
|
2014-11-29 22:45:53 +00:00
|
|
|
<*> xmlGetWidget xml castToBox "vbox10"
|
|
|
|
<*> xmlGetWidget xml castToEntry "rxMin"
|
|
|
|
<*> xmlGetWidget xml castToEntry "rxMax"
|
|
|
|
<*> xmlGetWidget xml castToEntry "ryMin"
|
|
|
|
<*> xmlGetWidget xml castToEntry "ryMax"
|
2014-10-05 17:32:36 +00:00
|
|
|
|
|
|
|
|
2014-10-05 19:47:00 +00:00
|
|
|
-- |Main entry point for the GTK GUI routines.
|
2014-10-02 11:14:16 +00:00
|
|
|
makeGUI :: FilePath -> IO ()
|
|
|
|
makeGUI startFile = do
|
2014-10-01 18:26:57 +00:00
|
|
|
homedir <- getHomeDirectory
|
|
|
|
|
|
|
|
-- init gui
|
|
|
|
_ <- initGUI
|
|
|
|
|
2014-10-05 17:32:36 +00:00
|
|
|
-- get GUI object
|
|
|
|
mygui <- makeMyGladeGUI
|
2014-10-01 18:26:57 +00:00
|
|
|
|
|
|
|
-- adjust properties
|
2014-10-05 01:12:58 +00:00
|
|
|
if startFile == ""
|
|
|
|
then do
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- fileChooserSetCurrentFolder (fileButton mygui) homedir
|
2014-10-05 01:12:58 +00:00
|
|
|
return ()
|
|
|
|
else do
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- fileChooserSetFilename (fileButton mygui) startFile
|
2014-10-05 01:12:58 +00:00
|
|
|
return ()
|
2014-11-15 23:10:57 +00:00
|
|
|
comboBoxSetActive (algoBox mygui) 0
|
2014-10-01 18:26:57 +00:00
|
|
|
|
|
|
|
-- callbacks
|
2014-11-16 15:45:51 +00:00
|
|
|
_ <- onDestroy (rootWin mygui) mainQuit
|
|
|
|
_ <- onClicked (delButton mygui) $ drawDiag mygui
|
|
|
|
_ <- onClicked (saveButton mygui) $ saveDiag mygui
|
|
|
|
_ <- onClicked (quitButton mygui) mainQuit
|
|
|
|
_ <- onResponse (aboutDialog mygui)
|
|
|
|
(\x -> case x of
|
|
|
|
ResponseCancel -> widgetHideAll (aboutDialog mygui)
|
|
|
|
_ -> return ())
|
2014-10-10 22:01:43 +00:00
|
|
|
-- have to redraw for window overlapping and resizing on expose
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- onExpose (mainDraw mygui) (\_ -> drawDiag mygui >>=
|
2014-11-16 15:45:51 +00:00
|
|
|
(\_ -> return True))
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- onExpose (treeDraw mygui) (\_ -> drawDiag mygui >>=
|
2014-11-16 15:45:51 +00:00
|
|
|
(\_ -> return True))
|
|
|
|
_ <- on (algoBox mygui) changed (drawDiag mygui)
|
|
|
|
_ <- on (algoBox mygui) changed (onAlgoBoxChange mygui)
|
|
|
|
_ <- on (gridCheckBox mygui) toggled (drawDiag mygui)
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- on (coordCheckBox mygui) toggled (drawDiag mygui)
|
2014-10-01 18:26:57 +00:00
|
|
|
|
|
|
|
-- hotkeys
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- rootWin mygui `on` keyPressEvent $ tryEvent $ do
|
2014-10-09 22:19:05 +00:00
|
|
|
[Control] <- eventModifier
|
|
|
|
"q" <- eventKeyName
|
|
|
|
liftIO mainQuit
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- treeWin mygui `on` keyPressEvent $ tryEvent $ do
|
2014-11-15 02:58:38 +00:00
|
|
|
[Control] <- eventModifier
|
|
|
|
"q" <- eventKeyName
|
2014-11-15 23:10:57 +00:00
|
|
|
liftIO (widgetHide $ treeWin mygui)
|
|
|
|
_ <- rootWin mygui `on` keyPressEvent $ tryEvent $ do
|
2014-10-09 22:19:05 +00:00
|
|
|
[Control] <- eventModifier
|
|
|
|
"s" <- eventKeyName
|
2014-10-11 00:01:17 +00:00
|
|
|
liftIO $ saveDiag mygui
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- rootWin mygui `on` keyPressEvent $ tryEvent $ do
|
2014-10-09 22:19:05 +00:00
|
|
|
[Control] <- eventModifier
|
|
|
|
"d" <- eventKeyName
|
2014-10-11 00:01:17 +00:00
|
|
|
liftIO $ drawDiag mygui
|
2014-11-15 23:10:57 +00:00
|
|
|
_ <- rootWin mygui `on` keyPressEvent $ tryEvent $ do
|
2014-10-09 22:19:05 +00:00
|
|
|
[Control] <- eventModifier
|
|
|
|
"a" <- eventKeyName
|
2014-11-15 23:10:57 +00:00
|
|
|
liftIO $ widgetShowAll (aboutDialog mygui)
|
2014-10-01 18:26:57 +00:00
|
|
|
|
|
|
|
-- draw widgets and start main loop
|
2014-11-15 23:10:57 +00:00
|
|
|
widgetShowAll (rootWin mygui)
|
|
|
|
widgetShowAll (treeWin mygui)
|
2014-11-14 23:32:16 +00:00
|
|
|
widgetHide (vbox7 mygui)
|
2014-11-29 22:45:53 +00:00
|
|
|
widgetHide (vbox10 mygui)
|
2014-11-15 23:10:57 +00:00
|
|
|
widgetHide (treeWin mygui)
|
2014-10-01 18:26:57 +00:00
|
|
|
mainGUI
|
|
|
|
|
|
|
|
|
2014-10-01 21:02:43 +00:00
|
|
|
-- |Pops up an error Dialog with the given String.
|
2014-10-01 18:26:57 +00:00
|
|
|
showErrorDialog :: String -> IO ()
|
|
|
|
showErrorDialog str = do
|
|
|
|
errorDialog <- messageDialogNew Nothing
|
|
|
|
[DialogDestroyWithParent]
|
|
|
|
MessageError
|
|
|
|
ButtonsClose
|
|
|
|
str
|
|
|
|
_ <- dialogRun errorDialog
|
|
|
|
widgetDestroy errorDialog
|
|
|
|
|
2014-10-01 18:52:07 +00:00
|
|
|
|
2014-11-14 23:32:16 +00:00
|
|
|
-- |May hide or show the widget that holds the quad tree path entry,
|
2014-11-15 02:58:38 +00:00
|
|
|
-- depending on the context and may also pop up the tree window.
|
2014-11-15 23:10:57 +00:00
|
|
|
onAlgoBoxChange :: MyGUI
|
|
|
|
-> IO ()
|
|
|
|
onAlgoBoxChange mygui = do
|
|
|
|
item <- comboBoxGetActive (algoBox mygui)
|
2014-11-15 02:58:38 +00:00
|
|
|
if item == 4
|
|
|
|
then do
|
2014-11-29 23:19:57 +00:00
|
|
|
widgetHide (vbox10 mygui)
|
2014-11-15 02:58:38 +00:00
|
|
|
widgetShow (vbox7 mygui)
|
2014-11-15 23:10:57 +00:00
|
|
|
widgetShow (treeWin mygui)
|
2014-11-29 23:02:58 +00:00
|
|
|
else
|
|
|
|
if item == 5
|
|
|
|
then do
|
2014-11-29 23:19:57 +00:00
|
|
|
widgetHide (vbox7 mygui)
|
2014-11-29 23:02:58 +00:00
|
|
|
widgetShow (vbox10 mygui)
|
|
|
|
widgetShow (treeWin mygui)
|
|
|
|
else do
|
2014-11-29 23:19:57 +00:00
|
|
|
widgetHide (vbox10 mygui)
|
2014-11-29 23:02:58 +00:00
|
|
|
widgetHide (vbox7 mygui)
|
|
|
|
widgetHide (treeWin mygui)
|
|
|
|
|
2014-11-14 23:32:16 +00:00
|
|
|
return ()
|
|
|
|
|
|
|
|
|
2014-10-01 21:02:43 +00:00
|
|
|
-- |Draws a Diagram which is built from a given file to
|
|
|
|
-- the gtk DrawingArea.
|
2014-10-11 00:01:17 +00:00
|
|
|
drawDiag :: MyGUI
|
|
|
|
-> IO ()
|
|
|
|
drawDiag mygui = do
|
2014-11-15 23:10:57 +00:00
|
|
|
fp <- fileChooserGetFilename (fileButton mygui)
|
2014-10-11 00:01:17 +00:00
|
|
|
case fp of
|
|
|
|
Just x -> do
|
|
|
|
ret <- saveAndDrawDiag x "" mygui
|
|
|
|
case ret of
|
|
|
|
1 -> showErrorDialog "No valid x/y dimensions!"
|
|
|
|
2 -> showErrorDialog "No valid Mesh file!"
|
|
|
|
_ -> return ()
|
2014-10-12 18:00:42 +00:00
|
|
|
Nothing -> return ()
|
2014-10-10 22:16:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Saves a Diagram which is built from a given file as an SVG.
|
2014-10-11 00:01:17 +00:00
|
|
|
saveDiag :: MyGUI
|
|
|
|
-> IO ()
|
|
|
|
saveDiag mygui = do
|
2014-11-15 23:10:57 +00:00
|
|
|
fp <- fileChooserGetFilename (fileButton mygui)
|
2014-10-11 00:01:17 +00:00
|
|
|
case fp of
|
|
|
|
Just x -> do
|
|
|
|
ret <- saveAndDrawDiag x "out.svg" mygui
|
|
|
|
case ret of
|
|
|
|
1 -> showErrorDialog "No valid x/y dimensions!"
|
|
|
|
2 -> showErrorDialog "No valid Mesh file!"
|
|
|
|
_ -> return ()
|
2014-10-12 18:00:42 +00:00
|
|
|
Nothing -> return ()
|
2014-10-10 22:16:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Draws and saves a Diagram which is built from a given file.
|
|
|
|
-- If the file to save is left empty, then nothing is saved.
|
|
|
|
saveAndDrawDiag :: FilePath -- ^ obj file to parse
|
|
|
|
-> FilePath -- ^ if/where to save the result
|
|
|
|
-> MyGUI
|
|
|
|
-> IO Int
|
|
|
|
saveAndDrawDiag fp fps mygui =
|
2014-11-15 23:25:17 +00:00
|
|
|
if (==) ".obj" . takeExtension $ fp
|
2014-10-05 01:12:58 +00:00
|
|
|
then do
|
2014-11-21 03:49:17 +00:00
|
|
|
mesh <- B.readFile fp
|
2014-11-15 23:10:57 +00:00
|
|
|
mainDrawWindow <- widgetGetDrawWindow (mainDraw mygui)
|
|
|
|
treeDrawWindow <- widgetGetDrawWindow (treeDraw mygui)
|
|
|
|
adjustment <- rangeGetAdjustment (ptScale mygui)
|
|
|
|
scaleVal <- adjustmentGetValue adjustment
|
|
|
|
xminEntryText <- entryGetText (xminEntry mygui)
|
|
|
|
xmaxEntryText <- entryGetText (xmaxEntry mygui)
|
|
|
|
yminEntryText <- entryGetText (yminEntry mygui)
|
|
|
|
ymaxEntryText <- entryGetText (ymaxEntry mygui)
|
|
|
|
algoActive <- comboBoxGetActive (algoBox mygui)
|
|
|
|
(daW, daH) <- widgetGetSize (mainDraw mygui)
|
|
|
|
(daTW, daTH) <- widgetGetSize (treeDraw mygui)
|
|
|
|
gridActive <- toggleButtonGetActive (gridCheckBox mygui)
|
|
|
|
coordTextActive <- toggleButtonGetActive (coordCheckBox mygui)
|
|
|
|
quadPathEntry' <- entryGetText (quadPathEntry mygui)
|
2014-11-29 22:45:53 +00:00
|
|
|
rxminEntryText <- entryGetText (rangeXminEntry mygui)
|
|
|
|
rxmaxEntryText <- entryGetText (rangeXmaxEntry mygui)
|
|
|
|
ryminEntryText <- entryGetText (rangeYminEntry mygui)
|
|
|
|
rymaxEntryText <- entryGetText (rangeYmaxEntry mygui)
|
2014-10-05 16:41:41 +00:00
|
|
|
|
2014-10-09 22:19:05 +00:00
|
|
|
let
|
2014-11-15 23:10:57 +00:00
|
|
|
xDim = (,) <$>
|
|
|
|
readMaybe xminEntryText <*>
|
|
|
|
readMaybe xmaxEntryText :: Maybe (Double, Double)
|
|
|
|
yDim = (,) <$>
|
|
|
|
readMaybe yminEntryText <*>
|
|
|
|
readMaybe ymaxEntryText :: Maybe (Double, Double)
|
2014-11-29 22:45:53 +00:00
|
|
|
rxDim = (,) <$>
|
|
|
|
readMaybe rxminEntryText <*>
|
|
|
|
readMaybe rxmaxEntryText :: Maybe (Double, Double)
|
|
|
|
ryDim = (,) <$>
|
|
|
|
readMaybe ryminEntryText <*>
|
|
|
|
readMaybe rymaxEntryText :: Maybe (Double, Double)
|
|
|
|
renderDiag winWidth winHeight buildDiag =
|
2014-11-15 02:58:38 +00:00
|
|
|
renderDia Cairo
|
|
|
|
(CairoOptions fps
|
|
|
|
(Dims (fromIntegral winWidth) (fromIntegral winHeight))
|
|
|
|
SVG False)
|
|
|
|
(buildDiag (def{
|
2014-11-15 23:10:57 +00:00
|
|
|
dotSize = scaleVal,
|
2014-11-29 22:45:53 +00:00
|
|
|
xDimension = fromMaybe (0, 500) xDim,
|
|
|
|
yDimension = fromMaybe (0, 500) yDim,
|
2014-11-15 23:10:57 +00:00
|
|
|
algo = algoActive,
|
|
|
|
haveGrid = gridActive,
|
|
|
|
showCoordText = coordTextActive,
|
2014-11-29 22:45:53 +00:00
|
|
|
quadPath = quadPathEntry',
|
|
|
|
rangeSquare = (fromMaybe (0, 500) rxDim,
|
|
|
|
fromMaybe (0, 500) ryDim)
|
|
|
|
})
|
2014-11-15 02:58:38 +00:00
|
|
|
mesh)
|
2014-11-29 22:45:53 +00:00
|
|
|
(s, r) = renderDiag daW daH diagS
|
|
|
|
(_, r') = renderDiag daTW daTH diagTreeS
|
2014-10-09 22:19:05 +00:00
|
|
|
|
2014-11-29 22:45:53 +00:00
|
|
|
renderWithDrawable mainDrawWindow r
|
|
|
|
renderWithDrawable treeDrawWindow r'
|
2014-11-15 02:58:38 +00:00
|
|
|
|
2014-11-29 22:45:53 +00:00
|
|
|
unless (null fps) s
|
|
|
|
return 0
|
2014-10-05 18:08:58 +00:00
|
|
|
|
|
|
|
else return 2
|