#!/bin/sh exec rep "$0" "$@" # -*- Sawfish -*- ``rep gtk note'' or ``Roolly Good Note'' -- a single sticky note (2001-10-02) !# (structure () (open rep rep.io.timers rep.io.files rep.io.processes gui.gtk) ;; options (define *save-delay-period* 10) (define *save-file* "~/.rgnote.txt") ;; lock file (define (make-lock-file-name filename) (concat filename ".lock")) (define (lock-file filename) (let ((lock (make-lock-file-name filename))) (if (file-exists-p lock) nil (let ((file (open-file lock 'write))) (write file (number->string (process-id))) (close-file file) t)))) (define (unlock-file filename) (let ((lock (make-lock-file-name filename))) (if (not (file-exists-p lock)) nil (let* ((file (open-file lock 'read)) (pid (read-line file))) (close-file file) (if (not (= (string->number pid) (process-id))) nil (delete-file lock) t))))) ;; main stuff (define (read-all-lines stream) (let loop ((output nil) (line (read-line stream))) (if (not line) (reverse output) (loop (cons line output) (read-line stream))))) (define (make-saver saver-proc timer-period) (let ((save-timer (make-timer saver-proc)) (need-save nil)) (lambda (action) (case action ((update-timer) (setq need-save t) (set-timer save-timer timer-period 0)) ((save-now) (when need-save (delete-timer save-timer) (setq need-save nil) (saver-proc))) (else (error "unknown action")))))) (define (main-window) (let ((window (gtk-window-new 'toplevel)) (text (gtk-text-new))) (gtk-window-set-title window "rgnote") (gtk-window-set-wmclass window "rgnote" "rgnote") (gtk-container-add window text) (gtk-text-set-editable text t) (gtk-text-set-word-wrap text t) (define (saver-proc) (if (file-exists-p *save-file*) (rename-file *save-file* (concat *save-file* "~"))) (let ((file (open-file *save-file* 'write)) (chars (gtk-editable-get-chars text 0 (gtk-text-get-length text)))) (write file chars) (if (not (eq #\newline (aref chars (1- (length chars))))) (write file #\newline)) (close-file file))) (let ((saver (make-saver saver-proc *save-delay-period*))) (gtk-signal-connect text "changed" (lambda () (saver 'update-timer))) (gtk-signal-connect window "destroy" (lambda () (saver 'save-now) (gtk-main-quit)))) ;; XXX bad place to put this? (if (file-exists-p *save-file*) (let* ((file (open-file *save-file* 'read)) (chars (apply concat (read-all-lines file)))) (gtk-text-insert text nil nil nil chars (length chars)) (close-file file))) (gtk-widget-show-all window) (gtk-main))) ;; confirm dialog (define (ask-y-or-n message default) (catch 'done (let ((window (gtk-window-new 'dialog))) (unwind-protect (let ((vbox (gtk-vbox-new t 10)) (label (gtk-label-new message)) (hbox (gtk-hbox-new t 10)) (yes (gtk-button-new-with-label "Yes")) (no (gtk-button-new-with-label "No"))) (gtk-container-border-width window 10) (gtk-container-add window vbox) (gtk-box-pack-start vbox label) (gtk-box-pack-end vbox hbox) (gtk-box-pack-start hbox yes) (gtk-box-pack-end hbox no) (gtk-signal-connect yes "clicked" (lambda () (throw 'done t))) (gtk-signal-connect no "clicked" (lambda () (throw 'done nil))) (gtk-signal-connect window "destroy" (lambda () (throw 'done default))) (gtk-widget-show-all window) (gtk-main)) ;; cleanup (if (gtk-object-p window) (gtk-widget-hide window)))))) ;; entry point (cond ((lock-file *save-file*) (unwind-protect (main-window) (unlock-file *save-file*))) ((ask-y-or-n "Could not lock the notes file. Continue anyway?" nil) (main-window))))