calendar.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. -- Calendar with Emacs org-mode agenda for Awesome WM
  2. -- Inspired by and contributed from the org-awesome module, copyright of Damien Leone
  3. -- Licensed under GPLv2
  4. -- Version 1.1-awesome-git
  5. -- @author Alexander Yakushev <yakushev.alex@gmail.com>
  6. local awful = require("awful")
  7. local util = awful.util
  8. local theme = require("beautiful")
  9. local naughty = require("naughty")
  10. local format = string.format
  11. local orglendar = { files = {},
  12. char_width = nil,
  13. text_color = theme.fg_normal or "#FFFFFF",
  14. today_color = theme.fg_focus or "#00FF00",
  15. event_color = theme.fg_urgent or "#FF0000",
  16. font = theme.font or 'monospace 8',
  17. parse_on_show = true,
  18. limit_todo_length = nil,
  19. date_format = "%d-%m-%Y" }
  20. local freq_table =
  21. { d = { lapse = 86400,
  22. occur = 5,
  23. next = function(t, i)
  24. local date = os.date("*t", t)
  25. return os.time{ day = date.day + i, month = date.month,
  26. year = date.year }
  27. end },
  28. w = { lapse = 604800,
  29. occur = 3,
  30. next = function(t, i)
  31. return t + 604800 * i
  32. end },
  33. y = { lapse = 220752000,
  34. occur = 1,
  35. next = function(t, i)
  36. local date = os.date("*t", t)
  37. return os.time{ day = date.day, month = date.month,
  38. year = date.year + i }
  39. end },
  40. m = { lapse = 2592000,
  41. occur = 1,
  42. next = function(t, i)
  43. local date = os.date("*t", t)
  44. return os.time{ day = date.day, month = date.month + i,
  45. year = date.year }
  46. end }
  47. }
  48. local calendar = nil
  49. local todo = nil
  50. local offset = 0
  51. local data = nil
  52. local function pop_spaces(s1, s2, maxsize)
  53. local sps = ""
  54. for i = 1, maxsize - string.len(s1) - string.len(s2) do
  55. sps = sps .. " "
  56. end
  57. return s1 .. sps .. s2
  58. end
  59. local function strip_time(time_obj)
  60. local tbl = os.date("*t", time_obj)
  61. return os.time{day = tbl.day, month = tbl.month, year = tbl.year}
  62. end
  63. function orglendar.parse_agenda()
  64. local today = os.time()
  65. data = { tasks = {}, dates = {}, maxlen = 20 }
  66. local task_name
  67. for _, file in pairs(orglendar.files) do
  68. local fd = io.open(file, "r")
  69. if not fd then
  70. print("W: orglendar: cannot find " .. file)
  71. else
  72. for line in fd:lines() do
  73. local scheduled = string.find(line, "SCHEDULED:")
  74. local closed = string.find(line, "CLOSED:")
  75. local deadline = string.find(line, "DEADLINE:")
  76. if (scheduled and not closed) or (deadline and not closed) then
  77. local _, _, y, m, d, h, min, recur = string.find(line, "(%d%d%d%d)%-(%d%d)%-(%d%d) %w%w%w ?(%d*)%:?(%d*)[^%+]*%+?([^>]*)>")
  78. if h ~= "" then
  79. h = tonumber(h)
  80. else
  81. h = 23
  82. end
  83. if min ~= "" then
  84. min = tonumber(min)
  85. else
  86. min = 59
  87. end
  88. local task_date = os.time{day = tonumber(d), month = tonumber(m),
  89. year = tonumber(y), hour = h, min = min}
  90. if d and task_name and (task_date >= today or recur ~= "") then
  91. local find_begin, task_start = string.find(task_name, "[A-Z]+%s+")
  92. if task_start and find_begin == 1 then
  93. task_name = string.sub(task_name, task_start + 1)
  94. end
  95. local task_end, _, task_tags = string.find(task_name,"%s+(:.+):")
  96. if task_tags then
  97. task_name = string.sub(task_name, 1, task_end - 1)
  98. else
  99. task_tags = " "
  100. end
  101. local len = string.len(task_name) + string.len(task_tags)
  102. if (len > data.maxlen) and (task_date >= today) then
  103. data.maxlen = len
  104. end
  105. if recur ~= "" then
  106. local _, _, interval, freq = string.find(recur, "(%d)(%w)")
  107. local now = os.time()
  108. local curr
  109. local event_time = task_date -- os.time({day = tonumber(d), month = tonumber(m), year = y})
  110. if freq == "d" then
  111. curr = math.max(now, event_time)
  112. elseif freq == "w" then
  113. local count = math.floor((now - event_time) / (freq_table.w.lapse * interval))
  114. if count < 0 then count = 0 end
  115. curr = event_time + count * (freq_table.w.lapse * interval)
  116. else
  117. curr = event_time
  118. end
  119. while curr < now do
  120. curr = freq_table[freq].next(curr, interval)
  121. end
  122. for i = 1, freq_table[freq].occur do
  123. local curr_date = os.date("*t", curr)
  124. table.insert(data.tasks, { name = task_name,
  125. tags = task_tags,
  126. date = curr,
  127. recur = recur})
  128. data.dates[strip_time(curr)] = true
  129. curr = freq_table[freq].next(curr, interval)
  130. end
  131. else
  132. table.insert(data.tasks, { name = task_name,
  133. tags = task_tags,
  134. date = task_date,
  135. recur = recur})
  136. data.dates[strip_time(task_date)] = true
  137. end
  138. end
  139. end
  140. _, _, task_name = string.find(line, "%*+%s+(.+)")
  141. end
  142. end
  143. end
  144. table.sort(data.tasks, function (a, b) return a.date < b.date end)
  145. end
  146. local function create_calendar()
  147. offset = offset or 0
  148. local now = os.date("*t")
  149. local cal_month = now.month + offset
  150. local cal_year = now.year
  151. if cal_month > 12 then
  152. cal_month = (cal_month % 12)
  153. cal_year = cal_year + 1
  154. elseif cal_month < 1 then
  155. cal_month = (cal_month + 12)
  156. cal_year = cal_year - 1
  157. end
  158. local last_day = tonumber(os.date("%d", os.time({ day = 1, year = cal_year,
  159. month = cal_month + 1}) - 86400))
  160. local first_day = os.time({ day = 1, month = cal_month, year = cal_year})
  161. local first_day_in_week =
  162. (os.date("%w", first_day) + 6) % 7
  163. local result = "Mo Tu We Th Fr Sa Su\n"
  164. for i = 1, first_day_in_week do
  165. result = result .. " "
  166. end
  167. local this_month = false
  168. for day = 1, last_day do
  169. local last_in_week = (day + first_day_in_week) % 7 == 0
  170. local day_str = pop_spaces("", day, 2) .. (last_in_week and "" or " ")
  171. if cal_month == now.month and cal_year == now.year and day == now.day then
  172. this_month = true
  173. result = result ..
  174. format('<span weight="bold" foreground = "%s">%s</span>',
  175. orglendar.today_color, day_str)
  176. elseif data.dates[os.time{day = day, month = cal_month, year = cal_year}] then
  177. result = result ..
  178. format('<span weight="bold" foreground = "%s">%s</span>',
  179. orglendar.event_color, day_str)
  180. else
  181. result = result .. day_str
  182. end
  183. if last_in_week and day ~= last_day then
  184. result = result .. "\n"
  185. end
  186. end
  187. local header
  188. if this_month then
  189. header = os.date("%a, %d %b %Y")
  190. else
  191. header = os.date("%B %Y", first_day)
  192. end
  193. return header, format('<span font="%s" foreground="%s">%s</span>',
  194. orglendar.font, orglendar.text_color, result)
  195. end
  196. local function create_todo()
  197. local result = ""
  198. local maxlen = data.maxlen + 3
  199. if limit_todo_length and limit_todo_length < maxlen then
  200. maxlen = limit_todo_length
  201. end
  202. local prev_date, limit, tname
  203. for i, task in ipairs(data.tasks) do
  204. if strip_time(prev_date) ~= strip_time(task.date) then
  205. result = result ..
  206. format('<span weight = "bold" foreground = "%s">%s</span>\n',
  207. orglendar.event_color,
  208. pop_spaces("", os.date(orglendar.date_format, task.date), maxlen))
  209. end
  210. tname = task.name
  211. limit = maxlen - string.len(task.tags) - 3
  212. if limit < string.len(tname) then
  213. tname = string.sub(tname, 1, limit - 3) .. "..."
  214. end
  215. result = result .. pop_spaces(tname, task.tags, maxlen)
  216. if i ~= #data.tasks then
  217. result = result .. "\n"
  218. end
  219. prev_date = task.date
  220. end
  221. if result == "" then
  222. result = " "
  223. end
  224. return format('<span font="%s" foreground="%s">%s</span>',
  225. orglendar.font, orglendar.text_color, result), data.maxlen + 3
  226. end
  227. function orglendar.get_calendar_and_todo_text(_offset)
  228. if not data or parse_on_show then
  229. orglendar.parse_agenda()
  230. end
  231. offset = _offset
  232. local header, cal = create_calendar()
  233. return format('<span font="%s" foreground="%s">%s</span>\n%s',
  234. orglendar.font, orglendar.text_color, header, cal), create_todo()
  235. end
  236. local function calculate_char_width()
  237. return theme.get_font_height(font) * 0.555
  238. end
  239. function orglendar.hide()
  240. if calendar ~= nil then
  241. naughty.destroy(calendar)
  242. naughty.destroy(todo)
  243. calendar = nil
  244. offset = 0
  245. end
  246. end
  247. function orglendar.show(inc_offset)
  248. inc_offset = inc_offset or 0
  249. if not data or parse_on_show then
  250. orglendar.parse_agenda()
  251. end
  252. local save_offset = offset
  253. orglendar.hide()
  254. offset = save_offset + inc_offset
  255. local char_width = char_width or calculate_char_width()
  256. local header, cal_text = create_calendar()
  257. calendar = naughty.notify({ title = header,
  258. text = cal_text,
  259. timeout = 0, hover_timeout = 0.5,
  260. screen = mouse.screen,
  261. })
  262. todo = naughty.notify({ title = "TO-DO list",
  263. text = create_todo(),
  264. timeout = 0, hover_timeout = 0.5,
  265. screen = mouse.screen,
  266. })
  267. end
  268. function orglendar.register(widget)
  269. widget:connect_signal("mouse::enter", function() orglendar.show(0) end)
  270. widget:connect_signal("mouse::leave", orglendar.hide)
  271. widget:buttons(util.table.join( awful.button({ }, 3, function()
  272. orglendar.parse_agenda()
  273. end),
  274. awful.button({ }, 4, function()
  275. orglendar.show(-1)
  276. end),
  277. awful.button({ }, 5, function()
  278. orglendar.show(1)
  279. end)))
  280. end
  281. return orglendar
  282. -- local widget = {}
  283. -- local calendar = nil
  284. -- local offset = 0
  285. -- widget.text = awful.widget.textclock()
  286. -- function remove_calendar()
  287. -- if calendar ~= nil then
  288. -- naughty.destroy(calendar)
  289. -- calendar = nil
  290. -- offset = 0
  291. -- end
  292. -- end
  293. -- function add_calendar(inc_offset)
  294. -- local save_offset = offset
  295. -- remove_calendar()
  296. -- offset = save_offset + inc_offset
  297. -- local datespec = os.date("*t")
  298. -- datespec = datespec.year * 12 + datespec.month - 1 + offset
  299. -- datespec = (datespec % 12 + 1) .. " " .. math.floor(datespec / 12)
  300. -- local cal = awful.util.pread("cal -m " .. datespec)
  301. -- cal = string.gsub(cal, "^%s*(.-)%s*$", "%1")
  302. -- calendar = naughty.notify({
  303. -- text = string.format('<span font_desc="%s">%s</span>', "monospace", os.date("%a, %d %B %Y") .. "\n" .. cal),
  304. -- timeout = 0, hover_timeout = 0.5,
  305. -- width = 160,
  306. -- })
  307. -- end
  308. -- -- change clockbox for your clock widget (e.g. widget.text)
  309. -- widget.text:add_signal("mouse::enter", function()
  310. -- add_calendar(0)
  311. -- end)
  312. -- widget.text:add_signal("mouse::leave", remove_calendar)
  313. -- -- widget.text:buttons(awful.util.table.join(
  314. -- -- button({ }, 4, function()
  315. -- -- add_calendar(-1)
  316. -- -- end),
  317. -- -- button({ }, 5, function()
  318. -- -- add_calendar(1)
  319. -- -- end)
  320. -- -- ))
  321. -- return widget