9 Defining New Widgets

You can define specialized widgets with define-widget. It allows you to create a shorthand for more complex widgets, including specifying component widgets and new default values for the keyword arguments.

Function: define-widget name class doc &rest args

Define a new widget type named name that derives from class.

name and class should both be symbols, and class should be one of the existing widget types.

The third argument doc is a documentation string for the widget.

args should be key-value pairs, overriding keyword values of class, or adding new recognized keywords for name.

Usually, you’ll want to derive from an existing widget type, like the editable-field widget, or the default widget, but it’s also possible to derive from nothing, by passing a value of nil as class. Note that if you do this, you’re entirely responsible for defining a whole new default behavior for your widgets.

After using this function, the following two calls will create identical widgets:

  • (widget-create name)
    
  • (apply widget-create class args)
    

Using define-widget just stores the definition of the widget type in the widget-type property of name, which is what widget-create uses.

If you only want to specify defaults for keywords with no complex conversions, you can use identity as your conversion function.

When defining new widgets, the :convert-widget property might be useful:

:convert-widget

Function to convert a widget type before creating a widget of that type.

It takes a widget type as an argument, and returns the converted widget type. When a widget is created, this function is called for the widget type and all the widget’s parent types, most derived first.

The predefined functions widget-types-convert-widget and widget-value-convert-widget can be used here.

Example:

(defvar widget-ranged-integer-map
  (let ((map (copy-keymap widget-keymap)))
    (define-key map [up] #'widget-ranged-integer-increase)
    (define-key map [down] #'widget-ranged-integer-decrease)
    map))

(define-widget 'ranged-integer 'integer
  "A ranged integer widget."
  :min-value most-negative-fixnum
  :max-value most-positive-fixnum
  :keymap widget-ranged-integer-map)

(defun widget-ranged-integer-change (widget how)
  "Change the value of the ranged-integer WIDGET, according to HOW."
  (let* ((value (widget-value widget))
         (newval (cond
                  ((eq how 'up)
                   (if (< (1+ value) (widget-get widget :max-value))
                       (1+ value)
                     (widget-get widget :max-value)))
                  ((eq how 'down)
                   (if (> (1- value) (widget-get widget :min-value))
                       (1- value)
                     (widget-get widget :min-value)))
                  (t (error "HOW has a bad value"))))
         (inhibit-read-only t))
    (widget-value-set widget newval)))

(defun widget-ranged-integer-increase (widget)
  "Increase the value of the ranged-integer WIDGET."
  (interactive (list (widget-at)))
  (widget-ranged-integer-change widget 'up))

(defun widget-ranged-integer-decrease (widget)
  "Decrease the value of the ranged-integer WIDGET."
  (interactive (list (widget-at)))
  (widget-ranged-integer-change widget 'down))