Index: main/grid/column-formats.lisp
===================================================================
--- main/grid/column-formats.lisp	(revision main,60)
+++ main/grid/column-formats.lisp	(revision main,60)
@@ -0,0 +1,68 @@
+(in-package #:grid)
+
+
+
+(defclass format-spec ()
+  ((width :type (integer 1) :initarg :width :reader width)))
+
+(defclass string-format (format-spec)
+  ())
+
+(defclass number-format (format-spec)
+  ((precision :type (integer 0) :initarg :precision :reader precision)))
+
+
+
+(defun format-string (s w)
+  (when (> (length s) w)
+    (let ((clipped (subseq s 0 (max 0 (- w 3)))))
+      (setf s (concatenate 'string clipped "..."))))
+  (when (> (length s) w)
+    (setf s (subseq s 0 w)))
+  (format nil (format nil "~~~AA" w) s))
+
+(defgeneric format-to-spec (format-spec obj)
+  (:method ((f string-format) s)
+    (format-string s (width f)))
+  (:method ((f number-format) x)
+    (let ((format-string (if (= (precision f) 0)
+                             (format nil "~~~AD" (width f))
+                             (format nil "~~~A,~A,,'#F" (width f)
+                                     (precision f)))))
+      (format nil format-string x))))
+
+
+
+(defun parse-string-format (args)
+  (destructuring-bind (width) args
+    (make-instance 'string-format :width width)))
+
+(defun parse-number-format (args)
+  (destructuring-bind (width &optional (precision 0)) args
+    (make-instance 'number-format :width width :precision precision)))
+
+(defun parse-spec (spec)
+  (destructuring-bind (type &rest args) spec
+    (ecase type
+      (:string (parse-string-format args))
+      (:number (parse-number-format args)))))
+
+(defun parse-format-specs (&rest specs)
+  (mapcar 'parse-spec specs))
+
+
+
+(defclass column-formats ()
+  ((format-specs :type (vector format-spec) :initarg :format-specs
+                 :reader format-specs)))
+
+
+
+(defmethod column-width ((d column-formats) column)
+  (width (aref (format-specs d) column)))
+
+(defmethod item :around ((d column-formats) row column)
+  (let ((raw (call-next-method d row column)))
+    (format-to-spec (aref (format-specs d) column) raw)))
+
+(defmethod uses-display-strings ((d column-formats) row column))
Index: main/grid/column-padding.lisp
===================================================================
--- main/grid/column-padding.lisp	(revision main,60)
+++ main/grid/column-padding.lisp	(revision main,60)
@@ -0,0 +1,15 @@
+(in-package #:grid)
+
+
+
+(defclass column-padding ()
+  ((padding :type (integer 0) :initform 1 :accessor padding)))
+
+
+
+(defmethod column-width :around ((d column-padding) column)
+  (+ (* 2 (padding d)) (call-next-method d column)))
+
+(defmethod item :around ((d column-padding) row column)
+  (let ((unpadded (call-next-method d row column)))
+    (format nil (format nil "~~,,~A:@<~~A~~>" (padding d)) unpadded)))
Index: main/grid/hash-data.lisp
===================================================================
--- main/grid/hash-data.lisp	(revision main,60)
+++ main/grid/hash-data.lisp	(revision main,60)
@@ -0,0 +1,21 @@
+(in-package #:grid)
+
+
+
+(defclass hash-per-row-grid-data ()
+  ((row-tables :type (vector hash-table) :initarg :row-tables)
+   (keys :type vector :initarg :keys)))
+
+(defmethod rows ((d hash-per-row-grid-data))
+  (with-slots (row-tables) d
+    (length row-tables)))
+
+(defmethod columns ((d hash-per-row-grid-data))
+  (with-slots (keys) d
+    (length keys)))
+
+(defmethod item ((d hash-per-row-grid-data) row column)
+  (with-slots (row-tables keys) d
+    (let ((data (aref row-tables row))
+          (key (aref keys column)))
+      (gethash key data))))
Index: main/grid/nice-header.lisp
===================================================================
--- main/grid/nice-header.lisp	(revision main,60)
+++ main/grid/nice-header.lisp	(revision main,60)
@@ -0,0 +1,28 @@
+(in-package #:grid)
+
+
+
+(defclass nice-header ()
+  ((column-heads :type (vector string) :initarg :column-heads
+                 :reader column-heads)))
+
+
+
+(defmethod rows :around ((d nice-header))
+  (1+ (call-next-method d)))
+
+(defmethod header-rows ((d nice-header))
+  1)
+
+(defmethod item :around ((d nice-header) row column)
+  (let* ((raw (if (= row 0)
+                  (format nil (format nil "~~~AA" (column-width d column))
+                          (enquote (aref (column-heads d) column)))
+                  (call-next-method d (1- row) column))))
+    (if (= row 0)
+        (concatenate 'string "`U`B" raw "`b`u")
+        raw)))
+
+(defmethod uses-display-strings :around ((d nice-header) row column)
+  (or (= row 0)
+      (call-next-method d (1- row) column)))
Index: main/grid/row-selectable.lisp
===================================================================
--- main/grid/row-selectable.lisp	(revision main,60)
+++ main/grid/row-selectable.lisp	(revision main,60)
@@ -0,0 +1,28 @@
+(in-package #:grid)
+
+
+
+(defclass row-selectable ()
+  ((selected-row :type (integer 0) :initform 0 :accessor selected-row)))
+
+
+
+(defmethod (setf selected-row) :around (i (d row-selectable))
+  (call-next-method (bound i 0 (rows d)) d))
+
+(defvar *recursive* nil)
+
+(defmethod uses-display-strings :around ((d row-selectable) row column)
+  (or (and (not *recursive*) (= row (selected-row d)))
+      (call-next-method d row column)))
+
+(defmethod item :around ((d row-selectable) row column)
+  (let ((plain (call-next-method d row column)))
+    (unless (= row (selected-row d))
+      (return-from item plain))
+    ;; FORMAT to ensure it's a string.
+    (setf plain (format nil "~A" plain))
+    (let ((*recursive* t))
+      (unless (uses-display-strings d row column)
+        (setf plain (enquote plain))))
+    (concatenate 'string "`R" plain "`r")))
Index: main/grid/package.lisp
===================================================================
--- main/grid/package.lisp	(revision main,57)
+++ main/grid/package.lisp	(revision main,60)
@@ -1,5 +1,17 @@
 (defpackage #:grid
   (:use #:cl #:dso-util #:tui-display-string #:tui-output #:tui-window)
-  (:export #:rows #:header-rows #:columns #:header-columns #:column-width
-           #:item #:uses-display-strings #:row-scroll #:column-scroll
-           #:make-grid #:draw))
+  (:export
+
+   ;; Model
+   #:rows #:header-rows #:columns #:header-columns #:column-width #:item
+   #:uses-display-strings
+
+   ;; Display
+   #:row-scroll #:column-scroll #:make-grid #:draw
+
+   ;; Extra
+   #:hash-per-row-grid-data
+   #:parse-format-specs #:column-formats
+   #:column-padding #:padding
+   #:nice-header
+   #:row-selectable #:selected-row))
Index: main/grid/test.lisp
===================================================================
--- main/grid/test.lisp	(revision main,57)
+++ main/grid/test.lisp	(revision main,60)
@@ -7,10 +7,8 @@
 
 
-(defclass test () ())
+(defclass test (nice-header row-selectable column-padding column-formats)
+  ())
 
 (defmethod rows ((gd test)) 10)
-
-(defmethod header-rows ((gd test))
-  1)
 
 (defmethod columns ((gd test)) 10)
@@ -19,20 +17,8 @@
   1)
 
-(defmethod column-width ((gd test) column)
-  5)
-
 (defmethod item ((gd test) row column)
   (assert (and (<= 0 row 9)
                (<= 0 column 9)))
-  (cond
-    ((= row 0)
-     (concatenate 'string "`B`U " (string (aref "*ABCDEFGHI" column)) " `u`b"))
-    ((= column 0)
-     (format nil " `B~A`b " row))
-    (t
-     (format nil (if (= row column 2) "`R~A,~A`r" "~A,~A") row column))))
-
-(defmethod uses-display-strings ((gd test) row column)
-  t)
+  (* (1+ row) (1+ column)))
 
 
@@ -42,6 +28,14 @@
   (refresh screen))
 
+(defconstant +format+ (first (parse-format-specs '(:number 3))))
+(defconstant +formats+ (make-sequence 'vector 10 :initial-element +format+))
+
 (defun test-draw ()
-  (let ((data (make-instance 'test)))
+  (let ((data (make-instance 'test
+                             :format-specs +formats+
+                             :column-heads #("   *" "   A" "   B" "   C" "   D"
+                                             "   E" "   F" "   G" "   H"
+                                             "   I"))))
+    (setf (selected-row data) 1)
     (with-screen (screen)
       (clear screen)
Index: main/tui.asd
===================================================================
--- main/tui.asd	(revision main,54)
+++ main/tui.asd	(revision main,60)
@@ -43,5 +43,11 @@
                                      (:file "hash-data"
                                       :depends-on ("model"))
-                                     (:file "formatted-data"
+                                     (:file "nice-header"
+                                      :depends-on ("model"))
+                                     (:file "row-selectable"
+                                      :depends-on ("model"))
+                                     (:file "column-formats"
+                                      :depends-on ("model"))
+                                     (:file "column-padding"
                                       :depends-on ("model"))
                                      (:file "display"
