Mercurial > hg > xemacs-beta
diff lisp/x11/x-font-menu.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ac2d302a0011 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/x11/x-font-menu.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,630 @@ +;; x-font-menu.el --- Managing menus of X fonts. + +;; Copyright (C) 1994 Free Software Foundation, Inc. +;; Copyright (C) 1995 Tinker Systems and INS Engineering Corp. + +;; Author: Jamie Zawinski <jwz@lucid.com> +;; Restructured by: Jonathan Stigelman <Stig@hackvan.com> + +;; This file is part of XEmacs. + +;; XEmacs 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, or (at your option) +;; any later version. + +;; XEmacs 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 XEmacs; see the file COPYING. If not, write to the Free +;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: +;;; +;;; Creates three menus, "Font", "Size", and "Weight", and puts them on the +;;; "Options" menu. The contents of these menus are the superset of those +;;; properties available on any fonts, but only the intersection of the three +;;; sets is selectable at one time. +;;; +;;; Known Problems: +;;; =============== +;;; Items on the Font menu are selectable if and only if that font exists in +;;; the same size and weight as the current font. This means that some fonts +;;; are simply not reachable from some other fonts - if only one font comes +;;; in only one point size (like "Nil", which comes only in 2), you will never +;;; be able to select it. It would be better if the items on the Fonts menu +;;; were always selectable, and selecting them would set the size to be the +;;; closest size to the current font's size. +;;; +;;; This attempts to change all other faces in an analagous way to the change +;;; that was made to the default face; if it can't, it will skip over the face. +;;; However, this could leave incongruous font sizes around, which may cause +;;; some nonreversibility problems if further changes are made. Perhaps it +;;; should remember the initial fonts of all faces, and derive all subsequent +;;; fonts from that initial state. +;;; +;;; xfontsel(1) is a lot more flexible (but probably harder to understand). +;;; +;;; The code to construct menus from all of the x11 fonts available from the +;;; server is autoloaded and executed the very first time that one of the Font +;;; menus is selected on each device. That is, if XEmacs has frames on two +;;; different devices, then separate font menu information will be maintained +;;; for each X display. If the font path changes after emacs has already +;;; asked the X server on a particular display for its list of fonts, this +;;; won't notice. Also, the first time that a font menu is posted on each +;;; display will entail a lengthy delay, but that's better than slowing down +;;; XEmacs startup. At any time (i.e.: after a font-path change or +;;; immediately after device creation), you can call +;;; `reset-device-font-menus' to rebuild the menus from all currently +;;; available fonts. +;;; +;;; There is knowledge here about the regexp match numbers in `x-font-regexp', +;;; `x-font-regexp-foundry-and-family', and +;;; `x-font-regexp-registry-and-encoding' defined in x-faces.el. +;;; +;;; There are at least three kinds of fonts under X11r5: +;;; +;;; - bitmap fonts, which can be assumed to look as good as possible; +;;; - bitmap fonts which have been (or can be) automatically scaled to +;;; a new size, and which almost always look awful; +;;; - and true outline fonts, which should look ok any any size, but in +;;; practice (on at least some systems) look awful at any size, and +;;; even in theory are unlikely ever to look as good as non-scaled +;;; bitmap fonts. +;;; +;;; It would be nice to get this code to look for non-scaled bitmap fonts +;;; first, then outline fonts, then scaled bitmap fonts as a last resort. +;;; But it's not clear to me how to tell them apart based on their truenames +;;; and/or the result of XListFonts(). I welcome any and all explanations +;;; of the subtleties involved... +;;; +;;; +;;; If You Think You'Re Seeing A Bug: +;;; ================================= +;;; When reporting problems, send the following information: +;;; +;;; - Exactly what behavior you're seeing; +;;; - The output of the `xlsfonts' program; +;;; - The value of the variable `fonts-menu-cache'; +;;; - The values of the following expressions, both before and after +;;; making a selection from any of the fonts-related menus: +;;; (face-font 'default) +;;; (font-instance-truename (face-font 'default)) +;;; (font-instance-properties (face-font 'default)) +;;; - The values of the following variables after making a selection: +;;; font-menu-preferred-resolution +;;; font-menu-preferred-registry +;;; +;;; There is a common misconception that "*-courier-medium-r-*-11-*", also +;;; known as "-adobe-courier-medium-r-normal--11-80-100-100-m-60-iso8859-1", +;;; is an 11-point font. It is not -- it is an 11-pixel font at 100dpi, +;;; which is an 8-point font (the number after -11- is the size in tenths +;;; of points). So if you expect to be seeing an "11" entry in the "Size" +;;; menu and are not, this may be why. + +;;; Code: + +;; #### - implement these... +;; +;;; (defvar font-menu-ignore-proportional-fonts nil +;;; "*If non-nil, then the font menu will only show fixed-width fonts.") + +;;;###autoload +(defvar font-menu-ignore-scaled-fonts t + "*If non-nil, then the font menu will try to show only bitmap fonts.") + +;;;###autoload +(defvar font-menu-this-frame-only-p t + "*If non-nil, then changing the default font from the font menu will only +affect one frame instead of all frames.") + +;; only call XListFonts (and parse) once per device. +;; ( (device . [parsed-list-fonts family-menu size-menu weight-menu]) ...) +(defvar device-fonts-cache nil) + +(defconst font-menu-preferred-registry nil) +(defconst font-menu-preferred-resolution nil) + +(defconst fonts-menu-junk-families + (purecopy + (mapconcat + #'identity + '("cursor" "glyph" "symbol" ; Obvious losers. + "\\`Ax...\\'" ; FrameMaker fonts - there are just way too + ; many of these, and there is a different + ; font family for each font face! Losers. + ; "Axcor" -> "Applix Courier Roman", + ; "Axcob" -> "Applix Courier Bold", etc. + ) + "\\|")) + "A regexp matching font families which are uninteresting (cursor fonts).") + +(defun hack-font-truename (fn) + "Filter the output of `font-instance-truename' to deal with Japanese fontsets." + (if (string-match "," (font-instance-truename fn)) + (let ((fpnt (nth 8 (split-string (font-instance-name fn) "-"))) + (flist (split-string (font-instance-truename fn) ",")) + ret) + (while flist + (if (string-equal fpnt (nth 8 (split-string (car flist) "-"))) + (progn (setq ret (car flist)) (setq flist nil)) + (setq flist (cdr flist)) + )) + ret) + (font-instance-truename fn))) + +;;;###autoload +(fset 'install-font-menus 'reset-device-font-menus) +(make-obsolete 'install-font-menus 'reset-device-font-menus) + +(defvar x-font-regexp-ja nil + "This is used to filter out fonts that don't work in the locale. +It must be set at run-time.") + +(defun vassoc (key valist) + "Search VALIST for a vector whose first element is equal to KEY. +See also `assoc'." + ;; by Stig@hackvan.com + (let (el) + (catch 'done + (while (setq el (pop valist)) + (and (equal key (aref el 0)) + (throw 'done el)))))) + +;;;###autoload +(defun reset-device-font-menus (&optional device debug) + "Generates the `Font', `Size', and `Weight' submenus for the Options menu. +This is run the first time that a font-menu is needed for each device. +If you don't like the lazy invocation of this function, you can add it to +`create-device-hook' and that will make the font menus respond more quickly +when they are selected for the first time. If you add fonts to your system, +or if you change your font path, you can call this to re-initialize the menus." + ;; by Stig@hackvan.com + ;; #### - this should implement a `menus-only' option, which would + ;; recalculate the menus from the cache w/o having to do list-fonts again. + (message "Getting list of fonts from server... ") + (if (or noninteractive + (not (or device (setq device (selected-device)))) + (not (eq (device-type device) 'x))) + nil + (if (and (getenv "LANG") + (string-match "^\\(ja\\|japanese\\)$" + (getenv "LANG"))) + ;; #### - this is questionable behavior left over from the I18N4 code. + (setq x-font-regexp-ja "jisx[^-]*-[^-]*$" + font-menu-preferred-registry '("*" . "*"))) + (let ((all-fonts nil) + (case-fold-search t) + name family size weight entry monospaced-p + dev-cache + (cache nil) + (families nil) + (sizes nil) + (weights nil)) + (cond ((stringp debug) ; kludge + (setq all-fonts (split-string debug "\n"))) + (t + (setq all-fonts + (or debug + (list-fonts "*-*-*-*-*-*-*-*-*-*-*-*-*-*" device))))) + (while (setq name (pop all-fonts)) + (cond ((and (or (not x-font-regexp-ja) + (string-match x-font-regexp-ja name)) + (string-match x-font-regexp name)) + (setq weight (capitalize (match-string 1 name)) + size (string-to-int (match-string 6 name))) + (or (string-match x-font-regexp-foundry-and-family name) + (error "internal error")) + (setq family (capitalize (match-string 1 name))) + (or (string-match x-font-regexp-spacing name) + (error "internal error")) + (setq monospaced-p (string= "m" (match-string 1 name))) + (if (string-match fonts-menu-junk-families family) + nil + (setq entry (or (vassoc family cache) + (car (setq cache + (cons (vector family nil nil t) + cache))))) + (or (member family families) + (setq families (cons family families))) + (or (member weight weights) + (setq weights (cons weight weights))) + (or (member weight (aref entry 1)) + (aset entry 1 (cons weight (aref entry 1)))) + (or (member size sizes) + (setq sizes (cons size sizes))) + (or (member size (aref entry 2)) + (aset entry 2 (cons size (aref entry 2)))) + (aset entry 3 (and (aref entry 3) monospaced-p)) + )))) + ;; + ;; Hack scalable fonts. + ;; Some fonts come only in scalable versions (the only size is 0) + ;; and some fonts come in both scalable and non-scalable versions + ;; (one size is 0). If there are any scalable fonts at all, make + ;; sure that the union of all point sizes contains at least some + ;; common sizes - it's possible that some sensible sizes might end + ;; up not getting mentioned explicitly. + ;; + (if (member 0 sizes) + (let ((common '(60 80 100 120 140 160 180 240))) + (while common + (or;;(member (car common) sizes) ; not enough slack + (let ((rest sizes) + (done nil)) + (while (and (not done) rest) + (if (and (> (car common) (- (car rest) 5)) + (< (car common) (+ (car rest) 5))) + (setq done t)) + (setq rest (cdr rest))) + done) + (setq sizes (cons (car common) sizes))) + (setq common (cdr common))) + (setq sizes (delq 0 sizes)))) + + (setq families (sort families 'string-lessp) + weights (sort weights 'string-lessp) + sizes (sort sizes '<)) + + (let ((rest cache)) + (while rest + (aset (car rest) 1 (sort (aref (car rest) 1) 'string-lessp)) + (aset (car rest) 2 (sort (aref (car rest) 2) '<)) + (setq rest (cdr rest)))) + + (message "Getting list of fonts from server... done.") + + (setq dev-cache (assq device device-fonts-cache)) + (or dev-cache + (setq dev-cache (car (push (list device) device-fonts-cache)))) + (setcdr dev-cache + (vector + cache + (mapcar #'(lambda (x) + (vector x + (list 'font-menu-set-font x nil nil) + ':style 'radio ':active nil ':selected nil)) + families) + (mapcar #'(lambda (x) + (vector (if (/= 0 (% x 10)) + ;; works with no LISP_FLOAT_TYPE + (concat (int-to-string (/ x 10)) "." + (int-to-string (% x 10))) + (int-to-string (/ x 10))) + (list 'font-menu-set-font nil nil x) + ':style 'radio ':active nil ':selected nil)) + sizes) + (mapcar #'(lambda (x) + (vector x + (list 'font-menu-set-font nil x nil) + ':style 'radio ':active nil ':selected nil)) + weights))) + (cdr dev-cache)))) + +;;;###autoload +(defun font-menu-family-constructor (ignored) + ;; by Stig@hackvan.com + (if (not (eq 'x (device-type (selected-device)))) + '(["Cannot parse current font" ding nil]) + (let ((dcache (cdr (assq (selected-device) device-fonts-cache))) + (name (hack-font-truename (face-font-instance 'default))) + (case-fold-search t) + family weight size ; parsed from current font + entry ; font cache entry + f) + (or dcache + (setq dcache (reset-device-font-menus (selected-device)))) + (if (not (string-match x-font-regexp name)) + ;; couldn't parse current font + '(["Cannot parse current font" ding nil]) + (setq weight (capitalize (match-string 1 name))) + (setq size (string-to-number (match-string 6 name))) + (and (string-match x-font-regexp-foundry-and-family name) + (setq family (capitalize (match-string 1 name)))) + (setq entry (vassoc family (aref dcache 0))) + (mapcar #'(lambda (item) + ;; + ;; Items on the Font menu are enabled iff that font + ;; exists in the same size and weight as the current + ;; font (scalable fonts exist in every size). Only the + ;; current font is marked as selected. + ;; + (setq f (aref item 0) + entry (vassoc f (aref dcache 0))) + (if (and (member weight (aref entry 1)) + (or (member size (aref entry 2)) + (and (not font-menu-ignore-scaled-fonts) + (member 0 (aref entry 2))))) + (enable-menu-item item) + (disable-menu-item item)) + (if (equal family f) + (select-toggle-menu-item item) + (deselect-toggle-menu-item item)) + item) + (aref dcache 1))) + ))) + +;;;###autoload +(defun font-menu-size-constructor (ignored) + ;; by Stig@hackvan.com + (if (not (eq 'x (device-type (selected-device)))) + '(["Cannot parse current font" ding nil]) + (let ((dcache (cdr (assq (selected-device) device-fonts-cache))) + (name (hack-font-truename (face-font-instance 'default))) + (case-fold-search t) + family size ; parsed from current font + entry ; font cache entry + s) + (or dcache + (setq dcache (reset-device-font-menus (selected-device)))) + (if (not (string-match x-font-regexp name)) + ;; couldn't parse current font + '(["Cannot parse current font" ding nil]) + (setq size (string-to-number (match-string 6 name))) + (and (string-match x-font-regexp-foundry-and-family name) + (setq family (capitalize (match-string 1 name)))) + (setq entry (vassoc family (aref dcache 0))) + (mapcar #'(lambda (item) + ;; + ;; Items on the Size menu are enabled iff current font has + ;; that size. Only the size of the current font is + ;; selected. (If the current font comes in size 0, it is + ;; scalable, and thus has every size.) + ;; + (setq s (nth 3 (aref item 1))) + (if (or (member s (aref entry 2)) + (and (not font-menu-ignore-scaled-fonts) + (member 0 (aref entry 2)))) + (enable-menu-item item) + (disable-menu-item item)) + (if (eq size s) + (select-toggle-menu-item item) + (deselect-toggle-menu-item item)) + item) + (aref dcache 2))) + ))) + +;;;###autoload +(defun font-menu-weight-constructor (ignored) + ;; by Stig@hackvan.com + (if (not (eq 'x (device-type (selected-device)))) + '(["Cannot parse current font" ding nil]) + (let ((dcache (cdr (assq (selected-device) device-fonts-cache))) + (name (hack-font-truename (face-font-instance 'default))) + (case-fold-search t) + family weight ; parsed from current font + entry ; font cache entry + w) + (or dcache + (setq dcache (reset-device-font-menus (selected-device)))) + (if (not (string-match x-font-regexp name)) + ;; couldn't parse current font + '(["Cannot parse current font" ding nil]) + (setq weight (capitalize (match-string 1 name))) + (and (string-match x-font-regexp-foundry-and-family name) + (setq family (capitalize (match-string 1 name)))) + (setq entry (vassoc family (aref dcache 0))) + (mapcar #'(lambda (item) + ;; + ;; Items on the Weight menu are enabled iff current font + ;; has that weight. Only the weight of the current font + ;; is selected. + ;; + (setq w (aref item 0)) + (if (member w (aref entry 1)) + (enable-menu-item item) + (disable-menu-item item)) + (if (equal weight w) + (select-toggle-menu-item item) + (deselect-toggle-menu-item item)) + item) + (aref dcache 3))) + ))) + + +;;; Changing font sizes + +(defun font-menu-set-font (family weight size) + ;; This is what gets run when an item is selected from any of the three + ;; fonts menus. It needs to be rather clever. + ;; (size is measured in 10ths of points.) + (let ((faces (delq 'default (face-list))) + (default-name (hack-font-truename (face-font-instance 'default))) + (case-fold-search t) + new-default-face-font + from-family from-weight from-size) + ;; + ;; First, parse out the default face's font. + ;; + (or (string-match x-font-regexp-foundry-and-family default-name) + (signal 'error (list "couldn't parse font name" default-name))) + (setq from-family (capitalize (match-string 1 default-name))) + (or (string-match x-font-regexp default-name) + (signal 'error (list "couldn't parse font name" default-name))) + (setq from-weight (capitalize (match-string 1 default-name))) + (setq from-size (match-string 6 default-name)) + (setq new-default-face-font + (font-menu-load-font (or family from-family) + (or weight from-weight) + (or size from-size) + default-name)) + (while faces + (cond ((face-font-instance (car faces)) + (message "Changing font of `%s'..." (car faces)) + (condition-case c + (font-menu-change-face (car faces) + from-family from-weight from-size + family weight size) + (error + (display-error c nil) + (sit-for 1))))) + (setq faces (cdr faces))) + ;; Set the default face's font after hacking the other faces, so that + ;; the frame size doesn't change until we are all done. + + ;;; WMP - we need to honor font-menu-this-frame-only-p here! + (set-face-font 'default new-default-face-font + (and font-menu-this-frame-only-p (selected-frame))) + (message "Font %s" (face-font-name 'default)))) + + +(defun font-menu-change-face (face + from-family from-weight from-size + to-family to-weight to-size) + (or (symbolp face) (signal 'wrong-type-argument (list 'symbolp face))) + (let* ((font (face-font-instance face)) + (name (hack-font-truename font)) + (case-fold-search t) + face-family + face-weight + face-size) + ;; First, parse out the face's font. + (or (string-match x-font-regexp-foundry-and-family name) + (signal 'error (list "couldn't parse font name" name))) + (setq face-family (capitalize (match-string 1 name))) + (or (string-match x-font-regexp name) + (signal 'error (list "couldn't parse font name" name))) + (setq face-weight (match-string 1 name)) + (setq face-size (match-string 6 name)) + + ;; If this face matches the old default face in the attribute we + ;; are changing, then change it to the new attribute along that + ;; dimension. Also, the face must have its own global attribute. + ;; If its value is inherited, we don't touch it. If any of this + ;; is not true, we leave it alone. + (if (and (face-font face 'global) + (cond + (to-family (equal face-family from-family)) + (to-weight (equal face-weight from-weight)) + (to-size (equal face-size from-size)))) + (set-face-font face + (font-menu-load-font (or to-family face-family) + (or to-weight face-weight) + (or to-size face-size) + name) + (and font-menu-this-frame-only-p + (selected-frame))) + nil))) + + +(defun font-menu-load-font (family weight size from-font) + (and (numberp size) (setq size (int-to-string size))) + (let ((case-fold-search t) + slant other-slant + registry encoding resx resy) + (or (string-match x-font-regexp-registry-and-encoding from-font) + (signal 'error (list "couldn't parse font name" from-font))) + (setq registry (match-string 1 from-font) + encoding (match-string 2 from-font)) + + (or (string-match x-font-regexp from-font) + (signal 'error (list "couldn't parse font name" from-font))) + (setq slant (capitalize (match-string 2 from-font)) + resx (match-string 7 from-font) + resy (match-string 8 from-font)) + (cond ((equal slant "O") (setq other-slant "I")) ; oh, bite me. + ((equal slant "I") (setq other-slant "O")) + (t (setq other-slant nil))) + ;; + ;; Remember these values for the first font we switch away from + ;; (the original default font). + ;; + (or font-menu-preferred-resolution + (setq font-menu-preferred-resolution (cons resx resy))) + (or font-menu-preferred-registry + (setq font-menu-preferred-registry (cons registry encoding))) + ;; + ;; Now we know all the interesting properties of the font we want. + ;; Let's see what we can actually *get*. + ;; + (or ;; First try the default resolution, registry, and encoding. + (make-font-instance + (concat "-*-" family "-" weight "-" slant "-*-*-*-" size + "-" (car font-menu-preferred-resolution) + "-" (cdr font-menu-preferred-resolution) + "-*-*-" + (car font-menu-preferred-registry) "-" + (cdr font-menu-preferred-registry)) + nil t) + ;; Then try that in the other slant. + (and other-slant + (make-font-instance + (concat "-*-" family "-" weight "-" other-slant + "-*-*-*-" size + "-" (car font-menu-preferred-resolution) + "-" (cdr font-menu-preferred-resolution) + "-*-*-" + (car font-menu-preferred-registry) "-" + (cdr font-menu-preferred-registry)) + nil t)) + ;; Then try the default resolution and registry, any encoding. + (make-font-instance + (concat "-*-" family "-" weight "-" slant "-*-*-*-" size + "-" (car font-menu-preferred-resolution) + "-" (cdr font-menu-preferred-resolution) + "-*-*-" + (car font-menu-preferred-registry) "-*") + nil t) + ;; Then try that in the other slant. + (and other-slant + (make-font-instance + (concat "-*-" family "-" weight "-" other-slant + "-*-*-*-" size + "-" (car font-menu-preferred-resolution) + "-" (cdr font-menu-preferred-resolution) + "-*-*-" + (car font-menu-preferred-registry) "-*") + nil t)) + ;; Then try the default registry and encoding, any resolution. + (make-font-instance + (concat "-*-" family "-" weight "-" slant "-*-*-*-" size + "-*-*-*-*-" + (car font-menu-preferred-registry) "-" + (cdr font-menu-preferred-registry)) + nil t) + ;; Then try that in the other slant. + (and other-slant + (make-font-instance + (concat "-*-" family "-" weight "-" other-slant + "-*-*-*-" size + "-*-*-*-*-" + (car font-menu-preferred-registry) "-" + (cdr font-menu-preferred-registry)) + nil t)) + ;; Then try the default registry, any encoding or resolution. + (make-font-instance + (concat "-*-" family "-" weight "-" slant "-*-*-*-" size + "-*-*-*-*-" + (car font-menu-preferred-registry) "-*") + nil t) + ;; Then try that in the other slant. + (and other-slant + (make-font-instance + (concat "-*-" family "-" weight "-" slant "-*-*-*-" + size "-*-*-*-*-" + (car font-menu-preferred-registry) "-*") + nil t)) + ;; Then try anything in the same slant, and error if it fails... + (and other-slant + (make-font-instance + (concat "-*-" family "-" weight "-" slant "-*-*-*-" + size "-*-*-*-*-*-*"))) + (make-font-instance + (concat "-*-" family "-" weight "-" (or other-slant slant) + "-*-*-*-" size "-*-*-*-*-*-*")) + ))) + +(defun flush-device-fonts-cache (device) + ;; by Stig@hackvan.com + (let ((elt (assq device device-fonts-cache))) + (and elt + (setq device-fonts-cache (delq elt device-fonts-cache))))) + +(add-hook 'delete-device-hook 'flush-device-fonts-cache) + +(provide 'x-font-menu) + +;;; x-font-menu.el ends here