{-# LANGUAGE ConstraintKinds, FlexibleContexts, FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, RankNTypes, TemplateHaskell, TupleSections #-} -- | Key bindings model. -- Designed to be imported as @qualified@. module Hbro.Keys ( Tree(..), Stroke, Bindings, Mode(..), Status(..), mode, strokes, StatusReader(..), StatusWriter(..), StatusState, mkStroke, merge, lookup, deserialize, prefixMod, serialize, toString, mkBinding, toBindings) where -- {{{ Imports -- import Hbro.Util import Control.Lens import Control.Monad hiding(forM_) -- import Control.Monad.Error hiding(forM_) -- import Control.Monad.IO.Class -- import Control.Monad.Reader hiding(forM_) -- import Control.Monad.Trans.Control import Data.Default -- import Data.Foldable import Data.Functor -- import Data.Monoid import Data.Map (Map) import qualified Data.Map as M import Data.Maybe import Data.Set (Set) import qualified Data.Set as S hiding(foldl) import Graphics.UI.Gtk.Abstract.Widget import Graphics.UI.Gtk.Gdk.EventM import Graphics.UI.Gtk.Gdk.Keys -- import Graphics.UI.Gtk.General.Enums import Prelude hiding(lookup, mapM_) -- }}} -- {{{ Types -- | A tree implementation that labels edges data Tree edge leaf = Empty | Leaf leaf | Branch (Map edge (Tree edge leaf)) deriving(Show) -- | A single keystroke, i.e. a set of modifiers and a single key (its string description) type Stroke = (Set Modifier, String) -- | List of keys bound to actions type Bindings m = Tree Stroke (m ()) data Mode = Normal | Insert deriving(Eq, Ord) -- | Global state data Status = Status { _mode :: Mode, -- ^ Current mode _strokes :: [Stroke] -- ^ Previous keystrokes } instance Default Status where def = Status Normal [] makeLenses ''Status -- | 'MonadReader' for 'Status' class StatusReader m where readStatus :: Simple Lens Status a -> m a -- | 'MonadWriter' for 'Status' class StatusWriter m where writeStatus :: Simple Lens Status a -> a -> m () -- | 'MonadState' for 'Status' type (StatusState m) = (StatusReader m, StatusWriter m) {-instance Monoid KeyMap where mempty = KeyBindings M.empty mappend (KeyBindings a) (KeyBindings b) = KeyBindings (mappend a b)-} instance Ord Modifier where compare x y = compare (show x) (show y) -- }}} mkStroke :: [Modifier] -> KeyVal -> Maybe Stroke mkStroke m k = Just . (S.fromList m,) =<< toString k --toTree :: Ord a => [([a], b)] -> Tree a b --toTree = foldl merge Empty . map toBranch toBranch :: Ord a => ([a], b) -> Tree a b toBranch ([], a) = Leaf a toBranch ((h:t), a) = Branch (M.fromList [(h, toBranch (t, a))]) -- | In case of conflicts, the rightmost operand is preferred merge :: Ord a => Tree a b -> Tree a b -> Tree a b merge Empty x = x merge x Empty = x merge (Leaf _) (Leaf b) = Leaf b merge (Leaf _) (Branch b) = Branch b merge (Branch _) (Leaf b) = Leaf b merge (Branch a) (Branch b) = Branch $ M.unionWith merge a b lookup :: Ord a => [a] -> Tree a b -> Maybe (Tree a b) lookup _ Empty = Nothing lookup [] (Leaf x) = Just (Leaf x) lookup [] x = Just x lookup _ (Leaf _) = Nothing lookup (h:t) (Branch m) = M.lookup h m >>= lookup t -- | Convert a KeyVal to a String. -- For printable characters, the corresponding String is returned, except for the space character for which "" is returned. -- For non-printable characters, the corresponding keyName wrapped into "< >" is returned. -- For modifiers, Nothing is returned. toString :: KeyVal -> Maybe String toString keyVal = case keyToChar keyVal of Just ' ' -> Just "" Just char -> Just [char] _ -> case keyName keyVal of "Caps_Lock" -> Nothing "Shift_L" -> Nothing "Shift_R" -> Nothing "Control_L" -> Nothing "Control_R" -> Nothing "Alt_L" -> Nothing "Alt_R" -> Nothing "Super_L" -> Nothing "Super_R" -> Nothing "Menu" -> Nothing "ISO_Level3_Shift" -> Nothing "dead_circumflex" -> Just "^" "dead_diaeresis" -> Just "ยจ" x -> Just ('<':x ++ ">") serialize :: Stroke -> String serialize (m, k) = S.foldr (++) "" (S.map serializeMod m) ++ k serializeMod :: Modifier -> String serializeMod Control = "C-" -- serializeMod Shift = "S-" serializeMod Alt = "M-" serializeMod _ = "" -- | Parse a 'String' representation of a keystrokes chain deserialize :: String -> Maybe [Stroke] deserialize "" = Just [] deserialize (' ':t) = deserialize t deserialize ('C':'-':t) = prefixMod Control =<< deserialize t deserialize ('M':'-':t) = prefixMod Alt =<< deserialize t -- deserialize ('S':'-':t) = prefixMod Shift =<< deserialize t deserialize (k:' ':t) = prepend k <$> deserialize t deserialize (k:t) = prefixVal k =<< deserialize t prefixMod :: Modifier -> [Stroke] -> Maybe [Stroke] prefixMod modifier ((m, keys):t) = Just ((S.insert modifier m, keys):t) prefixMod _ _ = Nothing prefixVal :: Char -> [Stroke] -> Maybe [Stroke] prefixVal k [] = Just [(S.empty, [k])] prefixVal k ((modifiers, keys):t) | S.null modifiers = Just ((modifiers, k:keys):t) | otherwise = Nothing prepend :: Char -> [Stroke] -> [Stroke] prepend k x = (S.empty, [k]):x mkBinding :: String -> m () -> Maybe (Bindings m) mkBinding keys action = toBranch . (, action) <$> deserialize keys toBindings :: [(String, m ())] -> Bindings m toBindings = foldl merge Empty . catMaybes . map (\(a, b) -> mkBinding a b)