]> code.delx.au - gnu-emacs-elpa/blob - packages/svg-clock/svg-clock.el
3c9f208edbacad0efdc73173799f765325ccb71d
[gnu-emacs-elpa] / packages / svg-clock / svg-clock.el
1 ;;; svg-clock.el --- Analog clock using Scalable Vector Graphics
2
3 ;; Copyright (C) 2011 Free Software Foundation, Inc.
4
5 ;; Author: Ulf Jasper <ulf.jasper@web.de>
6 ;; Created: 22. Sep. 2011
7 ;; Keywords: demo, svg, clock
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
26 ;; svg-clock provides a scalable analog clock. Rendering is done by
27 ;; means of svg (Scalable Vector Graphics). Works only with Emacsen
28 ;; which were built with svg support -- (image-type-available-p 'svg)
29 ;; must return t. Call `svg-clock' to start/stop the clock.
30 ;; Set `svg-clock-size' to change its size.
31
32 ;; Installation
33 ;; ------------
34
35 ;; Add the following lines to your Emacs startup file (`~/.emacs').
36 ;; (add-to-list 'load-path "/path/to/svg-clock.el")
37 ;; (autoload 'svg-clock "svg-clock" "Start/stop svg-clock" t)
38
39 ;; ======================================================================
40
41 ;;; History:
42
43 ;; 0.2 (2011-09-26)
44 ;; - Added automatic resizing. One clock fits all.
45
46 ;; 0.1 (2011-09-22)
47 ;; - Initial release.
48
49 ;;; Code:
50 (defconst svg-clock-version "0.2" "Version number of `svg-clock'.")
51
52 (require 'image-mode)
53
54 (defgroup svg-clock nil
55 "svg-clock"
56 :group 'applications)
57
58 (defcustom svg-clock-size t
59 "Size (width and height) of the clock.
60 Either an integer which gives the clock size in pixels, or t
61 which makes the clock fit to its window automatically."
62 :type '(choice (integer :tag "Fixed Size" :value 250)
63 (const :tag "Fit to window" t))
64 :group 'svg-clock)
65
66 (defvar svg-clock-timer nil)
67
68 (defconst svg-clock-template
69 "<?xml version=\"1.0\"?>
70 <!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"
71 \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">
72 <svg xmlns=\"http://www.w3.org/2000/svg\"
73 width=\"%SIZE%\" height=\"%SIZE%\" >
74 <defs>
75 <symbol id=\"tick\">
76 <line x1=\"50\" y1=\"2\" x2=\"50\" y2=\"4\"
77 style=\"stroke:%FG%;stroke-width:1\"/>
78 </symbol>
79 <symbol id=\"ticklong\">
80 <line x1=\"50\" y1=\"2\" x2=\"50\" y2=\"9\"
81 style=\"stroke:%FG%;stroke-width:2\"/>
82 </symbol>
83 <symbol id=\"hand-hour\">
84 <line x1=\"50\" y1=\"22\" x2=\"50\" y2=\"54\"
85 style=\"stroke:%FG%;stroke-width:3\"/>
86 </symbol>
87 <symbol id=\"hand-minute\">
88 <line x1=\"50\" y1=\"12\" x2=\"50\" y2=\"55\"
89 style=\"stroke:%FG%;stroke-width:3\"/>
90 </symbol>
91 <symbol id=\"hand-second\">
92 <line x1=\"50\" y1=\"10\" x2=\"50\" y2=\"59\"
93 style=\"stroke:%FG%;stroke-width:0.5\"/>
94 </symbol>
95 <g id=\"minute-ticks-a\">
96 <use xlink:href=\"#tick\"
97 transform=\"rotate(6, 50, 50)\" />
98 <use xlink:href=\"#tick\"
99 transform=\"rotate(12, 50, 50)\" />
100 <use xlink:href=\"#tick\"
101 transform=\"rotate(18, 50, 50)\" />
102 <use xlink:href=\"#tick\"
103 transform=\"rotate(24, 50, 50)\" />
104 </g>
105 <g id=\"minute-ticks-b\">
106 <use xlink:href=\"#minute-ticks-a\"
107 transform=\"rotate(0, 50, 50)\" />
108 <use xlink:href=\"#minute-ticks-a\"
109 transform=\"rotate(30, 50, 50)\" />
110 <use xlink:href=\"#minute-ticks-a\"
111 transform=\"rotate(60, 50, 50)\" />
112 <use xlink:href=\"#minute-ticks-a\"
113 transform=\"rotate(90, 50, 50)\" />
114 <use xlink:href=\"#minute-ticks-a\"
115 transform=\"rotate(120, 50, 50)\" />
116 <use xlink:href=\"#minute-ticks-a\"
117 transform=\"rotate(150, 50, 50)\" />
118 </g>
119
120 <g id=\"5-minute-ticks\">
121 <use xlink:href=\"#ticklong\" />
122 <use xlink:href=\"#ticklong\" transform=\"rotate(30, 50, 50)\" />
123 <use xlink:href=\"#ticklong\" transform=\"rotate(60, 50, 50)\" />
124 </g>
125
126 <g id=\"clock\">
127 <use xlink:href=\"#5-minute-ticks\"
128 transform=\"rotate(0, 50, 50)\" />
129 <use xlink:href=\"#5-minute-ticks\"
130 transform=\"rotate(90, 50, 50)\" />
131 <use xlink:href=\"#5-minute-ticks\"
132 transform=\"rotate(180, 50, 50)\" />
133 <use xlink:href=\"#5-minute-ticks\"
134 transform=\"rotate(270, 50, 50)\" />
135
136 <use xlink:href=\"#minute-ticks-b\"
137 transform=\"rotate(0, 50, 50)\" />
138 <use xlink:href=\"#minute-ticks-b\"
139 transform=\"rotate(180, 50, 50)\" />
140
141 <use xlink:href=\"#hand-second\"
142 transform=\"rotate(%SECOND%, 50, 50)\">
143 </use>
144 <use xlink:href=\"#hand-minute\"
145 transform=\"rotate(%MINUTE%, 50, 50)\">
146 </use>
147 <use xlink:href=\"#hand-hour\"
148 transform=\"rotate(%HOUR%, 50, 50)\">
149 </use>
150
151 <circle cx=\"50\" cy=\"50\" r=\"3\" fill=\"%FG%\"/>
152 </g>
153 </defs>
154 <use xlink:href=\"#clock\"
155 transform=\"scale(%SCALE%, %SCALE%)\"/>
156 </svg>"
157 "The template for drawing the `svg-clock'.")
158
159 (defvar svg-clock--actual-size 100
160 "Actual size of the svg clock.")
161
162 (defun svg-clock-color-to-hex (colour)
163 "Return hex representation of COLOUR."
164 (let ((values (color-values colour)))
165 (format "#%04x%04x%04x" (nth 0 values) (nth 1 values) (nth 2 values))))
166
167 (defun svg-clock-replace (from to)
168 "Replace all occurrences of FROM with TO."
169 (goto-char (point-min))
170 (while (re-search-forward from nil t)
171 (replace-match to)))
172
173 (defun svg-clock-do-update (time)
174 "Make the clock display TIME.
175 TIME must have the form (SECOND MINUTE HOUR ...), as returned by `decode-time'."
176 (with-current-buffer (get-buffer-create "*clock*")
177 (let* ((inhibit-read-only t)
178 (seconds (nth 0 time))
179 (minutes (nth 1 time))
180 (hours (nth 2 time))
181 (bg-colour (svg-clock-color-to-hex (face-background 'default)))
182 (fg-colour (svg-clock-color-to-hex (face-foreground 'default))))
183 (erase-buffer)
184 (insert svg-clock-template)
185
186 (svg-clock-replace "%BG%" bg-colour)
187 (svg-clock-replace "%FG%" fg-colour)
188 (svg-clock-replace "%HOUR%"
189 (format "%f" (+ (* hours 30) (/ minutes 2.0))))
190 (svg-clock-replace "%MINUTE%"
191 (format "%f" (+ (* minutes 6) (/ seconds 10.0))))
192 (svg-clock-replace "%SECOND%" (format "%f" (* seconds 6)))
193 (svg-clock-replace "%SIZE%" (format "%d" svg-clock--actual-size))
194 (svg-clock-replace "%SCALE%"
195 (format "%f" (/ svg-clock--actual-size 100.0)))
196
197 (image-toggle-display-image))))
198
199 (defun svg-clock-update ()
200 "Update the clock."
201 (if (integerp svg-clock-size)
202 (setq svg-clock--actual-size svg-clock-size)
203 (svg-clock-fit-window))
204 (svg-clock-do-update (decode-time (current-time))))
205
206 (defun svg-clock-set-size (size &optional perform-update)
207 "Set the SIZE of the clock and optionally PERFORM-UPDATE."
208 (setq svg-clock--actual-size size)
209 (if perform-update
210 (svg-clock-update)))
211
212 (defun svg-clock-grow ()
213 "Enlarge the size of the svg clock by 10 pixesl.
214 If `svg-clock-size' is t this command has no effect."
215 (interactive)
216 (svg-clock-set-size (+ 10 svg-clock--actual-size) t))
217
218 (defun svg-clock-shrink ()
219 "Reduce the size of the svg clock by 10 pixesl.
220 If `svg-clock-size' is t this command has no effect."
221 (interactive)
222 (svg-clock-set-size (max 10 (- svg-clock--actual-size 10)) t))
223
224 (defun svg-clock-fit-window (&optional perform-update)
225 "Make the svg clock fill the whole window it is displayed in.
226 Optionally PERFORM-UPDATE immediately."
227 (interactive)
228 (let ((clock-win (get-buffer-window "*clock*")))
229 (if clock-win
230 (let* ((coords (window-inside-pixel-edges clock-win))
231 (width (- (nth 2 coords) (nth 0 coords)))
232 (height (- (nth 3 coords) (nth 1 coords))))
233 (svg-clock-set-size (min width height) perform-update)))))
234
235 (defun svg-clock-stop ()
236 "Stop the svg clock and hide it."
237 (interactive)
238 (if (not svg-clock-timer)
239 (message "svg-clock is not running.")
240 (cancel-timer svg-clock-timer)
241 (setq svg-clock-timer nil)
242 (replace-buffer-in-windows "*clock*")
243 (message "Clock stopped")))
244
245 (defun svg-clock-start ()
246 "Start the svg clock."
247 (if svg-clock-timer
248 (message "svg-clock is running already")
249 (switch-to-buffer (get-buffer-create "*clock*"))
250 (unless (integerp svg-clock-size)
251 (svg-clock-fit-window))
252 (setq svg-clock-timer
253 (run-with-timer 0 1 'svg-clock-update))
254 (svg-clock-mode)
255 (message "Clock started")))
256
257 (define-derived-mode svg-clock-mode fundamental-mode "svg clock"
258 "Major mode for the svg-clock buffer.
259 \\{svg-clock-mode-map}")
260
261 (define-key svg-clock-mode-map [?+] 'svg-clock-grow)
262 (define-key svg-clock-mode-map [?-] 'svg-clock-shrink)
263 (define-key svg-clock-mode-map [?q] 'svg-clock-stop)
264 (define-key svg-clock-mode-map [?f] 'svg-clock-fit-window)
265
266 ;;;###autoload
267 (defun svg-clock ()
268 "Start/stop the svg clock."
269 (interactive)
270 (if svg-clock-timer
271 (svg-clock-stop)
272 (svg-clock-start)))
273
274 (provide 'svg-clock)
275
276 ;;; svg-clock.el ends here