;; jabber-widget.el - display various kinds of forms -*- lexical-binding: t; -*-
;; Copyright (C) 2003, 2004, 2007 - Magnus Henoch - mange@freemail.hu
;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
;; This file is a part of jabber.el.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(require 'widget)
(require 'wid-edit)
(require 'jabber-util)
(require 'jabber-disco)
(defvar jabber-widget-alist nil
"Alist of widgets currently used.")
(defvar jabber-form-type nil
"Type of form.
One of:
'x-data, jabber:x:data
'register, as used in jabber:iq:register and jabber:iq:search.")
(defvar jabber-submit-to nil
"JID of the entity to which form data is to be sent.")
(jabber-disco-advertise-feature "jabber:x:data")
(define-widget 'jid 'string
"JID widget."
:value-to-internal (lambda (widget value)
(let ((displayname (jabber-jid-rostername value)))
(if displayname
(format "%s <%s>" displayname value)
value)))
:value-to-external (lambda (widget value)
(if (string-match "<\\([^>]+\\)>[ \t]*$" value)
(match-string 1 value)
value))
:complete-function 'jid-complete)
(defun jid-complete ()
"Perform completion on JID preceding point."
(interactive)
;; mostly stolen from widget-color-complete
(let* ((prefix (buffer-substring-no-properties (widget-field-start widget)
(point)))
(list (append (mapcar #'symbol-name *jabber-roster*)
(delq nil
(mapcar #'(lambda (item)
(when (jabber-jid-rostername item)
(format "%s <%s>" (jabber-jid-rostername item)
(symbol-name item))))
*jabber-roster*))))
(completion (try-completion prefix list)))
(cond ((eq completion t)
(message "Exact match."))
((null completion)
(error "Can't find completion for \"%s\"" prefix))
((not (string-equal prefix completion))
(insert-and-inherit (substring completion (length prefix))))
(t
(message "Making completion list...")
(with-output-to-temp-buffer "*Completions*"
(display-completion-list (all-completions prefix list nil)))
(message "Making completion list...done")))))
(defun jabber-init-widget-buffer (submit-to)
"Setup buffer-local variables for widgets."
(make-local-variable 'jabber-widget-alist)
(make-local-variable 'jabber-submit-to)
(setq jabber-widget-alist nil)
(setq jabber-submit-to submit-to)
(setq buffer-read-only nil)
;; XXX: This is because data from other queries would otherwise be
;; appended to this buffer, which would fail since widget buffers
;; are read-only... or something like that. Maybe there's a
;; better way.
(rename-uniquely))
(defun jabber-render-register-form (query &optional default-username)
"Display widgets from element in IQ register or search namespace.
Display widgets from element in jabber:iq:{register,search} namespace.
DEFAULT-USERNAME is the default value for the username field."
(make-local-variable 'jabber-widget-alist)
(setq jabber-widget-alist nil)
(make-local-variable 'jabber-form-type)
(setq jabber-form-type 'register)
(if (jabber-xml-get-children query 'instructions)
(widget-insert "Instructions: " (car (jabber-xml-node-children (car (jabber-xml-get-children query 'instructions)))) "\n"))
(if (jabber-xml-get-children query 'registered)
(widget-insert "You are already registered. You can change your details here.\n"))
(widget-insert "\n")
(let ((possible-fields
;; taken from XEP-0077
'((username . "Username")
(nick . "Nickname")
(password . "Password")
(name . "Full name")
(first . "First name")
(last . "Last name")
(email . "E-mail")
(address . "Address")
(city . "City")
(state . "State")
(zip . "Zip")
(phone . "Telephone")
(url . "Web page")
(date . "Birth date"))))
(dolist (field (jabber-xml-node-children query))
(let ((entry (assq (jabber-xml-node-name field) possible-fields)))
(when entry
(widget-insert (cdr entry) "\t")
;; Special case: when registering a new account, the default
;; username is the one specified in jabber-username. Things
;; will break if the user changes that name, though...
(let ((default-value (or (when (eq (jabber-xml-node-name field) 'username)
default-username)
"")))
(setq jabber-widget-alist
(cons
(cons (car entry)
(widget-create 'editable-field
:secret (if (eq (car entry) 'password)
?* nil)
(or (car (jabber-xml-node-children
field)) default-value)))
jabber-widget-alist)))
(widget-insert "\n"))))))
(defun jabber-parse-register-form ()
"Return children of a tag containing information entered.
Return children of a tag containing information entered in the
widgets of the current buffer."
(mapcar
(lambda (widget-cons)
(list (car widget-cons)
nil
(widget-value (cdr widget-cons))))
jabber-widget-alist))
(defun jabber-render-xdata-form (x &optional defaults)
"Display widgets from element in jabber:x:data namespace.
DEFAULTS is an alist associating variable names with default values.
DEFAULTS takes precedence over values specified in the form."
(make-local-variable 'jabber-widget-alist)
(setq jabber-widget-alist nil)
(make-local-variable 'jabber-form-type)
(setq jabber-form-type 'xdata)
(let ((title (car (jabber-xml-node-children (car (jabber-xml-get-children x 'title))))))
(if (stringp title)
(widget-insert (jabber-propertize title 'face 'jabber-title-medium) "\n\n")))
(let ((instructions (car (jabber-xml-node-children (car (jabber-xml-get-children x 'instructions))))))
(if (stringp instructions)
(widget-insert "Instructions: " instructions "\n\n")))
(dolist (field (jabber-xml-get-children x 'field))
(let* ((var (jabber-xml-get-attribute field 'var))
(label (jabber-xml-get-attribute field 'label))
(type (jabber-xml-get-attribute field 'type))
(required (jabber-xml-get-children field 'required))
(values (jabber-xml-get-children field 'value))
(options (jabber-xml-get-children field 'option))
(desc (car (jabber-xml-get-children field 'desc)))
(default-value (assoc var defaults)))
;; "required" not implemented yet
(cond
((string= type "fixed")
(widget-insert (car (jabber-xml-node-children (car values)))))
((string= type "text-multi")
(if (or label var)
(widget-insert (or label var) ":\n"))
(push (cons (cons var type)
(widget-create 'text (or (cdr default-value)
(mapconcat #'(lambda (val)
(car (jabber-xml-node-children val)))
values "\n")
"")))
jabber-widget-alist))
((string= type "list-single")
(if (or label var)
(widget-insert (or label var) ":\n"))
(push (cons (cons var type)
(apply 'widget-create
'radio-button-choice
:value (or (cdr default-value)
(car (xml-node-children (car values))))
(mapcar (lambda (option)
`(item :tag ,(jabber-xml-get-attribute option 'label)
:value ,(car (jabber-xml-node-children (car (jabber-xml-get-children option 'value))))))
options)))
jabber-widget-alist))
((string= type "boolean")
(push (cons (cons var type)
(widget-create 'checkbox
:tag (or label var)
:value (if default-value
(cdr default-value)
(not (null
(member (car (xml-node-children (car values))) '("1" "true")))))))
jabber-widget-alist)
(if (or label var)
(widget-insert " " (or label var) "\n")))
(t ; in particular including text-single and text-private
(if (or label var)
(widget-insert (or label var) ": "))
(setq jabber-widget-alist
(cons
(cons (cons var type)
(widget-create 'editable-field
:secret (if (string= type "text-private") ?* nil)
(or (cdr default-value)
(car (jabber-xml-node-children (car values)))
"")))
jabber-widget-alist))))
(when (and desc (car (jabber-xml-node-children desc)))
(widget-insert "\n" (car (jabber-xml-node-children desc))))
(widget-insert "\n"))))
(defun jabber-parse-xdata-form ()
"Return an tag containing information entered in the widgets.
Return an tag containing information entered in the widgets of the current
buffer."
`(x ((xmlns . "jabber:x:data")
(type . "submit"))
,@(mapcar
(lambda (widget-cons)
(let ((values (jabber-xdata-value-convert (widget-value (cdr widget-cons)) (cdar widget-cons))))
;; empty fields are not included
(when values
`(field ((var . ,(caar widget-cons)))
,@(mapcar
(lambda (value)
(list 'value nil value))
values)))))
jabber-widget-alist)))
(defun jabber-xdata-value-convert (value type)
"Convert VALUE from form used by widget library to form required by XEP-0004.
Return a list of strings, each of which to be included as cdata in a
tag."
(cond
((string= type "boolean")
(if value (list "1") (list "0")))
((string= type "text-multi")
(split-string value "[\n\r]"))
(t ; in particular including text-single, text-private and list-single
(if (zerop (length value))
nil
(list value)))))
(defun jabber-render-xdata-search-results (xdata)
"Render search results in x:data form."
(let ((title (car (jabber-xml-get-children xdata 'title))))
(when title
(insert (jabber-propertize (car (jabber-xml-node-children title)) 'face 'jabber-title-medium) "\n")))
(if (jabber-xml-get-children xdata 'reported)
(jabber-render-xdata-search-results-multi xdata)
(jabber-render-xdata-search-results-single xdata)))
(defun jabber-render-xdata-search-results-multi (xdata)
"Render multi-record search results."
(let (fields
(jid-fields 0))
(let ((reported (car (jabber-xml-get-children xdata 'reported)))
(column 0))
(dolist (field (jabber-xml-get-children reported 'field))
(let (width)
;; Clever algorithm for estimating width based on field type goes here.
(setq width 20)
(setq fields
(append
fields
(list (cons (jabber-xml-get-attribute field 'var)
(list 'label (jabber-xml-get-attribute field 'label)
'type (jabber-xml-get-attribute field 'type)
'column column)))))
(setq column (+ column width))
(if (string= (jabber-xml-get-attribute field 'type) "jid-single")
(setq jid-fields (1+ jid-fields))))))
(dolist (field-cons fields)
(indent-to (plist-get (cdr field-cons) 'column) 1)
(insert (jabber-propertize (plist-get (cdr field-cons) 'label) 'face 'bold)))
(insert "\n\n")
;; Now, the items
(dolist (item (jabber-xml-get-children xdata 'item))
(let ((start-of-line (point))
jid)
;; The following code assumes that the order of the s in each
;; is the same as in the tag.
(dolist (field (jabber-xml-get-children item 'field))
(let ((field-plist (cdr (assoc (jabber-xml-get-attribute field 'var) fields)))
(value (car (jabber-xml-node-children (car (jabber-xml-get-children field 'value))))))
(indent-to (plist-get field-plist 'column) 1)
;; Absent values are sometimes "", sometimes nil. insert
;; doesn't like nil.
(when value
;; If there is only one JID field, let the whole row
;; have the jabber-jid property. If there are many JID
;; fields, the string belonging to each field has that
;; property.
(if (string= (plist-get field-plist 'type) "jid-single")
(if (not (eq jid-fields 1))
(insert (jabber-propertize value 'jabber-jid value))
(setq jid value)
(insert value))
(insert value)))))
(if jid
(put-text-property start-of-line (point)
'jabber-jid jid))
(insert "\n")))))
(defun jabber-render-xdata-search-results-single (xdata)
"Render single-record search results."
(dolist (field (jabber-xml-get-children xdata 'field))
(let ((label (jabber-xml-get-attribute field 'label))
(type (jabber-xml-get-attribute field 'type))
(values (mapcar #'(lambda (val)
(car (jabber-xml-node-children val)))
(jabber-xml-get-children field 'value))))
;; XXX: consider type
(insert (jabber-propertize (concat label ": ") 'face 'bold))
(indent-to 30)
(insert (apply #'concat values) "\n"))))
(defun jabber-xdata-formtype (x)
"Return the form type of the xdata form in X, by XEP-0068.
Return nil if no form type is specified."
(catch 'found-formtype
(dolist (field (jabber-xml-get-children x 'field))
(when (and (string= (jabber-xml-get-attribute field 'var) "FORM_TYPE")
(string= (jabber-xml-get-attribute field 'type) "hidden"))
(throw 'found-formtype (car (jabber-xml-node-children
(car (jabber-xml-get-children field 'value)))))))))
(provide 'jabber-widget)
;;; arch-tag: da3312f3-1970-41d5-a974-14b8d76156b8