ToC DocOverview CGDoc RelNotes FAQ Index PermutedIndex
Allegro CL version 11.0

About the clipboard in Common Graphics

The clipboard is a facility that allows the user to interactively copy or move text or other data from one application to another, or from one window to another within an application. This is typically done via the common "cut", "copy", and "paste" commands. Common Graphics provides a general programmatic interface to the clipboard that allows applications to implement arbitrary gestures to cut, copy, and paste arbitrary data objects or to otherwise manipulate data on the clipboard.

1.0 The two clipboards in Common Graphics

There are actually two clipboards in Common Graphics. The first is the operating system clipboard or OS clipboard, and the second is the Lisp clipboard. The OS clipboard is the one implemented within the operating system, and behaves as it does in many applications. The Lisp clipboard adds additional functionality on top of the OS clipboard. Some Common Graphics functions act directly on the OS clipboard, and may be used if the additional Lisp clipboard functionality is not needed, while other Common Graphics functions act on the Lisp clipboard (and thereby indirectly on the OS clipboard as well). The IDE's cut, copy, and paste commands always use the Lisp clipboard.

The OS clipboard allows copying data from one application to another, but allows copying only certain standard types of data, such as text, rich text, and pixmaps. The Lisp clipboard allows copying any type of data, but this works only among the windows of a single Lisp application. In the IDE, if you do a paste into the inspector, for example, the inspector knows to insert the actual Lisp object that's on the top of the Lisp clipboard into the currently selected slot. Custom pasting such as this is accomplished by defining a paste-selection method or a paste-command method (discussed later) for the window.

The OS clipboard holds only one data object at any time (or perhaps up to one object of each standard type). The Lisp clipboard can hold a stack of data items, and items can be pushed onto or popped off of the stack.

The two clipboards are kept in sync as long as the Lisp clipboard functions are used rather than the OS clipboard functions. This means that the top object on the Lisp clipboard is always the same as the single object on the OS clipboard. Popping the top object off of the Lisp clipboard, for example, will leave a different object not only on the top of the Lisp clipboard but also on the OS clipboard. Also, when a value is copied to the OS clipboard in another application, it is placed on the Lisp clipboard stack.

If the top Lisp clipboard object is not of one of the data types that are defined for the OS clipboard, then the OS clipboard value will be a string representation of the object, while the Lisp clipboard will hold the object itself. Therefore, pasting into another application will paste a string, while pasting into a window within the Lisp application may potentially paste the object itself, if the window knows how to handle that sort of object.

2.0 The OS clipboard

The OS clipboard value for any of the defined data types may be retrieved by calling clipboard-object, or set by calling (setf clipboard-object). For example, (clipboard-object :text) returns the plain-text value which is currently on the OS clipboard if any, and otherwise returns nil. (setf (clipboard-object :text) "foo") would place the string "foo" onto the OS clipboard, from where another application could paste the string.

available-clipboard-formats returns a list of symbols indicating which standard OS clipboard data formats are both implemented in CG and happen to be present at the moment on the OS clipboard. This tells an application which data types it could retrieve from the OS clipboard at that time. Currently CG implements the :text, :rich-text, and :pixmap data formats.

For example, if (available-clipboard-formats) returns the list (:pixmap), this indicates that the only thing currently on the OS clipboard that CG knows how to retrieve is a pixmap. An application could then either do nothing because it doesn't care about pixmaps, or it could call clipboard-object, passing :pixmap as the clipboard-format argument, to create and return a Common Graphics pixmap from the OS clipboard value. Then it could use that pixmap however it wishes, such as by "pasting" it into a window by calling copy-to-stream to draw the pixmap in a window.

Most applications first clear all values from the OS clipboard when a copy is done, so that only the newly copied object remains on the OS clipboard. A common exception is that when rich text is copied, it is often available as the :rich-text object on the OS clipboard while the plain text version of it is simultaneously available as the :text object. A Common Graphics application could then take whichever format it is interested in by passing the appropriate clipboard-format argument to clipboard-object. When calling (setf clipboard-object), the clipboard is first cleared unless the empty-clipboard keyword argument is passed as nil.

3.0 The Lisp clipboard

The functions discussed in this section manipulate the Lisp clipboard, and because the Lisp clipboard is built on top of the OS clipboard, they manipulate the OS clipboard as well.

The function push-lisp-clipboard adds a new value to the top of the Lisp clipboard stack and sets the OS clipboard value; this corresponds to calling (setf clipboard-object) to modify the OS clipboard only. The clipboard-history-limit configuration option prevents the Lisp clipboard stack from becoming overly long. The function pop-lisp-clipboard removes the top object from the Lisp clipboard stack and returns it, placing the object that remains at the top of the Lisp clipboard stack onto the OS clipboard; this corresponds to calling clipboard-object to retrieve the OS clipboard value directly without modifying the OS clipboard.

If an application would like to be notified whenever the Lisp clipboard changes, it can add a clipboard-changed method.

4.0 Built-In Cut, Copy, and Paste functionality

You can use the functions above to manipulate clipboard values however you like. But if you are implementing typical cut/copy/paste behavior, you may be able to do so with less code by using the built-in overridable generic functions that Common Graphics supplies for this. The functions cut-command, copy-command, and paste-command all manipulate the Lisp clipboard (and therefore the OS clipboard as well). The default methods also call the corresponding functions cut-selection, copy-selection, and paste-selection, which are responsible for actually retrieving the value from the window that is to be placed onto the clipboard, or inserting the value being pasted into the window in some way.

(An exception is that the cut-selection and copy-selection methods for rich-edit-pane do push the value onto the clipboards, due to constraints in the API of this operating system control. Similarly, the paste-selection method for rich-edit-pane will paste the OS clipboard rich text value if nil is passed as the object to paste.)

Typically an application can add a menu-bar command that calls copy-command (for example) on the selected window, and then add copy-selection methods for any custom windows that don't have a suitable copy-selection method built-in. The built-in copy-command method takes care of placing the value returned by the custom copy-selection method onto the clipboards. The built-in cut-command and paste-command methods behave similarly.

The built-in cut-selection, copy-selection, and paste-selection methods know how to cut, copy, and paste the :text format in text-edit-pane windows and the various controls that can edit text; how to cut, copy, and paste the :rich-text format in rich-edit-pane windows and the rich-edit control; and how to copy and paste the :pixmap format in bitmap-pane windows and drawable controls (when the drawable has the use-bitmap-stream property turned on). The built-in pixmap methods simply draw the pixmap at its standard size at the upper left of the window, and copy from the whole window.

The default paste-command method calls default-clipboard-format on the window where the paste is being done to determine which OS clipboard data format to read from the clipboard. It then looks down through the Lisp clipboard stack to find an item of that type, and calls paste-selection with that value (passing nil if nothing was found of the desired type). If you implement a window that should know how to paste pixmaps, for example, you could first define a paste-selection method that draws a pixmap that's passed to it in some way that's appropriate to that kind of window. Then you could define a default-clipboard-format method on the window class that returns the symbol :pixmap. Then whenever paste-command is called on the window, the default paste-command method will retrieve the current pixmap from the clipboard (if any) and call your paste-selection method for the window, passing the pixmap to paste.

Note that the default cut-command, copy-command, and paste-command methods recursively call themselves on the selected-window of the window that's passed until they come to a child window that has no children, and then they call cut-selection, copy-selection, or paste-selection on that child window. If that default behavior is not appropriate for your application, then you need to either not use those functions or add methods that override this behavior.

5.0 Adding Cut/Copy/Paste commands to a menu-bar

When creating a menu-bar programmatically, simply making menu-items that call cut-command, copy-command, and paste-command on a window should make them work with code written as described above.

When building a form window interactively, adding a menu-bar to the form will create an initial menu-bar with an Edit pull-down-menu that already includes menu-items that call cut-command, copy-command, and paste-command. For applications that implement any sort of cut/copy/paste functionality, these menu-items can likely be used as is.

6.0 Viewing the Lisp clipboard contents

The View | Clipboard command shows the Clipboard dialog, with a list of everything on its stack. This dialog may be useful for finding something that you cut or copied a while back, which you could then copy to the top of the stack and then paste somewhere.

The value of the variable *clipboard* is a tree containing the entire stack of Lisp clipboard data. And the variables =, ==, and === are bound to the three objects on the top of the stack, similar to the way *, **, and *** are bound to the three most recent returned values in a Lisp listener.

7.0 Clipboard example

Below is a complete sample application that has a window with a menu-bar containing the familiar Cut, Copy, and Paste commands, plus two child windows that know how to paste either text or pixmaps in a particular way. This code is also available as an example on the Help menu | CG Examples area of the Navigator dialog.

(in-package :cg-user)

;; Call this function to run the test.
(defun run-clipboard-example ()
  (let* ((parent (make-window :clipboard-test
                   :class 'frame-window
                   :menu (my-menu-bar)
                   :scrollbars nil
                   :exterior (make-box
                              0 150
                              (interior-width (screen *system*))
                              (interior-height (screen *system*)))))
         (width (interior-width parent))
         (height (interior-height parent))
         (margin 20))
    
    ;; Make a child window to paste strings into.
    (make-window :text-window
      :class 'my-text-window
      :owner parent
      :scrollbars nil
      :exterior (make-box margin margin (floor (- width margin) 2)
                          (- height margin)))
    
    ;; Make a child window to paste pixmaps into.
    (make-window :pixmap-window
      :class 'my-pixmap-window
      :owner parent
      :scrollbars nil
      :exterior (make-box (floor (+ width margin) 2)
                          margin (- width margin)(- height margin)))
    
    ;; Place an object of each type initially onto the clipboard.
    (push-lisp-clipboard :text "Initial Clipboard Text")
    (push-lisp-clipboard :pixmap (find-pixmap :gray-drop-arrow))
    
    parent))
    
;; -------------------------------------------------------------------
;; Define a class of windows that know how to paste text.
(defclass my-text-window (frame-window)
  ((my-string :accessor my-string :initform "Nothing pasted yet"))
  (:default-initargs
   :font (make-font-ex nil "Arial" 24)
   :title "Paste strings here to see them large"))

(defmethod default-clipboard-format ((window my-text-window))
  ;; This tells the default paste-command method to grab the
  ;; current :text object off of the clipboard, if any.
  :text)

(defmethod cut-selection ((window my-text-window))
  (let* ((string (my-string window)))
    (setf (my-string window) nil)
    ;; Cause the window to redisplay to erase the string.
    (invalidate window)
    (values window string)))

(defmethod copy-selection ((window my-text-window))
  ;; We have only one thing to copy to the clipboard, so return it.
  ;; A copy-selection or cut-selection method must return the window
  ;; and the object to be placed on the clipboard.
  (values window (my-string window)))

(defmethod paste-selection ((window my-text-window) object)
  ;; Just record the current string to draw here, so that all
  ;; drawing is encapsulated in the redisplay-window method, which
  ;; is called whenever the window is uncovered or otherwise invalidated.
  (when object
    (setf (my-string window)(princ-to-string object)))
  (invalidate window))

(defmethod redisplay-window ((window my-text-window) &optional box)
  (declare (ignore box))
  (call-next-method) ;; clear the window
  
  ;; Whenever the window is uncovered, draw its current string
  ;; wrapped to fit the window's width.
  (let* ((string (my-string window)))
    (when string
      (move-to-x-y window 0 0)
      (setf (right-margin window)(interior-width window))
      (draw-wrapped-string window string
                           :newline-spacing-in-lines .2))))

;; -------------------------------------------------------------------
;; Define a class of windows that know how to paste pixmaps.
(defclass my-pixmap-window (frame-window)
  ((my-pixmap :accessor my-pixmap :initform (find-pixmap :melvin)))
  (:default-initargs
      :title "Paste pixmaps here to see them double size"))

(defmethod default-clipboard-format ((window my-pixmap-window))
  ;; This tells the default paste-command method to grab the
  ;; current :pixmap object off of the clipboard, if any.
  :pixmap)

(defmethod paste-selection ((window my-pixmap-window) object)
  (when (typep object 'pixmap)
    (setf (my-pixmap window) object)
    (invalidate window)))

(defmethod redisplay-window ((window my-pixmap-window) &optional box)
  (declare (ignore box))
  (call-next-method) ;; clear the window
  
  ;; Whenever the window is uncovered, draw its current pixmap
  ;; centered in the window, scaling it by a factor of two.
  (when (my-pixmap window)
    (let* ((pixmap (my-pixmap window))
           (pixmap-width (* 2 (width pixmap)))
           (pixmap-height (* 2 (height pixmap)))
           (window-width (interior-width window))
           (window-height (interior-height window)))
      (with-boxes (box1)
        (copy-to-stream
         pixmap window
         (nmake-box box1
           (floor (- window-width pixmap-width) 2)
           (floor (- window-height pixmap-height) 2)
           (floor (+ window-width pixmap-width) 2)
           (floor (+ window-height pixmap-height) 2)))))))
  
;; -------------------------------------------------------------------
(defun my-menu-bar ()
  ;; Return a menu-bar with Cut, Copy, and Paste commands on it.
  ;; The menu-bar has a single menu-item whose value is the
  ;; Edit pull-down menu.
  (open-menu
   (list (make-instance 'menu-item
           :name :edit-menu
           :title "~Edit"
           :value
           (open-menu
            (list (make-instance 'menu-item
                    :name :cut
                    :title "~Cut"
                    :value 'cut-command
                    :event-synonym '(control-key #\X)
                    :help-string "Copy to clipboard and delete.")
                  (make-instance 'menu-item
                    :name :copy
                    :title "C~opy"
                    :value 'copy-command
                    :event-synonym '(control-key #\C)
                    :help-string "Copy to clipboard")
                  (make-instance 'menu-item
                    :name :paste
                    :title "~Paste"
                    :value 'paste-command
                    :event-synonym '(control-key #\V)
                    :help-string "Draw from clipboard"))
            'pull-down-menu (screen *system*)
            :name :edit-menu
            :on-click 'funcall-menu-item-with-window)))
   'menu-bar (screen *system*)
   :name :my-menu-bar))

;; These methods cause the windows to be redrawn when resized,
;; since they will then fit their content differently then
(defmethod invalidate-window-on-resize ((window my-text-window))
  t)
(defmethod invalidate-window-on-resize ((window my-pixmap-window))
  t)

;; -------------------------------------------------------------------
#+run-example
(run-clipboard-example)

Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.

ToC DocOverview CGDoc RelNotes FAQ Index PermutedIndex
Allegro CL version 11.0