;;; pdb-mode.el --- Major mode for editing Protein Data Bank files
;;; This file is not part of GNU Emacs.

;; Copyright (C) 1997, 2000, 2001 by Charlie Bond
;; Copyright (C) 2000 David Love, CLRC Daresbury Laboratory

;; Author: C.S.Bond@dundee.ac.uk
;; X-URL: http://stein.bioch.dundee.ac.uk/~charlie/scripts/pdb-mode.el
;; Version: Major update May 2001
;; Keywords: data

;; This file 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.

;; This file 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 GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;{{{ Documentation

;;; Commentary:

;; PDB mode is set up to do a few useful things to PDB (protein databank 
;; format) files.
;; Other programs (MOLEMAN and PDBSET etc) do all this and more, but not 
;; within an editor.

;; To take benefit of the PDB mode, add the following
;; line to your .emacs (or get your sysadmin to
;; add it to the site-start.el) and visit a pdb file.
;; (load-file "/{path-to}/pdb-mode.el")
;; ;; Need (setq pdb-rasmol-name "\"c:/Program Files/Rasmol/rw32b2a.exe\"")
;; ;; or something similar for windows
;; (setq auto-mode-alist
;;     (cons (cons "pdb$" 'pdb-mode) 
;;            auto-mode-alist ) )
;; (autoload 'pdb-mode "PDB")

;;; This package is known to work (insofar as it's tested) with XEmacs
;;; 20.4, XEmacs 21.1, Emacs 20.5 and Emacs 21.

;;}}}

;;; Code:

(require 'easymenu)

(defvar pdb-mode nil
  "This buffer specific variable tells if the pdb mode is active." )
(make-variable-buffer-local 'pdb-mode)

;; initialise menu and hooks
(defvar pdb-mode-map (make-sparse-keymap))
(defvar pdb-mode-hook nil "Mode hook for pdb-mode")
(defvar pdb-menu-hook nil "Menu hook for pdb-mode")
(defconst pdb-mode-menu-def
  '("PDB"
    ("Select ..."
    ["Select chain"                 pdb-select-chain  t]
    ["Select current residue"       pdb-select-residue  t]
    ["Select zone of residues"      pdb-select-zone  t])
    ("Navigate"
    ["Jump to next residue"         pdb-forward-residue  t]
    ["Jump to previous residue"     pdb-back-residue  t]
    ["Jump to next chain"           pdb-forward-chain  t]
    ["Jump to previous chain"       pdb-back-chain  t])
    ("Change Values"
    ["Set alternate conformer"      pdb-change-alternate  t]
    ["Set B-factor"                 pdb-change-bfactor  t]
    ["Set chain ID"                 pdb-change-chain  t]
    ["Set atom name"                pdb-change-name  t]
    ["Set occupancy"                pdb-change-occu  t]
    ["Set residue number"           pdb-change-residue  t]
    ["Set SEGID"                    pdb-change-segid  t]
    ["Set residue type"             pdb-change-type  t])
    ("Increment Values"
    ["Add value to B-factor"        pdb-increment-bfactor  t]
    ["Change centroid"              pdb-increment-centroid  t]
    ["Add value to residue number"  pdb-increment-residue  t]
    ["Add vector to x,y,z"          pdb-increment-xyz  t])
    ("Renumber"
    ["Consecutive atom numbers"     pdb-renumber-atoms  t]
    ["Consecutive water residue numbers" pdb-renumber-waters  t])
    ("Tidy Up"
    ["Remove non-protein atoms"       pdb-tidy-amino  t]
    ["Convert ATOM -> HETATM"       pdb-tidy-atom2hetatm  t]
    ["Reduce to CA only"            pdb-tidy-ca  t]
    ["Remove hydrogens"         pdb-tidy-dehydrogenate  t]
    ["Add END record at end"        pdb-tidy-end  t]
    ["Convert HETATM -> ATOM"       pdb-tidy-hetatm2atom  t]
    ["Cut back to poly-ALA/GLY"         pdb-tidy-polyalanine  t]
    ["Remove all but ATOM/HETATM records" pdb-tidy-xyz  t])
    "-----" "Miscellaneous" "-----"
    ["  Insert residue(s)"            pdb-new-sequence t]
    ["  Open in RASMOL"     pdb-view  t]
    ["  Toggle Fontification" (font-lock-mode) :style toggle :selected font-lock-mode]
))

;; initialise some variables
(eval-when-compile
  (defvar i nil "PDB Mode: Integer used for local counting")
  (defvar pdb-test-string nil "PDB Mode: String for temporary use")
  (defvar pdb-test-vector nil "PDB Mode: Vector for temporary use")
  (defvar pdb-test-vector2 nil "PDB Mode: Vector for temporary use")
  (defvar pdb-test-number nil "PDB Mode: Number for temporary use")
  (defvar pdb-start-user-region nil "PDB Mode: Used by some functions")
  (defvar pdb-end-user-region nil "PDB Mode: Used by some functions")
  (defvar pdb-start-chain nil "PDB Mode: Used by some functions")
  (defvar pdb-end-chain nil "PDB Mode: Used by some functions")
  (defvar pdb-start-number nil "PDB Mode: Used by some functions")
  (defvar pdb-end-number nil "PDB Mode: Used by some functions")
  (defvar pdb-atom-count nil "PDB Mode: Used by some functions")
  (defvar pdb-rasmol-name "rasmol" "PDB mode: Full path to rasmol executable (only necessary if typing rasmol in a shell doesn't work. In Windows \(setq pdb-rasmol-name \"c:/Program Files/Rasmol/rw32b2a.exe\"\) or something similar to .emacs for windows")
  (defvar pdb-amino-lookup nil "PDB Mode: Lookup table to convert single-letter to three-letter amino acid codes")
  (defvar pdb-record-lookup nil "PDB Mode: Lookup table to access side-chain PDB coordinates for amino acids from their three-letter code.")
  (defvar pdb-mode-syntax-table nil "Syntax table in use in set file mode buffers.")
  (defvar pdb-font-lock-keywords nil "Table of set file font lock keywords.")
)

;; Default binding for pdb-rasmol-name
;; Need to add (setq pdb-rasmol-name "\"c:/Program Files/Rasmol/rw32b2a.exe\"")
;; or something similar to .emacs for windows
(unless (boundp 'pdb-rasmol-name)
  (setq pdb-rasmol-name "rasmol")
  )

;; Some necessary data
  (setq pdb-amino-lookup '(("A" . "ALA") ("C" . "CYS") ("D" . "ASP") ("E" . "GLU") ("F" . "PHE") ("G" . "GLY") ("H" . "HIS") ("I" . "ILE") ("K" . "LYS") ("L" . "LEU") ("M" . "MET") ("N" . "ASN") ("P" . "PRO") ("Q" . "GLN") ("R" . "ARG") ("S" . "SER") ("T" . "THR") ("V" . "VAL") ("W" . "TRP") ("Y" . "TYR")))
  (setq pdb-record-lookup '(("ALA" . 
"ATOM      2  CB  ALA A   1      -0.532  -0.774  -1.196  1.00 20.00           C\n")
     ("ARG" . 
"ATOM      1  CB  ARG A   1      -0.526  -0.779  -1.207  1.00 20.00           C
ATOM      1  CG  ARG A   1      -2.041  -0.900  -1.254  1.00 20.00           C
ATOM      1  CD  ARG A   1      -2.494  -1.684  -2.476  1.00 20.00           C
ATOM      1  NE  ARG A   1      -3.946  -1.811  -2.537  1.00 20.00           N
ATOM      1  CZ  ARG A   1      -4.600  -2.437  -3.509  1.00 20.00           C
ATOM      1  NH1 ARG A   1      -3.929  -3.023  -4.492  1.00 20.00           N
ATOM      1  NH2 ARG A   1      -5.923  -2.518  -3.476  1.00 20.00           N\n")
     ("ASN" . 
"ATOM      1  CB  ASN A   1      -0.526  -0.778  -1.208  1.00 20.00           C
ATOM      1  CG  ASN A   1      -2.037  -0.888  -1.218  1.00 20.00           C
ATOM      1  OD1 ASN A   1      -2.638  -1.406  -0.276  1.00 20.00           O
ATOM      1  ND2 ASN A   1      -2.663  -0.345  -2.255  1.00 20.00           N\n")
     ("ASP" . 
"ATOM      1  CB  ASP A   1      -0.526  -0.779  -1.207  1.00 20.00           C
ATOM      1  CG  ASP A   1      -2.039  -0.881  -1.221  1.00 20.00           C
ATOM      1  OD1 ASP A   1      -2.585  -1.504  -2.156  1.00 20.00           O
ATOM      1  OD2 ASP A   1      -2.680  -0.356  -0.287  1.00 20.00           O\n")
     ("CYS" . 
"ATOM      1  CB  CYS A   1      -0.526  -0.779  -1.207  1.00 20.00           C
ATOM      1  SG  CYS A   1      -2.327  -0.928  -1.273  1.00 20.00           S\n")
     ("GLN" . 
"ATOM      1  CB  GLN A   1      -0.526  -0.778  -1.208  1.00 20.00           C
ATOM      1  CG  GLN A   1      -2.039  -0.907  -1.254  1.00 20.00           C
ATOM      1  CD  GLN A   1      -2.522  -1.683  -2.462  1.00 20.00           C
ATOM      1  OE1 GLN A   1      -2.803  -1.106  -3.513  1.00 20.00           O
ATOM      1  NE2 GLN A   1      -2.663  -2.995  -2.307  1.00 20.00           N\n")
     ("GLU" . 
"ATOM      1  CB  GLU A   1      -0.525  -0.778  -1.208  1.00 20.00           C
ATOM      1  CG  GLU A   1      -2.039  -0.902  -1.255  1.00 20.00           C
ATOM      1  CD  GLU A   1      -2.523  -1.677  -2.465  1.00 20.00           C
ATOM      1  OE1 GLU A   1      -2.792  -1.045  -3.508  1.00 20.00           O
ATOM      1  OE2 GLU A   1      -2.679  -2.911  -2.359  1.00 20.00           O\n")
     ("HIS" . 
"ATOM      1  CB  HIS A   1      -0.525  -0.779  -1.208  1.00 20.00           C
ATOM      1  CG  HIS A   1      -2.017  -0.903  -1.245  1.00 20.00           C
ATOM      1  ND1 HIS A   1      -2.826   0.022  -1.867  1.00 20.00           N
ATOM      1  CE1 HIS A   1      -4.089  -0.341  -1.737  1.00 20.00           C
ATOM      1  NE2 HIS A   1      -4.129  -1.468  -1.049  1.00 20.00           N
ATOM      1  CD2 HIS A   1      -2.846  -1.841  -0.728  1.00 20.00           C\n")
     ("ILE" . 
"ATOM      1  CB  ILE A   1      -0.504  -0.800  -1.215  1.00 20.00           C
ATOM      1  CG1 ILE A   1      -2.032  -0.886  -1.201  1.00 20.00           C
ATOM      1  CG2 ILE A   1       0.111  -2.191  -1.229  1.00 20.00           C
ATOM      1  CD1 ILE A   1      -2.612  -1.659  -2.365  1.00 20.00           C\n")
     ("LEU" . 
"ATOM      1  CB  LEU A   1      -0.525  -0.777  -1.209  1.00 20.00           C
ATOM      1  CG  LEU A   1      -2.045  -0.935  -1.304  1.00 20.00           C
ATOM      1  CD1 LEU A   1      -2.727   0.424  -1.304  1.00 20.00           C
ATOM      1  CD2 LEU A   1      -2.425  -1.730  -2.544  1.00 20.00           C\n")
     ("LYS" . 
"ATOM      1  CB  LYS A   1      -0.526  -0.781  -1.206  1.00 20.00           C
ATOM      1  CG  LYS A   1      -2.041  -0.890  -1.262  1.00 20.00           C
ATOM      1  CD  LYS A   1      -2.494  -1.671  -2.486  1.00 20.00           C
ATOM      1  CE  LYS A   1      -4.008  -1.792  -2.534  1.00 20.00           C
ATOM      1  NZ  LYS A   1      -4.466  -2.556  -3.727  1.00 20.00           N\n")
     ("MET" . 
"ATOM      1  CB  MET A   1      -0.525  -0.780  -1.206  1.00 20.00           C
ATOM      1  CG  MET A   1      -2.039  -0.908  -1.252  1.00 20.00           C
ATOM      1  SD  MET A   1      -2.617  -1.831  -2.688  1.00 20.00           S
ATOM      1  CE  MET A   1      -4.389  -1.785  -2.430  1.00 20.00           C
ATOM      1  SE  MET A   1      -2.652  -1.887  -2.776  1.00 20.00           S
ATOM      1  CE2 MET A   1      -4.533  -1.839  -2.502  1.00 20.00           C\n")
     ("PHE" . 
"ATOM      1  CB  PHE A   1      -0.525  -0.779  -1.208  1.00 20.00           C
ATOM      1  CG  PHE A   1      -2.023  -0.890  -1.253  1.00 20.00           C
ATOM      1  CD1 PHE A   1      -2.784   0.081  -1.880  1.00 20.00           C
ATOM      1  CE1 PHE A   1      -4.161  -0.024  -1.934  1.00 20.00           C
ATOM      1  CZ  PHE A   1      -4.794  -1.087  -1.322  1.00 20.00           C
ATOM      1  CE2 PHE A   1      -4.048  -2.050  -0.672  1.00 20.00           C
ATOM      1  CD2 PHE A   1      -2.672  -1.940  -0.624  1.00 20.00           C\n")
     ("PRO" . 
"ATOM      1  CB  PRO A   1      -0.525  -0.582  -1.313  1.00 20.00           C
ATOM      1  CG  PRO A   1      -1.785   0.165  -1.573  1.00 20.00           C
ATOM      1  CD  PRO A   1      -1.548   1.557  -1.063  1.00 20.00           C\n")
     ("SER" . 
"ATOM      1  CB  SER A   1      -0.526  -0.778  -1.207  1.00 20.00           C
ATOM      1  OG  SER A   1      -0.146  -0.155  -2.421  1.00 20.00           O\n")
     ("THR" . 
"ATOM      1  CB  THR A   1      -0.504  -0.800  -1.216  1.00 20.00           C
ATOM      1  OG1 THR A   1      -0.087  -0.151  -2.424  1.00 20.00           O
ATOM      1  CG2 THR A   1      -2.021  -0.899  -1.197  1.00 20.00           C\n")
     ("TRP" . 
"ATOM      1  CB  TRP A   1      -0.525  -0.777  -1.208  1.00 20.00           C
ATOM      1  CG  TRP A   1      -2.019  -0.894  -1.243  1.00 20.00           C
ATOM      1  CD1 TRP A   1      -2.895  -0.028  -1.831  1.00 20.00           C
ATOM      1  NE1 TRP A   1      -4.184  -0.464  -1.649  1.00 20.00           N
ATOM      1  CE2 TRP A   1      -4.161  -1.635  -0.937  1.00 20.00           C
ATOM      1  CZ2 TRP A   1      -5.210  -2.445  -0.505  1.00 20.00           C
ATOM      1  CH2 TRP A   1      -4.888  -3.567   0.205  1.00 20.00           C
ATOM      1  CZ3 TRP A   1      -3.556  -3.890   0.499  1.00 20.00           C
ATOM      1  CE3 TRP A   1      -2.514  -3.089   0.069  1.00 20.00           C
ATOM      1  CD2 TRP A   1      -2.814  -1.925  -0.646  1.00 20.00           C\n")
     ("TYR" . 
"ATOM      1  CB  TYR A   1      -0.525  -0.779  -1.207  1.00 20.00           C
ATOM      1  CG  TYR A   1      -2.033  -0.894  -1.254  1.00 20.00           C
ATOM      1  CD1 TYR A   1      -2.799   0.051  -1.923  1.00 20.00           C
ATOM      1  CE1 TYR A   1      -4.177  -0.052  -1.971  1.00 20.00           C
ATOM      1  CZ  TYR A   1      -4.802  -1.121  -1.366  1.00 20.00           C
ATOM      1  CE2 TYR A   1      -4.061  -2.087  -0.720  1.00 20.00           C
ATOM      1  CD2 TYR A   1      -2.684  -1.975  -0.676  1.00 20.00           C
ATOM      1  OH  TYR A   1      -6.173  -1.228  -1.413  1.00 20.00           O\n")
     ("VAL" . 
"ATOM      1  CB  VAL A   1      -0.504  -0.802  -1.214  1.00 20.00           C
ATOM      1  CG1 VAL A   1       0.110  -2.194  -1.227  1.00 20.00           C
ATOM      1  CG2 VAL A   1      -2.023  -0.883  -1.206  1.00 20.00           C\n")))

;(if pdb-mode-syntax-table
;    ()
(setq pdb-mode-syntax-table (make-syntax-table (standard-syntax-table)))
;;)

(make-face 'pdb-key1-face)
(set-face-background 'pdb-key1-face "grey95")
(make-face 'pdb-comment-face)
(set-face-foreground 'pdb-comment-face "grey50")
(setq pdb-font-lock-keywords
      (list
       '("^\\(^ATOM..\\|HETATM\\).\\{74\\}\\(.\\{2\\}\\)" 2 pdb-key1-face t)
       '("^\\(^ATOM..\\|HETATM\\).\\{70\\}\\(.\\{2\\}\\)" 2 pdb-key1-face t)
       '("^\\(^ATOM..\\|HETATM\\).\\{60\\}\\(.\\{6\\}\\)" 2 pdb-key1-face t)
       '("^\\(^ATOM..\\|HETATM\\).\\{48\\}\\(.\\{6\\}\\)" 2 pdb-key1-face t)
       '("^\\(^ATOM..\\|HETATM\\).\\{32\\}\\(.\\{8\\}\\)" 2 pdb-key1-face t)
       '("^\\(^ATOM..\\|HETATM\\).\\{16\\}\\(.\\{4\\}\\)" 2 pdb-key1-face t)
       '("^\\(^ATOM..\\|HETATM\\).\\{14\\}\\(.\\{1\\}\\)" 2 pdb-key1-face t)
       '("^\\(^ATOM..\\|HETATM\\).\\{10\\}\\(.\\{1\\}\\)" 2 pdb-key1-face t)
       '("^\\(^ATOM..\\|HETATM\\).\\{5\\}\\(.\\{1\\}\\)" 2 pdb-key1-face t)
       '("\\(^ATOM..\\|HETATM\\)" 1 pdb-key1-face t)
       '("\\(^\\([B-G]\\|[I-Z]\\|AU\\|HE[AL]\\).*$\\)" 1 pdb-comment-face t)
       )
      )
;  ;; Set up font lock

(if (string-match "XEmacs" (emacs-version))
(add-hook 'pdb-mode-hook (function 
			  (lambda()
			    (setq font-lock-mode-disable-list 
				  (cons 'pdb-mode font-lock-mode-disable-list))))))

;; PDB-MODE
(defun pdb-mode ()							     
  "Enter PDB mode."
  (interactive)
  
  ;; Set up mode stuff
  (kill-all-local-variables)		   
  (setq mode-name "PDB")
  (setq major-mode 'pdb-mode)
  (setq kill-whole-line t)

  ;; Set up menu stuff
  (use-local-map pdb-mode-map)
  (easy-menu-define pdb-mode-menu pdb-mode-map "Emacs menu for PDB mode"
		    pdb-mode-menu-def)

  ;; Need this for Xemacs
  (easy-menu-add pdb-mode-menu)

  ;; Customise tabbing to match PDB definition - Dave Love
  (set (make-local-variable 'indent-line-function) #'tab-to-tab-stop)
  (set (make-local-variable 'tab-stop-list)
       '( ;; ATOM
	 6				; serial no.
	 11				; Chemical symbol
	 14				; Remoteness indicator
	 15				; Branch designator
	 16				; Alternate location indicator
	 17				; Residue name
	 20				; Reserved
	 21				; Chain identifier
	 22				; Residue sequence number
	 26				; Code for inserting residue
	 27				; Reserved
	 30				; X
	 38				; Y
	 46				; Z
	 54				; Pdb-Occupancy
	 60				; Isotropic B-factor
	 66				; Reserved
	 72				; Segment identifier
	 76				; Element symbol
	 78				; Charge on atom
	 80
	 ))
  (local-set-key '[(control tab)] 'move-to-tab-stop)
  (local-set-key '[(control next)] 'pdb-forward-residue)
  (local-set-key '[(control prior)] 'pdb-back-residue)
  (local-set-key '[(control meta next)] 'pdb-forward-chain)
  (local-set-key '[(control meta prior)] 'pdb-back-chain)

  ;; Some things are handled differently in GNU- and X-emacs
  (if (string-match "XEmacs" (emacs-version))
      (progn (redraw-modeline)
	     (setq zmacs-region-stays t )
	     ;; Bind C-mouse2 to select clicked residue
	     (define-key pdb-mode-map [(control button2)] 'pdb-mouse-cmouse2)
	     ;; Bind C-mouse2 to select clicked chain
	     (define-key pdb-mode-map [(control meta button2)] 'pdb-mouse-cmmouse2)
	     ;; Setup Popup menu
	     (if (boundp 'mode-popup-menu)
		 (setq mode-popup-menu
		       (cons (concat mode-name " Mode Commands") pdb-mode-menu)))))
  (if (string-match "GNU Emacs" (emacs-version))
      (progn  
	;; Bind C-mouse2 to select clicked residue
	(define-key pdb-mode-map [(control down-mouse-2)] 'pdb-mouse-cmouse2)
	;; Bind C-mouse2 to select clicked residue
	(define-key pdb-mode-map [(control meta down-mouse-2)] 'pdb-mouse-cmmouse2)
	;; Some aliases
	(defalias 'point-at-bol 'line-beginning-position)
	(defalias 'point-at-eol 'line-end-position)
	(defun activate-region () 
	  (exchange-point-and-mark)
	  (exchange-point-and-mark))))

  (run-hooks 'pdb-menu-hook 'pdb-mode-hook)

  ;; Individual Functions:
  ;; SELECT...

  (defun pdb-select-chain  (pdb-test-string)
    "PDB: Select current chain\n"
    (interactive "sWhich chain ?(1 char. RET signifies current chain):")
    (if (= 0 (length pdb-test-string)) 
	(progn
	  (goto-char (point-at-bol))
	  (re-search-forward "^\\(ATOM  \\|HETATM\\|ANISOU\\)" nil t)
	  (setq pdb-test-string (buffer-substring (+ (point-at-bol) 21) (+ (point-at-bol) 22))))
      )
    (pdb-sub-selectlocal (concat "..............." pdb-test-string)))

  (defun pdb-select-residue  ( )
    "PDB: Select current residue"
    (interactive "")
    (let ((str (concat "..........." (buffer-substring (+ (point-at-bol) 17) (+ (point-at-bol) 26)))))
      (pdb-sub-selectlocal str)))

  (defun pdb-select-zone  (pdb-test-string)
    "PDB: Select zone"
    (interactive "sEnter zone ([Chain][Res1] [Chain][Res2]):")
    (setq pdb-test-vector (split-string pdb-test-string))
    (setq pdb-start-chain " ")
    (setq pdb-end-chain " ")
    ;; Input is a 2 element array - need to grok chain and numbers from it
    (if (string-match "^[A-Z]" (elt pdb-test-vector 0))
	(setq pdb-start-chain (match-string 0 (elt pdb-test-vector 0))))
    (if (string-match "^[A-Z]" (elt pdb-test-vector 1))
	(setq pdb-end-chain (match-string 0 (elt pdb-test-vector 1))))
    (string-match "[0-9]*$" (elt pdb-test-vector 0))
    (setq pdb-start-number (match-string 0 (elt pdb-test-vector 0)))  
    (string-match "[0-9]*$" (elt pdb-test-vector 1))
    (setq pdb-end-number (match-string 0 (elt pdb-test-vector 1)))  
    ;; Find the ends
    (goto-char (point-min))
    (let (( str (concat "^\\(ATOM  \\|HETATM\\)..............." pdb-start-chain (format "%4d" (string-to-number pdb-start-number)))))
      (re-search-forward str nil (point) nil))
    (setq pdb-start-user-region (point-at-bol))
    (let (( str (concat "^\\(ATOM  \\|HETATM\\)..............." pdb-end-chain (format "%4d" (string-to-number pdb-end-number)))))
      (goto-char (point-max))
      (re-search-backward str nil (point) nil))
    (setq pdb-end-user-region (+ (point-at-eol) 1))
    (pdb-sub-markregion))

  ;; NAVIGATE
  (defun pdb-forward-residue  ( )
    "PDB: Jump to start of next residue"
    (interactive "")
    (let ((str (concat "..... ....." (buffer-substring (+ (point-at-bol) 17) (+ (point-at-bol) 26)))))
    (re-search-forward "^\\(ATOM  \\|HETATM\\)" nil t)
    (goto-char (point-at-bol))
      ;; elisp has no not (regexp) search, so this is complicated
      (while (progn
	       (re-search-forward "^\\(ATOM  \\|HETATM\\)" (point-max) (point) nil)
	       (cond ((not (looking-at str))
		      (progn 
			(goto-char  (point-at-bol)))
		      nil)
		     (t)))))
      (goto-char (point-at-bol)))

  (defun pdb-forward-chain  ( )
    "PDB: Jump to start of next chain"
    (interactive "")
    (let ((str (concat "..............." (buffer-substring (+ (point-at-bol) 21) (+ (point-at-bol) 22)))))
    (re-search-forward "^\\(ATOM  \\|HETATM\\)" nil t)
    (goto-char (point-at-bol))
      (while (progn
	       (re-search-forward "^\\(ATOM  \\|HETATM\\)" (point-max) (point) nil)
	       (cond ((not (looking-at str))
		      (progn 
			;; roll back to previous matching residue
			(goto-char  (point-at-bol)))
		      nil)
		     (t)))))
      (setq pdb-end-user-region (point-at-bol))
      ;; mark out region
      (goto-char pdb-end-user-region))

  (defun pdb-back-residue  ( )
    "PDB: Jump to start of current/previous residue"
    (interactive "")
    (re-search-backward "^\\(ATOM  \\|HETATM\\)" nil t )
    (let ((str (concat "................." (buffer-substring (+ (point-at-bol) 17) (+ (point-at-bol) 26)))))
    (while (and
	    (re-search-backward "^\\(ATOM  \\|HETATM\\)" nil t )
	    (looking-at str)))
    (if (not (looking-at str)) (re-search-forward "^\\(ATOM  \\|HETATM\\)" nil t 2)))
    (setq pdb-end-user-region (point-at-bol))
    (goto-char pdb-end-user-region))

  (defun pdb-back-chain  ( )
    "PDB: Jump to start of current/previous chain"
    (interactive "")
    (re-search-backward "^\\(ATOM  \\|HETATM\\)" nil t )
    (let ((str (concat "....................." (buffer-substring (+ (point-at-bol) 21) (+ (point-at-bol) 22)))))
    (while (and
	    (re-search-backward "^\\(ATOM  \\|HETATM\\)" nil t )
	    (looking-at str)))
    (if (not (looking-at str)) (re-search-forward "^\\(ATOM  \\|HETATM\\)" nil t 2)))
    (setq pdb-end-user-region (point-at-bol))
    (goto-char pdb-end-user-region))

  ;; CHANGE VALUES
  (defun pdb-change-bfactor (b e pdb-test-number)
    "PDB: Change selected B-factors to requested value"
    (interactive "r\nnRequested B-factor (max 999.99): ")
    (if (> pdb-test-number 999.99) (error "ERROR: Number too big"))
    (if ( <  pdb-test-number 0) (setq pdb-test-number 20 ))
    (setq pdb-test-number (format "%6.2f"  pdb-test-number))
    (pdb-sub-defineregion b e)
    (pdb-sub-change pdb-test-number 60 6)
    (pdb-sub-markregion))

  (defun pdb-change-occu (b e pdb-test-number)
    "PDB: Change occupancy to requested value"
    (interactive "r\nnRequested occupancy: ")
    (if (>  pdb-test-number 99.99) (error "ERROR: Number too big"))
    (if (>  pdb-test-number 1) (print "WARNING: Occupancy greater than 1.00" t))
    (if (< pdb-test-number 0) (setq pdb-test-number "0.00" ))
    (setq pdb-test-number (format "%5.2f" pdb-test-number))
    (pdb-sub-defineregion b e)
    (pdb-sub-change pdb-test-number 55 5)
    (pdb-sub-markregion))

  (defun pdb-change-alternate (b e pdb-test-string)
    "PDB: Change alternate conformation ID of selected atoms"
    (interactive "r \ns:Alternate ID: ")
    (if (= (length pdb-test-string) 0)
	(error "One character please - try again"))
    (setq pdb-test-string (upcase  (substring pdb-test-string 0 1)))
    (pdb-sub-defineregion b e)
    (pdb-sub-change pdb-test-string 16 1)
    (pdb-sub-markregion))

  (defun pdb-change-chain (b e pdb-test-string)
    "PDB: Change chain ID of selected atoms"
    (interactive "r \nsChain ID: ")
    (if (= (length pdb-test-string) 0)
	(error "One character please - try again"))
    (setq pdb-test-string (upcase  (substring pdb-test-string 0 1)))
    (pdb-sub-defineregion b e)
    (pdb-sub-change pdb-test-string 21 1)
    (pdb-sub-markregion))

  (defun pdb-change-name (b e pdb-test-string)
    "PDB: Change selected atom names"
    (interactive "r \nsAtom Name (4 chars, space first if not metal): ")
    (if (/= (length pdb-test-string) 4)
	   (error "Failed: 4 characters please. Space first if not a metal"))
    (setq pdb-test-string (upcase pdb-test-string))
    (pdb-sub-defineregion b e)
    (pdb-sub-change pdb-test-string 12 4)
    (pdb-sub-markregion))

  (defun pdb-change-residue (b e pdb-test-number)
    "PDB: Change residue number to given value"
    (interactive "r \nnNew residue number:")
    (setq pdb-test-number (format "%4d" pdb-test-number))
    (pdb-sub-defineregion b e )
    (pdb-sub-change pdb-test-number 22 4)
    (pdb-sub-markregion))

  (defun pdb-change-segid (b e pdb-test-string)
    "PDB: Add SEGID to selected atoms"
    (interactive "r \nsSEGID: ")
    (if ( = (length pdb-test-string) 0)
	(setq pdb-test-string (buffer-substring (+ (point-at-bol) 21) (+ (point-at-bol) 22))))
    (setq pdb-test-string (upcase pdb-test-string))
    (setq pdb-test-string (format "%-4s" pdb-test-string))
    (goto-char b)
    (setq pdb-start-user-region (point-at-bol))
    (goto-char pdb-start-user-region)
    (while (< (point) e)
      (re-search-forward "^\\(ATOM\\|HETATM\\)"  e e nil)
      (cond ((< (point) e)
    ;; Often have to pad out file to char 72
	     (pdb-sub-pad)
	     (goto-char (+ (point-at-bol) 72))
	     (delete-char 4)
	     (insert pdb-test-string))))
    (setq pdb-end-user-region (point-at-bol))
    (pdb-sub-markregion))
  
  (defun pdb-change-type (b e pdb-test-string)
    "PDB: Change residue type of selected atoms"
    (interactive "r \nsNew Residue type: ")
    (if (< (length pdb-test-string) 3)
	(error "Three characters please - try again"))
    (setq pdb-test-string (upcase  (substring pdb-test-string 0 3)))
    (pdb-sub-defineregion b e)
    (pdb-sub-change pdb-test-string 17 3)
    (pdb-sub-markregion))
  
  ;; INCREMENT VALUES
  (defun pdb-increment-bfactor (b e pdb-test-string)
    "PDB: Increment B-factor by given value"
    (interactive "r \nsNumber to add to B-factor:")
    (pdb-sub-defineregion b e)
    (while (< (point) e)
      (re-search-forward "^\\(ATOM\\|HETATM\\)" e e nil)
      (cond ((< (point) e)
	     (let (( str (buffer-substring (+ (point-at-bol) 61) (+ (point-at-bol) 66))))
	       (let (( str  (+ (eval (string-to-number str)) (eval (string-to-number pdb-test-string)))))
		 (goto-char (+ (point-at-bol) 66))
		 (delete-backward-char 6) 
		 (insert-string (format "%6.2f" str)))))))
    (pdb-sub-markregion))
  
  (defun pdb-increment-residue (b e pdb-test-string)
    "PDB: Increment residue number by given value"
    (interactive "r \nsNumber to increase residue number by:")
    (pdb-sub-defineregion b e)
    (while (< (point) e)
      (re-search-forward "^\\(ATOM\\|HETATM\\)" e e nil)
      (cond ((< (point) e)
	     (let ((str (buffer-substring (+ (point-at-bol) 22) (+ (point-at-bol) 26))))
	       (let ((str (+ (eval (string-to-number str)) (eval (string-to-number pdb-test-string)))))
	     (goto-char (+ (point-at-bol) 26))
	     (delete-backward-char 4) 
	     (insert-string (format "%4d" str)))))))
    (pdb-sub-markregion))

  (defun pdb-increment-xyz (b e pdb-test-string)
    "PDB: Translate selected atoms by given vector"
    (interactive "r \nsGive vector (3 numbers, space delimited):")
    (setq pdb-test-vector (split-string pdb-test-string))
    (pdb-sub-defineregion b e)
    (while (< (point) e)
      (re-search-forward "^\\(ATOM\\|HETATM\\)" e e nil)
      (cond ((< (point) e)
	     (string-match "[-0-9].*[0-9]" (buffer-substring (+ (point-at-bol) 31) (+ (point-at-bol) 54)))
	     (let ((str (split-string (match-string 0 (buffer-substring (+ (point-at-bol) 31) (+ (point-at-bol) 54))))))
	       (goto-char (+ (point-at-bol) 54))
	       (delete-backward-char 24) 
	       (setq i 0)
	       (while (< i 3)
		 (progn
		   (insert-string (format "%8.3f" (+ (eval (string-to-number (elt str i))) (string-to-number (elt pdb-test-vector i)))))
		   (setq i (+ i 1))))))))
    (pdb-sub-markregion))

  (defun pdb-increment-centroid (b e pdb-test-string)
    "PDB: Translate selected atoms by given vector"
    (interactive "r \nsGive vector (3 numbers, space delimited):")
    (setq pdb-test-vector (split-string pdb-test-string))
    (setq pdb-test-vector2 (vector 0 0 0))
    (setq pdb-atom-count 0)
    ;; first pass to calculate average
    (pdb-sub-defineregion b e)
    (while (< (point) e)
      (re-search-forward "^\\(ATOM\\|HETATM\\)" e e nil)
      (cond ((< (point) e)
	     ;; get current xyz
	     (string-match "[-0-9].*[0-9]" (buffer-substring (+ (point-at-bol) 31) (+ (point-at-bol) 54)))
	     (let (( current-xyz (split-string (match-string 0 (buffer-substring (+ (point-at-bol) 31) (+ (point-at-bol) 54))))))
	       ;; add current xyz to running total (pdb-test-vector2)
	       (setq pdb-test-vector2 (vector (+ (eval (string-to-number (elt current-xyz 0))) (elt pdb-test-vector2 0)) (+ (eval (string-to-number (elt current-xyz 1))) (elt pdb-test-vector2 1)) (+ (eval (string-to-number (elt current-xyz 2))) (elt pdb-test-vector2 2)))))
	     ;; keep count of atoms
	     (setq pdb-atom-count (+ pdb-atom-count 1)))))
    ;; calculate average xyz and store in pdb-test-vector2
    (setq pdb-test-vector2 (vector (/ (elt pdb-test-vector2 0) pdb-atom-count)(/ (elt pdb-test-vector2 1) pdb-atom-count)(/ (elt pdb-test-vector2 2) pdb-atom-count)))
    ;; second pass to adjust numbers
    (goto-char pdb-start-user-region)
    (while (< (point) e)
      (re-search-forward "^\\(ATOM\\|HETATM\\)" e e nil)
      (cond ((< (point) e)
	     (string-match "[-0-9].*[0-9]" (buffer-substring (+ (point-at-bol) 31) (+ (point-at-bol) 54)))
	     (let ((str (split-string (match-string 0 (buffer-substring (+ (point-at-bol) 31) (+ (point-at-bol) 54))))))
	       (goto-char (+ (point-at-bol) 54))
	       (delete-backward-char 24) 
	       (setq i 0)
	       (while (< i 3)
		 (progn
		   (insert-string (format "%8.3f" (- (eval (string-to-number (elt str i))) (- (elt pdb-test-vector2 i) (string-to-number (elt pdb-test-vector i))))))
		   (setq i (+ i 1))))))))
    (pdb-sub-markregion))

  ;; RENUMBER
  (defun pdb-renumber-atoms (b e pdb-start-number)
    "PDB: Renumber selected atoms consecutively"
    (interactive "r \nnRenumber atoms, starting at?:")
    (if ( =  pdb-start-number 0) (setq pdb-start-number 1)) 
    (pdb-sub-defineregion b e)
    (setq i (- pdb-start-number 1))
    (while (< (point) e)
      (setq i (+ 1 i))
      (re-search-forward "^\\(ATOM\\|HETATM\\)" e e nil)
      (goto-char  (+ (point-at-bol) 11))
      (if (< (point) e)
	     (progn 
	       (delete-backward-char 5)
	       (insert-string (format "%5s" (int-to-string i))))))
    (pdb-sub-markregion))

  (defun pdb-renumber-waters (b e pdb-start-number)
    "PDB: Renumber selected waters consecutively"
    (interactive "r \nnRenumber waters, starting at?:")
    (if ( =  pdb-start-number 0) (setq pdb-start-number 1)) 
    (pdb-sub-defineregion b e)
    (setq i (- pdb-start-number 1))
    (while (< (point) e)
      (setq i (+ 1 i))
      (re-search-forward "^\\(ATOM\\|HETATM\\)" e e nil)
      (goto-char  (+ (point-at-bol) 26))
      (if (< (point) e)
	  (progn
	    (delete-backward-char 4)
	    (insert-string (format "%4s" (int-to-string i))))))
    (pdb-sub-markregion))

  ;;TIDY UP
  (defun pdb-tidy-atom2hetatm (b e)
    "PDB: Replace selected ATOMs with HETATM"
    (interactive "r")
    (pdb-sub-defineregion b e)
    (goto-char pdb-start-user-region)
    (while (and (progn
      (re-search-forward "^ATOM" (point-max) e nil))
      (not (progn
      (beginning-of-line)
      (delete-char 6)
      (insert "HETATM")))))
    (pdb-sub-markregion))

  (defun pdb-tidy-hetatm2atom (b e)
    "PDB: Replace selected HETATMS with ATOM"
    (interactive "r")
    (pdb-sub-defineregion b e)
    (goto-char pdb-start-user-region)
    (while (and (progn
      (re-search-forward "^HETATM" (point-max) e nil))
      (not (progn
      (beginning-of-line)
      (delete-char 6)
      (insert "ATOM  ")))))
    (pdb-sub-markregion))

  (defun pdb-tidy-ca  ( b e )
    "PDB: Reduce to CAs only"
    (interactive "r" )
    (pdb-sub-defineregion b e)
    (while (< (point) pdb-end-user-region)
      (re-search-forward "^." pdb-end-user-region pdb-end-user-region nil)
      (if (and (< (point) pdb-end-user-region) (not (looking-at "TOM  .....  CA" )))
	  (progn
	    (beginning-of-line)
	    (setq pdb-end-user-region (- pdb-end-user-region (+ 1 (- (point-at-eol) (point-at-bol)))))
	    (kill-line))))
    (pdb-sub-markregion))

  (defun pdb-tidy-dehydrogenate (b e )
    "PDB: Remove hydrogen atoms"
    (interactive "r" )
    (pdb-sub-defineregion b e)
    (while (< (point) e)
      (re-search-forward "^\\(ATOM  \\|HETATM\\)..... .H.*$" e e nil)
      (if (< (point) e)
 	  (progn
	     (beginning-of-line)
	     (setq pdb-end-user-region (- pdb-end-user-region (+ 1 (- (point-at-eol) (point-at-bol)))))
	     (kill-line))))
    (pdb-sub-markregion))

  (defun pdb-tidy-end ()
    "PDB: Add END after last ATOM/HETATM record" 
    (interactive)
    (end-of-buffer)
    (re-search-backward "^\\(ATOM\\|HETATM\\)" nil e nil)
    (forward-line) 
    (insert "END\n"))

  (defun pdb-tidy-polyalanine  (b e)
    "PDB: Reduce to poly-ALA (leave GLY as GLY)"
    (interactive "r" )
    (pdb-sub-defineregion b e)
    (while (< (point) pdb-end-user-region)
      (re-search-forward "^." pdb-end-user-region pdb-end-user-region nil)
      (if  (not (looking-at "TOM  .....  \\(CA\\|C \\|O \\|N \\|CB\\)  \\(ALA\\|CYS\\|ASP\\|GLU\\|PHE\\|GLY\\|HIS\\|ILE\\|LYS\\|LEU\\|MET\\|ASN\\|PRO\\|GLN\\|ARG\\|SER\\|THR\\|VAL\\|TRP\\|TYR\\)" ))
	  (progn
	    (beginning-of-line)
	    (setq pdb-end-user-region (- pdb-end-user-region (+ 1 (- (point-at-eol) (point-at-bol)))))
	    (kill-line))
	(progn
	  (if (and (< (point) e) (not( string= "GLY" (buffer-substring (+ (point-at-bol) 17) (+ (point-at-bol) 20)))))
	      (progn
		 (goto-char (+ (point-at-bol) 20))    
		 (delete-backward-char 3)
		 (insert "ALA"))))))
    (pdb-sub-markregion))

  (defun pdb-tidy-xyz ()
    "PDB: Delete all non-ATOM/HETATM records"
    (interactive)
    (beginning-of-buffer)
    (delete-non-matching-lines "^\\(ATOM\\|HETATM\\)"))
  
  (defun pdb-tidy-amino ()
    "PDB: Delete all non-protein records"
    (interactive)
    (beginning-of-buffer)
    (delete-non-matching-lines "^\\(ATOM  .......... \\(ALA\\|CYS\\|ASP\\|GLU\\|PHE\\|GLY\\|HIS\\|ILE\\|LYS\\|LEU\\|MET\\|ASN\\|PRO\\|GLN\\|ARG\\|SER\\|THR\\|VAL\\|TRP\\|TYR\\)\\|HEA\\|AU\\|[B-G]\\|[I-Z]\\)"))

  ;; MISCELLANEOUS
  (defun pdb-view  ( b e )
    "PDB: Display selected atoms in rasmol"
    (interactive "r" )
    (pdb-sub-defineregion b e)
    (pdb-sub-markregion)
    (let (( filestamp (concat "/tmp/.xemacs-rasmol" (number-to-string (elt (current-time) 1)) (number-to-string (emacs-pid)))))
      (write-region pdb-start-user-region pdb-end-user-region filestamp)
      (switch-to-buffer (get-buffer-create "*RASMOL*"))
      (delete-windows-on "*RASMOL*")
      (kill-buffer "*RASMOL*")
      (shell-command ( concat pdb-rasmol-name " " filestamp "&")  "*RASMOL*" )
      (if (string-match "XEmacs" (emacs-version))
	  (rename-buffer "*RASMOL*"))
      (other-window 1 nil )
      (shrink-window (- (window-height) 4))
      (sleep-for 4) 
      (delete-file filestamp)
      (end-of-buffer))
    (pdb-sub-markregion))
  
  (defun pdb-new-residue (pdb-test-number pdb-test-string)
    "Insert residue"
    (interactive "nResidue number: \nsResidue type: ")
    (if (< (length pdb-test-string) 1)
	(error "Sequence is required"))
    (setq pdb-test-string (cdr (assoc-ignore-case pdb-test-string pdb-amino-lookup)))
    (goto-char (point-at-bol))
    (setq pdb-start-user-region (point))
    (let ((str  
	   "ATOM      1  N   GLY A   1      -0.527   1.359   0.000  1.00 20.00           N
ATOM      2  CA  GLY A   1       0.000   0.000   0.000  1.00 20.00           C
ATOM      3  C   GLY A   1       1.525   0.000   0.000  1.00 20.00           C
ATOM      4  O   GLY A   1       2.155   1.057   0.000  1.00 20.00           O\n"))
      (let ((str (concat str (cdr (assoc-ignore-case pdb-test-string pdb-record-lookup)))))
	(insert-string str)))
    (setq pdb-end-user-region (point))
    (pdb-sub-markregion)
    (pdb-change-type pdb-start-user-region pdb-end-user-region pdb-test-string)
    (pdb-change-residue pdb-start-user-region pdb-end-user-region pdb-test-number)
    (setq pdb-test-number (+ pdb-test-number 1)))
  
  (defun pdb-new-sequence (pdb-test-number pdb-test-string)
    "PDB file from sequence\nReads in start number and a single letter code sequence, ignoring anything but standard 20 amino acids"
    (interactive "nNumber of first residue: \nsInput sequence (single letter): ")
    (setq i 0)
    (while (< i (length pdb-test-string))
      (let ((str (substring pdb-test-string i (1+ i))))
	(if (string-match str "ACDEFGHIKLMNPQRSTVWY")
	    (progn (pdb-new-residue (+ pdb-test-number i) str)))
	(setq i (1+ i)))))
  
  ;; Subroutines
  (defun pdb-sub-pad ()
    "Pad out end of each line to char 80 with spaces"
    (while (> 80 (- (point-at-eol) (point-at-bol)))
      (progn 
	(goto-char (point-at-eol))
	(insert-string " ")
	(setq e (+ 1 e)))))
  

  (defun pdb-sub-defineregion (b e)
    "sort out region limits"
    (goto-char (- e 1))
    (cond ((< (point-at-eol) (point-max))
	   (setq e (+ (point-at-eol) 1 )))
	  ((= (point-at-eol) (point-max))
	     (setq e (point-at-eol)))
	  )
    (setq pdb-end-user-region e)
    (goto-char b)
    (setq pdb-start-user-region (point-at-bol))
    (goto-char pdb-start-user-region))
  
  (defun pdb-sub-markregion ()
    "set mark etc"
    (goto-char pdb-end-user-region)
    (set-mark pdb-start-user-region)
    (activate-region))

  (defun pdb-sub-selectlocal (pdb-test-string)
    "PDB: Select current chain"
    ;; elisp has no not (regexp) search, so this is complicated
    (beginning-of-line)
    ;; go back to closest non-matching residue
    (while (progn
	     (re-search-backward "^." nil t)
	     (cond ((= (point) (point-min))
		    (goto-char (point-at-bol))
		    (setq pdb-start-user-region (point-at-bol))
		    nil)
		   ((not (looking-at
			  (concat "^\\(ATOM  \\|HETATM\\|ANISOU\\)"
				  pdb-test-string)))
		    (setq pdb-start-user-region (+ (point-at-eol) 1))
		    nil)
		   (t))))
    (goto-char pdb-start-user-region)
    ;; find next non-matching residue
    (while (progn
	     (re-search-forward "^\\(ATOM  \\|HETATM\\|ANISOU\\)" (point-max) e nil)
	     (cond ((not (looking-at pdb-test-string))
		    (progn 
;	     ;; roll back to previous matching residue
		      (goto-char (- (point-at-bol) 1 ))
		      (re-search-backward "^\\(ATOM  \\|HETATM\\|ANISOU\\)" nil (point) nil)
		      (goto-char (+ 1 (point-at-eol))))
		    nil)
		   (t))))
    (setq pdb-end-user-region (point-at-bol))
    ;; mark out region
    (set-mark pdb-start-user-region)
    (goto-char pdb-end-user-region)
    (activate-region))

  (defun pdb-sub-change ( pdb-test-string start length)
    "change"
    (while (< (point) e)
      (cond ((< start 30)
	     (re-search-forward "^\\(ATOM\\|HETATM\\|ANISOU\\)" nil e nil))
	    ((> start 29)
	     (re-search-forward "^\\(ATOM\\|HETATM\\)" nil e nil)))
      (cond ((< (point) e)
	     (delete-region (+ (point-at-bol) start) (+ (point-at-bol) (+ start length)))
	     (goto-char  (+ (point-at-bol) start))
	     (insert pdb-test-string)
	     ))))

  (defun pdb-mouse-cmouse2 (event)
    "Sets the point at the mouse location, then highlights current residue"
    (interactive "@e")
    (mouse-set-point event)
    (pdb-select-residue))
  (defun pdb-mouse-cmmouse2 (event)
    "Sets the point at the mouse location, then highlights current chain"
    (interactive "@e")
    (mouse-set-point event)
    (pdb-select-chain ""))
  (provide 'pdb)
  )
