bir-mercy.el 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. ;;; bir-mercy.el --- Mercy interface for BIR -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2022 c1-g
  3. ;; Author: c1-g <char1iegordon@protonmail.com>
  4. ;; Keywords: extensions
  5. ;; This program is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;
  17. ;;; Code:
  18. (require 'widget)
  19. (eval-when-compile
  20. (require 'wid-edit))
  21. (defvar-local bir-mercy--last-edited-field nil)
  22. (defvar-local bir-mercy--total nil)
  23. (defvar-local bir-mercy--origin-buffer nil)
  24. (defun bir-mercy-time-in (days)
  25. (let ((seconds (* days 60 60 24))
  26. (now (time-to-seconds)))
  27. (format-time-string "%b %d, %Y" (seconds-to-time (+ now seconds)))))
  28. (defmacro bir-mercy-with-widget-deactivated (widget &rest body)
  29. (declare (debug (body)))
  30. `(let ((widget ,widget))
  31. (if (widget-apply ,widget :active)
  32. (progn ,@body)
  33. (widget-apply ,widget :activate)
  34. (progn ,@body)
  35. (widget-apply ,widget :deactivate))))
  36. (defun bir-mercy (&optional match scope &rest skip)
  37. (interactive)
  38. (let* ((buf (get-buffer-create "*Mercy scheduling*"))
  39. (ids (org-map-entries #'org-id-get-create match scope skip))
  40. (total (length ids))
  41. (origin (current-buffer))
  42. (inhibit-read-only t))
  43. (with-current-buffer buf
  44. (remove-overlays)
  45. (erase-buffer)
  46. (kill-all-local-variables)
  47. (setq bir-mercy--origin-buffer origin)
  48. (setq bir-mercy--total total)
  49. (add-hook 'after-change-functions #'bir-maybe-get-widget-after-change nil t)
  50. (add-hook 'after-change-functions #'widget-after-change nil t)
  51. (widget-insert (format "Elements to schedule %s"
  52. (propertize (make-string 1 ?\s) 'display '(space :align-to 45))))
  53. (widget-apply (widget-create 'integer
  54. :size 7
  55. :tag 'total
  56. :format "%v\n\n"
  57. total)
  58. :deactivate)
  59. (widget-insert (make-string fill-column ?-) "\n\n")
  60. (widget-create 'integer
  61. :size 10
  62. :valid-regexp "[[:digit:]]+"
  63. :tag 'elt-per-day
  64. :format (format "Number of elements per day: %s%%v"
  65. (propertize (make-string 1 ?\s) 'display '(space :align-to 45)))
  66. total)
  67. (widget-insert "\n\n")
  68. (widget-create 'integer
  69. :valid-regexp "[[:digit:]]+"
  70. :tag 'period
  71. :size 10
  72. :format (format "Scheduling period: %s%%v"
  73. (propertize (make-string 1 ?\s) 'display '(space :align-to 45)))
  74. 1)
  75. (widget-apply (widget-create 'text
  76. :tag 'date
  77. :size 12
  78. :format " %v\n\n"
  79. (bir-mercy-time-in 1))
  80. :deactivate)
  81. (widget-insert (make-string fill-column ?-) "\n\n")
  82. (widget-insert "Choosing OK will result in scheduling ")
  83. (widget-apply (widget-create 'integer
  84. :size 7
  85. :tag 'total
  86. :format "%v"
  87. total)
  88. :deactivate)
  89. (widget-insert " elements collected from\na collecting period of ")
  90. (widget-apply (widget-create 'integer
  91. :valid-regexp "[[:digit:]]+"
  92. :size 4
  93. :tag 'period
  94. :format "%v"
  95. 1)
  96. :deactivate)
  97. (widget-insert " days(s) in a period from today till ")
  98. (widget-apply (widget-create 'text
  99. :tag 'date
  100. :size 12
  101. :format "%v\n"
  102. (bir-mercy-time-in 1))
  103. :deactivate)
  104. (widget-insert "with ")
  105. (widget-apply (widget-create 'integer
  106. :valid-regexp "[[:digit:]]+"
  107. :size 5
  108. :tag 'elt-per-day
  109. :format "%v"
  110. total)
  111. :deactivate)
  112. (widget-insert " repetitions per day.")
  113. (widget-insert "\n\n")
  114. (widget-insert (propertize (make-string 1 ?\s) 'display '(space :align-to 40)))
  115. (widget-insert " ")
  116. (widget-create 'push-button
  117. :notify (apply-partially #'bir-mercy-complete ids)
  118. :button-face 'org-checkbox-statistics-done
  119. "✓ OK")
  120. (widget-insert " ")
  121. (widget-create 'push-button
  122. :notify (lambda (&rest ignore)
  123. (kill-buffer))
  124. :button-face 'org-checkbox-statistics-todo
  125. "❌ Cancel")
  126. (widget-insert " ")
  127. (widget-create 'push-button
  128. :notify (lambda (&rest ignore)
  129. (bir-mercy-update bir-mercy--last-edited-field))
  130. :button-face 'org-date
  131. "⭯ Update")
  132. (widget-insert " ")
  133. (use-local-map widget-keymap)
  134. (widget-setup)
  135. (display-buffer-in-side-window buf '((dedicated . t)
  136. (side . right)))
  137. (select-window (get-buffer-window buf))
  138. (let ((fit-window-to-buffer-horizontally t))
  139. (fit-window-to-buffer)))))
  140. (defun bir-maybe-get-widget-after-change (from to _old)
  141. (let ((field (widget-field-find from))
  142. (other (widget-field-find to)))
  143. (when field
  144. (unless (eq field other)
  145. (error "Change in different fields"))
  146. (setq-local bir-mercy--last-edited-field field))))
  147. (defun bir-mercy-complete (ids &rest _ignore)
  148. "docstring"
  149. (let* ((widgets (cl-remove-if-not
  150. (lambda (w)
  151. (widget-apply w :active))
  152. widget-field-list))
  153. (elt-per-day (widget-value (seq-find (lambda (w)
  154. (eq 'elt-per-day (widget-get w :tag)))
  155. widgets)))
  156. (period (widget-value (seq-find (lambda (w)
  157. (eq 'period (widget-get w :tag)))
  158. widgets)))
  159. (ids (seq-partition ids elt-per-day)))
  160. (with-current-buffer bir-mercy--origin-buffer
  161. (save-excursion
  162. (dotimes (i period)
  163. (dolist (id (nth i ids))
  164. (goto-char (org-find-entry-with-id id))
  165. (org-schedule nil (format "+%d" i))))))))
  166. (defun bir-mercy-update (field)
  167. (when field
  168. (let* ((widgets (seq-group-by (lambda (w)
  169. (widget-get w :tag))
  170. widget-field-list))
  171. (active-period-widget (car
  172. (cl-remove-if-not
  173. (lambda (w)
  174. (widget-apply w :active))
  175. (alist-get 'period widgets))))
  176. (tag (widget-get field :tag))
  177. (period))
  178. (when (memq tag '(elt-per-day period))
  179. (dolist (widget (alist-get tag widgets))
  180. (bir-mercy-with-widget-deactivated
  181. widget
  182. (widget-value-set
  183. widget
  184. (widget-value field))))
  185. (dolist (widget (alist-get (car (delq tag '(elt-per-day period))) widgets))
  186. (bir-mercy-with-widget-deactivated
  187. widget
  188. (widget-value-set
  189. widget
  190. (round (/ bir-mercy--total
  191. (widget-value field)))))))
  192. (when active-period-widget
  193. (dolist (date-widget (alist-get 'date widgets))
  194. (bir-mercy-with-widget-deactivated
  195. date-widget
  196. (widget-value-set
  197. date-widget
  198. (bir-mercy-time-in (widget-value active-period-widget))))))
  199. (widget-setup))))
  200. (provide 'bir-mercy)
  201. ;;; bir-mercy.el ends here