;;; weekly-view.el --- bar graph view of the week's diary events ;; Copyright (C) 2003 Doug Alcorn ;; Author: Doug Alcorn ;; Keywords: calendar, diary, week, view ;; This program is NOT part of GNU Emacs ;; 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 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. ;;; Commentary: ;; Version: 2 ;; X-URL: http://www.lathi.net/twiki-bin/view/Main/EmacsWeeklyView ;; This package makes a bar graph of the week's diary events ;; ;; Usage: ;; (require 'weekly-view) ;; (add-hook 'diary-display-hook 'fancy-diary-display-week-graph) ;; Now when you view your diary it will display the weekly graph. ;;; History: ;; version 1: original code, mostly worked ;; version 2: patch from Nemeth Felician fixes lots of bugs ;;; Code: (require 'cal-desk-calendar) (define-key calendar-mode-map "w" 'week-graph-view-diary-entries) ;;; User Variables: (defcustom week-graph-work-week nil "*If non-nil, only the 5 workdays are displayed (instead of 7)." :type 'boolean) (defcustom week-graph-dynamic-width t "*If non-nil, the graph fully fills the window. If nil, `weekly-graph-day-width' is used." :type 'boolean) ;;; Compatibility: ;; ;; emacs doesn't have replace-in-string ;; from gnus-util.el (eval-and-compile (cond ((fboundp 'replace-in-string) (defalias 'weekly-view-replace-in-string 'replace-in-string)) ((fboundp 'replace-regexp-in-string) (defun replace-in-string (string regexp newtext &optional literal) (replace-regexp-in-string regexp newtext string nil literal))) (t (defun replace-in-string (string regexp newtext &optional literal) (let ((start 0) tail) (while (string-match regexp string start) (setq tail (- (length string) (match-end 0))) (setq string (replace-match newtext nil literal string)) (setq start (- (length string) tail)))) string)))) ;;;###autoload (defun week-graph-view-diary-entries () "Prepare and display a buffer with diary entries of the current week. See `view-diary-entries' for more." (interactive) (save-excursion (calendar-cursor-to-nearest-date) (let ((diary-display-hook 'fancy-diary-display-week-graph) (day (calendar-day-of-week (calendar-cursor-to-date)))) (unless (= day calendar-week-start-day) (calendar-beginning-of-week 1)) (view-diary-entries 7)))) (defun week-graph-convert-time (time) (let ((hour (/ time 100)) (mins (% time 100))) (+ (* hour 3600) (* mins 60)))) (defun weekly-graph-display-time-format (time) (format "%2d:%02d" (/ cur 3600) (% (/ cur 60) 60))) (defun week-graph-day-titles (&optional width) "Return of a vector whose members are the day names. If WIDTH is non-nil and non-zero, truncate the day name to that width." (mapcar (lambda (s) (let* ((extra (- width (length s))) (pre (/ extra 2)) (post (- extra pre))) (cond ((= extra 0) s) ((> extra 0) (format (format "%%%ds%%%ds" (- width post) post) s "")) (t (substring s 0 width))))) calendar-day-name-array)) (defvar weekly-graph-day-width 10 "Width of the each day in the bar graph") (defvar weekly-graph-day-start 800 "Time to start each day's view in military style integers (i.e. \"8:00am\" is 800)") (defvar weekly-graph-day-end 1800 "Time to end each day's view in military style integers (i.e. \"8:00am\" is 800)") (defvar weekly-graph-increment 30 "Increment in minutes for each row on the weekly graph of diary entries") (defvar weekly-graph-use-hash-marks t "If non-nill use hash marks '\#' to graph events rather than pertinent event text") (defvar weekly-graph-default-event-length 60 "Length of events that don't specify an end time in minutes.") (defvar diary-time-regexp-list `( "^[ ]*\\([0-9]?[0-9]\\)-\\([0-9]?[0-9]\\)\\([ap]\\)m\\>" ; HH-HH[ap]m "^[ ]*\\([0-9]?[0-9]\\):?\\([0-9][0-9]\\)-\\([0-9]?[0-9]\\):?\\([0-9][0-9]\\)\\(\\|[^ap]\\)" ; military range "^[ ]*\\([0-9]?[0-9]\\):?\\([0-9][0-9]\\)\\(\\>\\|[^ap]\\)" ; military time "^[ ]*\\([0-9]?[0-9]\\):\\([0-9][0-9]\\)-\\([0-9]?[0-9]\\):\\([0-9][0-9]\\)\\([ap]\\)m\\>" ; hh:mm range "^[ ]*\\([0-9]?[0-9]\\):\\([0-9][0-9]\\)\\([ap]\\)m-\\([0-9]?[0-9]\\):\\([0-9][0-9]\\)\\([ap]\\)m\\>" "^[ ]*\\([0-9]?[0-9]\\):\\([0-9][0-9]\\)\\([ap]\\)m-\\([0-9]?[0-9]\\):\\([0-9][0-9]\\)\\([ap]\\)m\\>" ; h/m range "^[ ]*\\([0-9]?[0-9]\\):\\([0-9][0-9]\\)\\([ap]\\)m\\>" ; hh:mm "^[ ]*\\([0-9]?[0-9]\\)\\([ap]\\)m-\\([0-9]?[0-9]\\)\\([ap]\\)m\\>" ; HH[ap]m-HH[ap]m "^[ ]*\\([0-9]?[0-9]\\)\\([ap]\\)m\\>" ; hour only ,diary-schedule-sunrise-sunset-pattern ,diary-schedule-lunar-phase-pattern ,diary-schedule-equinox-solstice-pattern ,diary-schedule-morning-pattern ;,diary-schedule-afternoon-patern ,diary-schedule-workday-pattern ,diary-schedule-all-day-pattern)) (defun fancy-diary-display-week-graph () "Given a set of appointment DATA, draw a weekly bar graph of it. DATA must be a vector of seven elements (for each day of the week), where each element is a list of cons cells representing the beginning and ending seconds of each appointment within the day, where midnight begins at 0 seconds. For example, an appointment for one hour on Monday at 10am would look like: [nil ((36000 . 39600)) nil nil nil nil nil]" (save-excursion ;; Prepare the fancy diary buffer. ;; setup (message "Preparing diary...") (set-buffer (get-buffer-create fancy-diary-buffer)) (setq buffer-read-only nil) (make-local-variable 'mode-line-format) (calendar-set-mode-line " Desk Calendar ") (erase-buffer) ;; get ready to insert schedule (let* ((num-days (if week-graph-work-week 5 7)) (day-width (if week-graph-dynamic-width (/ (- (window-width) 7 num-days) num-days) weekly-graph-day-width)) (begin (week-graph-convert-time weekly-graph-day-start)) (end (week-graph-convert-time weekly-graph-day-end)) (incr (* weekly-graph-increment 60)) (cur begin) (day-titles (vconcat (week-graph-day-titles day-width))) (data (make-vector 7 nil))) ;; "cook" the diary-entries-list into a more regular form (dolist (elem diary-entries-list) (let* ((dow (calendar-day-of-week (car elem))) (day-of-week-ref (aref data dow)) (entry-times (diary-entry-times (cadr elem))) (entry-text (cadr elem)) (beginning-time (week-graph-convert-time (car entry-times))) (ending-time (week-graph-convert-time (cadr entry-times))) entry-alist) (if (= beginning-time ending-time) (setq ending-time (+ ending-time (* weekly-graph-default-event-length 60)))) (mapcar (lambda (regexp) (setq entry-text (replace-in-string entry-text regexp ""))) diary-time-regexp-list) (setq entry-text (replace-in-string entry-text "^[ ]+" "")) (setq entry-text (replace-in-string entry-text "[ \t\n]+" " ")) (setq entry-text (replace-in-string entry-text "[ \t]+$" "")) (setq entry-plist `(:beginning ,beginning-time :ending ,ending-time :text ,entry-text :marker ,(nth 3 elem))) (unless (= (car entry-times) -9999) (if day-of-week-ref (nconc day-of-week-ref (list entry-plist)) (aset data dow (list entry-plist)))))) ;; insert day of week headers (dotimes (j 7) (if (= j 0) (insert " ")) (setq i (% (+ j calendar-week-start-day) 7)) (unless (and week-graph-work-week (or (= i 0) (= i 6))) (insert "|" (aref day-titles i)))) (insert "|\n" (make-string (+ 6 (* day-width num-days) num-days) ?-) ?\n) ;; now handle entries (while (< cur end) (dotimes (j 7) (setq i (% (+ j calendar-week-start-day) 7)) ;; insert time on lieft (if (= j 0) (insert (weekly-graph-display-time-format cur))) (unless (and week-graph-work-week (or (= i 0) (= i 6))) (insert "|") (let ((day-of-week-list (aref data i)) (print-text nil) (print-marker nil)) (setq day-of-week-list (mapcar (lambda (appt) (let* ((appt-text (plist-get appt :text)) (beginning (plist-get appt :beginning)) (ending (plist-get appt :ending)) (times-printed (plist-get appt :times-printed)) (start (if times-printed (* times-printed day-width) 0)) (end (if times-printed (* (1+ times-printed) day-width) day-width))) (message "times printed: %s start: %s end: %s appt: %s" times-printed start end appt-text) (if (and (>= cur beginning) (< cur ending)) (progn (unless times-printed (setq print-marker (plist-get appt :marker))) (setq print-text (if (> (length appt-text) end) (substring appt-text start end) (if (> start (length appt-text)) (make-string day-width ?#) (concat (substring appt-text start (length appt-text)) (make-string (- day-width (- (length appt-text) start)) ?#))))) (setq appt (plist-put appt :times-printed (if times-printed (1+ times-printed) 1)))) ;(setq print-text nil) ) appt)) day-of-week-list)) (aset data i day-of-week-list) (if print-text (if print-marker (insert-button print-text 'marker print-marker :type 'diary-entry) (insert print-text)) (insert (make-string day-width ? )))))) (insert "|\n") (setq cur (+ cur incr))) (insert (make-string (+ 6 (* day-width num-days) num-days) ?-) ?\n)) ;; clean-up (set-buffer-modified-p nil) (setq buffer-read-only t) (display-buffer fancy-diary-buffer) (message "Preparing diary...done"))) (provide 'weekly-view) ;;; weekly-view.el ends here