(* $Id$ *) (* A small calendar *) (* Needs Unix module, so use with lablgtk_t *) open Printf type date = { mutable year: int; mutable mon: int; mutable mday: int } (* Load the schedule data *) let calendar_file = Filename.concat (Sys.getenv "HOME") ".camlendar" let schedule = try let ichan = open_in calendar_file in let (s : (int * int * int, string) Hashtbl.t) = Marshal.from_channel ichan in close_in ichan; s with Sys_error msg -> prerr_endline msg; flush stderr; Hashtbl.create 13;; (* Saves the schedule data when the application terminates *) at_exit (fun () -> let ochan = open_out calendar_file in Marshal.to_channel ochan schedule ~mode: []; close_out ochan);; (* date: Current date initialized to "today" *) let date = let tm = Unix.localtime (Unix.time ()) in { year = 1900 + tm.Unix.tm_year; mon = tm.Unix.tm_mon; mday = 1 } (* previous_month, next_month: change application status *) let previous_month () = date.mday <- 1; if date.mon = 0 then (date.year <- date.year - 1; date.mon <- 11) else date.mon <- date.mon - 1 let next_month () = date.mday <- 1; if date.mon = 11 then (date.year <- date.year + 1; date.mon <- 0) else date.mon <- date.mon + 1 (* leap, mon_name, wday_name: Calendar related function and data *) let leap year = (year mod 400 = 0) or (year mod 4 = 0) & (year mod 100 <> 0) let mdays_in_month = [|31; 28; 31; 30; 31; 30; 31; 31; 30; 31; 30; 31|] let mon_name = [|"Jan"; "Feb"; "Mar"; "Apr"; "May"; "Jun"; "Jul"; "Aug"; "Sep"; "Oct"; "Nov"; "Dec"|] let wday_name = [|"Sun"; "Mon"; "Tue"; "Wed"; "Thu"; "Fri"; "Sat"|] let s_normal = 0 and s_focused = 1 and s_planned = 2 let styles = let default = (Obj.magic () : GObj.style) in [| default; default; default |] (* class date_button: one button for each day in the month *) class date_button i (calendar : GPack.table) = let mday = i + 1 in object (self) val widget = GButton.button ~label: (string_of_int mday) ~show: false () val mday = mday val mutable show = false val mutable have_plan = false method widget = widget method focus_on = date.mday <- mday; widget#misc#set_style styles.(s_focused) method focus_off = widget#misc#set_style styles.(if have_plan then s_planned else s_normal) method set_plan = have_plan <- true; widget#misc#set_style styles.(s_planned) method unset_plan = have_plan <- false; widget#misc#set_style styles.(s_normal) method show wday0 = if not show then let top = (mday + wday0) / 7 + 1 and left = (mday + wday0) mod 7 in calendar#attach ~left ~top ~expand:`BOTH widget#coerce; widget#misc#show (); show <- true method hide = if show then (widget#misc#hide (); calendar#remove widget#coerce; show <- false) end let update_calendar (calendar : GPack.table) (buttons : date_button array) = let now = Unix.localtime (Unix.gettimeofday ()) in let _, first = Unix.mktime { now with Unix.tm_mday = 1; Unix.tm_mon = date.mon; Unix.tm_year = date.year - 1900 } in (* wday0: day of the week of the zero'th day in the month *) let wday0 = (first.Unix.tm_wday - 1 + 7) mod 7 in let ndays = if date.mon = 1 & leap date.year then mdays_in_month.(date.mon) + 1 else mdays_in_month.(date.mon) in Array.iter ~f: (fun button -> button#hide) buttons; for i = 0 to ndays - 1 do buttons.(i)#show wday0 done let create_GUI () = (* views part *) let win = GWindow.window ~title: "Camlendar" ~show: true ~allow_shrink: false ~allow_grow: false () in win#event#connect#delete ~callback: (fun _ -> GMain.Main.quit (); exit 0; false); let style = win#misc#style#copy in styles.(s_normal) <- style; let style = style#copy in style#set_bg [`NORMAL, `NAME "light green"; `PRELIGHT, `NAME "light green"]; styles.(s_focused) <- style; styles.(s_focused) <- style; let style = style#copy in style#set_bg [`NORMAL, `NAME "sky blue"; `PRELIGHT, `NAME "sky blue"]; styles.(s_planned) <- style; let vbox = GPack.vbox ~packing: win#add () in let packing = vbox#add in let toolbar = GButton.toolbar ~style: `TEXT ~packing () in let prev = toolbar#insert_button ~text: "Prev" ~tooltip: "Show previous month" () in let next = toolbar#insert_button ~text: "Next" ~tooltip: "Show next month" () in let calendar = GPack.table ~homogeneous: true ~rows: 7 ~columns: 7 ~border_width: 10 ~row_spacings: 2 ~col_spacings: 2 ~packing () in Array.iteri ~f: (fun i wday -> ignore (GButton.button ~label: wday ~packing:(calendar#attach ~top: 0 ~left: i ~expand:`BOTH) ())) wday_name; let buttons = Array.init 31 ~f: (fun i -> new date_button i calendar) in let date_view = GMisc.label ~justify: `CENTER ~packing () in let text = GEdit.text ~editable: true ~width: 70 ~height: 50 ~packing () in (* Controls part *) let save_text () = let data = text#get_chars ~start: 0 ~stop: text#length in let key = (date.year, date.mon, date.mday) in Hashtbl.remove schedule key; if data <> "" then (Hashtbl.add schedule ~key ~data; buttons.(date.mday - 1)#set_plan) else buttons.(date.mday - 1)#unset_plan in let restore_text () = text#delete_text ~start: 0 ~stop: text#length; try text#insert_text ~pos: 0 (Hashtbl.find schedule (date.year, date.mon, date.mday)); () with Not_found -> () in let update_date_view () = date_view#set_text (sprintf "%d %s, %d\n" date.mday mon_name.(date.mon) date.year) in let update_view () = update_calendar calendar buttons; update_date_view (); Array.iteri ~f: (fun i button -> (try Hashtbl.find schedule (date.year, date.mon, i + 1); button#set_plan with Not_found -> button#unset_plan); button#focus_off) buttons; win#set_title (sprintf "Camlendar: %s, %d" mon_name.(date.mon) date.year) in prev#connect#clicked ~callback: (fun () -> save_text (); previous_month (); update_view (); restore_text (); buttons.(0)#focus_on); next#connect#clicked ~callback: (fun () -> save_text (); next_month (); update_view (); restore_text (); buttons.(0)#focus_on); Array.iteri ~f: (fun i button -> button#widget#connect#clicked ~callback: (fun () -> save_text (); buttons.(date.mday - 1)#focus_off; button#focus_on; restore_text (); update_date_view ()); ()) buttons; update_view (); buttons.(0)#focus_on;; GMain.Main.init (); print_endline (GtkMain.Main.set_locale ()); flush stdout; create_GUI (); GMain.Main.main ()