{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls #-}
-- | This module implements a TrayManager - an integral part of a
-- Linux system tray widget, though it is not itself a widget.  This
-- package exports a single GObject (for use with gtk2hs) that
-- implements the freedesktop.org system tray specification (it
-- handles receiving events and translating them into convenient
-- signals, along with the messy work of dealing with XEMBED).
--
-- The basic usage of the object is to:
--
-- 1. Instantiate the object with 'trayManagerNew'
--
-- 2. Have it manage a specific screen with 'trayManagerManageScreen'
--
-- 3. Set up handlers for the events exposed by the tray (e.g., 'trayIconAdded').
--
-- As an example, a functional system tray widget looks something like:
--
-- > import Graphics.UI.Gtk
-- > import Graphics.UI.Gtk.Misc.TrayManager
-- > systrayNew = do
-- >   box <- hBoxNew False 5
-- >   trayManager <- rayManagerNew
-- >   Just screen <- screenGetDefault
-- >   trayManagerManageScreen trayManager screen
-- >   on trayManager trayIconAdded $ \w -> do
-- >     widgetShowAll w
-- >     boxPackStart box w PackNatural 0
--
-- Note that the widgets made available in the event handlers are not
-- shown by default; you need to explicitly show them if you want that
-- (and you probably do).
module Graphics.UI.Gtk.Misc.TrayManager (
  -- * Types
  TrayManager,
  TrayManagerChild,
  TrayManagerClass,
  castToTrayManager,
  toTrayManager,
  gTypeTrayManager,

  -- * Functions
  trayManagerCheckRunning,
  trayManagerNew,
  trayManagerManageScreen,
  trayManagerGetChildTitle,

  -- * Signals
  trayIconAdded,
  trayIconRemoved,
  trayMessageSent,
  trayMessageCanceled,
  trayLostSelection
  ) where

import Graphics.UI.Gtk hiding ( after )
import Graphics.UI.Gtk.Abstract.Object ( makeNewObject )
import Graphics.UI.GtkInternals
import System.Glib.GError ( failOnGError )
import System.Glib.GType
import System.Glib.UTFString ( peekUTFString )

import Control.Monad ( liftM )

import Foreign
import Foreign.C.String ( CString, peekCString )
import Foreign.C.Types
import Foreign.ForeignPtr.Unsafe as UF
import Unsafe.Coerce ( unsafeCoerce )

newtype TrayManager = TrayManager (ForeignPtr TrayManager)
                    deriving (Eq, Ord)
type TrayManagerChild = Ptr EggTrayManagerChild

-- Empty data tags to classify some foreign pointers
data EggTrayManager
data EggTrayManagerChild

foreign import ccall "egg_tray_manager_check_running"
  c_egg_tray_manager_check_running :: Ptr Screen -> IO CInt

trayManagerCheckRunning :: Screen -> IO Bool
trayManagerCheckRunning gdkScreen = do
  let ptrScreen = unsafeCoerce gdkScreen :: ForeignPtr Screen
  withForeignPtr ptrScreen $ \realPtr -> do
    res <- c_egg_tray_manager_check_running realPtr
    return (res /= 0)

mkTrayManager :: (ForeignPtr TrayManager -> TrayManager, FinalizerPtr a)
mkTrayManager = (TrayManager, objectUnrefFromMainloop)

unTrayManager :: TrayManager -> ForeignPtr TrayManager
unTrayManager (TrayManager o) = o

class GObjectClass o => TrayManagerClass o
toTrayManager :: TrayManagerClass o => o -> TrayManager
toTrayManager = unsafeCastGObject . toGObject

instance TrayManagerClass TrayManager
instance GObjectClass TrayManager where
  toGObject = GObject . castForeignPtr . unTrayManager
  unsafeCastGObject = TrayManager . castForeignPtr . unGObject

castToTrayManager :: GObjectClass o => o -> TrayManager
castToTrayManager = castTo gTypeTrayManager "TrayManager"

foreign import ccall "egg_tray_manager_get_type"
  c_egg_tray_manager_get_type :: CULong

gTypeTrayManager :: GType
gTypeTrayManager = fromIntegral c_egg_tray_manager_get_type

foreign import ccall "egg_tray_manager_new"
  c_egg_tray_manager_new :: IO (Ptr EggTrayManager)

trayManagerNew :: IO TrayManager
trayManagerNew =
  makeNewObject mkTrayManager $ liftM (castPtr :: Ptr EggTrayManager -> Ptr TrayManager) $
    c_egg_tray_manager_new
  -- tm <- c_egg_tray_manager_new
  -- return (unsafeCoerce tm)

foreign import ccall "egg_tray_manager_manage_screen"
  c_egg_tray_manager_manage_screen :: Ptr EggTrayManager -> Ptr Screen -> IO CInt

trayManagerManageScreen :: TrayManager -> Screen -> IO Bool
trayManagerManageScreen trayManager screen = do
  let ptrManager = unsafeCoerce trayManager :: ForeignPtr EggTrayManager
      ptrScreen = unsafeCoerce screen :: ForeignPtr Screen
  res <- withForeignPtr ptrManager $ \realManager -> do
    withForeignPtr ptrScreen $ \realScreen -> do
      c_egg_tray_manager_manage_screen realManager realScreen
  return (res /= 0)

foreign import ccall "egg_tray_manager_get_child_title"
  c_egg_tray_manager_get_child_title :: Ptr EggTrayManager -> Ptr EggTrayManagerChild -> IO (Ptr CChar)


trayManagerGetChildTitle :: TrayManager -> TrayManagerChild -> IO String
trayManagerGetChildTitle trayManager child = do
  let ptrManager = unsafeCoerce trayManager :: ForeignPtr EggTrayManager
  res <- withForeignPtr ptrManager $ \realManager -> do
    c_egg_tray_manager_get_child_title realManager child
  peekCString res

-- | The signal emitted when a new tray icon is added.  These are
-- delivered even for systray icons that already exist when the tray
-- manager is created.
trayIconAdded :: (TrayManagerClass self) => Signal self (Widget -> IO ())
trayIconAdded = Signal (connect_OBJECT__NONE "tray_icon_added")

-- | This signal is emitted when a tray icon is removed by its parent
-- application.  No action is really necessary here (the icon is
-- removed without any intervention).  You could do something here if
-- you wanted, though.
trayIconRemoved :: (TrayManagerClass self) => Signal self (Widget -> IO ())
trayIconRemoved = Signal (connect_OBJECT__NONE "tray_icon_removed")

-- | This signal is emitted when the application that displayed an
-- icon wants a semi-persistent notification displayed for its icon.
-- The standard doesn't seem to require that these be honored.
trayMessageSent :: (TrayManagerClass self) => Signal self (Widget -> String -> Int64 -> Int64 -> IO ())
trayMessageSent = Signal (connect_OBJECT_STRING_INT64_INT64__NONE "message_sent")

-- | Similarly, the applciation can send this to cancel a previous
-- persistent message.
trayMessageCanceled :: (TrayManagerClass self) => Signal self (Widget -> Int64 -> IO ())
trayMessageCanceled = Signal (connect_OBJECT_INT64__NONE "message_canceled")

-- | ??
trayLostSelection :: (TrayManagerClass self) => Signal self (IO ())
trayLostSelection = Signal (connect_NONE__NONE "lost_selection")


-- Boilerplate stolen from gtk to make this library compatible.  These
-- functions aren't exported at all so I just copied them.

-- stolen from gtk
castTo :: (GObjectClass obj, GObjectClass obj') => GType -> String -> (obj -> obj')
castTo gtype objTypeName obj =
  case toGObject obj of
    gobj@(GObject objFPtr)
      | typeInstanceIsA ((UF.unsafeForeignPtrToPtr.castForeignPtr) objFPtr) gtype
                  -> unsafeCastGObject gobj
      | otherwise -> error $ "Cannot cast object to " ++ objTypeName

connect_OBJECT__NONE ::
  (GObjectClass a', GObjectClass obj) => SignalName ->
  ConnectAfter -> obj ->
  (a' -> IO ()) ->
  IO (ConnectId obj)
connect_OBJECT__NONE signal after obj user =
  connectGeneric signal after obj action
  where action :: Ptr GObject -> Ptr GObject -> IO ()
        action _ obj1 =
          failOnGError $
          makeNewGObject (GObject, objectUnrefFromMainloop) (return obj1) >>= \obj1' ->
          user (unsafeCastGObject obj1')

connect_NONE__NONE ::
  GObjectClass obj => SignalName ->
  ConnectAfter -> obj ->
  (IO ()) ->
  IO (ConnectId obj)
connect_NONE__NONE signal after obj user =
  connectGeneric signal after obj action
  where action :: Ptr GObject -> IO ()
        action _ =
          failOnGError $
          user

connect_OBJECT_INT64__NONE :: (GObjectClass a', GObjectClass obj)
                              => SignalName
                              -> ConnectAfter
                              -> obj
                              -> (a' -> Int64 -> IO ())
                              -> IO (ConnectId obj)
connect_OBJECT_INT64__NONE signal after obj user =
  connectGeneric signal after obj action
  where
    action :: Ptr GObject -> Ptr GObject -> Int64 -> IO ()
    action _ obj1 int2 =
      failOnGError $ makeNewGObject (GObject, objectUnrefFromMainloop) (return obj1) >>= \obj1' ->
        user (unsafeCastGObject obj1') int2

connect_OBJECT_STRING_INT64_INT64__NONE :: (GObjectClass a', GObjectClass obj)
                                           => SignalName
                                           -> ConnectAfter
                                           -> obj
                                           -> (a' -> String -> Int64 -> Int64 -> IO ())
                                           -> IO (ConnectId obj)
connect_OBJECT_STRING_INT64_INT64__NONE signal after obj user =
  connectGeneric signal after obj action
  where
    action :: Ptr GObject -> Ptr GObject -> CString -> Int64 -> Int64 -> IO ()
    action _ obj1 str2 int3 int4 =
      failOnGError $ makeNewGObject (GObject, objectUnrefFromMainloop) (return obj1) >>= \obj1' ->
        peekUTFString str2 >>= \str2' ->
          user (unsafeCastGObject obj1') str2' int3 int4
