(* This file is part of our reusable OCaml BRICKS library
   Copyright (C) 2007, 2008  Luca Saiu

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. *)


open GMain

(**

Constants

Some global constant definitions, for fine-tuning. *)


(** The duration of a LED light "flash", in milliseconds: *)

let flash_duration = 80 (* 125 *)

(** The duration of a LED light "blink", in milliseconds. The time is measured from the first to the last state change: *)

let blink_duration = 250

(** How many times a LED light changes state during a blink. This includes both on->off and off->on transitions: *)

let blink_toggles_no = 8 (* 4 times on + 4 times off *)

(**

Exception

The ways this brick can fail. *)


(** An exception raised whenever the user refers a non-existing LED light: in a LED grid *)

exception Non_existing_led_light of int * int

(** An exception raised whenever the user refers a non-existing port in a device LED grid: *)

exception Non_existing_port of int

(**

Utility stuff

*)


(* To do: use this *)
let tooltips = GData.tooltips ()

(** Make a pixmap data structure (not a widget) from the given file: *)

let make_pixmap_from_xpm_file ~file_name =
  GDraw.pixmap_from_xpm ~file:file_name ()

(**

A single LED light

Gtk+ simulation of just one LED light. Particularly useful when arranged in a grid. *)


(** A LED light is a widget mimicking a single physical LED light, whose state at any given moment can be on or off: its state is represented as a boolean value, and by convention 'true' means 'on'. A LED light keeps its default state until its state is explitly changed by the user. The user can simply set the object's state, or can set its state *also changing the default*. As soon as the current state changes the widget's appearance on screen is updated. A LED light can be also 'flashed', i.e. set to its non-default value for a short time, after which it automatically reverts to its default state, or 'blinked', i.e. ordered to repeatedly toggle its state very fast for a short time, before automatically reverting to its default state. Flashing and blinking are *asynchronous* operations: when the user requests them they are scheduled to be executed in background, and the user is immediately given back control. This allows to use concurrency in an extremely simple way, without even exposing a thread interface. LED lights can be used in isolation, but they are mainly intended to be arranged within a grid, allowing for more complex behaviour. Note that already initialized Gtk+ pixmap objects of type GDraw.pixmap (and *not* widgets) must be explicitly supplied at construction time. Pixmaps can and should be shared among differnet LED lights. *)

class led_light ?default:(default=false) ?x:(x= -1) ?y:(y= -1) ~off_pixmap ~on_pixmap ~packing () =
object(self)
  
  (** A notebook with hidden tabs and border is the main widget: it contains two pages with the 'on' and 'off' pixmaps, and can easily change state by 'going' to a different page: *)

  val notebook =
    let notebook = GPack.notebook ~tab_pos:`TOP ~packing ~show_border:false ~show_tabs:false () in
    let _ = (* "on" pixmap widget *)
      GMisc.pixmap
        off_pixmap
        ~packing:(fun widget -> ignore (notebook#insert_page ~pos:0 widget))
        () in
    let _ = (* "on" pixmap widget *)
      GMisc.pixmap
        on_pixmap
        ~packing:(fun widget -> ignore (notebook#insert_page ~pos:1 widget))
        () in
      notebook

  
  (** Default state and current state; see above: *)

  val default = ref(default)
  val state = ref(false)
  
  
  (** Return the current default state: *)

  method get_default = !default

  
  (** Update the default state *and also the current state*; this changes the widget's appearance if the new value is different from the current state: *)

  method set_default value = default := value; 
                             self#set value;
                             ()

  
  (** Return the current state: *)

  method get = !state

  
  (** Update the current state, possibly changing the widget's appearance: *)

  method set value = state := value;
                     notebook#goto_page (if value then 1 else 0);
                     ()

  
  (** Set the widget current state to be equal to its default. This may change the widget's appearance: *)

  method reset = self#set(!default); ()

  
  (** Set the widget current state to be on if it's currently off, or vice-versa. This always changes the widget's appearance: *)

  method toggle = self#set(not self#get); ()
  
  
  (** Return the widget position as it was set at creation time, or (-1, -1) if it was not set: *)

  method get_position = x, y

  
  (** Return the main Gtk+ widget making up the LED light: *)

  method get_widget = notebook

  
  (** Order the LED light to flash (see above) for the established time, and return immediately: *)

  method flash = self#set (not !default);
                 ignore (GMain.Timeout.add 
                           flash_duration
                           (function () -> self#reset; false))

  
  (** Schedule the LED light to blink 'times' times, then to reset itself. This is internally used to implement blinking: *)

  method private blink_this_number_of_times times =
  if times = 0 then
    self#set(!default)
  else begin
    self#toggle;
    ignore (GMain.Timeout.add
              (blink_duration / blink_toggles_no)
              (fun () -> self#blink_this_number_of_times (times - 1); false));
  end

  
  (** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default state reflects what is visually displayed at creation time: *)

  initializer self#set !default
end

(** These variables are just used as parameters to Array.make so that types can be correctly inferred. useless_label's widget is never displayed: *)

let useless_array_of_led_light_options = Array.make 0 None
let useless_label = GMisc.label ()


(**

LED grid

Gtk+ simulation of a grid of LED lights. *)



(** A LED grid visually represents a matrix of LED lights, where each light is independently controllable. A light is identified by its 0-based coordinates, where the origin is top-left. The optional parameter no_leds_at represents a list of coordinates (such as (0, 1); (3, 4)) where *no* lights should be placed. Each end of each row and column contains an optional, user-settable text label. Vertical labels can be rotated,pan>&mment">(** Order the LED light to blink (see above) for the established time, and return immediately: *)

  method blink = self#blink_this_number_of_times blink_toggles_no; ()

  
  (** This just assures that the default