calendar.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. -- @author Cédric Le Moigne cedlemo
  2. local helpers = require("blingbling.helpers")
  3. local superproperties = require("blingbling.superproperties")
  4. local grid = require("blingbling.grid")
  5. local text_box = require("blingbling.text_box")
  6. local awful = require("awful")
  7. local util = awful.util
  8. local pairs = pairs
  9. local calendar = { mt = {} }
  10. local data = setmetatable({}, { __mode = "k" })
  11. local properties = { "prev_next_widget_style", "current_date_widget_style",
  12. "days_of_week_widget_style", "days_of_month_widget_style",
  13. "weeks_number_widget_style", "current_day_widget_style",
  14. "focus_widget_style", "focus_days", "focus_days_enter_callback",
  15. "focus_days_leave_callback"}
  16. local function get_month_name(calendar)
  17. local month_name = os.date("%B",
  18. os.time{ year = data[calendar].year,
  19. month = data[calendar].month,
  20. day=01
  21. }
  22. )
  23. return month_name
  24. end
  25. local function get_date_label(calendar)
  26. local year = tostring(data[calendar].year)
  27. return get_month_name(calendar) .. " " .. year
  28. end
  29. local function generate_header_widgets(calendar)
  30. local props = data[calendar].props
  31. data[calendar].date = text_box(props.current_date_widget_style)
  32. data[calendar].date:set_text(get_date_label(calendar))
  33. data[calendar].prev_month = text_box(props.prev_next_widget_style)
  34. data[calendar].prev_month:set_text(util.escape("<<"))
  35. data[calendar].next_month = text_box(props.prev_next_widget_style)
  36. data[calendar].next_month:set_text(util.escape(">>"))
  37. end
  38. local function generate_week_day_names()
  39. names = {}
  40. for i=6,12 do
  41. table.insert(names,(os.date("%a",os.time({month=2,day=i,year=2012}))))
  42. end
  43. return names
  44. end
  45. local function generate_week_days(calendar)
  46. names = generate_week_day_names()
  47. data[calendar].week_days = {}
  48. local props = data[calendar].props
  49. for i, n in ipairs(names) do
  50. data[calendar].week_days[i] = text_box(props.days_of_week_style)
  51. data[calendar].week_days[i]:set_text(n)
  52. end
  53. end
  54. local function set_weeks_numbers(calendar)
  55. local numbers = helpers.get_ISO8601_weeks_number_of_month(data[calendar].month,
  56. data[calendar].year)
  57. for i,w in ipairs(data[calendar].weeks_numbers) do
  58. w:set_text(numbers[i])
  59. end
  60. end
  61. local function generate_weeks_numbers(calendar)
  62. local props = data[calendar].props
  63. data[calendar].weeks_numbers = {}
  64. for i=1,6 do
  65. data[calendar].weeks_numbers[i] = text_box(props.weeks_number_style)
  66. end
  67. set_weeks_numbers(calendar)
  68. end
  69. local function generate_days_of_month(calendar)
  70. data[calendar].days_of_month = {}
  71. for i=1,42 do
  72. data[calendar].days_of_month[i] = text_box(data[calendar].props.days_of_month_widget_style)
  73. end
  74. end
  75. local function generate_widgets(calendar)
  76. generate_header_widgets(calendar)
  77. generate_week_days(calendar)
  78. generate_weeks_numbers(calendar)
  79. generate_days_of_month(calendar)
  80. end
  81. local function add_header_widgets(calendar)
  82. calendar:add_child(data[calendar].prev_month, 1, 1, 1, 1)
  83. calendar:add_child(data[calendar].date, 3, 1, 4, 1)
  84. calendar:add_child(data[calendar].next_month, 8, 1, 1, 1)
  85. end
  86. local function add_week_days(calendar)
  87. for i,v in ipairs(data[calendar].week_days) do
  88. calendar:add_child(v, i + 1, 2, 1, 1)
  89. end
  90. end
  91. local function add_weeks_numbers(calendar)
  92. for i, w in ipairs(data[calendar].weeks_numbers) do
  93. calendar:add_child(w, 1, 2 + i, 1, 1)
  94. end
  95. end
  96. local function add_days_of_month(calendar)
  97. for y=1,6 do
  98. for x=1,7 do
  99. local index = x + ((y - 1 ) * 7)
  100. local child = data[calendar].days_of_month[index]
  101. calendar:add_child(child, 1 + x, 2 + y, 1, 1)
  102. end
  103. end
  104. end
  105. local function fill_grid(calendar)
  106. add_header_widgets(calendar)
  107. add_week_days(calendar)
  108. add_weeks_numbers(calendar)
  109. add_days_of_month(calendar)
  110. end
  111. local function get_current_month_year(calendar)
  112. data[calendar].month = tonumber(os.date("%m"))
  113. data[calendar].year = tonumber(os.date("%Y"))
  114. end
  115. local function find_first_last_days_of_month(calendar)
  116. --find the first week day of the month
  117. --it is the number used as start for displaying day in the
  118. --table data[calendar].days_of_month
  119. local d = os.date('*t',
  120. os.time{year = data[calendar].year,
  121. month = data[calendar].month,
  122. day = 01}
  123. )
  124. --We use Monday as first day of week
  125. -- TODO is it ok with other locale ?
  126. local day_1 = d['wday'] - 1 == 0 and 7 or d["wday"]
  127. local day_n = helpers.get_days_in_month(data[calendar].month, data[calendar].year)
  128. data[calendar].day_1 = day_1
  129. data[calendar].day_n = tonumber(day_n)
  130. end
  131. local function hide_day_of_month_cell(text_box)
  132. text_box:set_text("")
  133. text_box:set_background_color("#00000000")
  134. text_box:set_text_background_color("#00000000")
  135. text_box:set_background_text_border("#0000000")
  136. end
  137. local function apply_style(widget, style)
  138. for k,v in pairs(style) do
  139. if widget['set_'..k] ~= nil and type(widget['set_'..k]) == "function" then
  140. widget['set_' ..k](widget, v)
  141. end
  142. end
  143. end
  144. local function is_current_month(calendar)
  145. local current_month = tonumber(os.date("%m"))
  146. local current_year = tonumber(os.date("%Y"))
  147. if data[calendar].year == current_year and
  148. data[calendar].month == current_month then
  149. return true
  150. else
  151. return false
  152. end
  153. end
  154. local function display_days_of_month(calendar)
  155. find_first_last_days_of_month(calendar)
  156. local days = data[calendar].days_of_month
  157. local day_1 = data[calendar].day_1
  158. local day_n = data[calendar].day_n
  159. local day_number = 0
  160. for i=1,42 do
  161. if i < day_1 - 1 then
  162. hide_day_of_month_cell(days[i])
  163. elseif i >= (day_n + day_1 - 1) then
  164. hide_day_of_month_cell(days[i])
  165. else
  166. day_number = day_number + 1
  167. local current_day = tonumber(os.date("%d"))
  168. days[i]:set_text(day_number)
  169. if is_current_month(calendar) and current_day == day_number then
  170. apply_style(days[i],
  171. data[calendar].props.current_day_widget_style)
  172. else
  173. apply_style(days[i],
  174. data[calendar].props.days_of_month_widget_style)
  175. end
  176. end
  177. end
  178. end
  179. local function add_focus_style(widget, focus, normal)
  180. widget:connect_signal("mouse::enter",
  181. function()
  182. if widget._layout.text ~= "" then
  183. apply_style(widget, focus)
  184. end
  185. end)
  186. widget:connect_signal("mouse::leave",
  187. function()
  188. if widget._layout.text ~= "" then
  189. apply_style(widget, normal)
  190. end
  191. end)
  192. end
  193. local function add_focus_style_with_ref(widget, focus, normal, refs)
  194. refs.mouse_enter[widget] = function()
  195. if widget._layout.text ~= "" then
  196. apply_style(widget, focus)
  197. end
  198. end
  199. widget:connect_signal("mouse::enter", refs.mouse_enter[widget])
  200. refs.mouse_leave[widget] = function()
  201. if widget._layout.text ~= "" then
  202. apply_style(widget, normal)
  203. end
  204. end
  205. widget:connect_signal("mouse::leave", refs.mouse_leave[widget])
  206. end
  207. local function add_focus_signals_on_date_prev_next(calendar, props)
  208. local focus = props.focus_widget_style
  209. local normal = props.prev_next_widget_style
  210. add_focus_style(data[calendar].prev_month, focus, normal)
  211. add_focus_style(data[calendar].next_month, focus, normal)
  212. normal = props.current_date_widget_style
  213. add_focus_style(data[calendar].date, focus, normal)
  214. end
  215. local function reset_focus(calendar, widget)
  216. local enter_fn = data[calendar].focus_handlers_ref.mouse_enter[widget]
  217. local leave_fn = data[calendar].focus_handlers_ref.mouse_leave[widget]
  218. if enter_fn then
  219. widget:disconnect_signal("mouse::enter", enter_fn)
  220. end
  221. if leave_fn then
  222. widget:disconnect_signal("mouse::leave", leave_fn)
  223. end
  224. end
  225. local function add_focus_leave_enter_callbacks(widget, calendar)
  226. local enter_cb = data[calendar].focus_days_enter_callback
  227. local leave_cb = data[calendar].focus_days_leave_callback
  228. local m = data[calendar].month
  229. local y = data[calendar].year
  230. if enter_cb ~= nil then
  231. widget:connect_signal("mouse::enter",
  232. function()
  233. if widget._layout.text ~= "" then
  234. local callback = enter_cb.cb
  235. local data = enter_cb.data
  236. callback(widget, m, y, data)
  237. end
  238. end)
  239. end
  240. if leave_cb ~= nil then
  241. widget:connect_signal("mouse::leave",
  242. function()
  243. if widget._layout.text ~= "" then
  244. local callback = leave_cb.cb
  245. local data = leave_cb.data
  246. callback(widget, m, y, data)
  247. end
  248. end)
  249. end
  250. end
  251. local function add_focus_signals_on_days_of_month(calendar, props)
  252. local focus = props.focus_widget_style
  253. local normal = props.days_of_month_widget_style
  254. local current = props.current_day_widget_style
  255. find_first_last_days_of_month(calendar)
  256. local days = data[calendar].days_of_month
  257. local day_1 = data[calendar].day_1
  258. local day_n = data[calendar].day_n
  259. local day_number = 0
  260. for i=1,42 do
  261. add_focus_leave_enter_callbacks(days[i], calendar)
  262. if i < day_1 - 1 then
  263. reset_focus(calendar, days[i])
  264. elseif i >= (day_n + day_1 - 1) then
  265. reset_focus(calendar, days[i])
  266. else
  267. reset_focus(calendar, days[i])
  268. day_number = day_number + 1
  269. local current_day = tonumber(os.date("%d"))
  270. if is_current_month(calendar) and current_day == day_number then
  271. add_focus_style_with_ref(days[i], focus, current,
  272. data[calendar].focus_handlers_ref)
  273. else
  274. add_focus_style_with_ref(days[i], focus, normal,
  275. data[calendar].focus_handlers_ref)
  276. end
  277. end
  278. end
  279. end
  280. local function add_focus_signals(calendar)
  281. local props = data[calendar].props
  282. add_focus_signals_on_date_prev_next(calendar, props)
  283. if data[calendar].focus_days == true then
  284. add_focus_signals_on_days_of_month(calendar, props)
  285. end
  286. end
  287. local function next_month(calendar)
  288. if data[calendar].month == 12 then
  289. data[calendar].month = 1
  290. data[calendar].year = data[calendar].year + 1
  291. else
  292. data[calendar].month = data[calendar].month + 1
  293. end
  294. end
  295. local function prev_month(calendar)
  296. if data[calendar].month == 1 then
  297. data[calendar].month = 12
  298. data[calendar].year = data[calendar].year - 1
  299. else
  300. data[calendar].month = data[calendar].month - 1
  301. end
  302. end
  303. local function add_change_month_signals(calendar)
  304. data[calendar].prev_month:buttons(util.table.join(
  305. awful.button({ }, 1, function()
  306. prev_month(calendar)
  307. display_days_of_month(calendar)
  308. set_weeks_numbers(calendar)
  309. data[calendar].date:set_text(get_date_label(calendar))
  310. if data[calendar].focus_days == true then
  311. add_focus_signals_on_days_of_month(calendar, data[calendar].props)
  312. end
  313. end)
  314. ))
  315. data[calendar].next_month:buttons(util.table.join(
  316. awful.button({ }, 1, function()
  317. next_month(calendar)
  318. display_days_of_month(calendar)
  319. set_weeks_numbers(calendar)
  320. data[calendar].date:set_text(get_date_label(calendar))
  321. if data[calendar].focus_days == true then
  322. add_focus_signals_on_days_of_month(calendar, data[calendar].props)
  323. end
  324. end)
  325. ))
  326. data[calendar].date:buttons(util.table.join(
  327. awful.button({ }, 1, function()
  328. get_current_month_year(calendar)
  329. display_days_of_month(calendar)
  330. set_weeks_numbers(calendar)
  331. data[calendar].date:set_text(get_date_label(calendar))
  332. if data[calendar].focus_days == true then
  333. add_focus_signals_on_days_of_month(calendar, data[calendar].props)
  334. end
  335. end)
  336. ))
  337. end
  338. function calendar.new(args)
  339. local args = args or {}
  340. local _calendar = grid()
  341. if args.locale then
  342. os.setlocale(args.locale)
  343. end
  344. data[_calendar] = {}
  345. data[_calendar].focused_day = nil
  346. -- Used to keep tracks of the function used to change the focus
  347. -- in order to be able to delete them and change them
  348. -- a text_box can be an empty day, a normal day or the current
  349. -- day depending on the month we choose to display
  350. data[_calendar].focus_handlers_ref = {}
  351. data[_calendar].focus_handlers_ref.mouse_enter = {}
  352. data[_calendar].focus_handlers_ref.mouse_leave = {}
  353. for _, prop in ipairs(properties) do
  354. data[_calendar][prop] = args[prop]
  355. end
  356. data[_calendar].props = helpers.load_properties(properties,
  357. data,
  358. _calendar,
  359. superproperties.calendar)
  360. get_current_month_year(_calendar)
  361. generate_widgets(_calendar)
  362. fill_grid(_calendar)
  363. display_days_of_month(_calendar)
  364. add_focus_signals(_calendar)
  365. add_change_month_signals(_calendar)
  366. -- Build properties function
  367. for _, prop in ipairs(properties) do
  368. if not _calendar["set_" .. prop] then
  369. _calendar["set_" .. prop] = function(cal, value)
  370. data[cal][prop] = value
  371. return cal
  372. end
  373. end
  374. end
  375. return _calendar
  376. end
  377. function calendar.mt:__call(...)
  378. return calendar.new(...)
  379. end
  380. return setmetatable(calendar, calendar.mt)