I am unable to get gtk's cell renderer combo box to display any options. I need to use a different model for each row in my tree, which should be possible with cellComboTextModel. My result is the combo box renderer is completely empty.
Here is a simplified example:
{-# OPTIONS_GHC -Wall #-}
module Main ( main ) where
import Graphics.UI.Gtk ( AttrOp( (:=) ) )
import qualified Graphics.UI.Gtk as Gtk
import System.Glib.Signals ( on )
-- the element in the list store
data ListElement =
ListElement
{ leName :: String
, leListStore :: Gtk.ListStore String
}
makeMyTreeview :: IO Gtk.TreeView
makeMyTreeview = do
-- make a list store with two elements
-- the storage for each combo box cell renderer
comboListStore0 <- Gtk.listStoreNew ["hi", "there"]
comboListStore1 <- Gtk.listStoreNew ["A", "B", "C"]
let elements =
[ ListElement "joe" comboListStore0
, ListElement "bob" comboListStore1
]
listStore <- Gtk.listStoreNew elements :: IO (Gtk.ListStore ListElement)
treeview <- Gtk.treeViewNewWithModel listStore :: IO Gtk.TreeView
Gtk.treeViewSetHeadersVisible treeview True
-- add some columns
nameCol <- Gtk.treeViewColumnNew
comboCol <- Gtk.treeViewColumnNew
Gtk.treeViewColumnSetTitle nameCol "name"
Gtk.treeViewColumnSetTitle comboCol "combo"
-- add cell renderers
nameRenderer <- Gtk.cellRendererTextNew
comboRenderer <- Gtk.cellRendererComboNew
Gtk.cellLayoutPackStart nameCol nameRenderer True
Gtk.cellLayoutPackStart comboCol comboRenderer True
_ <- Gtk.treeViewAppendColumn treeview nameCol
_ <- Gtk.treeViewAppendColumn treeview comboCol
-- this sets the name column strings
Gtk.cellLayoutSetAttributes nameCol nameRenderer listStore $
\x -> [Gtk.cellText := leName x ++ "!"]
-- combo box
Gtk.cellLayoutSetAttributes comboCol comboRenderer listStore $ \x ->
[ Gtk.cellComboTextModel := ( leListStore x, Gtk.makeColumnIdString 0 :: Gtk.ColumnId String String
)
, Gtk.cellMode := Gtk.CellRendererModeActivatable
, Gtk.cellComboHasEntry := True
]
-- an action when the combo box is edited
_ <- on comboRenderer Gtk.edited $ \_treePath newVal -> do
putStrLn "combo box is being edited (this never happens)"
putStrLn $ "new value: " ++ newVal
return treeview
main :: IO ()
main = do
_ <- Gtk.initGUI
win <- Gtk.windowNew
_ <- Gtk.set win [ Gtk.containerBorderWidth := 8
, Gtk.windowTitle := "help"
]
treeview <- makeMyTreeview
_ <- Gtk.set win [ Gtk.containerChild := treeview ]
Gtk.widgetShowAll win
Gtk.mainGUI
Any insight would be greatly appreciated.
EDIT:
I figured out what I was missing:
Gtk.treeModelSetColumn comboListStore0 (Gtk.makeColumnIdString 0) id
Gtk.treeModelSetColumn comboListStore1 (Gtk.makeColumnIdString 0) id
This sets the ListStore column ID so that the later line
Gtk.cellComboTextModel := (leListStore x, Gtk.makeColumnIdString 0 :: Gtk.ColumnId String String)
is referencing the text correctly. This does not seem to work in GTK3 however
I figured it out for GTK3. (See my edit above for the GTK2 solution.) For GTK3 you have to use the editingStarted signal to edit the combo box's liststore manually. Unfortunately this causes a segfault for GTK2.
Here is the complete working code for GTK3
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE PackageImports #-}
module Main ( main ) where
import qualified Data.Text as T
import "gtk3" Graphics.UI.Gtk ( AttrOp( (:=) ) )
import qualified "gtk3" Graphics.UI.Gtk as Gtk
import System.Glib.Signals ( on )
-- the element in the list store
data ListElement =
ListElement
{ leName :: String
, leListStore :: Gtk.ListStore String
, leOptions :: [String]
}
makeMyTreeview :: IO Gtk.TreeView
makeMyTreeview = do
-- the storage for each combo box cell renderer
comboListStore0 <- Gtk.listStoreNew []
comboListStore1 <- Gtk.listStoreNew []
Gtk.treeModelSetColumn comboListStore0 (Gtk.makeColumnIdString 0) id
Gtk.treeModelSetColumn comboListStore1 (Gtk.makeColumnIdString 0) id
let elements =
[ ListElement "joe" comboListStore0 ["hi", "there"]
, ListElement "bob" comboListStore1 ["A", "B", "C"]
]
listStore <- Gtk.listStoreNew elements :: IO (Gtk.ListStore ListElement)
treeview <- Gtk.treeViewNewWithModel listStore :: IO Gtk.TreeView
Gtk.treeViewSetHeadersVisible treeview True
-- add some columns
nameCol <- Gtk.treeViewColumnNew
comboCol <- Gtk.treeViewColumnNew
Gtk.treeViewColumnSetTitle nameCol "name"
Gtk.treeViewColumnSetTitle comboCol "combo"
-- add cell renderers
nameRenderer <- Gtk.cellRendererTextNew
comboRenderer <- Gtk.cellRendererComboNew
Gtk.cellLayoutPackStart nameCol nameRenderer True
Gtk.cellLayoutPackStart comboCol comboRenderer True
_ <- Gtk.treeViewAppendColumn treeview nameCol
_ <- Gtk.treeViewAppendColumn treeview comboCol
-- this sets the name column
Gtk.cellLayoutSetAttributes nameCol nameRenderer listStore $
\x -> [Gtk.cellText := leName x ++ "!"]
Gtk.cellLayoutSetAttributes comboCol comboRenderer listStore $ \x ->
[ Gtk.cellTextEditable := True
, Gtk.cellComboTextModel := (leListStore x, Gtk.makeColumnIdString 0 :: Gtk.ColumnId String String)
, Gtk.cellComboHasEntry := False
]
-- here is where we will set the combo options!!!!
_ <- on comboRenderer Gtk.editingStarted $ \widget treepath -> do
case treepath of
[k] -> do
listElement <- Gtk.listStoreGetValue listStore k
comboListStore <- Gtk.comboBoxSetModelText (Gtk.castToComboBox widget)
mapM_ (Gtk.listStoreAppend comboListStore . T.pack) (leOptions listElement)
ks -> error $ "bad treepath for liststore: " ++ show ks
-- an action when the combo box is edited
_ <- on comboRenderer Gtk.edited $ \_treePath newVal -> do
putStrLn $ "combo box edited, new value: " ++ newVal
return treeview
main :: IO ()
main = do
_ <- Gtk.initGUI
win <- Gtk.windowNew
_ <- Gtk.set win [ Gtk.containerBorderWidth := 8
, Gtk.windowTitle := "help"
]
treeview <- makeMyTreeview
_ <- Gtk.set win [ Gtk.containerChild := treeview ]
Gtk.widgetShowAll win
Gtk.mainGUI
Related
I am scraping https://books.toscrape.com using Haskell's Scalpel library. Here's my code so far:
import Text.HTML.Scalpel
import Data.List.Split (splitOn)
import Data.List (sortBy)
import Control.Monad (liftM2)
data Entry = Entry {entName :: String
, entPrice :: Float
, entRate :: Int
} deriving Eq
instance Show Entry where
show (Entry n p r) = "Name: " ++ n ++ "\nPrice: " ++ show p ++ "\nRating: " ++ show r ++ "/5\n"
entries :: Maybe [Entry]
entries = Just []
scrapePage :: Int -> IO ()
scrapePage num = do
items <- scrapeURL ("https://books.toscrape.com/catalogue/page-" ++ show num ++ ".html") allItems
let sortedItems = items >>= Just . sortBy (\(Entry _ a _) (Entry _ b _) -> compare a b)
>>= Just . filter (\(Entry _ _ r) -> r == 5)
maybe (return ()) (mapM_ print) sortedItems
allItems :: Scraper String [Entry]
allItems = chroots ("article" #: [hasClass "product_pod"]) $ do
p <- text $ "p" #: [hasClass "price_color"]
t <- attr "href" $ "a"
star <- attr "class" $ "p" #: [hasClass "star-rating"]
let fp = read $ flip (!!) 1 $ splitOn "£" p
let fStar = drop 12 star
return $ Entry t fp $ r fStar
where
r f = case f of
"One" -> 1
"Two" -> 2
"Three" -> 3
"Four" -> 4
"Five" -> 5
main :: IO ()
main = mapM_ scrapePage [1..10]
Basically, allItems scrapes for each book's title, price and rating, does some formatting for price to get a float, and returns it as a type Entry. scrapePage takes a number corresponding to the result page number, scrapes that page to get IO (Maybe [Entry]), formats it - in this case, to filter for 5-star books and order by price - and prints each Entry. main performs scrapePage over pages 1 to 10.
The problem I've run into is that my code scrapes, filters and sorts each page, whereas I want to scrape all the pages then filter and sort.
What worked for two pages (in GHCi) was:
i <- scrapeURL ("https://books.toscrape.com/catalogue/page-1.html") allItems
j <- scrapeURL ("https://books.toscrape.com/catalogue/page-2.html") allItems
liftM2 (++) i j
This returns a list composed of page 1 and 2's results that I could then print, but I don't know how to implement this for all 50 result pages. Help would be appreciated.
Just return the entry list without any processing (or you can do filtering in this stage)
-- no error handling
scrapePage :: Int -> IO [Entry]
scrapePage num =
concat . maybeToList <$> scrapeURL ("https://books.toscrape.com/catalogue/page-" ++ show num ++ ".html") allItems
Then you can process them later together
process = filter (\e -> entRate e == 5) . sortOn entPrice
main = do
entries <- concat <$> mapM scrapePage [1 .. 10]
print $ process entries
Moreover you can easily make your code concurrent with mapConcurrently from async package
main = do
entries <- concat <$> mapConcurrently scrapePage [1 .. 20]
print $ process entries
I have this piece of code and it works perfectly fine. It toggles some styles on the text input field depending on the field value.
numberInput :: (MonadWidget t m) => m (Dynamic t (Maybe Double))
numberInput = divClass "form-group" $ do
let errorState = "style" =: "border-color: red"
validState = "style" =: "border-color: green"
rec n <- textInput $ def & textInputConfig_inputType .~ "number"
& textInputConfig_initialValue .~ "0"
& textInputConfig_attributes .~ attrs
let result = fmap (readMay . unpack) $ _textInput_value n
attrs = fmap (maybe errorState (const validState)) result
return result
I ran into some problems making the parent element dynamic. I'd like to toggle the styles on the parent element of text input. I'd like to write something like but failed!
numberInput :: (MonadWidget t m) => m (Dynamic t (Maybe Double))
numberInput = do
rec
dynAttrs <- -- reference to textInput
elDynAttr "div" dynAttrs $ do
n <- textInput $ def & textInputConfig_inputType .~ "number"
& textInputConfig_initialValue .~ "0"
...
Thanks for helping out!
Here is a little program where the attributes (align right or left) of the parent element depend on the state of a child element:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecursiveDo #-}
import Reflex.Dom
import qualified Data.Text as T
import Safe
main :: IO ()
main = mainWidget bodyElement
bodyElement :: MonadWidget t m => m ()
bodyElement = el "div" $ do
el "h2" $ text "Dynamic parent element"
numberInput
return ()
numberInput :: (MonadWidget t m) => m (Dynamic t (Maybe Double))
numberInput = do
rec
let errorState = ("align" :: T.Text) =: "right"
validState = "align" =: "left"
let result = fmap (readMay . T.unpack) $ _textInput_value n
attrs = fmap (maybe errorState (const validState)) result
n <- elDynAttr "div" attrs $ do
n1 <- textInput $ def & textInputConfig_inputType .~ "number"
& textInputConfig_initialValue .~ "0"
text "Some additional control"
return n1
return result
As user user2407038 mentions, we have to return the numeric text element from an inner scope to the outer scope. Then we can use recursive do to access the element we define later in the program.
As soon as you type a letter into the numeric field, it will jump to the right.
Duplicating this from github as per #HeinrichApfelmus's suggestion:
This may be just a usage error on my part, but I am noticing a strange phenomenon when trying to set up conditional visibility/layout for dynamically created UI elements (in WX of course). As somewhat of a toy-example, I tried to create a widget that created StaticText elements on the fly and allowed the user to "browse" through these elements through '<' '>' buttons.
The problem I am noting is that all labels are invisible until a new one is created, at which point the current widget in focus becomes visible. Whether this is a bug or just a paradigm I am misusing, or a subtlety with reactive frameworks, I am unsure as to how to resolve this. Here is the code I have at this point, which exhibits the problem:
{-# LANGUAGE RecursiveDo #-}
module Test.Adder where
import Reactive.Banana
import Reactive.Banana.WX
import Graphics.UI.WX.Attributes
import Graphics.UI.WX hiding (Event, newEvent, empty, Identity)
import Graphics.UI.WXCore hiding (Event, Timer, empty, Identity, newEvent)
import Graphics.UI.WXCore.Frame
-- | Combine Unit-Events
anyEvent :: [Event ()] -> Event ()
anyEvent = foldl1 (unionWith (\_ _ -> ()))
-- | Unsugared if-then-else function
if_ :: Bool -> a -> a -> a
if_ True x _ = x
if_ False _ y = y
-- | Apply a function to the value at an index, or return a default value
-- if the index is out of range
(!?) :: (a -> b) -> b -> Int -> ([a] -> b)
(f!? ~y) n xs
| n < 0 = y
| otherwise = case drop n xs of
x:_ -> f x
[] -> y
main :: IO ()
main = start test
create :: Window w -> Int -> Behavior Int -> Event Int -> Event () -> MomentIO (StaticText ())
create t i bi ei eRef = do
let tx = replicate i '\t' ++ show i
x <- liftIO $ staticText t [ text := tx ]
let beq = (==i) <$> bi
let eMe = filterE (==i) ei
sink x [ visible :== beq ]
reactimate (refresh x <$ anyEvent [ eRef, () <$ eMe ])
return x
test :: IO ()
test = do
f <- frame [text := "Test"]
add <- button f [ text := "+" ]
prv <- button f [ text := "<" ]
cur <- staticText f []
nxt <- button f [ text := ">" ]
tab <- panel f [ clientSize := sz 200 300 ]
deb <- staticText f []
ref <- button f [ text := "refresh" ]
let networkDescription :: MomentIO ()
networkDescription = mdo
eAdd <- event0 add command
eRef <- event0 ref command
let bNotFirst = (>0) <$> bCur
bNotLast = (<) <$> bCur <*> bNext
sink prv [ enabled :== bNotFirst ]
sink cur [ text :== show <$> bCur ]
sink nxt [ enabled :== bNotLast ]
ePrev <- event0 prv command
eNext <- event0 nxt command
let eDelta :: Enum n => Event (n -> n)
eDelta = unions [ pred <$ whenE bNotFirst ePrev
, succ <$ whenE bNotLast eNext ]
eChange = flip ($) <$> bCur <#> eDelta
bCur <- stepper 0 $ eChange
(eIndex, bCount) <- mapAccum 0 ((\x -> (x, succ x)) <$ eAdd)
let bView = (\n i -> if_ (n==0) (0) i) <$> bCount <*> bCur
bNext = pred <$> bCount
eCreate = (\n -> create tab n bView eChange $ anyEvent [eRef,eAdd]) <$> eIndex
reCreate <- execute eCreate
bItemer <- accumB id $ flip (.) . (:) <$> reCreate
let bItems = ($[]) <$> bItemer
bThis = (widget!?(nullLayouts!!0)) <$> bCur <*> bItems
sink tab [ layout :== bThis ]
liftIO $ set f [ layout := column 5 [ margin 10 $ row 5 [ widget add
, widget prv
, widget cur
, widget nxt
, widget ref
]
, fill $ widget tab
]
]
network <- compile networkDescription
actuate network
>
I'm trying to make a combo box of pictures (as bellow) inside a treeview cell to make a selection.
I tried to use a cellRendererComboNew to render the combo but the options to fill the combobox cellComboTextModel := work only for String and I can't render pictures.
I tried to use a cellRendererPixbufNew. It render images but I can't perform a selection on it.
What is the correct approach to make that?
An example in Haskell, Python, or in any language would be very helpfull.
Best regards.
In PyGobject I came up with this solution. The example is fully functional but requires 2 png files in the same directory. I used two pngs with 100 x 20 pixel format.
The previous example used Gtk.ComboBox.new_with_model_and_entry() and I was missing the set_entry_text_colum() function that has to go with such kind of combobox.
#!/usr/bin/python3
from gi.repository import Gtk, Gdk, GdkPixbuf
class ComboBoxWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="ComboBox Pixbuf Example")
self.set_border_width(10)
store = Gtk.ListStore(str, GdkPixbuf.Pixbuf)
solid_line = GdkPixbuf.Pixbuf.new_from_file("solid_line.png")
store.append(["1", solid_line])
dashed_line = GdkPixbuf.Pixbuf.new_from_file("dashed_line.png")
store.append(["2", dashed_line])
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
combo = Gtk.ComboBox.new_with_model(store)
rend_int = Gtk.CellRendererText()
rend_pixbuf = Gtk.CellRendererPixbuf()
combo.pack_start(rend_int, False)
combo.add_attribute(rend_int, "text", 0)
combo.pack_start(rend_pixbuf, True)
combo.add_attribute(rend_pixbuf, "pixbuf", 1)
combo.connect("changed", self.on_combo_changed)
vbox.pack_start(combo, False, False, 0)
self.add(vbox)
def on_combo_changed(self, combo):
tree_iter = combo.get_active_iter()
if tree_iter != None:
model = combo.get_model()
row = model[tree_iter][0]
print("Selected row {0}".format(row))
win = ComboBoxWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Similar question:
Show icon or color in Gtk TreeView tree
Source:
http://python-gtk-3-tutorial.readthedocs.org/en/latest/combobox.html
This is my solution in Haskell:
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.String
import Graphics.UI.Gtk
import qualified Data.Map as Map
import qualified Data.Text as T
import Data.Maybe
import qualified Graphics.UI.Gtk.Gdk.Pixbuf as Pixbuf
import Control.Monad
colorsRawL = [(0,0,0),(254,26,89),(255,0,0),(0,255,0),(0,0,255),(255,255,0),(0,255,255),(255,0,255),(192,192,192),(128,128,128),(128,0,0),(128,128,0),(0,128,0),(128,0,128),(0,128,128),(0,0,128)]
manufacturers = [("Sony"::String), ("LG"::String), ("Panasonic"::String), ("Toshiba"::String), ("Nokia"::String), ("Samsung"::String)]
data ListElement = ListElement { name :: String , selected::Pixbuf }
getManufacturers::IO[ListElement]
getManufacturers = mapM (\x -> do
pbn <- Pixbuf.pixbufNew ColorspaceRgb False 8 16 16
Pixbuf.pixbufFill pbn 255 255 255 1
let el = ListElement x pbn
return el
) manufacturers
pixBufListS::IO [(String,Pixbuf)]
pixBufListS = mapM (\(r,g,b)-> do
pbn <- Pixbuf.pixbufNew ColorspaceRgb False 8 16 16
Pixbuf.pixbufFill pbn r g b 1
let name::String = ("Color ("++(show r)++" "++(show g)++" "++(show b)++ ")")
return (name,pbn)
) colorsRawL
getMap::IO (Map.Map String Pixbuf)
getMap = do
list <- pixBufListS
let mp = Map.fromList list
return mp
main :: IO ()
main = do
initGUI
window <- windowNew
fixed <- fixedNew
pixList <-pixBufListS
manus <- getManufacturers
lststoreManus::(ListStore ListElement) <- listStoreNew manus
treeview <- treeViewNew
treeViewSetModel treeview lststoreManus
treeViewSetHeadersVisible treeview True
colName <- treeViewColumnNew
imgCol <- treeViewColumnNew
colCombo <- treeViewColumnNew
treeViewColumnSetTitle imgCol ("Image column"::T.Text )
treeViewColumnSetTitle colName ("String column"::T.Text )
treeViewColumnSetTitle colCombo ("Combo"::T.Text )
iconRenderer <- cellRendererPixbufNew
renderer1 <- cellRendererTextNew
comboRenderer <- cellRendererComboNew
cellLayoutPackStart imgCol iconRenderer True
cellLayoutPackStart colName renderer1 True
cellLayoutPackStart colCombo comboRenderer True
cellLayoutSetAttributes imgCol iconRenderer lststoreManus $ (\ListElement { selected = t } -> [cellPixbuf := t])
cellLayoutSetAttributes colName renderer1 lststoreManus $ \row -> [ cellText := name row ]
cellLayoutSetAttributeFunc colCombo comboRenderer lststoreManus $
(\iter -> do (tmodel, colid) <- comboTextModel
(ListElement a b) <- treeModelGetRow lststoreManus iter
set comboRenderer [ cellVisible := True
, cellComboTextModel := (tmodel, colid)
, cellTextEditable := True
, cellComboHasEntry := False
, cellText := ("Choose pixbuf"::String)])
treeViewAppendColumn treeview colName
treeViewAppendColumn treeview imgCol
treeViewAppendColumn treeview colCombo
_ <- on comboRenderer editingStarted $ \widget treepath -> do
case treepath of
[k] -> do
let comboPix::ComboBox = castToComboBox widget
lststorerep::(ListStore (String,Pixbuf)) <- listStoreNew pixList
customStoreSetColumn lststorerep (makeColumnIdString 0) fst
customStoreSetColumn lststorerep (makeColumnIdPixbuf 1) snd
comboBoxSetModel comboPix (Just lststorerep)
rendertxt <- cellRendererTextNew
renderpic <- cellRendererPixbufNew
cellLayoutPackStart comboPix rendertxt False
cellLayoutPackStart comboPix renderpic True
cellLayoutAddColumnAttribute comboPix renderpic cellPixbuf $ makeColumnIdPixbuf 1
_ <- on comboRenderer edited $ \_treePath newStringValue -> do
case _treePath of
[k] -> do
(ListElement a b) <- listStoreGetValue lststoreManus k
myMap <- getMap
let finded = fromJust ( Map.lookup newStringValue myMap )
let toStore = ListElement a finded
listStoreSetValue lststoreManus k toStore
putStrLn $ "new value: " ++ newStringValue
fixedPut fixed treeview (10,10)
widgetSetSizeRequest treeview 500 100
containerAdd window fixed
onDestroy window mainQuit
windowSetDefaultSize window 600 500
windowSetPosition window WinPosCenter
widgetShowAll window
mainGUI
comboTextModel = do store <- listStoreNew []
let column = makeColumnIdString 0 :: ColumnId String String
return (store, column)
{-
dependencies :
- base >= 4.7 && < 5
- gtk
- text
- containers
-}
I have written a small application which tracks my progress in TV Series. The application is written in Haskell with functional reactive programming (FRP) with reactive banana.
The application can:
add/remove new TV Series to the table
change the season and episode of an series
I have problems writing the code that adds a new TV series to the table and wires the new events. The CRUD example from here didn't quite help me because I have more requirements then just selecting an element from the list.
How do I write a reactiveTable function like the reactiveListDisplay function from the CRUD Example in a FRP way? How can events be added for the remove button and the season and episode spin buttons after the network has been compiled?
data Series = Series { name :: String
, season :: Int
, episode :: Int
}
insertIntoTable :: TableClass t => t -> SeriesChangeHandler -> SeriesRemoveHandler -> Series -> IO ()
insertIntoTable table changeHandler removeHandler (Series name s e) = do
(rows, cols) <- tableGetSize table
tableResize table (rows+1) cols
nameLabel <- labelNew $ Just name
adjustmentS <- adjustmentNew (fromIntegral s) 1 1000 1 0 0
adjustmentE <- adjustmentNew (fromIntegral e) 1 1000 1 0 0
seasonButton <- spinButtonNew adjustmentS 1.0 0
episodeButton <- spinButtonNew adjustmentE 1.0 0
removeButton <- buttonNewWithLabel "remove"
let getSeries = do
s <- spinButtonGetValue seasonButton
e <- spinButtonGetValue episodeButton
return $ Series name (round s) (round e)
handleSeries onEvent widget handler = do
onEvent widget $ do
series <- getSeries
handler series
handleSeries onValueSpinned seasonButton changeHandler
handleSeries onValueSpinned episodeButton changeHandler
onPressed removeButton $ do
series <- getSeries
containerRemove table nameLabel
containerRemove table seasonButton
containerRemove table episodeButton
containerRemove table removeButton
removeHandler series
let tadd widget x = tableAdd table widget x (rows - 1)
tadd nameLabel 0
tadd seasonButton 1
tadd episodeButton 2
tadd removeButton 3
widgetShowAll table
main :: IO ()
main = do
initGUI
window <- windowNew
scroll <- scrolledWindowNew Nothing Nothing
table <- tableNew 1 5 True
addButton <- buttonNewWithLabel "add series"
vbox <- vBoxNew False 10
containerAdd window vbox
boxPackStart vbox addButton PackNatural 0
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
addEvent <- eventButton addButton
(changeHandler,fireChange) <- liftIO $ newAddHandler
changeEvent <- fromAddHandler changeHandler
(removeHandler,fireRemove) <- liftIO $ newAddHandler
removeEvent <- fromAddHandler removeHandler
let insertIntoTable' = insertIntoTable table fireChange fireRemove
addSeries e = do
s <- addSeriesDialog
liftIO $ insertIntoTable' s
liftIO $ mapM_ insertIntoTable' initSeries
reactimate $ addSeries <$> addEvent
reactimate $ updateSeries conn <$> changeEvent
reactimate $ removeSeries conn <$> removeEvent
network <- compile networkDescription
actuate network
onDestroy window $ do
D.disconnect conn
mainQuit
widgetShowAll window
mainGUI
I want to refactor the insertIntoTable method to use events and behaviors rather than using simple callbacks.
EDIT:
I have tried the gtk TreeView with a ListStore backend. In this scenario you don't need dynamic event switching. I have written the reactiveList function below to get a list behavior out of insert, change and remove events. It works ^^
reactiveList :: (Frameworks t)
=> ListStore a
-> Event t (Int,a) -- insert event
-> Event t (Int,a) -- change event
-> Event t (Int,a) -- remove event
-> Moment t (Behavior t [a])
reactiveList store insertE changeE removeE = do
(listHandler,fireList) <- liftIO $ newAddHandler
let onChange f (i,a) = do
f i a
list <- listStoreToList store
fireList list
reactimate $ onChange (listStoreInsert store) <$> insertE
reactimate $ onChange (listStoreSetValue store) <$> changeE
reactimate $ onChange (const . listStoreRemove store) <$> removeE
initList <- liftIO $ listStoreToList store
fromChanges initList listHandler
main :: IO ()
main = do
initGUI
window <- windowNew
addButton <- buttonNewWithLabel "add series"
vbox <- vBoxNew False 10
seriesList <- listStoreNew (initSeries :: [Series])
listView <- treeViewNewWithModel seriesList
treeViewSetHeadersVisible listView True
let newCol title newRenderer f = do
col <- treeViewColumnNew
treeViewColumnSetTitle col title
renderer <- newRenderer
cellLayoutPackStart col renderer False
cellLayoutSetAttributes col renderer seriesList f
treeViewAppendColumn listView col
return renderer
newCol "Image" cellRendererPixbufNew $ \s -> [cellPixbuf :=> newPixbuf s]
newCol "Name" cellRendererTextNew $ \s -> [cellText := name s]
seasonSpin <- newCol "Season" cellRendererSpinNew $ \s ->
[ cellRendererSpinAdjustment :=> adjustmentNew (fromIntegral (season s)) 1 1000 1 0 0
, cellText := (show $ season s)
, cellTextEditable := True
]
episodeSpin <- newCol "Episode" cellRendererSpinNew $ \s ->
[ cellRendererSpinAdjustment :=> adjustmentNew (fromIntegral (episode s)) 1 1000 1 0 0
, cellText := (show $ episode s)
, cellTextEditable := True
]
containerAdd window vbox
boxPackStart vbox listView PackGrow 0
boxPackStart vbox addButton PackNatural 0
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
(addHandler,fireAdd) <- liftIO $ newAddHandler
maybeSeriesE <- fromAddHandler addHandler
(removeHandler,fireRemove) <- liftIO $ newAddHandler
removeE <- fromAddHandler removeHandler
-- when the add button was pressed,
-- open a dialog and return maybe a new series
askSeriesE <- eventButton addButton
reactimate $ (const $ fireAdd =<< askSeries) <$> askSeriesE
-- ommit all nothing series
let insertE = filterJust maybeSeriesE
insert0E = ((,) 0) <$> insertE
seasonSpinE <- eventSpin seasonSpin seriesList
episodeSpinE <- eventSpin episodeSpin seriesList
let changeSeason (i,d,s) = (i,s {season = round d})
changeEpisode (i,d,s) = (i,s {episode = round d})
let changeE = (changeSeason <$> seasonSpinE) `union` (changeEpisode <$> episodeSpinE)
listB <- reactiveList seriesList insert0E changeE removeE
listE <- (changes listB)
reactimate $ (putStrLn . unlines . map show) <$> listE
reactimate $ insertSeries conn <$> insertE
reactimate $ updateSeries conn . snd <$> changeE
reactimate $ removeSeries conn . snd <$> removeE
return ()
network <- compile networkDescription
actuate network
onDestroy window $ do
D.disconnect conn
mainQuit
widgetShowAll window
mainGUI
I'm open for comments and suggestions.
It sounds like your problem is much closer to the Bar Tab example than the CRUD one.
The basic idea for adding new widgets--along with new behaviors and events--is to use so-called "dynamic event switching". Essentially, this is a way to put newly created events and behaviors back into your network.
The action to create a new widget has two parts. The first part is to just create the widget, using liftIO. The second is to get its inputs and use trimE or trimB as appropriate. Leaving out most of the GTk-specific details (I don't know how to use GTk :P), it'll look something like this:
let newSeries name = do
label <- liftIO . labelNew $ Just name
liftIO $ tadd labelNew 0
{- ... the rest of your controls here ... -}
seasonNumber <- trimB $ getSpinButtonBehavior seasonButton
{- ... wrap the rest of the inputs using trimB and trimE ... -}
return (label, seasonNumber, ...)
So this function creates a new widget, "trims" its inputs and returns the values to you. Now you have to actually use these values:
newSeasons <- execute (FrameworkMoment newSeries <$> nameEvents)
here nameEvents should be an Event String containing an event with the name of the new series each time you want to add it.
Now that you have a stream of all of the new seasons, you can combine it all into a single behavior of a list of entries using something like stepper.
For more details--including things like getting the aggregate information out of all of your widgets--look at the actual example code.