Can someone please point me in the right direction when it comes to changing properties of an element in Gtk2Hs.
For example, how do I change the background-color of a DrawingArea?
There are various methods for modifying a widget's style. For example to modify the background style you can use widgetModifyBg (corresponding to the C function gtk_widget_modify_bg()). In principle, if you change the style for one state (e.g. StateNormal) then you should also change it for the others.
Y would suggest you describe the styles you want in an RC file, and then load that file from your application, but it seems that functions like gtk_rc_parse() are not bound in gtk2hs.
Here's an example:
import Graphics.UI.Gtk
main = do
initGUI
window <- windowNew
window `onDestroy` mainQuit
drawingArea <- drawingAreaNew
window `containerAdd` drawingArea
widgetModifyBg drawingArea StateNormal (Color 0xffff 0 0)
widgetShowAll window
mainGUI
If you need to do custom drawing based on a widget's styles, you can do that using widgetGetState, the widgetStyle property and the styleGet* family of functions (e.g. styleGetText). Here's an example of that:
import Graphics.Rendering.Cairo
import Graphics.UI.Gtk hiding (fill)
import Graphics.UI.Gtk.Gdk.Events (Event(Expose))
expose widget rect = do
state <- widgetGetState widget
style <- widget `get` widgetStyle
(Color red green blue) <- styleGetText style state
drawWindow <- widgetGetDrawWindow widget
renderWithDrawable drawWindow $ do
moveTo 50 50
setFontSize 20
setSourceRGB (fromIntegral red / 0xffff)
(fromIntegral green / 0xffff)
(fromIntegral blue / 0xffff)
showText "O HAI"
fill
return False
main = do
initGUI
window <- windowNew
window `onDestroy` mainQuit
drawingArea <- drawingAreaNew
drawingArea `onExpose` \(Expose sent area region count) ->
expose drawingArea area
window `containerAdd` drawingArea
widgetShowAll window
mainGUI
Related
I've done this polyhedron with Haskell OpenGL:
The sides of the polyhedron are transparent:
renderPrimitive Quads $ do
materialDiffuse Front $= col
......
where col is a color with "a" component equal to 0.2.
And in the main function, the blending is enabled:
blend $= Enabled
blendFunc $= (SrcAlpha, OneMinusSrcAlpha)
One can see that the sides are indeed transparent. However the edges of the polyhedron do not appear in the interior. Why that? I have tried numerous initialDisplayMode, such as:
initialDisplayMode $= [RGBAMode, DoubleBuffered, WithDepthBuffer, WithAlphaComponent]
I have these material colors and light colors:
clearColor $= Color4 0 0 0 0
materialSpecular Front $= white
materialShininess Front $= 50
lighting $= Enabled
light (Light 0) $= Enabled
position (Light 0) $= Vertex4 0 0 100 1
ambient (Light 0) $= white
diffuse (Light 0) $= white
specular (Light 0) $= white
I'm trying to create an app which takes the screenshot of running application.
I've used code from this answer https://stackoverflow.com/a/11966931/2064205.
I have a problem with the createBMPFile function, it saves black images. I've tried to create the similar app in C++, also has the same problem.
I'm new in Haskell, do not pay attention to the code.
import Graphics.Win32.Window
import Graphics.Win32.GDI.Bitmap
import Graphics.Win32.GDI.HDC
import Graphics.Win32.GDI.Graphics2D
import Graphics.Win32.Window.ForegroundWindow
import Graphics.Win32.GDI.Types (HWND)
import Foreign.Ptr
getValueFromMaybe :: Maybe HWND -> HWND
getValueFromMaybe mx =
case mx of
Just value -> value
Nothing -> nullPtr
main = do
desktop <- findWindowByName "slack" -- Grab the Hwnd of the window with title "slack", GetDC 0, GetDC NULL etc all work too
setForegroundWindow . getValueFromMaybe $ desktop
desktop <- findWindowByName "slack"
hdc <- getWindowDC desktop -- Get the dc handle of the desktop
(x,y,r,b) <- getWindowRect . getValueFromMaybe $ desktop -- Find the size of the desktop so we can know which size the destination bitmap should be
-- (left, top, right, bottom)
newDC <- createCompatibleDC (Just hdc) -- Create a new DC to hold the copied image. It should be compatible with the source DC
let width = r - x -- Calculate the width
let height = b - y -- Calculate the Height
newBmp <- createCompatibleBitmap hdc width height -- Create a new Bitmap which is compatible with the newly created DC
selBmp <- selectBitmap newDC newBmp -- Select the Bitmap into the DC, drawing on the DC now draws on the bitmap as well
bitBlt newDC 0 0 width height hdc 0 0 sRCCOPY -- use SRCCOPY to copy the desktop DC into the newDC
createBMPFile "Foo.bmp" newBmp newDC -- Write out the new Bitmap file to Foo.bmp
putStrLn "Bitmap image copied" -- Some debug message
deleteBitmap selBmp -- Cleanup the selected bitmap
deleteBitmap newBmp -- Cleanup the new bitmap
deleteDC newDC -- Cleanup the DC we created.
I'm experimenting with the sample program at https://github.com/gtk2hs/gtk2hs/blob/master/gtk/demo/hello/World.hs, reproduced below:
-- A simple program to demonstrate Gtk2Hs.
module Main (Main.main) where
import Graphics.UI.Gtk
main :: IO ()
main = do
initGUI
-- Create a new window
window <- windowNew
-- Here we connect the "destroy" event to a signal handler.
-- This event occurs when we call widgetDestroy on the window
-- or if the user closes the window.
on window objectDestroy mainQuit
-- Sets the border width and tile of the window. Note that border width
-- attribute is in 'Container' from which 'Window' is derived.
set window [ containerBorderWidth := 10, windowTitle := "Hello World" ]
-- Creates a new button with the label "Hello World".
button <- buttonNew
set button [ buttonLabel := "Hello World" ]
-- When the button receives the "clicked" signal, it will call the
-- function given as the second argument.
on button buttonActivated (putStrLn "Hello World")
-- Gtk+ allows several callbacks for the same event.
-- This one will cause the window to be destroyed by calling
-- widgetDestroy. The callbacks are called in the sequence they were added.
on button buttonActivated $ do
putStrLn "A \"clicked\"-handler to say \"destroy\""
widgetDestroy window
-- Insert the hello-world button into the window.
set window [ containerChild := button ]
-- The final step is to display this newly created widget. Note that this
-- also allocates the right amount of space to the windows and the button.
widgetShowAll window
-- All Gtk+ applications must have a main loop. Control ends here
-- and waits for an event to occur (like a key press or mouse event).
-- This function returns if the program should finish.
mainGUI
If I build and run this on Mac OS X, Cmd-Q or the Quit command from the app's menu does not close the application. How do I trap this event and cause it to close the app?
Update
I've added a gtk3-mac-integration dependency to my project, an import Graphics.UI.Gtk.OSX to my source file and the following immediately after calling initGUI:
app <- applicationNew
on app willTerminate (return ())
I'm definitely missing something as this doesn't seem to do anything (see https://github.com/rcook/gtkapp/commit/8531509d0648ddb657633a33773c09bc5a576014).
Update no. 2
Thanks to #Jack Henahan and OSXDemo.hs, I now have a working solution:
-- A simple program to demonstrate Gtk2Hs.
module Main (Main.main) where
import Control.Exception
import Control.Monad
import Graphics.UI.Gtk
import Graphics.UI.Gtk.OSX
showDialog :: Window -> String -> String -> IO ()
showDialog window title message = bracket
(messageDialogNew (Just window) [] MessageInfo ButtonsOk message)
widgetDestroy
(\d -> do
set d [ windowTitle := title ]
void $ dialogRun d)
main :: IO ()
main = do
void initGUI
-- Create a new window
window <- windowNew
-- Here we connect the "destroy" event to a signal handler.
-- This event occurs when we call widgetDestroy on the window
-- or if the user closes the window.
void $ on window objectDestroy mainQuit
-- Sets the border width and tile of the window. Note that border width
-- attribute is in 'Container' from which 'Window' is derived.
set window [ containerBorderWidth := 10, windowTitle := "Hello World" ]
-- Creates a new button with the label "Hello World".
button <- buttonNew
set button [ buttonLabel := "Hello World" ]
-- When the button receives the "clicked" signal, it will call the
-- function given as the second argument.
void $ on button buttonActivated (putStrLn "Hello World")
void $ on button buttonActivated $ showDialog window "THE-TITLE" "THE-MESSAGE"
-- Gtk+ allows several callbacks for the same event.
-- This one will cause the window to be destroyed by calling
-- widgetDestroy. The callbacks are called in the sequence they were added.
void $ on button buttonActivated $ do
putStrLn "A \"clicked\"-handler to say \"destroy\""
widgetDestroy window
-- Insert the hello-world button into the window.
set window [ containerChild := button ]
-- The final step is to display this newly created widget. Note that this
-- also allocates the right amount of space to the windows and the button.
widgetShowAll window
app <- applicationNew
-- blockTermination: return True to prevent quit, False to allow
on app blockTermination $ do
putStrLn "blockTermination"
return False
-- willTerminate: handle clean-up etc.
on app willTerminate $ do
putStrLn "willTerminate"
menuBar <- menuBarNew
applicationSetMenuBar app menuBar
applicationReady app
-- All Gtk+ applications must have a main loop. Control ends here
-- and waits for an event to occur (like a key press or mouse event).
-- This function returns if the program should finish.
mainGUI
You need to send an NSApplicationWillTerminate signal.
willTerminate :: ApplicationClass self => Signal self (IO ())
willTerminate = Signal (connect_NONE__NONE "NSApplicationWillTerminate")
is how it's handled in gtk-mac-integration.
I need to do some action while button is pressed. How can I do it?
I have version 0.12.4.
P. S.:
For some reason, onButtonActivate in
import Graphics.UI.Gtk
import Control.Concurrent
main = do
initGUI
window <- windowNew
but <-buttonNewWithLabel "Write A"
onButtonActivate but $ do
putStr "A"
threadDelay 1000000
return()
containerAdd window but
widgetShowAll window
onDestroy window mainQuit
mainGUI
do not do anything.
Also, it's good to go, if action will be done repeatedly while pressed some key on keyboard.
According to the docs onButtonActivate is depreciated so you probably shouldn't use it. Im having trouble finding the correct way though, there probably are some generics signals somewhere that you should use. You can try my solution that uses onPressed and onRelease (these are also noted as depreciated). You could do as suggested in the comment and fork a thread:
import Control.Concurrent (forkIO, killThread, threadDelay)
import Control.Monad (void, when)
whilePressed button action = do
onPressed button $ do
threadId <- forkIO go
onReleased button (killThread threadId)
where
go = do
action
threadDelay 1000000
go
Then rewrite your main to do:
whilePressed but (putStr "A")
Im not sure if this is safe though, as it seems it could be possible for the buttonReleased event to be fired before killThread is registered. It might be safer to use an IORef:
import Data.IORef
whilePressed button action = do
isPressed <- newIORef False
onPressed button $ do
writeIORef isPressed True
void $ forkIO $ go isPressed
onReleased button (writeIORef isPressed False)
where
go isPressed = do
c <- readIORef isPressed
when c $ do
action
threadDelay 1000000
void $ forkIO $ go isPressed
Unfortunately I haven't compiled or tested the code sine I cannot install GTK on this computer, but let me know if you have any issues.
This uses a ToggleButton, a "timeout" object (see timeoutAdd in the module System.Glib.MainLoop in the docs) and an IORef. A timer is started when the ToggleButton is pressed down, but only if no other timer is currently running (that's the purpose of the IORef). If the button is released, the timer is stopped. The timer's callback function returns IO False to stop and destroy the timer object.
import Graphics.UI.Gtk
import Data.IORef
import Control.Monad(void, when)
action but idle = do
butDown <- get but toggleButtonActive
if butDown
then do
putStrLn "A"
writeIORef idle False
return True
else do
writeIORef idle True
return False
main = do
initGUI
window <- windowNew
but <-toggleButtonNewWithLabel "Write A"
idle <- newIORef True
on but toggled $ do
butDown <- get but toggleButtonActive
isIdle <- readIORef idle
when (butDown && isIdle) $ void $ timeoutAdd (action but idle) 1000
containerAdd window but
widgetShowAll window
on window objectDestroy mainQuit
mainGUI
The preferred way of registering signal callbacks is using "on". Also note that "on window objectDestroy mainQuit" correctly destroys the window and stops the Gtk main loop (you version didn't destroy the timers in GHCi, they kept running after calling "main" again).
I am experimenting with wxHaskell. I wasn't able to run the app under ghci, so I have to use application to test it. I wanted to test the program with println debugging. However, it seems that putStrLn doesn't work in GUI:
{-# LANGUAGE Haskell2010 #-}
module Main where
import Graphics.UI.WX
drawUI dc view = do
circle dc (point 10 10) 5 [penKind := PenSolid, color := red]
putStrLn "painted"
helloGui :: IO ()
helloGui = do
f <- frame [
text := "Example",
resizeable := False,
bgcolor := white,
layout := space 400 300,
on paint := drawUI]
return ()
main :: IO ()
main = do
putStrLn "Started"
start helloGui
If I comment out start helloGui, everything is printed well. However, if I return it, nothing is printed but the window is displayed. What's wrong here?
This is probably output buffering; the output is not written until the program exits.
Either flush explicitly:
putStrLn "Started"
hFlush stdout
Or turn on line buffering:
hSetBuffering stdout LineBuffering -- or even NoBuffering
putStrLn "Started"