aboutsummaryrefslogtreecommitdiff
path: root/modules/chrono-tools.el
blob: 9ccba66766ae837866ad2039eabb2e9304081ce0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
;;; chrono-tools.el --- Config for Date and Time-Related Utils -*- lexical-binding: t; coding: utf-8; -*-
;; author Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;;
;; Layer: 3 (Domain Workflow).
;; Category: D/P.
;; Load shape: eager.
;; Eager reason: none; calendar/timer commands, a command-loaded deferral
;;   candidate.
;; Top-level side effects: package configuration via use-package.
;; Runtime requires: user-constants.
;; Direct test load: yes.
;;
;; This module centralizes configuration for Emacs time-related tools:
;;
;; – time-zones: interactive world clock with fuzzy search and time shifting
;; – calendar: quick navigation keybindings by day, month, and year
;; – tmr: lightweight timer setup with sounds, notifications, and history
;;
;;; Code:

(require 'user-constants)

;; -------------------------------- Time Zones ---------------------------------

(use-package time-zones
  :defer
  :commands time-zones
  :init
  (setq time-zones--city-list-file
        (expand-file-name "persist/time-zones-cities.el" user-emacs-directory))
  :bind ("M-S-c" . time-zones))  ;; was M-C, overrides capitalize-word

(use-package calendar
  :ensure nil ;; built-in
  :defer 0.5
  :bind (("M-#" . calendar)
		 :map calendar-mode-map
		 ("."   . calendar-goto-today)
		 ("<left>"   . calendar-backward-day)
		 ("<right>"   . calendar-forward-day)
		 ("C-<left>"   . calendar-backward-month)
		 ("C-<right>"   . calendar-forward-month)
		 ("M-<left>" . calendar-backward-year)
		 ("M-<right>" . calendar-forward-year)))


;; ------------------------------------ TMR ------------------------------------

(defconst cj/tmr--audio-extensions
  '("mp3" "m4a" "ogg" "opus" "wav" "flac" "aac")
  "Audio file extensions offered to `cj/tmr-select-sound-file'.")

(defun cj/tmr--available-sound-files ()
  "Return a list of audio filenames in `sounds-dir', or nil if none.
Returns nil if `sounds-dir' does not exist."
  (when (and (boundp 'sounds-dir) (file-directory-p sounds-dir))
    (let ((regex (concat "\\." (regexp-opt cj/tmr--audio-extensions t) "$")))
      (directory-files sounds-dir nil regex))))

(defun cj/tmr-reset-sound-to-default ()
  "Reset the tmr sound file to the default notification sound."
  (interactive)
  (setq tmr-sound-file notification-sound)
  (message "Timer sound reset to default: %s"
		   (file-name-nondirectory notification-sound)))

(defun cj/tmr-select-sound-file ()
  "Select a sound file from `sounds-dir' to use for tmr timers.

Present all audio files in the sounds directory and set the chosen file as
`tmr-sound-file'. Use \\[universal-argument] to reset to the default sound."
  (interactive)
  (cond
   (current-prefix-arg
    (cj/tmr-reset-sound-to-default))
   ((not (and (boundp 'sounds-dir) (file-directory-p sounds-dir)))
    (message "Sounds directory does not exist: %s"
             (if (boundp 'sounds-dir) sounds-dir "<unset>")))
   (t
    (let ((sound-files (cj/tmr--available-sound-files)))
      (cond
       ((null sound-files)
        (message "No audio files found in %s" sounds-dir))
       (t
        (let* ((current-file (when (and tmr-sound-file
                                        (file-exists-p tmr-sound-file))
                               (file-name-nondirectory tmr-sound-file)))
               (selected-file
                (completing-read
                 (format "Select timer sound%s: "
                         (if current-file
                             (format " (current: %s)" current-file)
                           ""))
                 sound-files nil t nil nil current-file)))
          (cond
           ((or (null selected-file) (string-empty-p selected-file))
            (message "No file selected"))
           (t
            (setq tmr-sound-file (expand-file-name selected-file sounds-dir))
            (if (equal tmr-sound-file notification-sound)
                (message "Timer sound set to default: %s" selected-file)
              (message "Timer sound set to: %s" selected-file)))))))))))

(use-package tmr
  :defer 0.5
  :init
  (global-unset-key (kbd "M-t"))
  :bind (("M-t" . tmr-prefix-map)
		 :map tmr-prefix-map
		 ("*" . tmr)
		 ("t" . tmr-with-details)
		 ("S" . cj/tmr-select-sound-file)
		 ("R" . cj/tmr-reset-sound-to-default))
  :config
  (setq tmr-sound-file notification-sound)
  (setq tmr-notification-urgency 'normal)
  (setq tmr-descriptions-list 'tmr-description-history))

(provide 'chrono-tools)
;;; chrono-tools.el ends here