Boolean Filters Layout

CAPI includes a filtering-layout that adds a text filter to a list-panel. This project shows how to make the filtering-layout work with a multi-column-list-panel, and adds boolean filters that allow you to filter items according to boolean values in specified columns:


Complete listing

The definition

Although the example would be more compact if I'd taken advantage of capi:define-interface, I've done it the long way for clarity. First here's the definition of the window class, with slots for the items, text filter, boolean filters, and list panel:

(defclass items-window (capi:interface)
  ((things :initarg :things :reader things)
   (filter :initarg :filter :reader filter)
   (booleans :initarg :booleans :reader booleans)
   (list-panel :initarg :list-panel :reader list-panel)))

Make items window

The items window is drawn by the function make-items-window. It assumes each item is a list of values, specified by *columns*:

(defparameter *columns* '("Name" "No" "Even?" "Prime?"))

The columns that contain boolean values are specified by *boolean-columns*:

(defparameter *boolean-columns* '("Even?" "Prime?"))

Here's the function to create the items window:

 (defun make-items-window (things)
  (let* ((list-panel
          (make-instance 'capi:multi-column-list-panel :items things :external-min-height 140
                         :columns (map 'list #'(lambda (column) (list :title column :width 100)) *columns*)))
          (make-instance 'capi:filtering-layout :change-callback 'update-items-window))
          (map 'list
               #'(lambda (column)
                   (make-instance 'capi:option-pane :title column :items '("" "Yes" "No")
:external-max-width 64 :callback-type :interface
:selection-callback #'update-items-window))
          (make-instance 'capi:row-layout :description booleans))
          (make-instance 'capi:column-layout 
                         :description (list filter booleans-row list-panel)))
          (make-instance 'items-window :title "Filtering example" :best-width 480 :things things 
                         :list-panel list-panel :filter filter :booleans booleans :layout layout)))
    (capi:display window)))

A pop-up menu with the options blank, Yes, and No is created for each of the columns specified by *boolean-columns*:


Typing into the filter text-input-pane, or changing any of the boolean pop-up menus, calls the update-items-window update function. This recalculates the list panel's collection items to reflect the new values of the filters:

(defun update-items-window (window)
  (let* ((things (things window))
         (options (map 'list #'capi:choice-selection (booleans window)))
         (positions (map 'list #'(lambda (boolean)
                                   (position (capi:titled-object-title boolean) *columns* :test #'equalp))
                         (booleans window)))
          (multiple-value-bind (regexp excludep)
              (capi:filtering-layout-match-object-and-exclude-p (filter window) nil)
             #'(lambda (item)
                  (if regexp (find-regexp-in-string regexp (first item)) t)
                  (every #'(lambda (option position)
                             (or (zerop option) 
                                 (eq (not (= 1 option)) (not (nth position item)))))
                         options positions)))
    (setf (capi:collection-items
           (list-panel window)) filtered-things)))

Finally some routines to generate a list of data to display in the items-window:

(defun prime? (n)
  (let ((test 2))
     (when (> (* test test) n) (return t))
     (when (zerop (rem n test)) (return nil))
     (incf test))))

This generates a list of 1000 entries for the integers 0 to 999:

(setq *things*
   (let (things)
     (dotimes (x 1000 (reverse things)) 
        (list (format nil "~r" x) x (evenp x) (prime? x))

Finally we display the items-window with:

(make-items-window *things*)

blog comments powered by Disqus