CLIM mail archive


Table rules, as promised

The following code adds rules to a table once the table has been formatted.
You can add horizontal or vertical rules separately; the format of :HORIZONTAL
and :VERTICAL args is a list of row or column numbers before which a rule gets
added.  :RULE-DRAWER and :RULE-DRAWING-OPTIONS are self-explanatory.

There is a little calendar example at the end.

Note that this only works in CLIM 2.0.

-------- cut here --------
(in-package :clim-internals)

;; Hack to draw rules in tables.
;;--- Note that this doesn't interact at all with incremental redisplay, so
;;--- if you need to redisplay table rules you'll need to do it yourself.
(defmethod draw-table-rules ((table standard-table-output-record) stream 
			     &key vertical horizontal
				  (rule-drawer #'draw-line*) rule-drawing-options)
  (let* ((nrows 0)
	 (ncols nil)
	 (cells 0)
	 (row-table-p (row-table-p table))
	 (table-mapper (if row-table-p #'map-over-table-rows #'map-over-table-columns)))
    (labels ((count-rows (row)
	       (incf nrows)
	       (setq cells 0)
	       (map-over-row-cells #'count-cells row)
	       (cond ((null ncols)
		      (setq ncols cells))
		      (maxf ncols cells))))
	     (count-cells (cell)
	       (assert (cell-output-record-p cell))
	       (incf cells)))
      (declare (dynamic-extent #'count-rows #'count-cells))
      (funcall table-mapper #'count-rows table))
    (when (or (null ncols) (= ncols 0))
      (return-from draw-table-rules))
    ;; In a column table, we track widths across the columns and heights
    ;; within the column, so we swap the "row" and cell count
    (unless row-table-p
      (rotatef nrows ncols))
    (with-stack-array (cell-heights nrows :initial-element 0)
      (declare (type simple-vector row-array))
      (with-stack-array (cell-widths ncols :initial-element 0)
	(declare (type simple-vector column-array))
	(let ((row-n 0))
	  (funcall table-mapper
		   #'(lambda (row) 
		       (let ((col-n 0))
			   #'(lambda (cell) 
			       (multiple-value-bind (width height)
				   (bounding-rectangle-size cell)
				 ;; "...a column is just a row tilted on its end."
				 (cond (row-table-p
					(maxf (svref cell-widths  col-n) width)
					(maxf (svref cell-heights row-n) height))
					(maxf (svref cell-widths  row-n) width)
					(maxf (svref cell-heights col-n) height))))
			       (incf col-n))
		       (incf row-n))
	(flet ((draw-rules ()
		 (multiple-value-bind (initial-x initial-y)
		     (output-record-position table)
		   (multiple-value-bind (xoff yoff)
			 stream (output-record-parent table))
		     (translate-coordinates xoff yoff initial-x initial-y))
		   (let* ((final-x 
			    (+ initial-x (bounding-rectangle-width  table)))
			    (+ initial-y (bounding-rectangle-height table)))
			  (x-spacing (slot-value table 'x-spacing))
			  (y-spacing (slot-value table 'y-spacing))
			  (half-x-spacing (floor x-spacing 2))
			  (half-y-spacing (floor y-spacing 2))
			    (floor (line-style-thickness (medium-line-style stream)) 2)))
		     (when vertical
		       (let ((x initial-x)
			     (sy (- initial-y half-y-spacing half-thickness))
			     (ey (+ final-y half-y-spacing half-thickness)))
			 (do ((col 0 (1+ col)))
			     ((> col ncols))
			   (when (member col vertical)
			     (funcall rule-drawer 
				      stream (- x half-x-spacing) sy (- x half-x-spacing) ey))
			   (unless (= col ncols)
			     (incf x (+ (svref cell-widths col) x-spacing))))))
		     (when horizontal
		       (let ((y initial-y)
			     (sx (- initial-x half-x-spacing half-thickness))
			     (ex (+ final-x half-x-spacing half-thickness)))
			 (do ((row 0 (1+ row)))
			     ((> row nrows))
			   (when (member row horizontal)
			     (funcall rule-drawer 
				      stream sx (- y half-y-spacing) ex (- y half-y-spacing)))
			   (unless (= row nrows)
			     (incf y (+ (svref cell-heights row) y-spacing))))))))))
	  (declare (dynamic-extent #'draw-rules))
	  (apply #'invoke-with-drawing-options
		 stream #'draw-rules rule-drawing-options))))))

;;; Silly "calendar" example

(in-package :clim-user)

(defvar *days-of-the-week* (vector "Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"))

(defvar *month-lengths* (vector 31 28 31 30 31 30 31 31 30 31 30 31))
(defun days-in-month (month year)
  (if (= month 2)
      (if (zerop (mod year 4))
	  (if (zerop (mod year 400)) 28 29)
      (svref *month-lengths* (1- month))))

(defun display-calendar (month year &key (stream *standard-output*))
  (let ((days-in-month (days-in-month month year)))
    (multiple-value-bind (nil nil nil nil nil nil start-day)
        (decode-universal-time (encode-universal-time 0 0 0 1 month year))
      (setq start-day (mod (+ start-day 1) 7))
      (let* ((nrows 0)
	       (formatting-table (stream :x-spacing "  " :y-spacing 6)
		 (formatting-row (stream)
		   (dotimes (d 7)
		     (formatting-cell (stream :align-x :center)
		       (with-text-face (stream :italic)
			 (write-string (svref *days-of-the-week* (mod d 7)) stream)))))
		 (do ((date 1)
		      (first-week t nil))
		     ((> date days-in-month))
		   (incf nrows)
		   (formatting-row (stream)
		     (dotimes (d 7)
		       (formatting-cell (stream :align-x :right)
			 (when (and (<= date days-in-month)
				    (or (not first-week) (>= d start-day)))
			   (format stream "~D" date)
			   (incf date)))))))))
	(draw-table-rules calendar stream
			  :vertical '(0 7) :horizontal (list 0 (1+ nrows))
			  :rule-drawing-options '(:line-thickness 2))
	(draw-table-rules calendar stream
			  :vertical '(1 2 3 4 5 6) :horizontal '(1 2 3 4 5))))))

Main Index | Thread Index