Blob Blame History Raw
(***********************************************************************)
(*                                                                     *)
(*                 MLTk, Tcl/Tk interface of OCaml                     *)
(*                                                                     *)
(*    Francois Rouaix, Francois Pessaux, Jun Furuse and Pierre Weis    *)
(*               projet Cristal, INRIA Rocquencourt                    *)
(*            Jacques Garrigue, Kyoto University RIMS                  *)
(*                                                                     *)
(*  Copyright 2002 Institut National de Recherche en Informatique et   *)
(*  en Automatique and Kyoto University.  All rights reserved.         *)
(*  This file is distributed under the terms of the GNU Library        *)
(*  General Public License, with the special exception on linking      *)
(*  described in file LICENSE found in the OCaml source tree.          *)
(*                                                                     *)
(***********************************************************************)

(* $Id$ *)

open StdLabels

(* easy balloon help facility *)

open Tk
open Widget
open Protocol
open Support

(* switch -- if you do not want balloons, set false *)
let flag = ref true
let debug = ref false

(* We assume we have at most one popup label at a time *)
let topw = ref default_toplevel
and popupw = ref (Obj.magic dummy : message widget)

let configure_cursor w cursor =
  (* DDDDDDDDDIIIIIIIRRRRRRRRTTTTTTTTYYYYYYY *)
  Protocol.tkCommand [| TkToken (name w);
                    TkToken "configure";
                    TkToken "-cursor";
                    TkToken cursor |]

let put ~on: w ~ms: millisec mesg =
  let t = ref None in
  let cursor = ref "" in

  let reset () =
      begin
        match !t with
          Some t -> Timer.remove t
        | _ -> ()
      end;
      (* if there is a popup label, unmap it *)
      if Winfo.exists !topw && Wm.state !topw <> "withdrawn" then
        begin
          Wm.withdraw !topw;
          if Winfo.exists w then configure_cursor w !cursor
        end
  and set ev =
    if !flag then
      t := Some (Timer.add ~ms: millisec ~callback: (fun () ->
        t := None;
        if !debug then
          prerr_endline ("Balloon: " ^ Widget.name w);
        update_idletasks();
        Message.configure !popupw ~text: mesg;
        raise_window !topw;
        Wm.geometry_set !topw (* 9 & 8 are some kind of magic... *)
          ("+"^(string_of_int (ev.ev_RootX + 9))^
           "+"^(string_of_int (ev.ev_RootY + 8)));
        Wm.deiconify !topw;
        cursor := cget w `Cursor;
        configure_cursor w "hand2"))
  in

  List.iter [[`Leave]; [`ButtonPress]; [`ButtonRelease]; [`Destroy];
             [`KeyPress]; [`KeyRelease]]
    ~f:(fun events -> bind w ~events ~extend:true ~action:(fun _ -> reset ()));
  List.iter [[`Enter]; [`Motion]] ~f:
    begin fun events ->
      bind w ~events ~extend:true ~fields:[`RootX; `RootY]
        ~action:(fun ev -> reset (); set ev)
    end

let init () =
  let t = Hashtbl.create 101 in
  Protocol.add_destroy_hook (fun w ->
    Hashtbl.remove t w);
  topw := Toplevel.create default_toplevel;
  Wm.overrideredirect_set !topw true;
  Wm.withdraw !topw;
  popupw := Message.create !topw ~name: "balloon"
              ~background: (`Color "yellow") ~aspect: 300;
  pack [!popupw];
  bind_class "all" ~events: [`Enter] ~extend:true ~fields:[`Widget] ~action:
    begin fun w ->
      try Hashtbl.find t w.ev_Widget
      with Not_found ->
        Hashtbl.add t w.ev_Widget ();
        let x = Option.get w.ev_Widget ~name: "balloon" ~clas: "Balloon" in
        if x <> "" then put ~on: w.ev_Widget ~ms: 1000 x
    end