;;; bibus.el --- ;; Copyright 2006 Ye Wenbin ;; ;; Author: wenbinye@163.com ;; Version: $Id: bibus.el,v 1.2 2006/08/27 10:54:51 ywb Exp ywb $ ;; Keywords: ;; X-URL: not distributed yet ;; 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. ;;;_* Commentary: ;; ;; Put this file into your load-path and the following into your ~/.emacs: ;; (require 'bibus) ;; To start bibus, just M-x bibus ;;;_. Code: (provide 'bibus) (eval-when-compile (require 'cl)) (require 'mysql) (require 'tree-widget) (defvar bibus-database "biblio" "Database name of bibus") (defvar bibus-tables '((ref . "bibref") (query . "bibquery") (refkey . "bibrefKey") (reflink . "bibrefLink")) "Name of tables in database.") (defvar bibus-sql-process nil "Process for mysql query") (defvar bibus-ref-header nil "References table fields") (defvar bibus-refkey nil "Key tree. A tree-widget.") (defvar bibus-buffer-names '((ref "*bibus-ref*" bibus-ref-mode) (key "*bibus-key*" bibus-key-mode) (entry "*bibus-entry*" bibus-entry-mode)) "Buffer name and mode") (defsubst bibus-table (table) (assoc-default table bibus-tables)) (defsubst bibus-buffer (name) (let ((bufname (cadr (assoc name bibus-buffer-names)))) (or (get-buffer bufname) bufname))) (defun bibus-ensure-buffer (name) "Return the buffer according to NAME. If buffer not exists, create the buffer, and set mode assoc it. See `bibus-buffer-names'." (let ((buf (bibus-buffer name))) (if (bufferp buf) buf (with-current-buffer (get-buffer-create buf) (funcall (nth 2 (assoc name bibus-buffer-names))) (current-buffer))))) (defsubst bibus-clear-buffer (name) (with-current-buffer (bibus-ensure-buffer name) (let ((inhibit-read-only t)) (erase-buffer)))) (defun bibus-ref-field (ref field) "Return the content of reference whose field name is FIELD." (let ((mysql-header bibus-ref-header)) (if (listp field) (mapcar (lambda (f) (mysql-cell ref f)) field) (mysql-cell ref field)))) ;;;_. reference display (defvar bibus-elide-string ">" "The string to show when the field exceed") (defvar bibus-formats '(("Identifier" 15) " " ("Author" 20) " " ("Year" 5) " " ("Title" 40)) "Format to show the reference. Each has format as: (FIELD COLUMN JUSTFIED) JUSTFIED which can be right or omit which means left.") (defvar bibus-compiled-formats nil "Compiled `bibus-formats'. Each slot contain a function to return a string for this column.") (defvar bibus-field-column nil "A vector to restore position information of `bibus-formats'. If the slot is a positive number, it indicate the index is column start position, and its value is the index of the `bibus-formats'. If the slot is a nagtive number, it indicate the shift to the column start index. If the slot is zero, it indicates that is a blank") (defun bibus-format-string (str col frt) "If STR longer than col, strip the str and append the `bibus-elide-string'. Otherwise, format STR by FRT." (if (> (length str) col) (concat (substring str 0 (- col (length bibus-elide-string))) bibus-elide-string) (format frt str))) (defsubst bibus-header-line () (let ((header (mapcar 'car bibus-ref-header))) (concat (make-string (if scroll-bar-mode 4 2) 32) (mapconcat (lambda (frt) (funcall frt header)) bibus-compiled-formats "")))) (defun bibus-compile-format () (interactive) (let ((i 0) len (col 0)) (setq bibus-field-column (make-vector (dolist (frt bibus-formats i) (setq i (+ i (if (stringp frt) (length frt) (cadr frt))))) 0)) (setq i 0) (dolist (frt bibus-formats) (setq col (1+ col)) (if (stringp frt) (progn (setq len (length frt)) (aset bibus-field-column i 0)) (setq len (cadr frt)) (aset bibus-field-column i col)) (dotimes (v (1- len)) (aset bibus-field-column (+ i v 1) (- -1 v))) (setq i (+ i len)))) (setq bibus-compiled-formats (mapcar (lambda (frt) (if (listp frt) (if (eq (nth 2 frt) 'right) `(lambda (ref) (bibus-format-string (bibus-ref-field ref ,(car frt)) ,(cadr frt) (format "%%%ds" ,(cadr frt)))) `(lambda (ref) (bibus-format-string (bibus-ref-field ref ,(car frt)) ,(cadr frt) (format "%%-%ds" ,(cadr frt))))) `(lambda (ref) ,frt))) bibus-formats)) (with-current-buffer (bibus-ensure-buffer 'ref) (setq header-line-format (bibus-header-line)))) ;;;_. refkey display (define-widget 'bibus-key-widget 'push-button "bibus key widget" :format "%[%t%]%d" :notify 'bibus-display-key-refs :doc "" :keep '(:doc)) (defvar bibus-key-alist nil) (defsubst bibus-ref-keys () "Return the keys of References." (cdr (mapcar (lambda (key) (widget-get key :tag)) (widget-get (bibus-key-widget "References") :children)))) (defun bibus-key-widget (tag &optional parent) "Get the widget which tag is TAG. It only find children of `bibus-refkey'." (let (pwid children child found) (setq pwid (if parent (bibus-key-widget parent) bibus-refkey) children (widget-get pwid :children)) (while (and (not found) children) (setq child (car children) children (cdr children)) (when (string= (widget-get child :tag) tag) (setq found t))) (if found child))) (defun bibus-update-key-alist () (setq bibus-key-alist (cdr (mysql-query (format "select key_Id, key_name from %s.%s" bibus-database (bibus-table 'refkey)) bibus-sql-process)))) (defsubst bibus-key-id (key) "Return the key_Id according to KEY" (car (rassoc (list key) bibus-key-alist))) (defsubst bibus-key-name (keyid) "Return the key_name according to KEYID" (cadr (assoc keyid bibus-key-alist))) ;; (defsubst bibus-key-id (key) ;; (caadr (mysql-query ;; (format ;; "select key_Id from %s.%s where key_name=%s" ;; bibus-database (bibus-table 'refkey) (mysql-quote key)) ;; bibus-sql-process))) (defun bibus-reference-keys (widget) "Create References widgets" (let ((refkid (bibus-key-id "References"))) (mapcar (lambda (row) `(bibus-key-widget :tag ,(cadr row) :sql ,(string-to-number (car row)) :keyid ,(car row))) (cdr (mysql-query (format "select key_Id, key_name from %s.%s where parent=%s" bibus-database (bibus-table 'refkey) refkid) bibus-sql-process))))) (defun bibus-query-keys (widget) "Create Query widgets" (let ((queryid (bibus-key-id "Query"))) (mapcar (lambda (row) `(bibus-key-widget :tag ,(car row) :sql ,(replace-regexp-in-string "\\\\n" "" (cadr row)))) (remove-if (lambda (row) (string= (car row) "*last-query*")) (cdr (mysql-query (format "select name, query from %s.%s" bibus-database (bibus-table 'query)) bibus-sql-process)))))) ;;;_. select reference from database (defvar bibus-query-cache 40 "Reference number for every query") (defvar bibus-query-offset 0 "Current query offset") (defvar bibus-query-total 0 "Total reference of `bibus-query-sql'") (defvar bibus-query-sql nil "Current sql to fetch reference") (defun bibus-fetch-sql (where &optional other-tables) "Format sql to fetch reference" (let ((sql (format " from %s.%s as ref" bibus-database (bibus-table 'ref)))) (and other-tables (setq sql (concat sql ", " (mapconcat (lambda (tbl) (format "%s.%s as %s" bibus-database (bibus-table tbl) (symbol-name tbl))) other-tables ", ")))) (setq sql (format "%s where %s" sql where)))) (defun bibus-fetch-reference (sql) "Fetch reference by the sql" (setq sql (format "select ref.* %s limit %d offset %d" sql bibus-query-cache bibus-query-offset)) (setq bibus-query-offset (+ bibus-query-offset bibus-query-cache)) (cdr (mysql-query sql bibus-sql-process))) (defun bibus-selected-key-widget () "Current selected" (let (widget) (with-current-buffer (bibus-ensure-buffer 'key) (save-excursion (goto-char (point-min)) (while (and (re-search-forward "[*]$" nil t) (progn (widget-backward 1) (setq widget (widget-at)) (not (eq (car widget) 'bibus-key-widget)) (forward-line 1)))) (if (and widget (eq (widget-type widget) 'bibus-key-widget)) widget))))) (defun bibus-display-key-refs (widget &rest ignore) (let ((prev (bibus-selected-key-widget))) (when prev (widget-put prev :doc "") (widget-value-set prev (widget-value prev))) (widget-put widget :doc "*") (widget-value-set widget (widget-value widget)) (setq bibus-entry-edit-mode nil) (bibus-display-key-refs-1 (widget-get widget :sql)))) (defun bibus-display-key-refs-1 (where) "Display the reference by the where_definition WHERE" (setq bibus-query-sql (if (stringp where) (bibus-fetch-sql where) (bibus-fetch-sql (format "reflink.key_Id=%d and ref.Id=reflink.ref_Id" where) '(reflink)))) (bibus-clear-buffer 'ref) (setq bibus-query-total (string-to-number (caadr (mysql-query (concat "select count(*)" bibus-query-sql) bibus-sql-process)))) (setq bibus-query-offset 0) (if (> bibus-query-total 0) (with-current-buffer (bibus-buffer 'ref) (bibus-insert-ref (bibus-fetch-reference bibus-query-sql)) (goto-char (point-min)) (display-buffer (current-buffer))) (message "No match reference!"))) (defun bibus-insert-ref (refs) "Insert reference the current buffer" (if (and refs (= (length (car refs)) (length bibus-ref-header))) (let ((inhibit-read-only t)) (dolist (ref refs) (insert (propertize (concat " " (mapconcat (lambda (frt) (funcall frt ref)) bibus-compiled-formats "")) 'reference ref) "\n"))))) ;;;_. window setup (defvar bibus-window-configuration '(key (horizontal 0.35 (ref (vertical 0.6 (entry))))) "Configuration of the bibus windows. The size can be a integer of a portion to divide the current window. It is recommended to use a portion which may less possible to raise an error. (key (horizontal 0.35 (ref (vertical 0.8 (entry))))) will set window like this: +-----+----------+ | key | ref | | | | | | | | | | | | | | +----------+ | | entry | +-----+----------+ (key (vertical 0.8 (entry)) (horizontal 0.35 (ref))) will set window like this: +-----+-----------+ | key | ref | | | | | | | | | | | | | +-----+-----------+ | entry | +-----------------+") (defvar bibus-current-window-configuration nil) (defun bibus-set-window-configuration (conf) "Set up window by `bibus-window-configuration'" (let ((buf (bibus-buffer (car conf))) full size min splitfunc window oldwin) (if buf (set-window-buffer (selected-window) buf)) (dolist (other (cdr conf)) (when other (setq oldwin (selected-window)) (if (eq (car other) 'horizontal) (setq splitfunc 'split-window-horizontally full (window-width) min window-min-width) (setq splitfunc 'split-window-vertically full (window-height) min window-min-height)) (setq size (if (integerp (cadr other)) (cadr other) (floor (* (cadr other) full)))) (if (and (> size min) (> (- full size) min)) (progn (setq window (funcall splitfunc size)) (when (nth 2 other) (select-window window) (bibus-set-window-configuration (nth 2 other))) (select-window oldwin)) (error "window size is too small or too large for %S" (car conf))))))) (defun bibus-erase-window-config () (interactive) (setq bibus-current-window-configuration nil)) ;;;_. buffer navigate (defun bibus-switch-buffer (name) (let ((buf (bibus-ensure-buffer name))) (if (get-buffer-window buf) (select-window (get-buffer-window buf)) (select-window (display-buffer buf))))) (defun bibus-switch-to-key () (interactive) (bibus-switch-buffer 'key)) (defun bibus-switch-to-entry () (interactive) (bibus-switch-buffer 'entry)) (defun bibus-switch-to-ref () (interactive) (bibus-switch-buffer 'ref)) ;;;_. mode define ;;;_ , keymaps (defvar bibus-common-map nil) (defvar bibus-ref-mode-map nil) (defvar bibus-key-mode-map nil) (defvar bibus-entry-mode-map nil) (or bibus-common-map (setq bibus-common-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-r") 'bibus-switch-to-ref) (define-key map (kbd "C-c C-k") 'bibus-switch-to-key) (define-key map (kbd "C-c C-e") 'bibus-switch-to-entry) (define-key map (kbd "C-c C-s") 'bibus-query) (define-key map (kbd "C-c C-n") 'bibus-name-last-query) map))) (or bibus-key-mode-map (setq bibus-key-mode-map (let ((map (copy-keymap widget-keymap))) (set-keymap-parent map bibus-common-map) (define-key map "d" 'bibus-delete-key) (define-key map "a" 'bibus-add-key) (define-key map "r" 'bibus-rename-key) (define-key map "e" 'bibus-edit-query) (define-key map "A" 'bibus-add-query) (define-key map "R" 'bibus-name-last-query) (define-key map "s" 'bibus-show-key) map))) (or bibus-ref-mode-map (setq bibus-ref-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map bibus-common-map) (define-key map (kbd "C-v") 'bibus-scroll-up) (define-key map (kbd "M-v") 'bibus-scroll-down) (define-key map (kbd "M->") 'bibus-end-of-buffer) (define-key map "s" 'bibus-show-field-at-point) (define-key map "\t" 'bibus-next-field) (define-key map (kbd "") 'bibus-prev-field) (define-key map "m" 'bibus-mark-ref-forward) (define-key map "u" 'bibus-unmark-ref-forward) (define-key map "U" 'bibus-unmark-ref-all) (define-key map "M" 'bibus-mark-ref-all) (define-key map "w" 'bibus-copy-field-at-point) (define-key map "t" 'bibus-set-ref-key) (define-key map (kbd "C-t") 'bibus-set-current-ref-key) (define-key map "T" 'bibus-tag-ref) (define-key map "d" 'bibus-delete-key-ref) (define-key map "D" 'bibus-delete-ref) (define-key map ">" 'bibus-widen-column) (define-key map "<" 'bibus-narrow-column) (define-key map "j" 'next-line) (define-key map "k" 'previous-line) (define-key map (kbd "C-c C-o") 'bibus-browse-ref-url) (define-key map (kbd "C-c C-f") 'bibus-open-ref-file) (define-key map "R" 'bibus-relocate-ref-file) (define-key map "S" 'bibus-sort-ref) (define-key map "K" 'bibus-ref-assoc-keys) map))) (or bibus-entry-mode-map (setq bibus-entry-mode-map (let ((map (copy-keymap widget-keymap))) (set-keymap-parent map bibus-common-map) (define-key map "E" 'bibus-entry-edit) (define-key map (kbd "C-c C-c") 'bibus-entry-edit-submit) map))) ;;;_ , ref mode (defvar bibus-file-field-name "Custom5" "The field name of file in reference table") (defvar bibus-file-app '(("pdf" . "xpdf \"%s\"")) "How to open file") (defvar bibus-default-file-directory "~/papers/" "Default directory of reference file") (define-derived-mode bibus-ref-mode nil "Ref" "Major mode for display and manage reference. \\{bibus-ref-mode-map}" (setq buffer-read-only nil) (erase-buffer) (setq buffer-read-only t) (setq truncate-lines t) (font-lock-add-keywords nil '(("^[*].+" 0 font-lock-warning-face prepend))) (unless header-line-format (setq header-line-format (bibus-header-line)))) ;;;_ . scroll (defun bibus-scroll-up (arg) "ARG is how many reference to append to this buffer" (interactive "P") (let ((col (current-column))) (if (> bibus-query-total bibus-query-offset) (save-excursion (or arg (setq arg bibus-query-cache)) (let ((bibus-query-cache arg)) (goto-char (point-max)) (bibus-insert-ref (bibus-fetch-reference bibus-query-sql))))) (call-interactively 'scroll-up) (move-to-column col))) (defun bibus-end-of-buffer () "Fetch all reference of current `bibus-query-sql' in the database." (interactive) (if (> bibus-query-total bibus-query-offset) (bibus-scroll-up (- bibus-query-total bibus-query-offset)) (end-of-buffer))) (defun bibus-scroll-down (arg) "Scroll down without change current column" (interactive "P") (let ((col (current-column))) (scroll-down arg) (move-to-column col))) ;;;_ . view (defsubst bibus-current-column () (let ((col (1- (current-column)))) (if (< col 0) 0 col))) (defun bibus-field-at-point () "Field name in the `bibus-formats' according to current column" (let ((col (bibus-current-column)) field) (when (< col (length bibus-field-column)) (setq field (aref bibus-field-column col)) (if (< field 0) (setq field (aref bibus-field-column (+ col field)))) (when (> field 0) (car (nth (1- field) bibus-formats)))))) (defun bibus-show-field-at-point () "Echo the field content." (interactive) (let ((field (bibus-field-at-point))) (when field (message "%s: %s" field (bibus-ref-field (bibus-ref-at-point) field))))) (defun bibus-copy-field-at-point () "Add the content of this column to kill ring" (interactive) (let ((field (bibus-field-at-point))) (when field (setq field (bibus-ref-field (bibus-ref-at-point) field)) (if (eq last-command 'kill-region) (kill-append field nil) (kill-new field)) (message field)))) (defun bibus-next-field (arg) "Jump the next ARG fields" (interactive "P") (or arg (setq arg 1)) (if (< arg 0) (bibus-prev-field (- arg)) (let ((col (bibus-current-column)) (len (1- (length bibus-field-column))) field) (setq field (aref bibus-field-column col)) (if (< field 0) (setq col (+ col field))) (while (> arg 0) (if (< col len) (setq col (1+ col)) (forward-line) (setq col 0)) (if (> (aref bibus-field-column col) 0) (setq arg (1- arg)))) (move-to-column (1+ col))))) (defun bibus-prev-field (arg) "Jump the previous ARG fields" (interactive "P") (or arg (setq arg 1)) (if (< arg 0) (bibus-next-field (- arg)) (let ((col (bibus-current-column)) (len (1- (length bibus-field-column))) field) (setq field (aref bibus-field-column col)) (if (< field 0) (setq col (+ col field))) (while (> arg 0) (if (> col 0) (setq col (1- col)) (forward-line -1) (setq col len)) (if (> (aref bibus-field-column col) 0) (setq arg (1- arg)))) (move-to-column (1+ col))))) (defun bibus-widen-column (arg) "Widen the current column by ARG." (interactive "p") (let ((col (bibus-current-column)) field) (when (< col (length bibus-field-column)) (setq field (aref bibus-field-column col)) (if (< field 0) (setq field (aref bibus-field-column (+ col field)))) (when (> field 0) (setq field (nth (1- field) bibus-formats)) (setcdr field (list (+ arg (cadr field)))) (bibus-compile-format) (bibus-redisplay-ref))))) (defun bibus-narrow-column (arg) "Narrow the current column by ARG." (interactive "p") (bibus-widen-column (- arg))) (defun bibus-redisplay-ref () "Redisplay the reference without query from database." (let ((inhibit-read-only t) (line (line-number-at-pos)) (col (bibus-current-column)) refs) (goto-char (point-min)) (while (not (eobp)) (setq refs (append refs (list (bibus-ref-at-point)))) (forward-line)) (bibus-clear-buffer 'ref) (bibus-insert-ref refs) (goto-line line) (move-to-column (1+ col)))) (defun bibus-sort-ref (arg) "Sort the reference by current column. With prefix, sort by descend." (interactive "P") (let ((field (bibus-field-at-point)) (inhibit-read-only t) (col (current-column)) pos) (if (setq pos (string-match "\\s-*order by ref\." bibus-query-sql)) (setq bibus-query-sql (substring bibus-query-sql 0 pos))) (setq bibus-query-sql (format "%s order by ref.%s %s" bibus-query-sql field (if arg "desc" "asc"))) (setq bibus-query-offset 0) (erase-buffer) (bibus-insert-ref (bibus-fetch-reference bibus-query-sql)) (goto-char (point-min)) (move-to-column col))) ;;;_ . reference manage (defvar bibus-mark-char ?*) (defsubst bibus-ref-at-point () (get-text-property (point) 'reference)) (defun bibus-ref-id () "If mark some reference, return all reference id of marked reference, otherwise return current reference id." (let (ids) (if (bibus-has-marked-refp) (bibus-do-with-marked-ref (lambda () (setq ids (append ids (list (bibus-ref-field (bibus-ref-at-point) "Id")))))) (setq ids (list (bibus-ref-field (bibus-ref-at-point) "Id")))) ids)) (defun bibus-mark-ref (&optional arg) (interactive "P") (or arg (setq arg 1)) (let ((inhibit-read-only t)) (save-excursion (beginning-of-line) (while (and (not (eobp)) (> arg 0)) (delete-char 1) (insert (propertize (string bibus-mark-char) 'reference (bibus-ref-at-point))) (forward-line 1) (setq arg (1- arg)))))) (defun bibus-mark-ref-forward (arg) (interactive "P") (bibus-mark-ref arg) (next-line arg)) (defun bibus-mark-ref-all () (interactive) (save-excursion (goto-char (point-min)) (bibus-mark-ref (count-lines (point-min) (point-max))))) (defun bibus-unmark-ref-forward (arg) (interactive "P") (let ((inhibit-read-only t) (bibus-mark-char 32)) (bibus-mark-ref-forward arg))) (defun bibus-unmark-ref-all () (interactive) (let ((bibus-mark-char 32)) (bibus-do-with-marked-ref 'bibus-mark-ref))) (defsubst bibus-has-marked-refp () (save-excursion (goto-char (point-min)) (not (null (re-search-forward "^[*]" nil t))))) (defun bibus-do-with-marked-ref (func) (save-excursion (goto-char (point-min)) (while (re-search-forward "^[*]" nil t) (funcall func)))) (defun bibus-set-current-ref-key (key) "Set marked or current reference association key" (interactive (list (completing-read "Set this ref to key: " (append '("Tagged") (bibus-ref-keys))))) (let ((keyid (bibus-key-id key)) (refid (bibus-ref-field (bibus-ref-at-point) "Id"))) (when (and keyid refid) (mysql-query (format "insert ignore %s.%s values (%s, %s)" bibus-database (bibus-table 'reflink) keyid refid) bibus-sql-process)))) (defun bibus-set-ref-key (key) "Set marked or current reference association key" (interactive (list (completing-read (if (bibus-has-marked-refp) "Set MARKED ref to key: " "Set THIS ref to key: ") (append '("Tagged") (bibus-ref-keys))))) (let ((keyid (bibus-key-id key)) (refid (bibus-ref-id))) (when (and keyid refid) (mysql-query (format "insert ignore %s.%s values %s" bibus-database (bibus-table 'reflink) (mapconcat (lambda (id) (format "(%s, %s)" keyid id)) refid ",")) bibus-sql-process)))) (defun bibus-tag-ref () "Set marked or current reference with key \"Tagged\"" (interactive) (if (yes-or-no-p (if (bibus-has-marked-refp) "Tag MARKED ref " "Tag this ref")) (bibus-set-ref-key "Tagged"))) (defun bibus-delete-key-ref () "Delete marked or current reference from current key" (interactive) (let* ((select (bibus-selected-key-widget)) (keytag (widget-get select :tag)) keyid refid) (if (string= keytag "All") (message "Nothing to do in key \"All\".") (setq keyid (bibus-key-id keytag) refid (bibus-ref-id)) (when (and keyid refid (yes-or-no-p (if (bibus-has-marked-refp) "Delete MARKED references " "Delete THIS reference "))) (mysql-query (format "delete from %s.%s where key_Id=%s and ref_Id in (%s)" bibus-database (bibus-table 'reflink) keyid (mapconcat 'identity refid ",")) bibus-sql-process) (let ((inhibit-read-only t)) (if (bibus-has-marked-refp) (save-excursion (goto-char (point-min)) (flush-lines "^[*]")) (delete-region (line-beginning-position) (progn (forward-line) (point))))))))) (defun bibus-delete-ref () "Delete marked or current reference from database. This only can't preform when in key \"All\"." (interactive) (let* ((select (bibus-selected-key-widget)) (keytag (widget-get select :tag)) keyid refid) (if (not (string= keytag "All")) (message "Delete the reference from key \"All\".") (setq keyid (bibus-key-id keytag) refid (bibus-ref-id)) (when (and keyid refid (yes-or-no-p (if (bibus-has-marked-refp) "Really delete MARKED references from database " "Really delete THIS reference from database "))) (setq refid (mapconcat 'identity refid ",")) (mysql-query (concat (format "delete from %s.%s where ref_Id in (%s)" bibus-database (bibus-table 'reflink) refid) ";" (format "delete from %s.%s where Id in (%s)" bibus-database (bibus-table 'ref) refid)) bibus-sql-process) (let ((inhibit-read-only t)) (if (bibus-has-marked-refp) (save-excursion (goto-char (point-min)) (flush-lines "^[*]")) (delete-region (line-beginning-position) (progn (forward-line) (point))))))))) (defun bibus-browse-ref-url () (interactive) (let ((url (bibus-ref-field (bibus-ref-at-point) "URL"))) (browse-url url) (message "Browser url: %s" url))) (defun bibus-open-ref-file () "Open file according to `bibus-file-app'." (interactive) (let* ((reffile (expand-file-name (bibus-ref-field (bibus-ref-at-point) bibus-file-field-name))) (ext (file-name-extension reffile))) (when (and reffile (assoc ext bibus-file-app)) (message "Open file: %s" reffile) (start-process-shell-command "bibus-run" nil (format (assoc-default ext bibus-file-app) reffile))))) (defun bibus-update-ref (ref fields) "Update the reference in database according to FIELDS. FIELDS is a list as ((field content)...). " (mysql-query (format "update %s.%s set %s where Id=%s" bibus-database (bibus-table 'ref) (mapconcat (lambda (field) (format "%s=%s" (car field) (mysql-quote (cadr field)))) fields ", ") (bibus-ref-field ref "Id")) bibus-sql-process) (bibus-ref-update-line)) (defun bibus-ref-update-line () "Reset 'reference property" (with-current-buffer (bibus-ensure-buffer 'ref) (let ((ref (bibus-ref-at-point)) (inhibit-read-only t)) (setq ref (cadr (mysql-query (format "select * from %s.%s where Id=%s" bibus-database (bibus-table 'ref) (bibus-ref-field ref "Id")) bibus-sql-process))) (if ref (put-text-property (line-beginning-position) (line-end-position) 'reference ref) (error "The ref in line %d is not exists in database!" (line-number-at-pos)))))) (defun bibus-relocate-ref-file (file) (interactive (list (read-file-name "Set assoc file: " bibus-default-file-directory))) (when file (bibus-update-ref (bibus-ref-at-point) (list (list bibus-file-field-name file))))) (defun bibus-ref-assoc-keys () (interactive) (let ((keys (cdr (mysql-query (format (concat "SELECT refkey.key_name FROM %s.%s as refkey, %s.%s as reflink " "where reflink.key_Id=refkey.key_Id and reflink.ref_Id=%s") bibus-database (bibus-table 'refkey) bibus-database (bibus-table 'reflink) (bibus-ref-field (bibus-ref-at-point) "Id")) bibus-sql-process)))) (if keys (message "Keys: %s" (mapconcat 'car keys ", ") "")))) ;;;_ , key mode (defvar bibus-expert-query t "When non-nil, write query sql directly. Otherwise, use widget form to set up query sql(Not implement).") (defvar bibus-minibuffer-local-map (let ((map (make-sparse-keymap))) (set-keymap-parent map minibuffer-local-map) (define-key map "\t" 'bibus-complete-field) map)) (defun bibus-complete-field () (interactive) (let ((system-type 'cygwin) ; ignore case (comint-completion-addsuffix nil)) (comint-dynamic-simple-complete (current-word) (mapcar 'car bibus-ref-header)) (message nil))) ; we are in minibuffer (define-derived-mode bibus-key-mode nil "Key" "Major mode for display and manage reference keys. \\{bibus-key-mode-map}" (toggle-truncate-lines 1) (let ((inhibit-read-only t)) (erase-buffer)) (bibus-update-key-alist) (setq bibus-refkey (widget-create (append '(tree-widget :open t :tag "Bibus") (mapcar (lambda (row) (cond ((string= (cadr row) "References") `(tree-widget :open t :tag "References" :keyid ,(car row) :expander bibus-reference-keys)) ((string= (cadr row) "Query") `(tree-widget :open nil :keyid ,(car row) :tag "Query" :expander bibus-query-keys)) ((string= (cadr row) "All") `(bibus-key-widget :tag "All" :keyid ,(car row) :sql "Id > 0")) (t `(bibus-key-widget :tag ,(cadr row) :sql ,(string-to-number (car row)) :keyid ,(car row))))) (cdr (mysql-query (format "select key_Id, key_name from %s.%s where parent=1" bibus-database (bibus-table 'refkey)) bibus-sql-process)))))) (widget-setup) (goto-char (point-min))) (defun bibus-update-keys (name) "Redraw tree of References or Query" (interactive (list (completing-read "References or Query? " '("References" "Query")))) (when (member name '("References" "Query")) (with-current-buffer (bibus-ensure-buffer 'key) (let ((refkey (bibus-key-widget name))) (widget-put refkey :args nil) (when (widget-get refkey :open) (widget-apply refkey :action) (widget-apply refkey :action)))) (bibus-update-key-alist))) (defun bibus-add-key (name) (interactive "sNew KEY name: ") (let* ((refid (bibus-key-id "References")) (keys (mysql-col (mysql-query (format "select key_name from %s.%s where parent=%s" bibus-database (bibus-table 'refkey) refid) bibus-sql-process) 0))) (unless (or (string= name "") (member name keys)) (mysql-query (format "insert %s.%s (user, parent, key_name) values (\"root\", %s, \"%s\")" bibus-database (bibus-table 'refkey) refid name) bibus-sql-process) (bibus-update-keys "References") (bibus-display-key-refs (bibus-key-widget name "References"))))) (defun bibus-key-at-point () "Return the key widget at point" (let (key) (save-excursion (end-of-line) (while (progn (backward-char) (and (null (setq key (widget-at))) (> (point) (line-beginning-position)))))) (if (and key (eq (widget-type key) 'bibus-key-widget)) key))) (defun bibus-show-key () "Show keyid or query sql of current key" (interactive) (let* ((key (bibus-key-at-point)) (sql (widget-get key :sql))) (cond ((numberp sql) (message "Key id: %d" sql)) ((stringp sql) (message "Query sql: %s" sql))))) (defun bibus-delete-key () (interactive) (let ((key (bibus-key-at-point)) parent) (when key (setq parent (widget-get (widget-get key :parent) :tag)) (setq key (widget-get key :tag)) (cond ((string= parent "References") (when (yes-or-no-p (format "Delete KEY %s " key)) (let ((keyid (bibus-key-id key))) (when keyid (mysql-query (format "delete from %s.%s where key_Id=%s; delete from %s.%s where key_id=%s" bibus-database (bibus-table 'refkey) keyid bibus-database (bibus-table 'reflink) keyid) bibus-sql-process))) (bibus-update-keys "References"))) ((string= parent "Query") (when (yes-or-no-p (format "Delete QUERY %s" key)) (mysql-query (format "delete from %s.%s where name=%s" bibus-database (bibus-table 'query) (mysql-quote key)) bibus-sql-process) (bibus-update-keys "Query"))))))) (defun bibus-rename-key () (interactive) (let ((key (bibus-key-at-point)) keyname parent name) (when key (setq keyname (widget-get key :tag)) (setq name (read-from-minibuffer "Change name to: " keyname)) (when (not (string= name "")) (setq parent (widget-get (widget-get key :parent) :tag)) (cond ((string= parent "References") (let ((keyid (bibus-key-id keyname))) (when keyid (mysql-query (format "update %s.%s set key_name=%s where key_Id=%s" bibus-database (bibus-table 'refkey) (mysql-quote name) keyid) bibus-sql-process) (bibus-update-keys "References") (bibus-display-key-refs (bibus-key-widget name "References"))))) ((string= parent "Query") (progn (mysql-query (format "update ignore %s.%s set name=%s where name=%s" bibus-database (bibus-table 'query) (mysql-quote name) (mysql-quote keyname)) bibus-sql-process) (bibus-update-keys "Query") (bibus-display-key-refs (bibus-key-widget name "Query"))))))))) (defun bibus-query () "Query some reference." (interactive) (let (sql) (if bibus-expert-query (setq sql (read-from-minibuffer "Query: " nil bibus-minibuffer-local-map)) ) (when (and sql (not (string= sql ""))) (bibus-add-query-1 "*last-query*" sql)))) (defun bibus-edit-query () "Edit sql of query key at point" (interactive) (let ((key (bibus-key-at-point)) sql parent) (when key (setq parent (widget-get (widget-get key :parent) :tag)) (when (string= parent "Query") (setq sql (read-from-minibuffer "Query: " (widget-get key :sql) bibus-minibuffer-local-map)) (widget-put key :sql sql) (mysql-query (format "update %s.%s set query=%s where name=%s" bibus-database (bibus-table 'query) (mysql-quote sql) (mysql-quote (widget-get key :tag))) bibus-sql-process) (bibus-display-key-refs key))))) (defun bibus-name-last-query (name) "Give a name to last query" (interactive "sName last query to: ") (bibus-add-query-1 name (caadr (mysql-query (format "select query from %s.%s where name='*last-query*'" bibus-database (bibus-table 'query)) bibus-sql-process)))) (defun bibus-add-query () "Add a new query" (interactive) (let (name sql) (setq name (read-from-minibuffer "New QUERY name: ") sql (read-from-minibuffer "Query: " nil bibus-minibuffer-local-map)) (bibus-add-query-1 name sql))) (defun bibus-add-query-1 (name sql) (mysql-query (format "replace %s.%s (user, name, query) values (\"root\", %s, %s)" bibus-database (bibus-table 'query) (mysql-quote name) (mysql-quote sql)) bibus-sql-process) (if (string= name "*last-query*") (bibus-display-key-refs-1 sql) (bibus-update-keys "Query") (bibus-display-key-refs (bibus-key-widget name "Query")))) ;;;_ , entry mode (defvar bibus-current-ref nil "Current reference to display in entry buffer") (defvar bibus-entry-timer nil "Timer to update entry display") (defvar bibus-entry-edit-mode nil "When non-nil, means we are edit certain reference.") (defvar bibus-entry-header-width 15 "Header width of field in `bibus-entry-list-fields'") (defvar bibus-entry-not-fill-fields '("URL" "File") "All field when display in entry mode is filled. The field in this list will not be filled") (defvar bibus-entry-list-fields '( ;; I don't know why add this, sometimes emacs will lose control. ;; Is it possible query too frequence to mysql? ;; But i set mysql-finished to test whether last query is finished. ;; I don't know how to debug it. ;; ("Keys" (lambda () ;; (let ((keys (cdr ;; (mysql-query ;; (format (concat "SELECT refkey.key_name FROM %s.%s as refkey, %s.%s as reflink " ;; "where reflink.key_Id=refkey.key_Id and reflink.ref_Id=%s") ;; bibus-database (bibus-table 'refkey) ;; bibus-database (bibus-table 'reflink) ;; (bibus-ref-field bibus-current-ref "Id")) ;; bibus-sql-process)))) ;; (if keys (mapconcat 'car keys ", ") "")))) "Author" "Title" ("Journal" (lambda () (apply 'format (append '("%s. Vol %s: %s") (bibus-ref-field bibus-current-ref '("Journal" "Volume" "Pages")))))) ("Pub Date" (lambda () (mapconcat 'identity (bibus-ref-field bibus-current-ref '("Year" "Month")) " "))) "URL" ("File" (lambda () (bibus-ref-field bibus-current-ref bibus-file-field-name))) "Abstract") "Field to display in entry buffer. Can be a field name in reference table or a customized field with content given by a function.") (defvar bibus-entry-edit-fields '("BibliographicType" "Annote" "Author" "Journal" "Month" "Note" "Number" "Pages" "Title" "Report_Type" "Volume" "Year" "URL" "Custom1" "Custom2" "Custom3" "Custom4" "Custom5" "Abstract") "A list of field to edit") (defvar bibus-entry-display-intrival 0.3 "Second to update entry buffer") (define-derived-mode bibus-entry-mode nil "Entry" "Major mode for display reference entry. \\{bibus-entry-mode-map}" (make-local-variable 'bibus-current-ref) (if bibus-entry-timer (cancel-timer bibus-entry-timer)) (setq bibus-entry-timer (run-with-idle-timer bibus-entry-display-intrival t 'bibus-entry-display)) (setq truncate-lines t) (setq buffer-read-only t)) (defun bibus-entry-display () "Display reference. This is called by `bibus-entry-timer'." (when (not bibus-entry-edit-mode) (save-excursion (let ((prev (buffer-local-value 'bibus-current-ref (bibus-ensure-buffer 'entry))) (fill-prefix (make-string (+ bibus-entry-header-width 1) 32)) new) (set-buffer (bibus-ensure-buffer 'ref)) (setq new (bibus-ref-at-point)) (when (and new (not (eq prev new))) (set-buffer (bibus-buffer 'entry)) (setq bibus-current-ref new) (let ((inhibit-read-only t)) (erase-buffer) (dolist (field bibus-entry-list-fields) (let ((beg (point)) (frt (format "%%-%ds %%s\n" bibus-entry-header-width))) (insert (if (stringp field) (format frt (propertize field 'face 'bold) (bibus-ref-field new field)) (format frt (propertize (car field) 'face 'bold) (funcall (cadr field))))) (unless (member (if (stringp field) field (car field)) bibus-entry-not-fill-fields) (fill-region beg (point) nil t)))))))))) (defsubst bibus-kill-wid-variables () (mapc 'kill-local-variable '(widget-field-new widget-field-list before-change-functions after-change-functions help-button-cache))) (defun bibus-entry-edit () (interactive) (setq bibus-entry-edit-mode t) (setq truncate-lines nil) (switch-to-buffer (bibus-ensure-buffer 'entry)) (let ((ref bibus-current-ref)) (delete-other-windows) (bibus-kill-wid-variables) (let ((inhibit-read-only t)) (erase-buffer)) (remove-overlays) (setq buffer-read-only nil) (let ((frt (format "%%-%ds " bibus-entry-header-width))) (dolist (field bibus-entry-edit-fields) (widget-insert (format frt field)) (widget-create 'editable-field :header field :origin (bibus-ref-field ref field) (bibus-ref-field ref field)) (widget-insert "\n")) (widget-create 'push-button :notify (lambda (&rest ignore) (bibus-entry-edit-submit nil)) "Submit") (widget-create 'push-button :notify (lambda (&rest ignore) (bibus-entry-edit)) "Reset") (widget-create 'push-button :notify (lambda (&rest ignore) (bibus-entry-edit-submit t)) "Quit") (widget-setup) (goto-char (point-min))))) (defun bibus-entry-edit-restore () "Restore the value of current field." (interactive) (let ((wid (widget-at))) (when (eq (widget-type wid) 'editable-field) (widget-value-set (widget-get wid :origin)) (widget-setup)))) (defun bibus-entry-edit-submit (quit) "With prefix quit without make change in database. Otherwise, set the reference according to current buffer" (interactive "P") (when bibus-entry-edit-mode (when (or quit (when (yes-or-no-p "Set reference according to this buffer? ") (let (wid submit field) (dolist (wid widget-field-list) (when (setq field (widget-get wid :header)) (setq submit (append submit (list (list field (widget-value wid))))))) (bibus-update-ref bibus-current-ref submit) t))) (setq bibus-entry-edit-mode nil) (bibus-kill-wid-variables) (remove-overlays) (setq buffer-read-only t) (setq bibus-current-ref nil) ; force redisplay (bibus-entry-display) (bibus) (message "Finished")))) ;;;_. bibus start (defvar bibus-initialized nil) (defun bibus () (interactive) (unless (mysql-check-process bibus-sql-process) (if (and (processp bibus-sql-process) (not (eq (process-status bibus-sql-process) 'exit))) (mysql-disconnect bibus-sql-process)) (setq bibus-sql-process (mysql-connect))) (unless bibus-initialized (let ((refs (mysql-shell-query (format "select * from %s.%s limit 1" bibus-database (bibus-table 'ref))))) (setq bibus-ref-header (mysql-table-header refs))) (bibus-compile-format) (setq bibus-initialized t)) (dolist (buf bibus-buffer-names) (unless (bufferp (bibus-buffer (car buf))) (bibus-ensure-buffer (car buf)) (setq bibus-current-window-configuration nil))) (if bibus-current-window-configuration (set-window-configuration bibus-current-window-configuration) (delete-other-windows) (bibus-set-window-configuration bibus-window-configuration) (setq bibus-current-window-configuration (current-window-configuration)))) (defun bibus-reconnect () "If you encounter some error that can draw back, this command is to rescue." (interactive) (if (and (processp bibus-sql-process) (not (eq (process-status bibus-sql-process) 'exit))) (mysql-disconnect bibus-sql-process)) (setq bibus-sql-process nil) (mapc (lambda (buf) (if (buffer-live-p (bibus-buffer (car buf))) (kill-buffer (bibus-buffer (car buf))))) bibus-buffer-names) (bibus)) ;;; bibus.el ends here ;;;_. LocalVariable ;;; Local Variables: *** ;;; allout-layout:(1 : -1 -1 0) *** ;;; End: ***