{-# LANGUAGE FlexibleContexts #-} {- Bustle.UI.FilterDialog: allows the user to filter the displayed log Copyright © 2011 Collabora Ltd. Copyright © 2019 Will Thompson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -} module Bustle.UI.FilterDialog ( runFilterDialog ) where import Control.Monad (forM_) import Data.List (intercalate, groupBy, elemIndices, elemIndex) import qualified Data.Set as Set import Data.Set (Set) import qualified Data.Function as F import Data.IORef import Graphics.UI.Gtk import Graphics.UI.Gtk.ModelView.CellRendererCombo (cellComboTextModel) import Bustle.Translation (__) import Bustle.Types import Paths_bustle data NameVisibility = NameVisibilityDefault | NameVisibilityOnly | NameVisibilityNever deriving (Show, Eq, Ord, Enum, Bounded) nameVisibilityName :: NameVisibility -> String nameVisibilityName v = case v of NameVisibilityDefault -> __ "Default" NameVisibilityOnly -> __ "Only this" NameVisibilityNever -> __ "Hidden" data NameEntry = NameEntry { neUniqueName :: UniqueName , neOtherNames :: Set OtherName , neVisibility :: NameVisibility } namespace :: String -> (String, String) namespace name = case reverse (elemIndices '.' name) of [] -> ("", name) (i:_) -> splitAt (i + 1) name formatNames :: NameEntry -> String formatNames ne | Set.null os = unUniqueName (neUniqueName ne) | otherwise = intercalate "\n" . map (formatGroup . groupGroup) $ groups where os = neOtherNames ne groups = groupBy ((==) `F.on` fst) . map (namespace . unOtherName) $ Set.toAscList os groupGroup [] = error "unpossible empty group from groupBy" groupGroup xs@((ns, _):_) = (ns, map snd xs) formatGroup (ns, [y]) = ns ++ y formatGroup (ns, ys) = ns ++ "{" ++ intercalate "," ys ++ "}" type NameStore = ListStore NameEntry makeStore :: [(UniqueName, Set OtherName)] -> NameFilter -> IO NameStore makeStore names nameFilter = listStoreNew $ map toNameEntry names where toNameEntry (u, os) = NameEntry { neUniqueName = u , neOtherNames = os , neVisibility = toVisibility u } toVisibility u | Set.member u (nfOnly nameFilter) = NameVisibilityOnly | Set.member u (nfNever nameFilter) = NameVisibilityNever | otherwise = NameVisibilityDefault nameStoreUpdate :: NameStore -> Int -> (NameEntry -> NameEntry) -> IO () nameStoreUpdate nameStore i f = do ne <- listStoreGetValue nameStore i listStoreSetValue nameStore i $ f ne makeView :: NameStore -> TreeView -> IO () makeView nameStore nameView = do treeViewSetModel nameView (Just nameStore) -- Bus name column nameCell <- cellRendererTextNew nameColumn <- treeViewColumnNew nameColumn `set` [ treeViewColumnTitle := __ "Bus Name" , treeViewColumnExpand := True ] treeViewColumnPackStart nameColumn nameCell True treeViewAppendColumn nameView nameColumn cellLayoutSetAttributes nameColumn nameCell nameStore $ \ne -> [ cellText := formatNames ne ] -- TreeStore of possible visibility states let nameVisibilities = [minBound..] let nameVisibilityNames = map nameVisibilityName nameVisibilities visibilityModel <- listStoreNew nameVisibilityNames let visibilityNameCol = makeColumnIdString 1 treeModelSetColumn visibilityModel visibilityNameCol id -- Visibility column comboCell <- cellRendererComboNew comboCell `set` [ cellTextEditable := True , cellComboHasEntry := False ] comboColumn <- treeViewColumnNew comboColumn `set` [ treeViewColumnTitle := __ "Visibility" , treeViewColumnExpand := False ] treeViewColumnPackStart comboColumn comboCell True treeViewAppendColumn nameView comboColumn cellLayoutSetAttributes comboColumn comboCell nameStore $ \ne -> [ cellComboTextModel := (visibilityModel, visibilityNameCol) , cellText :=> do let Just j = elemIndex (neVisibility ne) nameVisibilities listStoreGetValue visibilityModel j ] comboCell `on` edited $ \[i] str -> do let (Just j) = elemIndex str nameVisibilityNames nameStoreUpdate nameStore i $ \ne -> ne { neVisibility = nameVisibilities !! j } return () runFilterDialog :: WindowClass parent => parent -- ^ The window to which to attach the dialog -> [(UniqueName, Set OtherName)] -- ^ Names, in order of appearance -> IORef NameFilter -- ^ Current filter -> IO () -- ^ Callback when filter changes -> IO () runFilterDialog parent names filterRef callback = do builder <- builderNew builderAddFromFile builder =<< getDataFileName "data/FilterDialog.ui" d <- builderGetObject builder castToDialog ("filterDialog" :: String) (_, windowHeight) <- windowGetSize parent windowSetDefaultSize d (-1) (windowHeight * 3 `div` 4) d `set` [ windowTransientFor := parent ] nameStore <- makeStore names =<< readIORef filterRef makeView nameStore =<< builderGetObject builder castToTreeView ("filterTreeView" :: String) resetButton <- builderGetObject builder castToButton ("resetButton" :: String) resetButton `on` buttonActivated $ do n <- listStoreGetSize nameStore forM_ [0..n-1] $ \i -> do ne <- listStoreGetValue nameStore i case neVisibility ne of NameVisibilityDefault -> return () _ -> listStoreSetValue nameStore i $ ne { neVisibility = NameVisibilityDefault } let updateResetSensitivity = do nf <- readIORef filterRef let isEmpty = Set.null (nfOnly nf) && Set.null (nfNever nf) widgetSetSensitive resetButton $ not isEmpty updateResetSensitivity nameStore `on` rowChanged $ \[i] _iter -> do ne <- listStoreGetValue nameStore i let u = neUniqueName ne -- Should we smush this into nameFilterModify, move the enum into -- Bustle.Types? let f = case neVisibility ne of NameVisibilityDefault -> nameFilterRemove NameVisibilityOnly -> nameFilterAddOnly NameVisibilityNever -> nameFilterAddNever modifyIORef' filterRef $ f u updateResetSensitivity callback _ <- dialogRun d widgetDestroy d