(***********************************************************************)
(*                                                                     *)
(*                           Objective Caml                            *)
(*                                                                     *)
(*    Pierre Weis and Jun Furuse, projet Cristal, INRIA Rocquencourt   *)
(*                                                                     *)
(*  Copyright 2001 Institut National de Recherche en Informatique et   *)
(*  en Automatique.  All rights reserved.  This file is distributed    *)
(*  under the terms of the GNU Library General Public License.         *)
(*                                                                     *)
(***********************************************************************)

(* $Id: graphicsY11.ml,v 1.40 2004/04/06 07:54:05 remy Exp $ *)

(* Module [GraphicsY11]:
   additional graphics primitives for the X Windows system *)

(* Re-imported from graphicsX11 *)

type color = Graphics.color;;

type window_geometry = string;;

type x = int
and y = int
and w = int
and h = int
and width = int
and height = int;;

type modifiers = int;;

type rectangle = {x : x; y : y; w : w; h : h};;

(* Introducing get_ variants of regular set_ functions from Graphics. *)
let set_font, get_font =
 let current_font = ref "8x13" in
 (fun s -> current_font := s; Graphics.set_font s),
 (fun () -> !current_font);;

let set_line_width, get_line_width =
 let current_line_width = ref 1 in
 (fun n -> current_line_width := n; Graphics.set_line_width n),
 (fun () -> !current_line_width);;

(* Returns the current painting color. *)
external get_color : unit -> color = "caml_gr_get_color";;

(* Sub-windows. *)
type window_id = string;;

let null_window = "-1";;

external flush : unit -> unit = "caml_gr_flush";;
        (* flush pending events *)

external sync : unit -> unit = "caml_gr_sync";;
        (* flush pending events and wait until all have been processed *)

external raw_draw_image_area : Graphics.image -> (int * int * int * int) ->
  int -> int -> unit = "caml_gr_draw_image_area";;

let draw_image_area ~img ~src_x ~src_y ~w ~h ~dest_x ~dest_y =
  if src_x < 0 || src_y < 0 then raise (Invalid_argument "draw_image_area")
  else raw_draw_image_area img (src_x, src_y, w, h) dest_x dest_y;;

external get_window_id : unit -> window_id = "caml_gr_window_id";;

let subwindows = Hashtbl.create 13;;

let iter_subwindows f = Hashtbl.iter f subwindows;;

external raw_open_subwindow : int -> int -> int -> int -> window_id 
    = "caml_gr_open_sub_window";;
external raw_close_subwindow : window_id -> unit
    = "caml_gr_close_subwindow2";;

let open_subwindow ~x ~y ~width ~height =
  if width = 0 && height = 0 then null_window else
  let wid = raw_open_subwindow x y width height in
  Hashtbl.add subwindows wid height;
  wid;;

let check_window fname wid =
  if not (Hashtbl.mem subwindows wid)
  then raise (Graphics.Graphic_failure (fname ^ ": no such window: " ^ wid));;

let close_subwindow wid =
  if wid != null_window then begin
  check_window "close_subwindow" wid;
  raw_close_subwindow wid;
  Hashtbl.remove subwindows wid end;;

external raw_map_window : window_id  -> unit = "caml_gr_map_window";;
external raw_unmap_window : window_id -> unit = "caml_gr_unmap_window";;

let map_subwindow wid =
  (*prerr_endline (Printf.sprintf "mapping subwindow %s" wid);*)
  if wid != null_window then begin
  check_window "map_subwindow" wid;
  raw_map_window wid end;;

let unmap_subwindow wid =
  if wid != null_window then begin
  check_window "unmap_subwindow" wid;
  raw_unmap_window wid end;;

external raw_move_window : window_id -> int -> int -> int -> unit
    = "caml_gr_move_window";;

external raw_resize_window : window_id -> int -> int -> unit
    = "caml_gr_resize_window";;

let resize_subwindow wid height width =
  if wid != null_window then begin 
  check_window "resize_subwindow" wid;
  Hashtbl.replace subwindows wid height;
  raw_resize_window wid height width end;;

let move_subwindow wid x y =
  if wid != null_window then begin
  check_window "move_subwindow" wid;
  let h = Hashtbl.find subwindows wid in
  raw_move_window wid x y h end;;

external flush : unit -> unit = "caml_gr_flush";;
        (* flush the content of the backing store *)

external bsize_x : unit -> int = "caml_gr_bsize_x"
external bsize_y : unit -> int = "caml_gr_bsize_y"
        (* Idem, but return the size of the backing store. *)
external screen_x : unit -> int = "caml_gr_screen_x"
external screen_y : unit -> int = "caml_gr_screen_y"
        (* Return the size of the screen. *)
external origin_x : unit -> int = "caml_gr_origin_x"
external origin_y : unit -> int = "caml_gr_origin_y"
        (* Return the size of the screen. *)
external reposition : int -> int -> int -> int -> int -> unit = "caml_gr_reposition"

external set_named_atom_property : string -> string -> unit
    = "caml_gr_set_named_atom_property"
        (* make_atom_property ATOM STRING define an X atom ATOM with
           property STRING *)

external bstore_id : unit -> int32 = "caml_gr_get_bstore_id"
 (** return the X id of the bstore canvas pixmap as an integer *)
external window_id : unit -> int32 = "caml_gr_get_window_id"
 (** return the X id of the canvas of the on-screen window as an integer *)

(* Setting the cursor *)
(* check by fd -fn cursor *)
type cursor = 
  | Cursor_id of int
  | Cursor_X_cursor
  | Cursor_arrow
  | Cursor_based_arrow_down
  | Cursor_based_arrow_up
  | Cursor_boat
  | Cursor_bogosity
  | Cursor_bottom_left_corner
  | Cursor_bottom_right_corner
  | Cursor_bottom_side
  | Cursor_bottom_tee
  | Cursor_box_spiral
  | Cursor_center_ptr
  | Cursor_circle
  | Cursor_clock
  | Cursor_coffee_mug
  | Cursor_cross
  | Cursor_cross_reverse
  | Cursor_crosshair
  | Cursor_diamond_cross
  | Cursor_dot
  | Cursor_dotbox
  | Cursor_double_arrow
  | Cursor_draft_large
  | Cursor_draft_small
  | Cursor_draped_box
  | Cursor_exchange
  | Cursor_fleur
  | Cursor_gobbler
  | Cursor_gumby
  | Cursor_hand1
  | Cursor_hand2
  | Cursor_heart
  | Cursor_icon
  | Cursor_iron_cross
  | Cursor_left_ptr
  | Cursor_left_side
  | Cursor_left_tee
  | Cursor_leftbutton
  | Cursor_ll_angle
  | Cursor_lr_angle
  | Cursor_man
  | Cursor_middlebutton
  | Cursor_mouse
  | Cursor_pencil
  | Cursor_pirate
  | Cursor_plus
  | Cursor_question_arrow
  | Cursor_right_ptr
  | Cursor_right_side
  | Cursor_right_tee
  | Cursor_rightbutton
  | Cursor_rtl_logo
  | Cursor_sailboat
  | Cursor_sb_down_arrow
  | Cursor_sb_h_double_arrow
  | Cursor_sb_left_arrow
  | Cursor_sb_right_arrow
  | Cursor_sb_up_arrow
  | Cursor_sb_v_double_arrow
  | Cursor_shuttle
  | Cursor_sizing
  | Cursor_spider
  | Cursor_spraycan
  | Cursor_star
  | Cursor_target
  | Cursor_tcross
  | Cursor_top_left_arrow
  | Cursor_top_left_corner
  | Cursor_top_right_corner
  | Cursor_top_side
  | Cursor_top_tee
  | Cursor_trek
  | Cursor_ul_angle
  | Cursor_umbrella
  | Cursor_ur_angle
  | Cursor_watch
  | Cursor_xterm
;;

let glyph_of_cursor = function
  | Cursor_id x -> x / 2 * 2 (* must be even *) 
  | Cursor_X_cursor -> 0
  | Cursor_arrow -> 2
  | Cursor_based_arrow_down -> 4
  | Cursor_based_arrow_up -> 6
  | Cursor_boat -> 8
  | Cursor_bogosity -> 10
  | Cursor_bottom_left_corner -> 12
  | Cursor_bottom_right_corner -> 14
  | Cursor_bottom_side -> 16
  | Cursor_bottom_tee -> 18
  | Cursor_box_spiral -> 20
  | Cursor_center_ptr -> 22
  | Cursor_circle -> 24
  | Cursor_clock -> 26
  | Cursor_coffee_mug -> 28
  | Cursor_cross -> 30
  | Cursor_cross_reverse -> 32
  | Cursor_crosshair -> 34
  | Cursor_diamond_cross -> 36
  | Cursor_dot -> 38
  | Cursor_dotbox -> 40
  | Cursor_double_arrow -> 42
  | Cursor_draft_large -> 44
  | Cursor_draft_small -> 46
  | Cursor_draped_box -> 48
  | Cursor_exchange -> 50
  | Cursor_fleur -> 52
  | Cursor_gobbler -> 54
  | Cursor_gumby -> 56
  | Cursor_hand1 -> 58
  | Cursor_hand2 -> 60
  | Cursor_heart -> 62
  | Cursor_icon -> 64
  | Cursor_iron_cross -> 66
  | Cursor_left_ptr -> 68
  | Cursor_left_side -> 70
  | Cursor_left_tee -> 72
  | Cursor_leftbutton -> 74
  | Cursor_ll_angle -> 76
  | Cursor_lr_angle -> 78
  | Cursor_man -> 80
  | Cursor_middlebutton -> 82
  | Cursor_mouse -> 84
  | Cursor_pencil -> 86
  | Cursor_pirate -> 88
  | Cursor_plus -> 90
  | Cursor_question_arrow -> 92
  | Cursor_right_ptr -> 94
  | Cursor_right_side -> 96
  | Cursor_right_tee -> 98
  | Cursor_rightbutton -> 100
  | Cursor_rtl_logo -> 102
  | Cursor_sailboat -> 104
  | Cursor_sb_down_arrow -> 106
  | Cursor_sb_h_double_arrow -> 108
  | Cursor_sb_left_arrow -> 110
  | Cursor_sb_right_arrow -> 112
  | Cursor_sb_up_arrow -> 114
  | Cursor_sb_v_double_arrow -> 116
  | Cursor_shuttle -> 118
  | Cursor_sizing -> 120
  | Cursor_spider -> 122
  | Cursor_spraycan -> 124
  | Cursor_star -> 126
  | Cursor_target -> 128
  | Cursor_tcross -> 130
  | Cursor_top_left_arrow -> 132
  | Cursor_top_left_corner -> 134
  | Cursor_top_right_corner -> 136
  | Cursor_top_side -> 138
  | Cursor_top_tee -> 140
  | Cursor_trek -> 142
  | Cursor_ul_angle -> 144
  | Cursor_umbrella -> 146
  | Cursor_ur_angle -> 148
  | Cursor_watch -> 150
  | Cursor_xterm -> 152
;;

(* The Caml version of the raw C primitive has the same name as the
   high level Caml function that calls it, to document the fact that
   you should not call the C primitive directly. *)
external set_cursor : int -> unit = "caml_gr_set_cursor";;
external unset_cursor : unit -> unit = "caml_gr_unset_cursor";;

let get_cursor, set_cursor =
 let cursor = ref Cursor_left_ptr in
 (fun () -> !cursor),
 (fun c ->
   if !cursor <> c then begin
     cursor := c;
     set_cursor (glyph_of_cursor c);
   end);;

external get_geometry : unit -> int * int * int * int = "caml_gr_get_geometry";;
        (* returns width, height, x, y of the graphics window *)

external get_modifiers : unit -> int = "caml_gr_get_modifiers";;
        (* returns modifiers as an integer *)

let button1 = 0x1
and button2 = 0x2
and button3 = 0x4
and button4 = 0x8
and button5 = 0x10
and shift = 0x100
and control = 0x200
and mod1 = 0x400
and mod2 = 0x800
and mod3 = 0x1000
and mod4 = 0x2000
and mod5 = 0x4000
and nomod = 0x0;;

external cut : string -> unit = "caml_gr_cut";;
        (* store string in the cut buffer *)

(* Redefinition of the events loop *)

type status = {
      mouse_x : int;
      mouse_y : int;
      button : bool;
      keypressed : bool;
      key : char; 
      modifiers : int;
};;

type event =
  | Button_down
  | Button_up
  | Key_pressed
  | Mouse_motion
  | Poll;;

external wait_next_event : event list -> status = "caml_gry_wait_event";;
external retrieve_events : unit -> unit = "caml_gry_retrieve_events";;

let mouse_pos () =
  let e = wait_next_event [Poll] in (e.mouse_x, e.mouse_y);;

let button_down () =
  let e = wait_next_event [Poll] in e.button;;

let read_key () =
  let e = wait_next_event [Key_pressed] in e.key;;

let key_pressed () =
  let e = wait_next_event [Poll] in e.keypressed;;

(* Additional primitives that operate on the screen only *)
external window_point_color : int -> int -> Graphics.color
  = "caml_gr_window_point_color";;
(** As [point_color] but read the information in the window display. *)

external anti_synchronize : unit -> unit = "caml_gr_anti_synchronize";;
(** Synchronize the backing store drawings from the window display:
  performs the inverse operation as the regular [synchronize] function. *)

(* This should be defined in regular Graphics to provide a mean to
   manage those flags with a stack discipline.  *)
let get_display_mode, set_display_mode =
  let display_mode_ref = ref false in
  (fun () -> !display_mode_ref),
  (fun b -> Graphics.display_mode b; display_mode_ref := b);;

(* This should be defined in regular Graphics. *)
let get_remember_mode, set_remember_mode =
  let remember_mode_ref = ref false in
  (fun () -> !remember_mode_ref),
  (fun b -> Graphics.remember_mode b; remember_mode_ref := b);;

(** Enable_display flag allows to inhibit display_mode commands. *)
let get_enable_display_mode, set_enable_display_mode =
  let enable_display_mode = ref true in
  (fun () -> !enable_display_mode),
  (fun b -> enable_display_mode :=  b);;

(** Synchronize according to [enable_display_mode]. *)
let synchronize () =
  let enable = get_enable_display_mode () in
  if enable then (
    Misc.debug_stop "Graphics.synchronize";
    Graphics.synchronize ()
  ) else (
    Misc.debug_stop "GraphicsY11.anti_synchronize";
    anti_synchronize ()
  );;

(** [display_mode] according to [enable_display_mode]. *)
let display_mode b =
  Misc.debug_endline ("GraphicsY11.display_mode " ^ string_of_bool b);
  let enable = get_enable_display_mode () in
  if enable then
   (Misc.debug_endline ("GraphicsY11.set_display_mode " ^ string_of_bool b);
    set_display_mode b);;

(** [point_color] according to [enable_display_mode]. *)
let point_color x y =
  let enable = get_enable_display_mode () in
  if enable then Graphics.point_color x y else window_point_color x y;;

(* Draw on the screen only. *)
let set_screen_only_mode () =
  (* Don't draw on the backing store canvas. *)
  set_remember_mode false;
  (* Draw on the screen display window. *)
  set_display_mode true;;

(* Draw in the memory only. *)
let set_backing_store_only_mode () =
  (* Draw on the backing store canvas. *)
  set_remember_mode true;
  (* Don't draw on the screen display window. *)
  set_display_mode false;;

(* Draw on both. *)
let set_both_mode () =
  set_remember_mode true;
  set_display_mode true;;

(* Set the drawing modes to the given arguments. *)
let restore_modes dm rm = set_remember_mode rm; set_display_mode dm;;

(* This functional computes the call [f x], after having remembered
   the inititial drawing modes (before calling [f x]) and, presumably
   set them by calling the function [set_mode].
   After the call [f x], the original drawing modes are left unaffected,
   even in case the computation of [f x] raises an exception. *)
let only_on set_mode f x =
  let dm, rm = get_display_mode (), get_remember_mode () in
  set_mode ();
  try
    let res = f x in
    restore_modes dm rm;
    res with
  | exn -> restore_modes dm rm; raise exn;;

(* This function performs [f x] on the screen display only,
   not affecting the backing store canvas.
   After the call, the drawing modes are left unaffected. *)
let only_on_screen f = only_on set_screen_only_mode f;;

(* Similar to [only_on_screen] for the backing store. *)
let only_on_backing_store f = only_on set_backing_store_only_mode f;;

let drawing_on_both f = only_on set_both_mode f;;

(* Graphics.sigio_signal is not exported. We declare it here again. *)
external sigio_signal: unit -> int = "caml_gr_sigio_signal";;

let init () =
  (* we disable the original Graphics event retrieveing system *)
  Sys.set_signal (sigio_signal ()) Sys.Signal_ignore;;
