packages feed

leksah-0.16.2.2: src/IDE/Pane/Search.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
-----------------------------------------------------------------------------
--
-- Module      :  IDE.Pane.Search
-- Copyright   :  (c) Juergen Nicklisch-Franken, Hamish Mackenzie
-- License     :  GNU-GPL
--
-- Maintainer  :  <maintainer at leksah.org>
-- Stability   :  provisional
-- Portability  :  portable
--
-- | The pane of ide where metadata searches can be done
--
-------------------------------------------------------------------------------

module IDE.Pane.Search (
    IDESearch(..)
,   SearchState
,   buildSearchPane
--,   launchSymbolNavigationDialog
,   getSearch
) where

import Prelude ()
import Prelude.Compat
import Data.IORef (newIORef, writeIORef, readIORef, IORef(..))
-- import IDE.Pane.SourceBuffer (goToDefinition)
import IDE.Metainfo.Provider (searchMeta)
import Data.Maybe
import Data.Typeable
import IDE.Core.State
import IDE.Utils.GUIUtils
import Distribution.Text(display)
import Control.Event (triggerEvent)
import Control.Monad.IO.Class (MonadIO(..))
import qualified Data.ByteString.Char8 as BS (empty, unpack)
import Control.Monad (void)
import Data.Text (Text)
import qualified Data.Text as T (unpack, pack, null)
import System.Log.Logger (debugM)
import Data.Monoid ((<>))
import GI.Gtk.Objects.ScrolledWindow
       (scrolledWindowSetPolicy, scrolledWindowSetShadowType,
        scrolledWindowNew, ScrolledWindow(..))
import Data.GI.Gtk.ModelView.SeqStore
       (seqStoreGetValue, seqStoreAppend, seqStoreClear, seqStoreNew,
        SeqStore(..))
import GI.Gtk.Objects.Entry
       (entrySetText, entryNew, entryGetText, Entry(..))
import GI.Gtk.Objects.TreeView
       (onTreeViewRowActivated, treeViewGetSelection,
        treeViewSetHeadersVisible, treeViewAppendColumn, treeViewSetModel,
        treeViewNew, TreeView(..))
import Data.GI.Base.ManagedPtr (unsafeCastTo)
import GI.Gtk.Objects.Widget
       (toWidget, setWidgetSensitive, onWidgetKeyReleaseEvent,
        afterWidgetFocusInEvent, Widget(..))
import GI.Gtk.Objects.RadioButton
       (RadioButton(..), radioButtonNewWithLabelFromWidget,
        radioButtonNewWithLabel)
import GI.Gtk.Objects.ToggleButton
       (toggleButtonGetActive, toggleButtonSetActive)
import GI.Gtk.Objects.CheckButton (checkButtonNewWithLabel)
import GI.Gtk.Objects.Box (boxPackEnd, boxPackStart, Box(..), boxNew)
import Graphics.UI.Editor.Parameters
       (boxPackEnd', boxPackStart', Packing(..))
import GI.Gtk.Objects.CellRendererText (cellRendererTextNew)
import GI.Gtk.Objects.CellRendererPixbuf (cellRendererPixbufNew)
import GI.Gtk.Objects.TreeViewColumn
       (treeViewColumnSetReorderable, treeViewColumnSetResizable,
        treeViewColumnSetSizing, treeViewColumnSetTitle, treeViewColumnNew)
import GI.Gtk.Interfaces.CellLayout (cellLayoutPackStart)
import Data.GI.Gtk.ModelView.CellLayout
       (cellLayoutSetDataFunction)
import GI.Gtk.Objects.TreeSelection
       (treeSelectionSetMode)
import GI.Gtk.Enums
       (PolicyType(..), ShadowType(..), SelectionMode(..),
        TreeViewColumnSizing(..), Orientation(..))
import GI.Gtk.Objects.Adjustment (noAdjustment)
import GI.Gtk.Objects.Container (containerAdd)
import GI.Gtk.Objects.ToggleButton (onToggleButtonToggled)
import GI.Gtk.Objects.Menu (Menu(..))
import GI.Gtk.Objects.MenuItem
       (onMenuItemActivate, menuItemNewWithLabel)
import GI.Gtk.Objects.MenuShell (menuShellAppend)
import Data.GI.Gtk.ModelView.Types
       (treeSelectionGetSelectedRows', treePathGetIndices')
import GI.Gtk
       (setCellRendererTextScaleSet, setCellRendererTextScale,
        setCellRendererPixbufStockId, setCellRendererTextText)

-- | A search pane description
--

data IDESearch      =   IDESearch {
    scrolledView    ::   ScrolledWindow
,   treeView        ::   TreeView
,   searchStore     ::   SeqStore Descr
,   searchScopeRef  ::   IORef Scope
,   searchModeRef   ::   IORef SearchMode
,   topBox          ::   Box
,   entry           ::   Entry
,   scopeSelection  ::   Scope -> IDEAction
,   modeSelection   ::   SearchMode -> IDEAction
,   searchMetaGUI   ::   Text -> IDEAction
,   setChoices     ::   [Descr] -> IDEAction
} deriving Typeable

data SearchState    =   SearchState {
    searchString    ::   Text
,   searchScope     ::   Scope
,   searchMode      ::   SearchMode
}   deriving(Eq,Ord,Read,Show,Typeable)

instance Pane IDESearch IDEM
    where
    primPaneName _  =   __ "Search"
    getAddedIndex _ =   0
    getTopWidget    =   liftIO . toWidget . topBox
    paneId b        =   "*Search"

instance RecoverablePane IDESearch SearchState IDEM where
    saveState p     =   do
        str     <-  entryGetText (entry p)
        mode    <-  liftIO $ readIORef (searchModeRef p)
        scope   <-  liftIO $ readIORef (searchScopeRef p)
        return (Just (SearchState str scope mode))
    recoverState pp (SearchState str scope mode) =   do
        nb      <-  getNotebook pp
        mbP@(Just search)     <-  buildPane pp nb builder
        scopeSelection search scope
        modeSelection search mode
        searchMetaGUI search str
        return mbP
    builder pp nb windows = buildSearchPane


buildSearchPane :: IDEM (Maybe IDESearch,Connections)
buildSearchPane =
    let scope   = SystemScope
        mode    = Prefix False
    in reifyIDE $ \ ideR -> do

        scopebox        <-  boxNew OrientationHorizontal 2
        rb1             <-  radioButtonNewWithLabel ([]::[RadioButton]) (__ "Package")
        rb2             <-  radioButtonNewWithLabelFromWidget (Just rb1) (__ "Workspace")
        rb3             <-  radioButtonNewWithLabelFromWidget (Just rb1) (__ "System")
        toggleButtonSetActive rb3 True
        cb2             <-  checkButtonNewWithLabel (__ "Imports")

        boxPackStart' scopebox rb1 PackGrow 2
        boxPackStart' scopebox rb2 PackGrow 2
        boxPackStart' scopebox rb3 PackGrow 2
        boxPackEnd' scopebox cb2 PackNatural 2

        modebox         <-  boxNew OrientationHorizontal 2
        mb1             <-  radioButtonNewWithLabel ([]::[RadioButton]) (__ "Exact")
        mb2             <-  radioButtonNewWithLabelFromWidget (Just mb1) (__ "Prefix")
        mb3             <-  radioButtonNewWithLabelFromWidget (Just mb1) (__ "Regex")
        toggleButtonSetActive
            (case mode of
                Exact _  -> mb1
                Prefix _ -> mb2
                Regex _  -> mb3) True
        mb4             <-  checkButtonNewWithLabel (__ "Case sensitive")
        toggleButtonSetActive mb4 (caseSense mode)
        boxPackStart' modebox mb1 PackNatural 2
        boxPackStart' modebox mb2 PackNatural 2
        boxPackStart' modebox mb3 PackNatural 2
        boxPackEnd' modebox mb4 PackNatural 2

        seqStore   <-  seqStoreNew []
        treeView    <-  treeViewNew
        treeViewSetModel treeView (Just seqStore)

        renderer3    <- cellRendererTextNew
        renderer30   <- cellRendererPixbufNew
        col3         <- treeViewColumnNew
        treeViewColumnSetTitle col3 (__ "Symbol")
        treeViewColumnSetSizing col3 TreeViewColumnSizingAutosize
        treeViewColumnSetResizable col3 True
        treeViewColumnSetReorderable col3 True
        treeViewAppendColumn treeView col3
        cellLayoutPackStart col3 renderer30 False
        cellLayoutPackStart col3 renderer3 True
        cellLayoutSetDataFunction col3 renderer3 seqStore
            $ setCellRendererTextText renderer3 . dscName
        cellLayoutSetDataFunction col3 renderer30 seqStore
            $ setCellRendererPixbufStockId renderer30
                . stockIdFromType . descrType . dscTypeHint


        renderer1    <- cellRendererTextNew
        renderer10   <- cellRendererPixbufNew
        col1         <- treeViewColumnNew
        treeViewColumnSetTitle col1 (__ "Module")
        treeViewColumnSetSizing col1 TreeViewColumnSizingAutosize
        treeViewColumnSetResizable col1 True
        treeViewColumnSetReorderable col1 True
        treeViewAppendColumn treeView col1
        cellLayoutPackStart col1 renderer10 False
        cellLayoutPackStart col1 renderer1 True
        cellLayoutSetDataFunction col1 renderer1 seqStore
            $ \row -> setCellRendererTextText renderer1 $
                            case dsMbModu row of
                                Nothing -> ""
                                Just pm -> T.pack . display $ modu pm
        cellLayoutSetDataFunction col1 renderer10 seqStore
            $ \row -> setCellRendererPixbufStockId renderer10 $
                            if isReexported row
                                then "ide_reexported"
                                    else if isJust (dscMbLocation row)
                                        then "ide_source"
                                        else ""

        renderer2   <- cellRendererTextNew
        col2        <- treeViewColumnNew
        treeViewColumnSetTitle col2 (__ "Package")
        treeViewColumnSetSizing col2 TreeViewColumnSizingAutosize
        treeViewColumnSetResizable col2 True
        treeViewColumnSetReorderable col2 True
        treeViewAppendColumn treeView col2
        cellLayoutPackStart col2 renderer2 True
        cellLayoutSetDataFunction col2 renderer2 seqStore
            $ \row -> setCellRendererTextText renderer2 $
                            case dsMbModu row of
                                Nothing -> ""
                                Just pm -> T.pack . display $ pack pm

        renderer3   <- cellRendererTextNew
        col3        <- treeViewColumnNew
        treeViewColumnSetTitle col3 (__ "Type/Kind")
        treeViewColumnSetSizing col3 TreeViewColumnSizingAutosize
        treeViewColumnSetResizable col3 True
        treeViewColumnSetReorderable col3 True
        treeViewAppendColumn treeView col3
        cellLayoutPackStart col3 renderer3 True
        cellLayoutSetDataFunction col3 renderer3 seqStore
            $ \row -> do
                setCellRendererTextText renderer3 $ T.pack . BS.unpack . fromMaybe BS.empty $
                            dscMbTypeStr row
                setCellRendererTextScale renderer3 0.8
                setCellRendererTextScaleSet renderer3 True

        treeViewSetHeadersVisible treeView True
        sel <- treeViewGetSelection treeView
        treeSelectionSetMode sel SelectionModeSingle

        sw <- scrolledWindowNew noAdjustment noAdjustment
        scrolledWindowSetShadowType sw ShadowTypeIn
        containerAdd sw treeView
        scrolledWindowSetPolicy sw PolicyTypeAutomatic PolicyTypeAutomatic

        entry   <-  entryNew

        box             <-  boxNew OrientationVertical 2
        boxPackStart' box scopebox PackNatural 0
        boxPackStart' box sw PackGrow 0
        boxPackStart' box modebox PackNatural 0
        boxPackEnd' box entry PackNatural 0

        scopeRef  <- newIORef scope
        modeRef   <- newIORef mode
        let search = IDESearch sw treeView seqStore scopeRef modeRef box entry scopeSelection_ modeSelection_ searchMetaGUI_ setChoices_
            scopeSelection_ :: Scope -> IDEAction
            scopeSelection_ scope = do
                liftIO $ writeIORef (searchScopeRef search) scope
                text   <- liftIO $ entryGetText entry
                searchMetaGUI_ text

            modeSelection_ :: SearchMode -> IDEAction
            modeSelection_ mode = do
                liftIO $ writeIORef (searchModeRef search) mode
                text   <- liftIO $ entryGetText entry
                searchMetaGUI_ text
            searchMetaGUI_ :: Text -> IDEAction
            searchMetaGUI_ str = do
                liftIO $ debugM "leksah" $ "searchMetGUI_ " <> T.unpack str
                bringPaneToFront search
                entrySetText entry str
                scope <- getScope search
                mode  <- getMode search
            --    let mode' = if length str > 2 then mode else Exact (caseSense mode)
                descrs <- if T.null str
                            then return []
                            else searchMeta scope str mode
                seqStoreClear (searchStore search)
                mapM_ (seqStoreAppend (searchStore search)) (take 500 descrs)
            modeSelectionCase :: Bool -> IDEAction
            modeSelectionCase caseSense = do
                oldMode <- liftIO $ readIORef (searchModeRef search)
                liftIO $ writeIORef (searchModeRef search) oldMode{caseSense = caseSense}
                text   <- entryGetText entry
                searchMetaGUI_ text
            setChoices_ :: [Descr] -> IDEAction
            setChoices_ descrs = do
                    seqStoreClear (searchStore search)
                    mapM_ (seqStoreAppend (searchStore search)) descrs
                    bringPaneToFront search
                    entrySetText entry
                        (case descrs of
                            []    -> ""
                            hd: _ -> dscName hd)
            scopeSelection' rb1 rb2 rb3 cb2 = do
                withImports <-  toggleButtonGetActive cb2
                s1 <- toggleButtonGetActive rb1
                s2 <- toggleButtonGetActive rb2
                s3 <- toggleButtonGetActive rb3
                scopeSelection_ $
                    if s1
                        then PackageScope withImports
                        else if s2
                                then WorkspaceScope withImports
                                else SystemScope

        cid1 <- ConnectC treeView <$> afterWidgetFocusInEvent treeView (\e -> do
            reflectIDE (makeActive search) ideR
            return True)
        onToggleButtonToggled rb1 $ liftIO (reflectIDE (scopeSelection' rb1 rb2 rb3 cb2) ideR )
        onToggleButtonToggled rb2 $ liftIO (reflectIDE (scopeSelection' rb1 rb2 rb3 cb2) ideR )
        onToggleButtonToggled rb3 $ liftIO (reflectIDE (scopeSelection' rb1 rb2 rb3 cb2) ideR )
        onToggleButtonToggled cb2 $ liftIO (reflectIDE (scopeSelection' rb1 rb2 rb3 cb2) ideR)
        onToggleButtonToggled mb1 $ liftIO $ do
            setWidgetSensitive mb4 False
            active <- toggleButtonGetActive mb4
            reflectIDE (modeSelection_ (Exact active)) ideR
        onToggleButtonToggled mb2 $ liftIO $ do
            setWidgetSensitive mb4 True
            active <- toggleButtonGetActive mb4
            reflectIDE (modeSelection_ (Prefix active)) ideR
        onToggleButtonToggled mb3 $ liftIO $ do
            setWidgetSensitive mb4 True
            active <- toggleButtonGetActive mb4
            reflectIDE (modeSelection_ (Regex active)) ideR
        onToggleButtonToggled mb4 $ liftIO $ do
            active <- toggleButtonGetActive mb4
            reflectIDE (modeSelectionCase active) ideR
        cids <- treeViewContextMenu treeView $ searchContextMenu ideR seqStore treeView
        cid4 <- ConnectC treeView <$> onTreeViewRowActivated treeView (\path col -> do
            p <- treePathGetIndices' path
            selectDescr ideR seqStore p col)
--            sel `onSelectionChanged` do
--                fillInfo search ideR
        onWidgetKeyReleaseEvent entry $ \e -> do
            text <- entryGetText entry
            reflectIDE (searchMetaGUI_ text) ideR
            return False
        return (Just search, cid1 : cid4 : cids)


getScope :: MonadIO m => IDESearch -> m Scope
getScope search = liftIO . readIORef $ searchScopeRef search

getMode :: MonadIO m => IDESearch -> m SearchMode
getMode search = liftIO . readIORef $ searchModeRef search

getSearch :: Maybe PanePath -> IDEM IDESearch
getSearch Nothing = forceGetPane (Right "*Search")
getSearch (Just pp)  = forceGetPane (Left pp)

searchContextMenu :: MonadIO m
                  => IDERef
                  -> SeqStore Descr
                  -> TreeView
                  -> Menu
                  -> m ()
searchContextMenu ideR store descrView theMenu = do
    item1           <-  menuItemNewWithLabel (__ "Go to definition")
    onMenuItemActivate item1 $ liftIO $ goToDef ideR store descrView
    menuShellAppend theMenu item1

goToDef ideR store descrView = do
    sel         <-  getSelectionDescr descrView store
    case sel of
        Just descr ->  void (reflectIDE (triggerEvent ideR (GotoDefinition descr)) ideR)
                                -- (goToDefinition descr) ideR
        _          ->  sysMessage Normal (__ "Search >> listViewPopup: no selection")

selectDescr ideR store [i] col = do
    descr <- seqStoreGetValue store i
    liftIO $ reflectIDE (triggerEvent ideR (SelectIdent descr)) ideR
    return ()

selectDescr _ _ _ _ = liftIO $ sysMessage Normal (__ "Search >> selectDescr: invalid path")

getSelectionDescr :: (Applicative m, MonadIO m)
                  => TreeView
                  ->  SeqStore Descr
                  -> m (Maybe Descr)
getSelectionDescr treeView seqStore = do
    treeSelection   <-  treeViewGetSelection treeView
    paths           <-  treeSelectionGetSelectedRows' treeSelection
    mapM treePathGetIndices' paths >>= \case
        [a]:r ->  do
            val     <-  seqStoreGetValue seqStore a
            return (Just val)
        _  ->  return Nothing

--fillInfo :: IDESearch
--    -> IDERef
--    -> IO ()
--fillInfo search ideR  = do
--    sel <- getSelectionDescr (treeView search) (searchStore search)
--    case sel of
--        Just descr      ->  do
--            reflectIDE (setInfo descr) ideR
--            entrySetText (entry search) (descrName descr)
--        otherwise       ->  return ()

{--
launchSymbolNavigationDialog :: Text -> (Descr -> IDEM ()) -> IDEM ()
launchSymbolNavigationDialog txt act = do
    dia                        <-   liftIO $ dialogNew
    win <- getMainWindow
    (Just searchPane, _) <- buildSearchPane
    liftIO $ do
        windowSetTransientFor dia win
        upper                      <-   dialogGetUpper dia
        lower                      <-   dialogGetActionArea dia
        boxPackStart' upper (topBox searchPane) PackNatural 7

        bb      <-  hButtonBoxNew
        closeB  <-  buttonNewFromStock "gtk-cancel"
        okB    <-  buttonNewFromStock "gtk-ok"
        okB `onClicked` do
            dialogResponse dia ResponseTypeOk
            widgetHideAll dia
        closeB `onClicked` do
            dialogResponse dia ResponseTypeCancel
            widgetHideAll dia
        boxPackEnd' bb closeB PackNatural 0
        boxPackEnd' bb okB PackNatural 0
        boxPackStart' lower bb PackNatural 7
        setWidgetCanDefault okB True
        buttonSetLabel okB "Goto"
        widgetGrabDefault okB
        widgetShowAll dia
        resp  <- dialogRun' dia
        return ()
    return ()
--}