-- Author:     Andy Stewart <lazycat.manatee@gmail.com>
-- Maintainer: Andy Stewart <lazycat.manatee@gmail.com>
-- 
-- Copyright (C) 2010 Andy Stewart, all rights reserved.
-- 
-- 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 3 of the License, or
-- 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, see <http://www.gnu.org/licenses/>.

module Manatee.Toolkit.Gtk.Editable where

import Control.Monad
import Graphics.UI.Gtk
import Manatee.Toolkit.General.List
import Manatee.Toolkit.General.String

type EditableContent = String
type EditableBound   = (Int, Int)
type EditableStatus  = (EditableContent, EditableBound)

-- | Focus editable and keep status.
editableFocus :: EditableClass self => self -> IO ()
editableFocus ed = do
  bound <- editableGetSelectionBounds ed
  widgetGrabFocus $ castToWidget ed
  editableSetBound ed bound

-- | Get unselect text.
editableGetUnselectText :: EditableClass self => self -> IO String
editableGetUnselectText ed = do
  allText   <- editableGetAllText ed         -- get editable content
  (start, _) <- editableGetSelectionBounds ed -- get highlight bound
  return $ take start allText               -- remove highlight completion part from content

-- | Select from current position to end.
editableSelectToEnd :: EditableClass self => self -> Int -> IO ()
editableSelectToEnd ed current = 
  editableSelectRegion ed current (-1)

-- | Get all text.
editableGetAllText :: EditableClass self => self -> IO String
editableGetAllText ed = editableGetChars ed 0 (-1)

-- | Set text.
editableSetText :: EditableClass self => self -> String -> IO ()
editableSetText ed content = do
  editableDeleteAllText ed
  end <- editableInsertText ed content 0
  editableSetPosition ed end

-- | Set completion text.
editableSetCompletionText :: EditableClass self => self -> String -> String -> IO ()
editableSetCompletionText ed input common = do
  editableSetText ed (input ++ common)
  editableSelectToEnd ed (length input)

-- | Set bound.
editableSetBound :: EditableClass self => self -> (Int, Int) -> IO ()  
editableSetBound ed (start, end) = do
  editableSelectRegion ed start end
  editableSetPosition ed start

-- | Get status.
editableGetStatus :: EditableClass self => self -> IO EditableStatus  
editableGetStatus ed = 
  liftM2 (,) (editableGetAllText ed) (editableGetSelectionBounds ed)

-- | Set status.
editableSetStatus :: EditableClass self => self -> EditableStatus -> IO ()
editableSetStatus ed (content, bound) = do
  editableSetText ed content
  editableSetBound ed bound

-- | Delete all text.
editableDeleteAllText :: EditableClass self => self -> IO ()  
editableDeleteAllText ed = editableDeleteText ed 0 (-1)

-- | Expand completion.
editableExpandCompletion :: EditableClass self => self -> IO ()
editableExpandCompletion ed =
  editableSetPosition ed (-1)

-- | Delete backward char.
editableDeleteBackwardChar :: EditableClass self => self -> IO ()
editableDeleteBackwardChar ed = do
  unselectText <- editableGetUnselectText ed
  editableSetText ed (init_ unselectText)

-- | Delete backward word.
editableDeleteBackwardWord :: EditableClass self => self -> IO ()
editableDeleteBackwardWord ed = do
  unselectText <- editableGetUnselectText ed
  let (word, restStr) = searchBackwardWord unselectText
      text = if null word
                -- If not word at last, delete char.
                then init_ unselectText
                -- Otherwise delete word.
                else restStr
  editableSetText ed text

-- | Backward char.
editableBackwardChar :: EditableClass self => self -> IO ()
editableBackwardChar ed = do
  pos <- editableGetPosition ed  
  editableSetPosition ed (if pos >= 1 then pos - 1 else 0)

-- | Whether changed after do editable action?
editableIsChanged :: EditableClass self => self -> IO () -> IO Bool
editableIsChanged ed action = do
  let getState = do
        text <- editableGetAllText ed
        (start, end) <- editableGetSelectionBounds ed
        return (text, start, end)
  beforeState <- getState
  action
  afterState <- getState
  return (beforeState /= afterState)