diff --git a/Component.hs b/Component.hs
new file mode 100644
--- /dev/null
+++ b/Component.hs
@@ -0,0 +1,28 @@
+module Component where
+
+import Data.IORef
+import Control.Applicative
+
+-- reference management:
+newtype Ref a =  Ref (IORef a)
+
+infixr 0 .<, .<<, .>, .>>
+
+(.>>) :: Ref (state) -> (state -> IO a) -> IO a
+(.>>) (Ref c) a = readIORef c >>= a
+
+(.<<) :: Ref (state) -> (state -> IO state) -> IO ()
+(.<<) (Ref c) a = readIORef c >>= a >>= writeIORef c
+
+(.<) :: Ref (state) -> (state -> state) -> IO ()
+(.<) (Ref c) a = readIORef c >>= writeIORef c . a
+
+(.>) :: Ref (state) -> (state -> a) -> IO a
+(.>) c getter =  c .>> return . getter
+    
+newRef :: state -> IO (Ref (state))
+newRef c = Ref <$> newIORef c 
+
+unRef :: Ref (state) -> IO (state)
+unRef (Ref c) = readIORef c
+
diff --git a/DataProcController.hs b/DataProcController.hs
new file mode 100644
--- /dev/null
+++ b/DataProcController.hs
@@ -0,0 +1,56 @@
+module DataProcController where
+
+import Graphics.UI.Gtk
+import Control.Monad
+import Control.Applicative
+
+import qualified DataProcView as View
+import qualified ShellCmdInputController as Shell
+import qualified LineSplitterController as RxDef
+import qualified TableController as Table
+
+import qualified Data.ByteString.Lazy.Char8 as L
+import SimpleRegex
+ 
+import WindowedApp
+import Component
+
+type Controller = Ref C
+
+view = View.mainWidget . gui
+
+new :: IO Controller
+new = do
+  s <- Shell.new
+  r <- RxDef.new
+  t <- Table.new
+  sv <- (s.>Shell.view) 
+  rv <- (r.>RxDef.view) 
+  tv <- (t.>Table.view)
+  g <- View.new sv rv tv 
+  this <- newRef $ C g s r t RxDef.defaultFilter
+  s .< Shell.onUpdate 
+        (Just (\lines -> this .>> showData lines))
+  r .< RxDef.onUpdate 
+        (Just (\filt -> do
+                   this .< (\stat -> stat {filterFun = filt})
+                   postGUIAsync $ s .<< Shell.runText))
+  return this
+
+-- internal
+
+showData :: L.ByteString -> C -> IO ()
+showData x state = (filterFun state x) >>= Table.push (table state) 
+
+data C = C {
+      gui :: View.ViewState
+    , shell :: Shell.Controller
+    , rxdef :: RxDef.Controller
+    , table :: Table.Controller
+    , filterFun :: RxDef.Filter
+    }
+
+-- tests
+test = windowedApp "DataProcController test" $ do
+         t <- new  :: IO Controller
+         t .> view
diff --git a/DataProcView.hs b/DataProcView.hs
new file mode 100644
--- /dev/null
+++ b/DataProcView.hs
@@ -0,0 +1,30 @@
+module DataProcView where
+
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.ModelView as MV
+
+data ViewState = V 
+    {
+      mainWidget :: Widget
+    }
+
+new :: Widget -> Widget -> Widget -> IO ViewState
+new w1 w2 w3 = do
+  vbox <- vBoxNew False 2
+  vbox2 <- vBoxNew False 0
+
+  w1ex <- expanderNew "Shell Command"
+  expanderSetExpanded w1ex True
+  containerAdd w1ex w1
+
+  w2ex <- expanderNew "Splitter Regex"
+  expanderSetExpanded w2ex False
+  containerAdd w2ex w2
+
+  boxPackStart vbox2 w1ex PackNatural 2
+  boxPackEnd vbox2 w2ex PackNatural 2
+
+  boxPackStart vbox vbox2 PackNatural 2
+  boxPackStart vbox w3 PackGrow 2
+  return (V (toWidget vbox))
+
diff --git a/DataSourceView.hs b/DataSourceView.hs
new file mode 100644
--- /dev/null
+++ b/DataSourceView.hs
@@ -0,0 +1,34 @@
+module DataSourceView where
+
+import Graphics.UI.Gtk
+import Control.Monad
+import Control.Applicative
+
+data ViewState = V 
+    { mainWidget :: Widget
+    , cmdModeRB :: RadioButton
+    , dynamicHaskellModeRB :: RadioButton
+    }
+
+new :: Widget -> IO ViewState
+new shellModeW dynHaskellModeW = do
+  vbox <- vBoxNew False 2
+  hbox <- vBoxNew False 2
+  
+  boxPackStart vbox hbox PackNatural 2
+  boxPackEnd vbox rw PackGrow 2
+
+
+
+  linesplit `toggleButtonSetActive` False
+  dis `toggleButtonSetActive` True
+
+  boxPackStart hbox dis PackNatural 2
+  boxPackStart hbox linesplit PackNatural 2
+  boxPackStart hbox submatch PackNatural 2
+
+  return $ V (toWidget vbox) dis submatch linesplit
+
+  
+
+  
diff --git a/FileChooser.hs b/FileChooser.hs
new file mode 100644
--- /dev/null
+++ b/FileChooser.hs
@@ -0,0 +1,42 @@
+module FileChooser 
+    (
+     openFileDialog
+    , saveFileDialog
+    ) where
+
+import Graphics.UI.Gtk
+
+saveFileDialog:: String -> (Maybe String) -> (FilePath -> IO ()) -> IO ()
+saveFileDialog title fp action = fileDialog title "gtk-save" FileChooserActionSave action fp
+
+openFileDialog:: String -> (Maybe String) -> (FilePath -> IO ()) -> IO ()
+openFileDialog title fp action = fileDialog title "gtk-open" FileChooserActionOpen action fp
+
+
+fileDialog thetitle accepttitle accepttype action filterPattern = do
+  dialog <- fileChooserDialogNew
+              (Just $ thetitle)
+              Nothing
+              accepttype
+	      [("gtk-cancel"       
+	       ,ResponseCancel)
+	      ,(accepttitle                                  
+	       , ResponseAccept)]
+  case filterPattern of 
+    Nothing -> return ()
+    Just filterPattern' ->
+        do
+          filt <- fileFilterNew
+          fileFilterAddPattern filt filterPattern'
+          set dialog [fileChooserFilter := filt]
+  fileChooserSetPreviewWidgetActive dialog True
+  widgetShow dialog
+  response <- dialogRun dialog
+  case response of 
+    ResponseAccept -> do 
+                  Just fileName <- fileChooserGetFilename dialog
+                  action fileName
+    ResponseCancel -> return ()
+    ResponseDeleteEvent -> return ()
+  widgetHide dialog
+
diff --git a/LineSplitterController.hs b/LineSplitterController.hs
new file mode 100644
--- /dev/null
+++ b/LineSplitterController.hs
@@ -0,0 +1,135 @@
+module LineSplitterController 
+    (
+     new
+    , test
+    , onUpdate
+    , view
+    , Controller
+    , Filter
+    , OnUpdate
+    , defaultFilter
+    ) where
+
+import Graphics.UI.Gtk
+import Control.Monad
+import Control.Applicative
+import Data.Maybe
+
+import WindowedApp
+import Component
+import SimpleRegex
+
+import qualified LineSplitterView as View
+import qualified RegexDefinitionController as RxDef
+import qualified Data.ByteString.Lazy.Char8 as L
+import SimpleRegex
+
+type Controller = Ref C
+type Filter = L.ByteString -> IO [[L.ByteString]]
+type OnUpdate = Filter -> IO ()
+
+view = View.mainWidget . gui
+
+new :: IO Controller
+new = do
+  rx <- RxDef.new
+  rxv <- rx .> RxDef.view
+  v@(View.V mw dis sm ls ) <- View.new rxv  
+  this <- newRef (C v Nothing defaultFilter rxv Nothing)
+  onToggled dis $ do
+        disA <- toggleButtonGetActive dis
+        unless disA $ widgetSetSensitivity rxv True
+        when disA (this .<< updateState)
+  rx .< RxDef.onUpdate 
+         (Just (\ s -> updateStateRx this s >> (this .<< updateState)))
+  this .<< updateState
+  return this
+
+onUpdate :: Maybe OnUpdate -> C -> C
+onUpdate u c = c { updateCB = u }
+
+-- internal functions
+defaultFilter :: Filter
+defaultFilter s = do
+  putStrLn "defaultFilter"
+  return (map (:[])  (L.lines s))
+
+updateStateRx :: Ref C -> Maybe (Regex,String) -> IO ()
+updateStateRx this rx = do
+  putStrLn $ "got new regex: " ++ maybe "Nothing" snd rx
+  this .< (\c -> c {regex = rx})
+  this .<< updateState
+
+updateState :: C -> IO C
+updateState state = do
+  di <- toggleButtonGetActive (View.disableRB (gui state))
+  sp <- toggleButtonGetActive (View.linesplitModeRB (gui state))
+  sm <- toggleButtonGetActive (View.submatchModeRB (gui state))
+  newFilter <- case (di, sp, sm) of
+                 (_, True, _) -> do
+                      postGUIAsync $ widgetSetSensitivity (regexDefView state) True
+                      makeRegexFilterFieldSep state                     
+                 (_, _, True) -> do
+                      postGUIAsync $ widgetSetSensitivity (regexDefView state) True
+                      makeRegexFilterSubMatches state
+                 _ -> do
+                      postGUIAsync $ widgetSetSensitivity (regexDefView state) False
+                      return defaultFilter
+  let newstate = (state {filterFun = newFilter})
+  notifyUpdateCallback newstate
+  return newstate
+
+withMaybeRegex :: Maybe a -> (a -> Filter) -> Filter
+withMaybeRegex Nothing _ = defaultFilter
+withMaybeRegex (Just rx) f = f rx
+
+makeRegexFilterSubMatches :: C -> IO Filter
+makeRegexFilterSubMatches state = do
+    return $ withMaybeRegex (regex state)
+               (\(rx,src) indat -> do
+                  putStrLn $ "makeRegexFilterSubMatches" ++ src
+                  let sms = catSubmatches rx $ L.lines indat
+                      res = map (\ (ss, s) -> 
+                                     case ss of
+                                       [] -> [s]
+                                       _ -> ss) sms
+                  return res)
+
+                                                         
+makeRegexFilterFieldSep :: C -> IO Filter
+makeRegexFilterFieldSep state =     
+    return $ withMaybeRegex (regex state)
+               (\(rx,src) d -> do
+                  putStrLn ("makeRegexFilterFieldSep " ++ src)
+                  let res = map (regexSplit rx) (L.lines d)
+                  return res)
+
+notifyUpdateCallback :: C -> IO ()
+notifyUpdateCallback state = 
+    case updateCB state of
+      Nothing -> do
+          return ()
+      Just updateCB' -> do       
+          updateCB' (filterFun state)
+                    
+data C = C {
+      gui :: View.ViewState
+    , updateCB :: Maybe OnUpdate
+    , filterFun :: Filter
+    , regexDefView :: Widget
+    , regex :: Maybe (Regex, String)
+    }
+
+test = windowedApp "RegexDefinitionController test" $ do
+         clut <- new
+         mw <- View.mainWidget <$>  (clut .> gui)
+         clut .< onUpdate (Just tryNewFilter)
+         return mw
+
+testdata = L.unlines $ L.pack <$>
+           [concat (map ((++ ";") . show) [x * 10 .. 9 + x * 10]) | 
+            x <- [1..10]]
+
+tryNewFilter f = do
+  res <- (f testdata)
+  putStrLn (take 100 (show res))
diff --git a/LineSplitterView.hs b/LineSplitterView.hs
new file mode 100644
--- /dev/null
+++ b/LineSplitterView.hs
@@ -0,0 +1,39 @@
+module LineSplitterView where
+
+import Graphics.UI.Gtk
+import Control.Monad
+import Control.Applicative
+
+data ViewState = V 
+    {
+       mainWidget :: Widget
+      , disableRB :: RadioButton
+      , submatchModeRB :: RadioButton
+      , linesplitModeRB :: RadioButton
+    }
+
+new :: Widget -> IO ViewState
+new rw = do
+  vbox <- vBoxNew False 2
+  hbox <- vBoxNew False 2
+  
+  boxPackStart vbox hbox PackNatural 2
+  boxPackEnd vbox rw PackGrow 2
+
+  dis <- radioButtonNewWithLabel "Disabled"
+  linesplit <- radioButtonNewWithLabelFromWidget dis "Field Seperation"
+  submatch <- radioButtonNewWithLabelFromWidget dis "Use Submatches"
+
+  linesplit `toggleButtonSetActive` False
+  submatch `toggleButtonSetActive` False
+  dis `toggleButtonSetActive` True
+
+  boxPackStart hbox dis PackNatural 2
+  boxPackStart hbox linesplit PackNatural 2
+  boxPackStart hbox submatch PackNatural 2
+
+  return $ V (toWidget vbox) dis submatch linesplit
+
+  
+
+  
diff --git a/LoadSaveController.hs b/LoadSaveController.hs
new file mode 100644
--- /dev/null
+++ b/LoadSaveController.hs
@@ -0,0 +1,95 @@
+module LoadSaveController 
+    ( Controller
+    , new
+    , view
+    , onSave
+    , onLoad
+    , clearLabel
+    ) where
+
+import qualified LoadSaveView as View
+import qualified Data.ByteString.Lazy.Char8 as L
+import Component
+import Graphics.UI.Gtk
+import WindowedApp
+import FileChooser
+import Control.Applicative
+import Data.List
+
+type Controller = Ref C
+type LoadFun = L.ByteString -> IO ()
+type SaveFun = IO (Maybe L.ByteString)
+
+view :: C -> Widget
+view = View.mainWidget . gui
+
+new :: Maybe String -> IO Controller
+new fnSuffix = do
+  v <- View.new
+  this <- newRef $ C v Nothing Nothing fnSuffix
+  (View.saveB v) `onClicked` (this .>> save)
+  (View.openB v) `onClicked` (this .>> load)
+  return this
+
+onSave :: Maybe SaveFun -> C -> C
+onSave action state = state { onSaveCB = action }
+
+onLoad :: Maybe LoadFun -> C -> C
+onLoad action state = state { onLoadCB = action }
+
+clearLabel :: C -> IO () 
+clearLabel state = labelSetText (View.statusL (gui state)) ""
+    
+-- internal affairs
+data C = C 
+    { gui :: View.ViewState
+    , onSaveCB :: Maybe SaveFun
+    , onLoadCB :: Maybe LoadFun
+    , filesuffix :: Maybe String
+    }
+
+save :: C -> IO ()
+save state = saveFileDialog "Save file " (maybe Nothing (Just . (++) "*.") (filesuffix state)) $ 
+             do \fileName ->
+                    case onSaveCB state of
+                      Nothing -> 
+                          return ()
+                      Just callback ->
+                          do
+                            c <- callback
+                            case c of
+                              Nothing -> 
+                                   return ()
+                              Just c' ->
+                                  let realfn = maybe 
+                                                fileName 
+                                                (extendFileName fileName) 
+                                                (filesuffix state)
+                                  in do 
+                                    L.writeFile realfn c'
+                                    postGUIAsync $ labelSetText (View.statusL $ gui state) $ realfn ++ " Saved."                                           
+    where
+      extendFileName fileName suffix = if isSuffixOf suffix fileName 
+                                         then fileName 
+                                         else fileName ++ "." ++ suffix
+
+load :: C -> IO ()
+load state = openFileDialog "Open file" (maybe Nothing (Just . (++) "*.") (filesuffix state)) $ 
+             do \fileName ->
+                    case onLoadCB state of
+                      Nothing -> 
+                          return ()
+                      Just callback ->
+                          do
+                            c <- L.readFile fileName
+                            callback c
+                            postGUIAsync $ labelSetText (View.statusL $ gui state) $ fileName ++ " Loaded."
+                            
+{-
+test = windowedApp "LoadSaveController test" $ do
+         clut <- new $ Nothing
+         mw <- View.mainWidget <$>  (clut .> gui)
+         clut .< onLoad (Just L.putStrLn)
+         clut .< onSave (Just (return (Just  (L.pack "this is a test"))))
+         return mw
+-}
diff --git a/LoadSaveView.hs b/LoadSaveView.hs
new file mode 100644
--- /dev/null
+++ b/LoadSaveView.hs
@@ -0,0 +1,25 @@
+module LoadSaveView where
+
+import Graphics.UI.Gtk
+
+data ViewState = ViewState 
+    {
+      mainWidget :: Widget
+    , statusL :: Label
+    , saveB :: Button
+    , openB :: Button
+    }
+
+new :: IO ViewState
+new = do
+  l <- labelNew Nothing
+  labelSetSelectable l True
+  s <- buttonNewFromStock stockSave
+  o <- buttonNewFromStock stockOpen
+
+  box <- hBoxNew False 2
+  boxPackStart box o PackNatural 2
+  boxPackStart box s PackNatural 2
+  boxPackEnd box l PackNatural 2
+
+  return $ ViewState (toWidget box) l s o
diff --git a/MultiWidgetContainerController.hs b/MultiWidgetContainerController.hs
new file mode 100644
--- /dev/null
+++ b/MultiWidgetContainerController.hs
@@ -0,0 +1,52 @@
+{-# LANGUAGE ExistentialQuantification #-}
+
+module MultiWidgetContainerController
+    (
+     new
+    , test
+    , view
+    , Controller
+    ) where
+
+import Graphics.UI.Gtk
+import qualified MultiWidgetContainerView as View
+import qualified DataProcController as DP
+import Component
+import WindowedApp
+import Control.Applicative
+import qualified Data.Map as M
+
+type Controller = Ref C
+
+view = View.mainWidget . gui
+
+new :: IO Controller
+new = do
+  v@(View.V _ _ _ ab) <- View.new 
+  this <- newRef (C v M.empty)
+  onClicked ab (this .<< addComponent) 
+  return this
+
+removeComponent :: String -> C -> C
+removeComponent name state = 
+  state { components = (M.delete name (components state)) }
+  
+addComponent :: C -> IO C
+addComponent (C g cs) = do
+  name <- entryGetText (View.nameE g)
+  putStrLn $ "add comp " ++ name
+  dpc <- DP.new
+  (dpc .> DP.view) >>= View.add g name
+  let cs' =  M.insert name dpc cs
+  return (C g cs')
+
+-- internal 
+data C = C 
+    { gui :: View.ViewState
+    , components :: M.Map String DP.Controller
+    }
+
+test = windowedApp "RegexDefinitionController test" $ do
+         clut <- new
+         mw <- View.mainWidget <$>  (clut .> gui)         
+         return mw
diff --git a/MultiWidgetContainerView.hs b/MultiWidgetContainerView.hs
new file mode 100644
--- /dev/null
+++ b/MultiWidgetContainerView.hs
@@ -0,0 +1,37 @@
+module MultiWidgetContainerView where
+
+import Graphics.UI.Gtk
+
+data ViewState = V 
+    { mainWidget :: Widget
+    , notebook :: Notebook
+    , nameE :: Entry
+    , addB :: Button
+    }
+
+new :: IO ViewState
+new = do
+  vbox <- vBoxNew False 4
+  nb <- notebookNew 
+  set nb [ notebookScrollable := True
+         , notebookEnablePopup := True
+         , notebookTabPos := PosRight 
+         ]
+  hbox <- hBoxNew False 4  
+  nameE <- entryNew
+  addB <- buttonNewFromStock stockAdd
+
+  boxPackEnd hbox addB PackNatural 2
+  boxPackEnd hbox nameE PackNatural 2
+  boxPackStart vbox nb PackGrow 2
+  sep <- hSeparatorNew
+  boxPackStart vbox sep PackNatural 2
+  boxPackStart vbox hbox PackNatural 2
+  return (V (toWidget vbox) nb nameE addB)
+
+add :: ViewState -> String -> Widget -> IO ()
+add vs name w = do
+  putStrLn $ "appending notebook page " ++ name
+  notebookAppendPage (notebook vs) w name
+  widgetShowAll (notebook vs) 
+  return ()
diff --git a/PerformanceTuning.hs b/PerformanceTuning.hs
new file mode 100644
--- /dev/null
+++ b/PerformanceTuning.hs
@@ -0,0 +1,41 @@
+module Main where 
+
+import qualified System.Process as P
+import System.IO
+import qualified Data.ByteString.Lazy.Char8 as L
+import SimpleRegex
+import Control.Concurrent
+
+main = do
+  lines <- L.readFile "testdata"
+  res <- testAction lines
+  putStrLn $ show res
+  return ()
+
+
+interactive_test = do
+  let text = "cat testdata"
+  (hin, hout, _, ph) <- P.runInteractiveProcess "/bin/bash" [] Nothing  Nothing
+  hPutStr hin text
+  lines <- L.hGetContents hout
+  res <- testAction lines
+  putStrLn $ show  res
+  ec <- P.waitForProcess ph 
+  return ()
+
+test_rx = "([0-9]{4}-[0-9]{2}-[0-9]{2} ..:..:..),....+[[][0-9]+[]] ([^ ]+) +(incoming|outgoing) c[Aa]ll #([0-9]+)/R:(.+)/C:(.+)/L:([0-9-]+) BCH#(.+)/DCH#(.+):(.+)/BRD#(.+) .+ CRN ([0-9A-Za-z]+) @([-0-9]+) (from|local) (.+) (to|remote) (.+) [[][^ ]+ *(.*)$"
+
+testAction c = do
+  rx <- compile "(incoming)"
+  return $ map (submatches rx) (L.lines c)
+
+testAction2 c = do
+--  rx <- compile "([0-9]{4}-[0-9]{2}-[0-9]{2} ..:..:..),....+[[][0-9]+[]] ([^ ]+) +(incoming|outgoing) c[Aa]ll #([0-9]+)/R:(.+)/C:(.+)/L:([0-9-]+) BCH#(.+)/DCH#(.+):(.+)/BRD#(.+) .+ CRN ([0-9A-Za-z]+) @([-0-9]+) local (.+) remote (.+) [[][^ ]+ *(.*)$"
+  rx <- compile "(incoming)"
+  let sms = catSubmatches rx $ L.lines c
+  return $  map (\ (ss, s) -> 
+                     case ss of
+                       [] -> [s]
+                       _ -> ss) sms
+
+
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,6 @@
+
+Tabloid is a gtk program that captures output from arbitrary shell commands in tables. 
+Each line can be separated with regular expressions. It aims to be somewhere between a 
+graphical awk and excel, although less powerful than both in every respect, it will 
+help analyze large logfiles. It allows outputs of diffrent programs/shellscripts 
+to be viewed directly.
diff --git a/RegexDefinitionController.hs b/RegexDefinitionController.hs
new file mode 100644
--- /dev/null
+++ b/RegexDefinitionController.hs
@@ -0,0 +1,95 @@
+module RegexDefinitionController
+    (  
+     test
+    , Controller
+    , new
+    , onUpdate
+    , view
+    ) where
+
+import qualified Graphics.UI.Gtk.ModelView as MV 
+import qualified RegexDefinitionView as View
+import qualified Data.ByteString.Lazy.Char8 as L
+import SimpleRegex
+import Component
+import Graphics.UI.Gtk
+import Data.IORef
+import Data.Maybe
+import Data.List
+import Control.Monad
+import Control.Applicative
+import Control.Concurrent
+import WindowedApp
+import qualified LoadSaveController as LSC
+
+type Controller = Ref C
+
+view = View.mainWidget . gui
+
+new =  do
+  lsc <- LSC.new (Just "regex")
+  lscv <- (lsc .> LSC.view)
+  v <- View.new lscv
+  this <- newRef $ C Nothing v Nothing
+  lsc .< (LSC.onLoad (Just (\cont -> 
+                                (this .<< (\state -> do
+                                             entrySetText (View.regexE (gui state)) (L.unpack cont)
+                                             onCh state)))))
+
+  lsc .< (LSC.onSave (Just (this .>> (\state -> 
+                                          Just <$> L.pack <$> (entryGetText $ View.regexE (gui state))))))
+                                        
+  (View.regexE v) `onEntryActivate` (this .>> onAct)
+  (View.regexE v) `onEditableChanged` (do 
+                                        lsc .>> LSC.clearLabel
+                                        this .<< onCh)
+  (View.applyB v) `onClicked` (this .>> onAct)
+  (View.applyB v) `widgetSetSensitivity` False
+  return this
+
+onUpdate :: Maybe ((Maybe (Regex,String)) -> IO ()) -> C -> C
+onUpdate ma c = c { regexUpdateCB = ma }
+
+-- internal main state record
+data C = C
+    { 
+      regexUpdateCB :: Maybe (Maybe (Regex, String) -> IO ())
+    , gui :: View.ViewState
+    , currentRegex :: Maybe (Regex, String)
+    }
+
+-- internal callbacks and functions
+onAct :: C -> IO ()
+onAct c = do   
+  case regexUpdateCB c of
+    Just uf ->  uf (currentRegex c)
+    _ -> return ()
+
+onCh :: C -> IO C
+onCh c = do 
+  str <- entryGetText $ View.regexE (gui c)
+  res <- compileWithError str
+  let v = gui c
+  c' <- case res of 
+          Right r -> 
+              do
+                labelSetText (View.errorL (gui c)) ""
+                (View.applyB v) `widgetSetSensitivity` (str /= "")
+                return (c {currentRegex = Just (r, str)})
+          Left err -> 
+              do
+                labelSetText (View.errorL (gui c)) err 
+                (View.applyB v) `widgetSetSensitivity` False
+                return (c {currentRegex = Nothing})
+  return c'
+
+-- tests
+test = windowedApp "RegexDefinitionController test" $ do
+         clut <- new
+         clut .< onUpdate $ Just (\r ->
+                                      case r of
+                                        Nothing -> putStrLn "not ok"
+                                        Just (_,str) -> putStrLn $ "ok" ++ str)
+         mw <- View.mainWidget <$>  (clut .> gui)
+         return mw
+
diff --git a/RegexDefinitionView.hs b/RegexDefinitionView.hs
new file mode 100644
--- /dev/null
+++ b/RegexDefinitionView.hs
@@ -0,0 +1,25 @@
+module RegexDefinitionView where
+
+import Graphics.UI.Gtk
+
+data ViewState = ViewState
+    {
+      mainWidget :: Widget
+    , regexE :: Entry
+    , errorL :: Label
+    , applyB :: Button
+    }
+
+new :: Widget -> IO ViewState
+new openSaveW = do
+  e <- entryNew 
+  vbox <- vBoxNew False 2
+  hbox <- hBoxNew False 2
+  boxPackStart vbox e PackGrow 2
+  boxPackStart vbox hbox PackNatural 2
+  l <- labelNew Nothing
+  applyB <- buttonNewFromStock stockApply
+  boxPackStart hbox openSaveW PackNatural 2
+  boxPackStart hbox l PackNatural 2
+  boxPackEnd hbox applyB PackNatural 2
+  return (ViewState (toWidget vbox) e l applyB)
diff --git a/ShellCmdInputController.hs b/ShellCmdInputController.hs
new file mode 100644
--- /dev/null
+++ b/ShellCmdInputController.hs
@@ -0,0 +1,111 @@
+module ShellCmdInputController
+    (
+     Controller
+     , test
+     , new
+     , onUpdate
+     , view
+     , OnUpdate
+     , runText
+     ) where
+
+import qualified Graphics.UI.Gtk.ModelView as MV 
+import qualified ShellCmdInputView as View
+import Graphics.UI.Gtk
+import Control.Monad
+import Control.Applicative
+import Control.Concurrent
+import qualified System.Process as P
+import System.IO
+import WindowedApp
+import Component
+import qualified Data.ByteString.Lazy.Char8 as L
+import SimpleRegex
+import Control.Concurrent.MVar
+import qualified LoadSaveController as LSC
+
+type Controller = Ref C
+
+view = View.mainWidget . gui
+
+new = do
+  buf <- textBufferNew Nothing
+  lsc <- LSC.new (Just "sh")
+  lscv <- (lsc .> LSC.view)
+  v@(View.V _ runB _ _ _ _) <- View.new buf lscv
+  lock <- newEmptyMVar
+  this <- newRef (C v "" Nothing buf lock)
+  runB `onClicked` (this .<< runText)
+  lsc .< (LSC.onLoad (Just (\cont -> textBufferSetText buf (L.unpack cont))))
+  lsc .< (LSC.onSave (Just (Just <$> L.pack <$> getBufferText buf)))       
+  onBufferChanged buf (lsc .>> LSC.clearLabel)
+  return this
+
+type OnUpdate = L.ByteString -> IO ()
+
+onUpdate :: Maybe OnUpdate -> C -> C
+onUpdate cb state = state {executeCB = cb}
+-- internal functions 
+data C = C 
+    { 
+      gui :: View.ViewState
+    , currentCmd :: String
+    , executeCB :: Maybe OnUpdate
+    , buffer :: TextBuffer
+    , execLock :: MVar Bool
+    }
+
+getBufferText buf = do
+  s <- textBufferGetStartIter buf
+  e <- textBufferGetEndIter buf
+  textBufferGetText buf s e True
+  
+runText :: C -> IO C
+runText state = do
+  locked <- tryPutMVar (execLock state) True
+  if locked then
+      case executeCB state of
+        Just execCB -> 
+            do
+              text <- getBufferText (buffer state)
+              theshell <- entryGetText (View.shellE (gui state))
+              forkOS $ executeCmd text execCB state (words theshell)
+              return $ state
+        Nothing -> 
+            return state
+    else return state
+
+executeCmd text execCB state (theshell:theargs) = do
+  postGUISync $ widgetSetSensitivity (View.cancelB $ gui state) True
+  postGUISync $ widgetSetSensitivity (View.executeB $ gui state) False
+  postGUISync $ widgetSetSensitivity (View.textView $ gui state) False
+  postGUISync $ labelSetText (View.exitCodeL $ gui state) $ "Executing..."
+  (hin, hout, _, ph) <- P.runInteractiveProcess theshell theargs Nothing  Nothing
+  postGUISync $ (View.cancelB $ gui state) `onClicked` (do
+                                                         P.terminateProcess ph
+                                                         return ())
+  forkOS $ do
+    hPutStr hin text
+    lines <- L.hGetContents hout
+    postGUISync $ execCB lines
+
+  ec <- P.waitForProcess ph 
+
+  postGUISync $ (View.cancelB $ gui state) `onClicked` (return ())
+  postGUISync $ widgetSetSensitivity (View.cancelB $ gui state) False
+  postGUISync $ widgetSetSensitivity (View.textView $ gui state) True
+  postGUISync $ widgetSetSensitivity (View.executeB $ gui state) True
+  postGUISync $ labelSetText (View.exitCodeL $ gui state) $ "Exit Code: " ++ show ec
+  takeMVar (execLock state)
+  return ()
+                     
+-- tests
+test = windowedApp "ShellCmdInputController test" $ do
+         t <- new  :: IO Controller
+         t .< onUpdate (Just ((mapM_ putStrLn) 
+                              . (take 100)  
+                              . lines 
+                              . L.unpack))
+         t .> view
+         
+
diff --git a/ShellCmdInputView.hs b/ShellCmdInputView.hs
new file mode 100644
--- /dev/null
+++ b/ShellCmdInputView.hs
@@ -0,0 +1,68 @@
+module ShellCmdInputView where
+
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.ModelView as MV
+
+data ViewState = V 
+    {
+      mainWidget :: Widget
+    , executeB :: Button
+    , exitCodeL :: Label
+    , textView :: TextView
+    , cancelB :: Button
+    , shellE :: Entry
+    }
+
+new buf fileButtons = do  
+  cmdE <- textViewNewWithBuffer buf
+
+  let stateTypes = [StateNormal, StateActive, StatePrelight, StateSelected, StateInsensitive]
+      fgCols = [Color 65535 49152 0, Color  65535 49152 0, Color  65535 49152 0, Color  65535 49152 0]
+      bgCols = [Color 255 192 0, Color 0 0 0, Color 0 0 0, Color 32535 32535 0]
+
+  mapM_ (uncurry (widgetModifyText cmdE)) $
+             stateTypes `zip` fgCols
+
+  mapM_ (uncurry (widgetModifyBase cmdE)) $
+             stateTypes `zip` bgCols
+
+  scrollWin <- scrolledWindowNew Nothing Nothing
+  set scrollWin 
+          [ containerBorderWidth := 5
+          , scrolledWindowShadowType := ShadowIn
+          , scrolledWindowVscrollbarPolicy := PolicyAutomatic
+          , scrolledWindowHscrollbarPolicy  := PolicyAutomatic
+          ]
+  containerAdd scrollWin cmdE
+
+  cmdBHbox <- hBoxNew False 2
+  cmdRunB <- buttonNewFromStock stockExecute
+  boxPackEnd cmdBHbox cmdRunB PackNatural 2
+             
+  cancB <- buttonNewFromStock stockCancel
+  widgetSetSensitivity cancB False
+  boxPackEnd cmdBHbox cancB PackNatural 2
+             
+  boxPackStart cmdBHbox fileButtons PackNatural 2
+
+  exitL <- labelNew Nothing
+  boxPackStart cmdBHbox exitL PackGrow 2
+
+  shellH <- hBoxNew False 2
+  shellL <- labelNew $ Just "Shell: "
+  shellE <- entryNew
+  entrySetText shellE "/bin/bash"
+  boxPackStart shellH shellL PackNatural 2
+  boxPackStart shellH shellE PackGrow 2
+
+  centerHB <- hBoxNew False 2
+  
+  cmdVbox <- vBoxNew False 2
+  boxPackStart cmdVbox shellH PackNatural 2
+  boxPackStart cmdVbox scrollWin PackGrow 2
+  boxPackStart cmdVbox cmdBHbox PackNatural 2  
+
+  vbox <- vBoxNew False 2
+  boxPackStart vbox  cmdVbox PackGrow 2
+
+  return $ V (toWidget vbox) cmdRunB exitL cmdE cancB shellE
diff --git a/SimpleRegex.hs b/SimpleRegex.hs
new file mode 100644
--- /dev/null
+++ b/SimpleRegex.hs
@@ -0,0 +1,99 @@
+module SimpleRegex 
+    (
+     Regex
+    , submatches
+    , compile
+    , compileWithError
+    , ByteString    
+    , regexSplit
+    , catSubmatches
+    ) where
+
+import qualified Text.Regex.Posix.ByteString.Lazy as R
+import Data.ByteString.Lazy.Char8 as L hiding (putStrLn, map, take, concat, zipWith)
+import Data.Either
+import Data.Maybe
+import Text.Regex.Base.RegexLike
+import Control.Monad
+import System.IO.Unsafe
+
+type Regex = R.Regex
+
+-- very simple matching functions for lazy bytestream regexp
+
+submatches :: Regex -> ByteString -> Maybe [ByteString]
+submatches r s = let m = unsafePerformIO (R.regexec r s) in
+                 case m of
+    Right (Just (_,_,_,ms)) -> 
+        Just ms
+    _ -> 
+        Nothing
+
+catSubmatches ::  Regex -> [ByteString] -> [([ByteString], ByteString)]
+catSubmatches r ss = let sms = map (submatches r) ss
+                     in catMaybes $ zipWith (\le ri -> le >>= (\le' -> return (le', ri))) sms ss
+
+regexSplit :: Regex -> ByteString -> [ByteString]
+regexSplit  r s = 
+    let
+      m = unsafePerformIO (R.regexec r s)
+    in case m of
+         Right (Just (bef,_,aft,_)) -> 
+            if L.length bef > 0 
+             then (bef : regexSplit r aft) 
+             else [aft]
+         _ -> 
+            [s]
+
+compile :: String -> IO Regex
+compile rx = do
+  (Right r) <- R.compile defaultCompOpt defaultExecOpt $ pack rx
+  return r
+
+compileWithError :: String -> IO (Either String Regex)
+compileWithError rx = do
+  r <- R.compile defaultCompOpt defaultExecOpt $ pack rx
+  return $ case r of
+             (Right r') -> 
+                 Right r'
+             (Left (rc, rs)) -> 
+                 Left ("Error: " ++ rs)
+
+
+-- tests
+{-
+test1 = do
+  rx <- compile ";"
+  let teststr = L.pack ";;123;3456;234;"
+      res = regexSplit rx teststr
+  putStrLn (show res)
+     
+test2 = do
+  rx <- compile ";"
+  let testlines = L.pack ";;123;3456;234;\na;;asd;;24;das"
+      res = (map (regexSplit rx)) (L.lines testlines)
+  putStrLn (show res)
+     
+test3 = do
+  rx <- compile ";"
+  let ff d = catSubmatches rx d
+      res = ff (L.lines testdata)
+  putStrLn (show (take 200 res))
+  
+test4 = do
+  rx <- compile " ;"
+  let ff d = map (regexSplit rx) d
+      res = ff (L.lines testdata)
+  putStrLn (take 200 (show res))
+  
+test5 = do
+  rx <- compile ""
+  let testlines = L.pack ";;123;3456;234;\na;;asd;;24;das"
+      res = (map (regexSplit rx)) (L.lines testlines)
+  putStrLn (show res)
+     
+testdata = L.unlines $ L.pack <$>
+           [concat (map ((++ ";") . show) [x * 10 .. 9 + x * 10]) | 
+            x <- [1..10]]
+
+-}
diff --git a/TableController.hs b/TableController.hs
new file mode 100644
--- /dev/null
+++ b/TableController.hs
@@ -0,0 +1,188 @@
+module TableController
+    (
+     Controller
+    , test
+    , new
+    , view
+    , push    
+    ) where
+    
+import qualified Data.ByteString.Lazy.Char8 as L
+import qualified Graphics.UI.Gtk.ModelView as MV 
+import qualified LoadSaveController as LSC
+import qualified TableView as View
+import Graphics.UI.Gtk
+import Data.Maybe
+import Data.List
+import SimpleRegex
+import Control.Monad
+import Control.Applicative
+import WindowedApp
+import Component
+
+type Controller = Ref C
+
+type StringList = MV.ListStore [L.ByteString]
+
+view :: C -> Widget
+view = View.mainWidget . gui
+
+new :: IO Controller
+new = do
+  lsc <- LSC.new (Just "jira")
+  lsv <- (lsc .> LSC.view)
+  l <- MV.listStoreNew [] 
+  v <- View.new l lsv
+  lsc .< (LSC.onSave (Just (Just <$> toJira l)))
+  onClicked (View.searchPB v) (searchTableBackward (View.searchE v) l (View.treeView v))
+  onClicked (View.searchNB v) (searchTableForward (View.searchE v) l (View.treeView v))
+  View.add_col (View.treeView v) l (L.unpack . (!! 0)) "Line" 0
+  this <- newRef (C v l 1 Nothing)
+  onToggled (View.groupB v) (this .<< updateGrouping)
+  onValueSpinned (View.groupE v) (this .<< updateGrouping)
+  return this
+
+push this rows = 
+  this .<< \s -> 
+      case groupCol s of
+        Nothing -> 
+           pushRaw (zipWith (:) (map (L.pack . show) [1..]) rows) s
+        Just (c, _) ->
+           pushRaw (zipWith (:) (map (L.pack . show) [1..]) rows) (s {groupCol = Just (c, rows)})
+
+-- internal functions
+groupRows [] _ = []
+groupRows (row:rows) col = row : sameGroup ++ groupRows rest col
+    where cell = row !! col
+          (sameGroup, rest) = partition ((== cell) . (!! col)) rows 
+
+
+updateGrouping state@(C g listM mc gCol) = 
+    do
+      col <- spinButtonGetValueAsInt (View.groupE g)
+      grouping <- toggleButtonGetActive (View.groupB g)
+      if grouping then 
+          do
+            rows <- MV.listStoreToList listM            
+            case gCol of
+              Nothing ->                   
+                  pushRaw rows (state { groupCol = Just (col, rows)})
+              Just (ccol, crows) ->
+                  pushRaw crows (state { groupCol = Just (col, crows)})
+
+          else
+              case gCol of
+                Nothing -> 
+                    return (state { groupCol = Nothing }) 
+                Just (ccols, crows) ->
+                    pushRaw crows (state { groupCol = Nothing})
+                    
+
+searchTableBackward entry liststore treeview = do
+  selI <- getSelectedRow treeview
+  rows' <- MV.listStoreToList liststore
+  let rows = reverse $ zip [0..] (take selI rows')
+  searchRowRange rows entry treeview
+
+searchTableForward entry liststore treeview = do
+  selI <- getSelectedRow treeview
+  rows' <- MV.listStoreToList liststore
+  let rows = zip [(selI + 1) ..] (drop (selI + 1) rows')
+  searchRowRange rows entry  treeview
+
+searchRowRange rows entry treeview = do
+  searchTerm <- entryGetText entry
+  let sI = filter (any (isInfixOf searchTerm) . (map L.unpack) . snd)  rows
+  case sI of
+    ((nexti, _):_) -> do
+            MV.treeViewSetCursor treeview [nexti] Nothing
+    _ -> 
+        beep
+
+getSelectedRow treeV = do
+  tsel <- MV.treeViewGetSelection treeV
+  sel <-  MV.treeSelectionGetSelectedRows tsel
+  return $ case sel of
+             ((fst_sel:_):_) -> fst_sel
+             _ -> 0
+
+{-
+toCsv :: StringList -> IO L.ByteString 
+toCsv liststore = do
+  cont <- MV.listStoreToList liststore
+  return (L.unlines (lineToCsv <$> cont))
+    where
+      lineToCsv :: [L.ByteString] -> L.ByteString
+      lineToCsv ls = 
+          let els = doQuotes <$> ls
+          in L.concat (intersperse (L.pack ";") els)
+      doQuotes :: L.ByteString -> L.ByteString
+      doQuotes cont = 
+          let escaped = L.foldl' (\s c -> 
+                                      s `L.append` 
+                                            (if c == '"' 
+                                              then L.pack ['\\', c] 
+                                              else L.pack [c])) 
+                        L.empty
+                        cont
+          in (L.pack "\"") `L.append` escaped `L.append` (L.pack "\"")
+-}
+toJira :: StringList -> IO L.ByteString 
+toJira liststore = do
+  cont <- MV.listStoreToList liststore
+  return (L.unlines (lineToCsv <$> cont))
+    where
+      lineToCsv :: [L.ByteString] -> L.ByteString
+      lineToCsv ls = 
+          let els = escape <$> ls
+          in (L.pack "| ") `L.append` 
+             L.concat (intersperse (L.pack " | ") els) `L.append` (L.pack " |")
+      escape :: L.ByteString -> L.ByteString
+      escape cont = L.foldl' (\s c -> 
+                                  s `L.append` 
+                                        (if c == '|' 
+                                          then L.pack ['\\', c] 
+                                          else L.pack [c])) 
+                    L.empty
+                    cont
+
+pushRaw :: [[L.ByteString]] -> C -> IO C
+pushRaw rows c@(C g listM mc groupCol) =
+    let 
+      addRows n [] = return n
+      addRows n (r:rs) = do
+        let r' = length r
+        when (r' > n) $ forM_ [n .. r' - 1] 
+                 (\i -> 
+                      View.add_col (View.treeView g)
+                          listM (at i) ("$" ++ show i) i)
+        (MV.listStoreAppend listM) r
+        addRows (max n r') rs
+      at :: Int -> [L.ByteString] -> String
+      at i xs 
+         | (length xs) <= i =  ""
+         | otherwise        = L.unpack $ xs !! i
+    in 
+      do
+        (MV.listStoreClear listM)
+        rowcount <- spinButtonGetValueAsInt (View.maxLines g)
+        let groupedRows = maybe selRows (groupRows selRows . fst) groupCol
+            selRows = (take rowcount rows)            
+        cs <- addRows mc groupedRows
+        spinButtonSetRange (View.groupE g) 0 (fromIntegral (cs - 1))
+        return (c { cols = cs })
+
+data C = C 
+    { 
+      gui :: View.ViewState
+    , listModel :: StringList
+    , cols :: Int
+    , groupCol :: Maybe (Int, [[L.ByteString]])
+    }
+
+
+-- tests
+test = windowedApp "TableController test" $ do
+         t <- new  :: IO Controller
+         t `push` [[(L.pack . show) (c `rem` 3)| c <- [r .. r + 10]] | r <- [0 .. 9]]        
+         t .> view
diff --git a/TableView.hs b/TableView.hs
new file mode 100644
--- /dev/null
+++ b/TableView.hs
@@ -0,0 +1,86 @@
+module TableView where
+
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.ModelView as MV
+import qualified Data.ByteString.Lazy.Char8 as L
+
+data ViewState = V 
+    {
+      mainWidget :: Widget
+    , treeView :: MV.TreeView
+    , maxLines :: SpinButton
+    , searchE :: Entry
+    , searchPB :: Button
+    , searchNB :: Button
+    , groupB :: ToggleButton
+    , groupE :: SpinButton
+    }
+
+new treeM loadSaveW = do
+  vbox <- vBoxNew False 2
+  hbox <- hBoxNew False 2
+  l <- labelNew (Just "Max. Lines: ")
+
+  sl <- labelNew (Just "Search in Table: ")
+  searchE <- entryNew
+  searchNB <- buttonNewFromStock stockGoDown
+  searchPB <- buttonNewFromStock stockGoUp
+
+  maxLines <- spinButtonNewWithRange 1 50000 1
+  spinButtonSetValue maxLines 5000.0 
+
+  groupL <- labelNew (Just "Column: ")
+  groupE <- spinButtonNewWithRange 0 50000 1
+  spinButtonSetWrap groupE True
+  spinButtonSetSnapToTicks groupE True
+  spinButtonSetValue groupE 0
+  groupB <- toggleButtonNewWithLabel "Group"
+
+  v1 <- vSeparatorNew
+  v2 <- vSeparatorNew
+  v3 <- vSeparatorNew
+
+  boxPackStart hbox loadSaveW PackNatural 2  
+  boxPackStart hbox v1 PackNatural 2  
+  boxPackStart hbox sl PackNatural 2  
+  boxPackStart hbox searchE PackNatural 2  
+  boxPackStart hbox searchPB PackNatural 2  
+  boxPackStart hbox searchNB PackNatural 2  
+  boxPackStart hbox v2 PackNatural 2  
+  boxPackStart hbox groupL PackNatural 2  
+  boxPackStart hbox groupE PackNatural 2  
+  boxPackStart hbox groupB PackNatural 2                 
+  boxPackStart hbox v3 PackNatural 2  
+  boxPackEnd hbox maxLines PackNatural 2  
+  boxPackEnd hbox l PackNatural 2
+
+  treeV <- MV.treeViewNewWithModel treeM
+  scrollWin <- scrolledWindowNew Nothing Nothing
+  containerAdd scrollWin treeV
+
+  boxPackStart vbox scrollWin PackGrow 2
+  boxPackStart vbox hbox PackNatural 2
+
+  set treeV [ MV.treeViewHeadersClickable := True
+            , MV.treeViewHeadersVisible := True
+--            , MV.treeViewEnableGridLines := MV.TreeViewGridLinesHorizontal 
+            , MV.treeViewReorderable := True
+            , MV.treeViewRulesHint := True
+            , MV.treeViewEnableSearch := True
+--            , MV.treeViewRubberBanding := True
+            ]
+  return $ V (toWidget vbox) treeV maxLines searchE searchPB searchNB groupB groupE
+
+add_col treeV listM row_sel title scolid = do
+  strCol <- MV.treeViewColumnNew
+  strColRenderer <- MV.cellRendererTextNew
+  set strColRenderer [ MV.cellTextEditable := True ]
+  MV.treeViewColumnPackStart strCol strColRenderer True
+  MV.treeViewColumnSetSortColumnId strCol scolid  
+  MV.cellLayoutPackStart strCol strColRenderer True
+  MV.treeViewColumnSetResizable strCol True
+  MV.cellLayoutSetAttributes strCol strColRenderer listM $ \row -> 
+      [MV.cellText := (row_sel row) ]
+  MV.treeViewColumnSetTitle strCol title
+  MV.treeViewAppendColumn treeV strCol
+  return ()
diff --git a/WindowedApp.hs b/WindowedApp.hs
new file mode 100644
--- /dev/null
+++ b/WindowedApp.hs
@@ -0,0 +1,22 @@
+module WindowedApp 
+    (
+     windowedApp
+    ) where
+    
+import Graphics.UI.Gtk
+
+windowedApp :: String -> IO Widget -> IO ()
+windowedApp title action = do
+  unsafeInitGUIForThreadedRTS
+  window <- windowNew
+  mainWidget <- action
+  set window [windowDefaultWidth := 800, 
+              windowDefaultHeight := 600,
+              containerBorderWidth := 10, 
+              windowTitle := title,
+              containerChild := mainWidget]
+
+  onDestroy window mainQuit
+  widgetShowAll window
+  mainGUI
+
diff --git a/tabloid.cabal b/tabloid.cabal
--- a/tabloid.cabal
+++ b/tabloid.cabal
@@ -1,5 +1,5 @@
 Name:	         tabloid
-Version:         0.2
+Version:         0.3
 Description:     GUI for shell commands and log analysis
 Category:	 System
 Synopsis:	 View the output of shell commands in a table
