🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.emacs
Article #8254

Re: New calendar window

#8254
From: reingold
Date: Thu, 05 Jan 1989 04:11
364 lines
16270 bytes
Thanks to all the many folks who responded to my request!  A number of bugs
were uncovered and suggestions made.  Here is a MUCH improved version that
includes proper marking of the current date in the window (even when it's
not the middle month), friendlier window treatment (i hope!), it's own major
mode so as not to screw up key bindings, and the ability to accept months
and years (directly) for display with the o command.

Again, I'd be grateful to anyone willing to play with it for a while to find
bugs or make suggestions for improvements.

----------------------------------------------------------------------------
;; Record version number of Emacs.
;; Copyright (C) 1988, 1989 Free Software Foundation, Inc.

;; This file is part of GNU Emacs.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.
;;
;; Comments, corrections, and improvements should be sent to
;;  Edward M. Reingold               Department of Computer Science
;;  (217) 333-6733                   University of Illinois at Urbana-Champaign
;;  reingold@a.cs.uiuc.edu           1304 West Springfield Avenue
;;                                   Urbana, Illinois 61801
;;
;; The author gratefully acknowledges the patient help of Richard Stallman
;; in making this function into a reasonable piece of code!
;;
;; Modification for month-offset arguments suggested and implemented by
;;  Constantine Rasmussen            Sun Microsystems, East Coast Division
;;  (617) 671-0404                   2 Federal Street;  Billerica, Ma.  01824
;;  ARPA: cdr@sun.com   USENET: {cbosgd,decvax,hplabs,seismo}!sun!suneast!cdr
;;
;; Modification to mark current day with stars suggested by
;;  Franklin Davis		     Thinking Machines Corp
;;  (617) 876-1111                   245 First Street, Cambridge, MA  02142
;;  fad@think.com
;;
;; Minor corrections made and code added for an 'infinite' calendar window
;; by E.M.R., January 4, 1989.  GNU Emacs users to numerous to list pointed
;; out a variety of problems with an earlier form of the 'infinite' calendar.
;;

(defvar today-visible-calendar-hook nil
  "List of functions called after the calendar buffer has been prepared
with the calendar when the current date is visible in the window.  This can
be used, for example, to highlight today's date with asterisks--a function
star-date is included for this purpose.  The variable
today-invisible-calendar-hook is the list of functions called when the
calendar function was called when the current date is not visible in the
window.")

(defvar today-invisible-calendar-hook nil
  "List of functions called after the calendar buffer has been prepared
with the calendar when the current date is not visible in the window.  The
variable today-visible-calendar-hook is the list of functions called when
the calendar function was called when the current date is visible in the
window.")

(defun calendar-help ()
  "Give a description of key-bindings in the calendar window."
  (interactive)
  (message
   ". backward a month  , forward a month  c current month  o other month  e exit"))

(defun calendar (&optional month-offset)
  "Display a three-month calendar in another window.
The three months appear side by side, with the current month in the middle
surrounded by the previous and next months.  The cursor is put on today's date.
Future months can be moved into view with ','; prior months can be moved into
view with '.'.

An optional prefix argument MONTH-OFFSET causes the calendar displayed to
be MONTH-OFFSET months in the future if MONTH-OFFSET is positive or in the
past if MONTH-OFFSET is negative; in this case the cursor goes on the first
day of the month.

The Gregorian calendar is assumed.

After preparing the calendar window, the hooks today-visible-calendar-hook
are run when the current date is visible in the window.  If it is not
visible, the hooks today-invisible-calendar-hook are run.  Thus, for
example, setting today-visible-calendar-hook to 'star-date will cause
today's date to be replaced by asterisks to highlight it in the window."

  (interactive "P")
  (let ((today (make-marker)))
    (save-excursion
      (set-buffer (get-buffer-create "*Calendar*"))
      (calendar-mode)
      (setq calendar-entry-configuration (current-window-configuration))
      (let* ((buffer-read-only nil)
             ;; Get today's date and extract the day, month and year.
             (date (current-time-string))
             (garbage (string-match
                       "^\\([A-Z][a-z]*\\) *\\([A-Z][a-z]*\\) *\\([0-9]*\\) .* \\([0-9]*\\)$"
                       date))
             (day-in-the-week
                (substring date (match-beginning 1) (match-end 1)))
             (month (substring date (match-beginning 2) (match-end 2)))
             (day-in-the-month
                (substring date (match-beginning 3) (match-end 3)))
             (year (substring date (match-beginning 4) (match-end 4)))
             (date-string
                (concat day-in-the-week ", "
                        month " " day-in-the-month ", " year)))
        (setq mode-line-format
              (format
                 "--period->   Calendar   e exit/o other/c current  %17s   <-comma--"
                 date-string))
        (erase-buffer)
        (setq current-month
              (cdr (assoc month
                        '(("Jan" . 1) ("Feb" . 2)  ("Mar" . 3)  ("Apr" . 4)
                         ("May" . 5) ("Jun" . 6)  ("Jul" . 7)  ("Aug" . 8)
                         ("Sep" . 9) ("Oct" . 10) ("Nov" . 11) ("Dec" . 12)))))
        (setq current-year (string-to-int year))
        (setq displayed-month current-month)
        (setq displayed-year current-year)
        (setq month-offset
              (if month-offset
                  (prefix-numeric-value month-offset)
                  0))
        (increment-month 'displayed-month 'displayed-year month-offset)
        (let ((i-month displayed-month)
              (i-year displayed-year)
              (i -1)
              (day (if (and (> 2  month-offset)
                            (< -2 month-offset))
                       (string-to-int day-in-the-month)))
              (c-month (1+ (interval displayed-month displayed-year
                                     current-month current-year))))
          (increment-month 'i-month 'i-year -2)
          ;; Generate the three-month window.
          (while (>= 2 (setq i (1+ i)))
            (increment-month 'i-month 'i-year 1)
            ;; Generate the month--record where today's date appears
            ;; in the marker TODAY.
            (if (= i c-month)
                (set-marker today
                            (generate-month i-month i-year (* 24 i) day))
                (generate-month i-month i-year (* 24 i)))))))
    ;; Display the buffer and put cursor on today's date.  Do it in another
    ;; window, but if this buffer is already visible, just select its window.
    (pop-to-buffer "*Calendar*")
    (goto-char (or (marker-position today)
                   (point-min)))
    ;; Make TODAY point nowhere so it won't slow down buffer editing until GC.
    (set-marker today nil))
  (or (one-window-p t)
      (shrink-window (- (window-height) 9)))
  (if (or (< 2  month-offset)
          (> -2 month-offset))
      (run-hooks 'today-invisible-calendar-hook)
      (run-hooks 'today-visible-calendar-hook)))

(defvar calendar-mode-map nil)

(if calendar-mode-map
    nil
    (setq calendar-mode-map (make-sparse-keymap))
    (define-key calendar-mode-map "." 'backward-month)
    (define-key calendar-mode-map "," 'forward-month)
    (define-key calendar-mode-map "c" 'show-current-month)
    (define-key calendar-mode-map "o" 'show-other-month)
    (define-key calendar-mode-map "e" 'exit-calendar)
    (define-key calendar-mode-map "?" 'calendar-help))

(defun calendar-mode ()
  "A major mode for the calendar window."
  (kill-all-local-variables)
  (setq major-mode 'calendar-mode)
  (setq mode-name "Calendar")
  (use-local-map calendar-mode-map)
  (setq buffer-read-only t)
  (make-local-variable 'calendar-entry-configuration)
  (make-local-variable 'mode-line-format)
  (make-local-variable 'current-month)  ;;  Current month.
  (make-local-variable 'current-year)   ;;  Current year.
  (make-local-variable 'displayed-month);;  Month in middle of window.
  (make-local-variable 'displayed-year));;  Year in middle of window.

(defun increment-month (mon yr n)
  "Move the variables MON and YR to the month and year N months forward
if N is positive or backward if N is negative."
  (let ((y (+ (+ (* (eval yr) 12) (- (eval mon) 1)) n)))
    (set mon (+ (% y 12) 1))
    (set yr (/ y 12))))

(defun exit-calendar ()
  "Get out of the calendar window and destroy it."
  (interactive)
  (set-window-configuration calendar-entry-configuration)
  (kill-buffer "*Calendar*"))

(defun show-current-month ()
  "Reposition the calendar window so the original request is visible."
  (interactive)
  (calendar))

(defun forward-month (&optional arg)
  "Advance the displayed calendar window by one month.
An optional prefix argument ARG causes the calendar to be advanced by
ARG months if ARG is positive or to be moved backward if ARG is negative."
  (interactive "p")
  (calendar (+ arg 
               (interval current-month current-year
                         displayed-month displayed-year))))

(defun backward-month (&optional arg)
  "Move the displayed calendar window backward by one month.
An optional prefix argument ARG causes the calendar to be move backward
by ARG months if ARG is positive or to be advanced if ARG is negative."
  (interactive "p")
  (forward-month (- arg)))

(defun show-other-month (month year)
  "Display a three-month calendar centered around MONTH and YEAR."
  (interactive "nMonth (1-12): \nnYear (>0): ")
  (if (or (< 12 month) (> 1 month) (> 1 year))
      (error "Unintelligible month/year!"))
  (calendar (interval current-month current-year month year)))

(defun last-line-p ()
  "Returns true if point is on the last line of the buffer."
  (save-excursion
    (end-of-line)
    (eobp)))

(defun interval (mon1 yr1 mon2 yr2)
  "The number of months difference between the two specified months."
  (+ (* 12 (- yr2 yr1))
     (- mon2 mon1)))

(defun leap-year-p (year)
  "Returns true if YEAR is a Gregorian leap year, and false if not."
  (or
    (and (=  (% year   4) 0)
         (/= (% year 100) 0))
    (= (% year 400) 0)))

(defun day-number (month day year)
  "Return day-number within year (origin-1) of the date MONTH DAY YEAR.
For example, (day-number 1 1 1987) returns the value 1,
while (day-number 12 31 1980) returns 366."
;;
;; An explanation of the calculation can be found in PascAlgorithms by
;; Edward and Ruth Reingold, Scott-foresman/Little, Brown, 1988.
;;
  (let ((day-of-year (+ day (* 31 (1- month)))))
    (if (> month 2)
        (progn
          (setq day-of-year (- day-of-year (/ (+ 23 (* 4 month)) 10)))
          (if (leap-year-p year)
              (setq day-of-year (1+ day-of-year)))))
    day-of-year))

(defun day-of-week (month day year)
  "Returns the day-of-the-week index of MONTH DAY, YEAR.
Value is 0 for Sunday, 1 for Monday, etc."
;;
;; Done by calculating the number of days elapsed since the (imaginary)
;; Gregorian date Sunday, December 31, 1 BC and taking that number mod 7.
;;
  (%
    (-
      (+ (day-number month day year)
         (* 365 (1- year))
         (/ (1- year) 4))
      (let ((correction (* (/ (1- year) 100) 3)))
        (if (= (% correction 4) 0)
            (/ correction 4)
            (1+ (/ correction 4)))))
    7))

(defun generate-month (month year indent &optional day)
  "Produce a calendar for MONTH, YEAR on the Gregorian calendar, inserted
in the buffer starting at the line on which point is currently located, but
indented INDENT spaces.  The position in the buffer of the optional
parameter DAY is returned.  The indentation is done from the first
character on the line and does not disturb the first INDENT characters on
the line.

Each month is 7 days wide and 6 weeks high and is followed by 4 spaces."

  (let* ((first-day-of-month (day-of-week month 1 year))
         (first-saturday (- 7 first-day-of-month))
         (last-of-month
           (if (and (leap-year-p year) (= month 2))
               29
               (aref [31 28 31 30 31 30 31 31 30 31 30 31] (1- month))))
         (month-name
           (aref ["January" "February" "March" "April" "May" "June"
                  "July" "August" "September" "October" "November" "December"]
                  (1- month)))
         (buffer-read-only nil))
    (save-excursion
      (goto-char (point-min))
      (let ((title-line (format "   %s %d" month-name year)))
        (insert-indented  ;;   Force title line to be correct width.
         (format
          (concat "%" (int-to-string (- 24 (length title-line))) "s") "")
         indent)
        (insert-indented title-line indent t))
      (insert-indented " S  M Tu  W Th  F  S    " indent t)
      (insert-indented "" indent);; Move point to appropriate spot on line.
        (let ((i (- first-day-of-month))
            (day-marker))
          (while (<= (setq i (1+ i)) 42)       ;; Put in the days of the month.
            (if (and (<= 1 i) (>= last-of-month i))
                (insert (format "%2d " i))
                (insert "   "))
            (and day (= i day)  ;; Save the location of the specified day.
                 (setq day-marker (- (point) 2)))
            (and (= (% i 7) (% first-saturday 7))
                 (progn (insert "   ") t)        ;; Separate from next month.
                 (insert-indented "" 0 t)        ;; Force onto following line.
                 (insert-indented "" indent)))   ;; Go to proper spot on line.
          (set-buffer-modified-p nil)
          day-marker))))


(defun insert-indented (string indent &optional newline)
  "Insert STRING at column INDENT.
If the optional parameter NEWLINE is true, leave point at start of next
line, inserting a newline if there was no next line; otherwise, leave point
after the inserted text.  Value is always `t'."
  ;; Try to move to that column.
  (move-to-column indent)
  ;; If line is too short, indent out to that column.
  (if (< (current-column) indent)
      (indent-to indent))
  (insert string)
  ;; Advance to next line, if requested.
  (if newline
      (progn
	(end-of-line)
	(if (eobp)
            (newline)
	  (forward-line 1))))
  t)

(defun star-date ()
  "Replace today's date with asterisks in the calendar window.
This function can be used with the calendar-hook run after the
calendar window has been prepared."
  (let ((buffer-read-only nil))
    (forward-char 1)
    (delete-backward-char 2)
    (insert "**")
    (backward-char 1)))
Technical Details
Message-ID: <4300029@m.cs.uiuc.edu>
Path: arc1-ca.pugleaf.net!utzoo!attcan!uunet!lll-winken!ames!mailrus!csd4.milw.wisc.edu!uxc!uxc.cso.uiuc.edu!m.cs.uiuc.edu!reingold
References: <4300028@m.cs.uiuc.edu>

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!utzoo!attcan!uunet!lll-winken!ames!mailrus!csd4.milw.wisc.edu!uxc!uxc.cso.uiuc.edu!m.cs.uiuc.edu!reingold
From: reingold@m.cs.uiuc.edu
Newsgroups: comp.emacs
Subject: Re: New calendar window
Message-ID: <4300029@m.cs.uiuc.edu>
Date: 5 Jan 89 04:11:00 GMT
References: <4300028@m.cs.uiuc.edu>
Lines: 364
Nf-ID: #R:m.cs.uiuc.edu:4300028:m.cs.uiuc.edu:4300029:000:15466
Nf-From: m.cs.uiuc.edu!reingold    Jan  4 22:11:00 1989