packages feed

coin-1.1: src/Coin/UI/Widgets/CheckList.hs

{-
 *  Programmer:	Piotr Borek
 *  E-mail:     piotrborek@op.pl
 *  Copyright 2016 Piotr Borek
 *
 *  Distributed under the terms of the GPL (GNU Public License)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-}

module Coin.UI.Widgets.CheckList (
    CheckListWidget,
    checkListNew,
    checkListAppend,
    checkListClear,
    checkListGetSelected,
    checkListGetSelectionIndex,
    checkListSetSelectionIndex
) where

import qualified System.Glib.GObject as Gtk
import qualified Graphics.UI.Gtk as Gtk
import Graphics.UI.Gtk ( AttrOp(..) )

import qualified Data.Text as T

import Control.Monad
import Data.Foldable
import Data.IORef

import Coin.Locale.Translate
import Coin.UI.Builder.GtkUIBuilder

data CheckListWidget a = CheckListWidget {
    checkListRoot     :: Gtk.Widget,
    checkListStore    :: Gtk.ListStore (Bool, T.Text),
    checkListOriginal :: IORef [a],
    checkListSelected :: IORef [Int],
    checkListToText   :: a -> T.Text
}

instance Gtk.GObjectClass (CheckListWidget a) where
    toGObject = Gtk.toGObject . checkListRoot
    unsafeCastGObject = undefined

instance Gtk.WidgetClass (CheckListWidget a)

reserved :: Int
reserved = 2

checkListNew :: String -> (a -> T.Text) -> IO (CheckListWidget a)
checkListNew name toText = do
    store <- Gtk.listStoreNew []
    treeView <- Gtk.treeViewNewWithModel store
    Gtk.treeViewSetHeadersVisible treeView False

    col1 <- Gtk.treeViewColumnNew
    col2 <- Gtk.treeViewColumnNew

    cell1 <- Gtk.cellRendererToggleNew
    Gtk.cellRendererToggleSetRadio cell1 False
    cell2 <- Gtk.cellRendererTextNew

    Gtk.cellLayoutSetAttributes col1 cell1 store $ \(x, _) -> [ Gtk.cellToggleActive := x ]
    Gtk.cellLayoutSetAttributes col2 cell2 store $ \(_, x) -> [ Gtk.cellText := x ]

    Gtk.treeViewColumnPackStart col1 cell1 True
    Gtk.treeViewColumnPackStart col2 cell2 True

    forM_ [col1, col2] $ Gtk.treeViewAppendColumn treeView

    (_, root) <- uiBuildGtk $
        vbox Nothing False 2 $ do
            pack Gtk.PackNatural 2 $ do
                labelAttrs [ Gtk.miscXalign := 0.0 ]
                label Nothing $ " " ++ name
            scrolledWindow Nothing $ putWidget treeView

    selectedList <- newIORef []
    originalList <- newIORef []
    let checkList = CheckListWidget root store originalList selectedList toText

    checkListClear checkList

    void $ Gtk.on cell1 Gtk.cellToggled $ \s -> do
        let i = read s
        (selected, _) <- Gtk.listStoreGetValue store i
        if | i == 0    -> if selected then
                              checkListLoadState checkList
                          else do
                              checkListSaveState checkList
                              checkListSelectAll checkList
                              checkListModifySelection checkList 0 $ const True
           | i == 1    -> if selected then
                              checkListLoadState checkList
                          else do
                              checkListSaveState checkList
                              checkListDeselectAll checkList
                              checkListModifySelection checkList 1 $ const True
           | otherwise -> do
                              checkListModifySelection checkList 0 $ const False
                              checkListModifySelection checkList 1 $ const False
                              checkListModifySelection checkList i not

    return checkList

checkListSaveState :: CheckListWidget a -> IO ()
checkListSaveState checkList = do
    indexes <- checkListGetSelectionIndex checkList
    modifyIORef' (checkListSelected checkList) $ const indexes

checkListLoadState :: CheckListWidget a -> IO ()
checkListLoadState checkList = do
    indexes <- readIORef $ checkListSelected checkList
    checkListSetSelectionIndex checkList indexes

checkListModifySelection :: CheckListWidget a -> Int -> (Bool -> Bool) -> IO ()
checkListModifySelection checkList i f = do
    (selected, text) <- Gtk.listStoreGetValue (checkListStore checkList) i
    Gtk.listStoreSetValue (checkListStore checkList) i (f selected, text)

checkListDeselectAll :: CheckListWidget a -> IO ()
checkListDeselectAll checkList = do
    size <- Gtk.listStoreGetSize $ checkListStore checkList
    forM_ [0 .. size - 1] $ \k -> checkListModifySelection checkList k $ const False

checkListSelectAll :: CheckListWidget a -> IO ()
checkListSelectAll checkList = do
    size <- Gtk.listStoreGetSize $ checkListStore checkList
    forM_ [0 .. size - 1] $ \k -> checkListModifySelection checkList k $ const True
    checkListModifySelection checkList 0 $ const False
    checkListModifySelection checkList 1 $ const False

checkListGetSelected :: CheckListWidget a -> IO [a]
checkListGetSelected checkList = do
    indexes <- checkListGetSelectionIndex checkList
    elements <- readIORef $ checkListOriginal checkList
    forM indexes $ \i -> return $ elements !! i

checkListSetSelectionIndex :: CheckListWidget a -> [Int] -> IO ()
checkListSetSelectionIndex checkList indexes = do
    checkListDeselectAll checkList
    forM_ indexes $ \k -> checkListModifySelection checkList (k + reserved) $ const True

checkListGetSelectionIndex :: CheckListWidget a -> IO [Int]
checkListGetSelectionIndex checkList = do
    size <- Gtk.listStoreGetSize $ checkListStore checkList
    foldrM (\i acc -> do
                (sel, _) <- Gtk.listStoreGetValue (checkListStore checkList) i
                if sel then
                    return (i - reserved: acc)
                else
                    return acc)
           []
           [reserved .. size - 1]

checkListAppend' :: CheckListWidget a -> Bool -> T.Text -> IO ()
checkListAppend' checkList toggle text = void $ Gtk.listStoreAppend (checkListStore checkList) (toggle, text)

checkListAppend :: CheckListWidget a -> Bool -> a -> IO ()
checkListAppend checkList toggle entity = do
    checkListAppend' checkList toggle (checkListToText checkList entity)
    modifyIORef' (checkListOriginal checkList) (++[entity])

checkListClear :: CheckListWidget a -> IO ()
checkListClear checkList = do
    Gtk.listStoreClear $ checkListStore checkList
    checkListAppend' checkList False (T.pack $ __"Select all")
    checkListAppend' checkList False (T.pack $ __"Deselect all")