List Panels with Drag
CAPI provides a double-list-panel for applications in which you need to select a subset of a list. For example, selecting the packages you want to search for a symbol, or selecting the users to be added to a group.
LispWorks Version 6 added drag-and-drop capabilities to list-panels. This application shows how to create a double-list-panel similar in functionality to the built-in one, but with drag and drop capabilities.
For example, if you create it with:
(make-chooser '("Alligator" "Buzzard" "Cheetah" "Dolphin" "Elephant"))
you can drag items from either list to the other list, as an alternative to using the ">>>" or "<<<" buttons:
First we create a class from list-panel, with :drag-callback and :drop-callback callbacks to detect a drag from the panel, or a drop onto the panel:
(defclass drag-list (capi:list-panel) () (:default-initargs :drag-callback #'drag-callback :drop-callback #'drop-callback :interaction :extended-selection))
Here's the definition of the routine to create the interface:
(defun make-chooser (items) (let* ((deselected-items-pane (make-instance 'drag-list :title "Unselected Items:" :items items)) (selected-items-pane (make-instance 'drag-list :title "Selected Items:")) (select-button (make-instance 'capi:push-button :text ">>>" :callback-type :none :callback #'(lambda () (move-items (capi:choice-selected-items deselected-items-pane) deselected-items-pane selected-items-pane)))) (deselect-button (make-instance 'capi:push-button :text "<<<" :callback-type :none :callback #'(lambda () (move-items (capi:choice-selected-items selected-items-pane) selected-items-pane deselected-items-pane)))) (button-layout (make-instance 'capi:column-layout :description (list select-button deselect-button))) (layout (make-instance 'capi:row-layout :adjust :center :description (list deselected-items-pane button-layout selected-items-pane) )) (window (make-instance 'capi:interface :title "Double-List Panel Test" :best-width 500 :best-height 300 :layout layout))) (capi:display window)))
The list panels have :drag-callback and :drop-callback callbacks to detect a drag from the panel, or a drop onto the panel.
The drag callback creates a list containing the pane followed by the collection items, and gives it type :lisp:
(defun drag-callback (pane indices) (list :lisp (cons pane (map 'list #'(lambda (i) (elt (capi:collection-items pane) i)) indices))))
The drop callback has three stages:
The :formats stage specifies what formats are accepted by the drop target. In this case it's just our :lisp format.
The :drag stage highlights the drop target when there's a drag over it from a different pane. The LispWorks drag-and-drop support allow you to highlight:
- Individual items (eg for applications in which the drop will replace an item).
- The gaps between items (eg for applications in which the drop will insert an item at that position).
- The whole panel, for cases where the position of the drop is irrelevant.
In this application we're not interested in the position of the drop, so we set the capi:drop-object-collection-index of the object to the values -1 :item.
The :drop stage receives the items when there's a drop.
(defmethod drop-callback (pane drop-object stage) (case stage (:formats (capi:set-drop-object-supported-formats drop-object (list :lisp))) (:drag (when (and (capi:drop-object-provides-format drop-object :lisp) (capi:drop-object-allows-drop-effect-p drop-object :move) ;; Don't allow drop on self (not (eq pane (car (capi:drop-object-get-object drop-object pane :lisp))))) ;; Ignore drop position (setf (capi:drop-object-collection-index drop-object) (values -1 :item)) (setf (capi:drop-object-drop-effect drop-object) :move))) (:drop (when (and (capi:drop-object-provides-format drop-object :lisp) (capi:drop-object-allows-drop-effect-p drop-object :move)) (let ((drag (capi:drop-object-get-object drop-object pane :lisp))) (move-items (cdr drag) (car drag) pane) (setf (capi:drop-object-drop-effect drop-object) :copy))))))
(defun move-items (items from to) (capi:remove-items from items) (capi:append-items to items))
This example can be generalised to a window with three or more list panels, with the ability to drag items between any of them, simply by adding more instances of drag-list:
For example, an application might need to allow you to allocate a set of users between several groups. This would be quite difficult to implement with buttons alone.
Note that this example doesn't implement some refinements provided by the built-in CAPI double-list-panel:
- Disabling of the buttons when no items are selected in the appropriate panel.
- Use of Return key.
- Sorting of items.
blog comments powered by Disqus