(**************************************************************************)
(*    Lablgtk - Examples                                                  *)
(*                                                                        *)
(*    This code is in the public domain.                                  *)
(*    You may freely copy parts of it in your application.                *)
(*                                                                        *)
(**************************************************************************)

(* $Id$ *)

open StdLabels

let check_cache ~cond ~create ~destroy = function
    Some pm ->
      if cond pm then pm else begin
        destroy pm;
        create ()
      end
  | None -> create ()

class timer ?packing ?show () =
  let da = GMisc.drawing_area (*~width:200 ~height:200*) ?packing ?show () in
  let context = da#misc#create_pango_context in
  object (self)
    inherit GObj.widget_full da#as_widget
    val mutable talk = 25 * 60
    val mutable buffer = 5 * 60
    val mutable questions = 5 * 60
    val mutable start = 0.
    val mutable stop = 0.
    val mutable timer = None
    val mutable size = 0, 0
    val mutable pixmap = None
    method set_talk x = talk <- x * 60
    method set_buffer x = buffer <- x * 60
    method set_questions x = questions <- x * 60
    method private to_angle t =
      let total = float (talk + buffer + questions) in
      float t /. total *. 360.
    method draw =
      let current =
        if start = 0. then 0 else truncate (Unix.time () -. start) in
      let {Gtk.x=x0; y=y0; width=width; height=height} =
        da#misc#allocation in
      let size = (min width height) * 49 / 50 in
      let x = (width - size) / 2
      and y = (height - size) / 2 in
      let dr = check_cache pixmap
          ~cond:(fun pm -> pm#size = (width, height))
          ~destroy:(fun pm -> Gdk.Pixmap.destroy pm#pixmap)
          ~create:
          (fun () ->
            context#set_font_by_name ("sans " ^ string_of_int (size*2/13));
            GDraw.pixmap ~width ~height ~window:da ())
      in
      pixmap <- Some dr;
      dr#set_foreground `WHITE;
      dr#rectangle ~x:0 ~y:0 ~width ~height ~filled:true ();
      let draw_arc ~color ~start ~stop =
        dr#set_foreground (`NAME color);
        dr#arc  ~x ~y ~width:size ~height:size ~filled:true
          ~start:(450. -. self#to_angle stop)
          ~angle:(self#to_angle (stop - start) +. 1.) ()
      in
      draw_arc ~color:"blue" ~start:(-60)
        ~stop:(min current (talk+buffer+questions));
      if current < talk then
        draw_arc ~color:"green" ~start:current ~stop:talk;
      if current < talk + buffer then
        draw_arc ~color:"yellow"
          ~start:(max talk current) ~stop:(talk+buffer);
      if current < talk + buffer + questions then
        draw_arc ~color:"red"
          ~start:(max (talk+buffer) current) ~stop:(talk+buffer+questions);
      dr#set_foreground `WHITE;
      let size' = size * 3 / 5 in
      dr#arc ~x:((width - size') / 2) ~y:((height - size') / 2)
        ~width:size' ~height:size' ~filled:true ();
      let layout = context#create_layout in
      Pango.Layout.set_text layout
        (Printf.sprintf "%02d:%02d" (current/60) (current mod 60));
      let (w,h) = Pango.Layout.get_pixel_size layout in
      dr#put_layout ~x:((width-w)/2) ~y:((height-h)/2) ~fore:`BLACK layout;
      (new GDraw.drawable da#misc#window)#put_pixmap ~x:0 ~y:0 dr#pixmap
    method start =
      self#stop;
      if start = 0. then start <- Unix.time ()
      else start <- start +. Unix.time () -. stop;
      stop <- 0.;
      timer <-
        Some(GMain.Timeout.add ~ms:1000 ~callback:(fun () -> self#draw; true));
      self#draw
    method stop =
      if stop = 0. then stop <- Unix.time ();
      match timer with None -> ()
      | Some id ->
          GMain.Timeout.remove id; timer <- None
    method reset =
      self#stop;
      start <- 0.;
      stop <- 0.;
      self#draw
    initializer
      da#event#connect#expose ~callback:(fun _ -> self#draw; true); ()
  end

let () =
  let w = GWindow.window () in
  w#connect#destroy ~callback:GMain.quit;
  let hbox = GPack.hbox ~packing:w#add () in
  let fr = GBin.frame ~border_width:3 ~shadow_type:`IN ~packing:hbox#add () in
  let timer = new timer ~packing:fr#add () in
  let vbox = GPack.vbox ~border_width:3 ~spacing:4 ~packing:hbox#pack () in
  let make_spin ~label ~value ~callback =
    GMisc.label ~text:label ~xalign:0. ~packing:vbox#pack ();
    let x = GEdit.spin_button ~digits:0 ~packing:vbox#pack () in
    x#adjustment#set_bounds ~lower:0. ~upper:999. ~step_incr:1. ();
    x#adjustment#set_value (float value);
    x#connect#value_changed ~callback:
      (fun () -> callback x#value_as_int; timer#draw);
    x
  in
  let talk = make_spin ~label:"Talk" ~value:25 ~callback:timer#set_talk
  and buffer = make_spin ~label:"Buffer" ~value:5 ~callback:timer#set_buffer
  and questions =
    make_spin ~label:"Questions" ~value:5 ~callback:timer#set_questions in
  let total =
    make_spin ~label:"Total" ~value:35 ~callback:
      (fun v ->
        talk#set_value
          (float (v - buffer#value_as_int - questions#value_as_int)))
  in
  let set_total () =
    total#set_value (talk#value +. buffer#value +. questions#value) in
  List.iter [talk;buffer;questions] ~f:
    (fun (x:GEdit.spin_button) ->
      ignore(x#connect#value_changed ~callback:set_total));
  let start = GButton.button ~label:"Start" ~packing:vbox#pack () in
  let stop = GButton.button ~label:"Stop" ~packing:vbox#pack () in
  let reset = GButton.button ~label:"Reset" ~packing:vbox#pack () in
  start#connect#clicked ~callback:(fun () -> timer#start);
  stop#connect#clicked ~callback:(fun () -> timer#stop);
  reset#connect#clicked ~callback:(fun () -> timer#reset);
  w#show ();
  GMain.main ()
