--- /dev/null
+(* $Id$ *)
+
+open Gaux
+open Gtk
+open GObj
+open GContainer
+
+open Utils
+open Common
+open Property
+
+(* possible children; used to make the menus *)
+let widget_add_list =
+ [ "vbox"; "hbox"; "vbutton_box"; "hbutton_box"; "fixed";
+ "frame"; "aspect_frame"; "handle_box"; "event_box";
+ "hseparator"; "vseparator"; "statusbar"; "label"; "notebook";
+ "color_selection";
+ "button";
+ "toggle_button"; "check_button"; "radio_button"; "scrolled_window";
+
+ "entry"; "spin_button"; "combo"; "clist"; "toolbar"]
+
+
+(*********** selection ***********)
+
+let selection = ref ""
+let window_selection = ref ""
+
+
+(**************** signals class ***************)
+
+class tiwidget_signals ~signals =
+ let name_changed : string signal = signals in
+ object
+ val after = false
+ method after = {< after = true >}
+ method name_changed = name_changed#connect ~after
+ end
+
+
+(************* class type ***************)
+(* the ti<gtkwidget> classes encapsulate the corresponding gtk
+ widget which will be in the gtk-window and a tree item
+ labelled with the name of the widget which will be in the
+ tree-window.
+ all these classes have the same following interface *)
+
+class virtual tiwidget0 = object
+ method virtual widget : GObj.widget
+ method virtual connect_event : GObj.event_signals
+ method virtual parent : tiwidget0 option
+ method virtual set_parent : tiwidget0 -> unit
+ method virtual base : GObj.widget
+ method virtual tree_item : GTree2.tree_item
+ method virtual tree : GTree2.tree
+ method virtual children : (tiwidget0 * Gtk.Tags.pack_type) list
+ method virtual name : string
+ method virtual proplist : (string * prop) list
+ method virtual add_to_proplist : (string * prop) list -> unit
+ method virtual change_name_in_proplist : string -> string -> unit
+ method virtual set_property : string -> string -> unit
+ method virtual forall : callback:(tiwidget0 -> unit) -> unit
+ method virtual remove : tiwidget0 -> unit
+(* method virtual add_child_with_name : string -> string -> pos:int -> tiwidget0 *)
+ method virtual add_children : ?pos:int -> yywidget_tree -> unit
+ method virtual add_children_wo_undo : ?pos:int -> yywidget_tree -> string
+ method virtual remove_me : unit -> unit
+ method virtual remove_me_without_undo : unit -> unit
+ method virtual emit_code : Format.formatter -> char list -> unit
+ method virtual emit_init_code : Format.formatter -> packing:string -> unit
+ method virtual emit_method_code : Format.formatter -> unit
+ method virtual emit_initializer_code : Format.formatter -> unit
+ method virtual save : Format.formatter -> unit
+ method virtual copy : unit -> unit
+ method virtual connect : tiwidget_signals
+ method virtual disconnect : GtkSignal.id -> bool
+ method virtual child_up : tiwidget0 -> unit
+ method virtual up : unit -> unit
+ method virtual child_down : tiwidget0 -> unit
+ method virtual down : unit -> unit
+ method virtual next : tiwidget0
+ method virtual next_child : tiwidget0 -> tiwidget0
+ method virtual last : tiwidget0
+ method virtual prev : tiwidget0
+ method virtual set_full_menu : bool -> unit
+end
+
+class virtual window_and_tree0 = object
+ method virtual tiwin : tiwidget0
+(* method virtual tree_window : window *)
+ method virtual change_selected : tiwidget0 -> unit
+ method virtual remove_sel : tiwidget0 -> unit
+ method virtual add_param : char
+ method virtual remove_param : char -> unit
+(* method virtual emit : unit -> unit *)
+end
+
+(* forward declaration of function new_widget *)
+let new_tiwidget :
+ (classe:string -> ?pos:int -> name:string ->parent_tree:GTree2.tree ->
+ ?insert_evbox:bool -> ?listprop:(string * string) list -> window_and_tree0 -> tiwidget0) ref =
+ ref (fun ~classe ?pos ~name ~parent_tree ?insert_evbox ?listprop w -> failwith "new_tiwidget")
+
+
+let widget_map = Hashtbl.create 17
+
+(* list of names of radio_buttons (for groups) *)
+let radio_button_pool = ref []
+
+
+(************* window creation class *************)
+(* an instance of this class is created for each window opened
+ in radtest. It contains the tree window and the gtk window (tiwin) *)
+
+class window_and_tree ~name =
+ let tree_window = GWindow.window ~show:true ~title:(name ^ "-Tree") () in
+ let vbox = GPack.vbox ~spacing:2 ~packing:tree_window#add () in
+ let root_tree = GTree2.tree ~packing:vbox#pack ~selection_mode:`EXTENDED () in
+ let project_tree_item = GTree2.tree_item () in
+ let label = GMisc.label ~text:name ~xalign:0. ~yalign:0.5
+ ~packing:project_tree_item#add () in
+
+ object(self)
+
+ inherit window_and_tree0
+
+(* the params of the window class; because the class clist needs a param
+ I suppose there will be no more than 26 clists in a single window *)
+ val param_list = Array.create 26 false
+
+ method add_param =
+ let i = ref 0 in
+ while param_list.(!i) do incr i done;
+ param_list.(!i) <- true;
+ char_of_int (97 + !i)
+
+ method remove_param c =
+ param_list.(int_of_char c - 97) <- false
+
+ method private param_list =
+ let r = ref [] in
+ for i = 25 downto 0 do
+ if Array.unsafe_get param_list i then r := (char_of_int (i+97)) :: !r
+ done;
+ !r
+
+(* I use magic here because the real initialization is done
+ below in the initializer part. It can't be done here because
+ of the call to self *)
+ val mutable tiwin = (Obj.magic 0 : tiwidget0)
+
+ method tiwin = tiwin
+ method tree_window = tree_window
+
+ method project_tree_item = project_tree_item
+
+(* the selected item in this window *)
+ val mutable selected = (None : tiwidget0 option)
+
+(* what to do when a new item is selected.
+ this method is passed to all the tiwidgets (by the select_fun
+ parameter) and they will call it when they are clicked on;
+ she is also called when changing the selection the arrow keys
+ (see in the initializer part) *)
+ method change_selected sel =
+ match selected with
+ | None ->
+ selected <- Some sel;
+ sel#tree_item#misc#set_state `SELECTED;
+ sel#base#misc#set_state `SELECTED;
+ Propwin.show sel
+ | Some old_sel ->
+ if sel = old_sel then begin
+ selected <- None;
+ sel#base#misc#set_state `NORMAL;
+ sel#tree_item#misc#set_state `NORMAL
+ end else begin
+ old_sel#tree_item#misc#set_state `NORMAL;
+ old_sel#base#misc#set_state `NORMAL;
+ selected <- Some sel;
+ sel#tree_item#misc#set_state `SELECTED;
+ sel#base#misc#set_state `SELECTED;
+ Propwin.show sel
+ end
+
+(* the tiwidget tiw is being removed; if it was selected,
+ put the selection to None *)
+ method remove_sel tiw =
+ match selected with
+ | Some sel when sel = tiw -> selected <- None
+ | _ -> ()
+
+(* emits the code corresponding to this window *)
+ method emit c = tiwin#emit_code c self#param_list;
+
+ method delete () =
+ tiwin#remove_me_without_undo ();
+ tree_window#destroy ();
+
+ initializer
+ tiwin <- !new_tiwidget ~classe:"window" ~name ~parent_tree:root_tree
+ (self : #window_and_tree0 :> window_and_tree0);
+
+ tiwin#connect#name_changed ~callback:
+ (fun n -> label#set_text n; tree_window#set_title (n ^ "-Tree"));
+
+ Propwin.show tiwin;
+
+ tree_window#event#connect#key_press ~callback:
+ begin fun ev ->
+ let state = GdkEvent.Key.state ev in
+ let keyval = GdkEvent.Key.keyval ev in
+ if keyval = GdkKeysyms._Up then begin
+ match selected with
+ | None -> ()
+ | Some t ->
+ if List.mem `CONTROL state then t#up ()
+ else try
+ self#change_selected t#prev
+ with Not_found -> ()
+ end
+ else if keyval = GdkKeysyms._Down then begin
+ match selected with
+ | None -> ()
+ | Some t ->
+ if List.mem `CONTROL state then t#down ()
+ else try
+ self#change_selected t#next
+ with Not_found -> ()
+ end;
+ GtkSignal.stop_emit ();
+ true
+ end;
+ ()
+ end
+
+
+
+(***************** class implementation *****************)
+(* this is the base class of the ti<gtkwidget> hierarchy.
+ all these classes will inherit from tiwidget, but without
+ adding new methods. In this way all the classes have the
+ same interface and we can use them in lists, pass them to
+ functions without caring on the type.
+ All methods needed by any of the classes are defined in
+ tiwidget but if a method is not pertinent in tiwidget
+ it has for implementation:
+ failwith "<name of the method>"
+ the real implementation of the method is done in the
+ class (or classes) in which it is needed (or sometimes
+ in tiwidget anyway).
+ Additionally, to workaround some problem with recursive types
+ the type of the (public) methods of tiwidget is defined in
+ tiwidget0 of which tiwidget inherits.
+ The parent_tree parameter is the tree in which the
+ tiwidget#tree_item will be inserted at position :pos.
+*)
+
+class virtual tiwidget ~name ~parent_tree:(parent_tree : GTree2.tree) ~pos
+ ~widget ?(insert_evbox=true) (parent_window : window_and_tree0) =
+object(self)
+
+ inherit tiwidget0
+ inherit has_ml_signals
+
+ val evbox =
+ if insert_evbox then
+ let ev = GBin.event_box () in ev#add widget#coerce; Some ev
+ else None
+
+(* used only for windows delete_event *)
+ method connect_event = failwith "tiwidget::connect_event"
+
+ val widget = widget#coerce
+ method widget = widget
+
+ val mutable parent = None
+ method set_parent p = parent <- Some p
+ method parent = parent
+ method private sure_parent =
+ match parent with
+ | None -> failwith "sure_parent"
+ | Some p -> p
+
+ method base =
+ match evbox with
+ | None -> widget#coerce
+ | Some ev -> ev#coerce
+
+(* this is the name used in new_tiwidget for the creation
+ of an object of this class *)
+ val mutable classe = ""
+
+ val tree_item = GTree2.tree_item ()
+ method tree_item = tree_item
+
+ val mutable stree = GTree2.tree ()
+ method tree = stree
+
+ val label = GMisc.label ~text:name ~xalign:0. ~yalign:0.5 ()
+
+ val mutable name : string = name
+ method name = name
+
+(* this is the complete name for the creation of the widget
+ in lablgtk e.g. GPack.vbox; used in emit_init_code *)
+ method private class_name = ""
+
+ val mutable proplist : (string * prop) list = []
+ method proplist = proplist
+ method private get_mandatory_props = []
+
+ method add_to_proplist plist = proplist <- proplist @ plist
+
+(* for children of a box *)
+ method change_name_in_proplist : string -> string -> unit =
+ fun _ _ -> ()
+ method set_property name value_string = try
+ (List.assoc name proplist)#set value_string
+ with Not_found -> Printf.printf "Property not_found %s, %s\n" name value_string;
+ flush stdout
+
+ method private get_property name =
+ (List.assoc name proplist)#get
+
+
+(* the proplist with some items removed e.g. the expand... in a box
+ used for saving and emitting code *)
+ method private emit_clean_proplist =
+ List.fold_left ~f:(fun l p -> List.remove_assoc p l)
+ ~init:proplist
+ ([ "name"; "expand"; "fill"; "padding" ] @ self#get_mandatory_props)
+(* method private emit_clean_proplist plist =
+ List.fold_left ~init:plist ~f:
+ (fun pl propname -> List.remove_assoc propname pl)
+ [ "name"; "expand"; "fill"; "padding" ]
+*)
+
+ method private save_clean_proplist =
+ List.fold_left ~f:(fun l p -> List.remove_assoc p l)
+ ~init:proplist ("name" :: self#get_mandatory_props)
+(* method private save_clean_proplist =
+ List.remove_assoc "name" proplist *)
+
+ val mutable children : (tiwidget0 * Gtk.Tags.pack_type) list = []
+ method children = children
+ method forall =
+ fun ~callback -> List.iter (List.map children ~f:fst) ~f:callback
+
+(* encapsulate container#add and container#remove
+ they are here because they depend on the type of the widget:
+ e.g.: gtkbin->add scrolled_window->add_with_viewport box->pack *)
+ method private add = failwith (name ^ "::add")
+ method remove = failwith (name ^ "::remove")
+
+
+(* removes self from his parent;
+ will be different for a window *)
+ method remove_me () =
+ let sref = ref "" in
+ self#save_to_string sref;
+ let pos = list_pos ~item:(self : #tiwidget0 :> tiwidget0)
+ (List.map self#sure_parent#children ~f:fst) in
+ let lexbuf = Lexing.from_string !sref in
+ let node = Load_parser.widget Load_lexer.token lexbuf in
+ add_undo (Add (self#sure_parent#name, node, pos));
+ self#remove_me_without_undo ()
+
+ method remove_me_without_undo () =
+(* it should be enough to only recursively remove the children from the
+ name_list and do the tip#remove and tip#tree#remove
+ only for self *)
+ self#forall ~callback:(fun tiw -> tiw#remove_me_without_undo ());
+ parent_window#remove_sel (self : #tiwidget0 :> tiwidget0);
+ match parent with
+ | None -> failwith "remove without parent"
+ | Some (tip : #tiwidget0) ->
+ tip#tree#remove tree_item;
+ tip#remove (self : #tiwidget0 :> tiwidget0);
+ name_list := list_remove !name_list ~f:(fun n -> n=name);
+ Hashtbl.remove widget_map name;
+ Propwin.remove name
+
+(* used for undo *)
+ method private remove_child_by_name name () =
+ let child = fst (List.find children
+ ~f:(fun (ch, _) -> ch#name = name)) in
+ child#remove_me ()
+
+(* for most widgets we make a child with new_tiwidget and then add it
+ to self; for toolbars we use toolbar#insert_button... *)
+ method private make_child = !new_tiwidget
+
+(* adds a child and shows his properties;
+ used when adding a child by the menu or DnD *)
+ method private add_child classe ?name ?(undo = true) ?(affich = true) ?(pos = -1) ?(listprop = []) () =
+ let name = match name with
+ | None -> make_new_name classe
+ | Some n -> n in
+ let child = self#make_child ~classe ~name ~parent_tree:stree parent_window ~pos ~listprop in
+ child#set_parent (self : #tiwidget0 :> tiwidget0);
+ self#add child ~pos;
+ if affich then Propwin.show child;
+ if undo then add_undo (Remove name);
+ child
+
+
+(* adds the subtree saved in the Node *)
+ method add_children ?(pos = -1) node =
+ let child_name = self#add_children_wo_undo node ~pos in
+ add_undo (Remove child_name)
+
+ method add_children_wo_undo ?(pos = -1) (Node (child, children)) =
+ let classe, name, property_list = child in
+ let rname = change_name name in
+ let tc = self#add_child classe ~name:rname ~undo:false ~affich:false ~pos ~listprop:property_list () in
+ List.iter (List.rev children)
+ ~f:(fun c -> tc#add_children_wo_undo c; ());
+ List.iter property_list ~f:(fun (n,v) -> tc#set_property n v);
+ rname
+
+(* only a tiwindow can emit code *)
+ method emit_code = failwith "emit_code"
+
+(* some methods for emitting code *)
+(* this one calculates the expand, fill and padding parameters
+ of a box child *)
+ method private get_packing packing =
+ let aux name =
+ let prop = List.assoc name proplist in
+ if prop#modified then " ~" ^ name ^ ":" ^ prop#code else ""
+ in
+ let efp = try
+ (aux "expand") ^ (aux "fill") ^ (aux "padding")
+ with Not_found -> "" in
+ if efp = "" then ("~packing:" ^ packing)
+ else ("~packing:(" ^ packing ^ efp ^ ")")
+
+(* this one emits the declaration code of the widget *)
+ method emit_init_code formatter ~packing =
+ Format.fprintf formatter "@ @[<hv 2>let %s =@ @[<hov 2>%s"
+ name self#class_name;
+ List.iter self#get_mandatory_props
+ ~f:begin fun name ->
+ Format.fprintf formatter "@ ~%s:%s" name
+ (List.assoc name proplist)#code
+ end;
+ let packing = self#get_packing packing in
+ if packing <> "" then Format.fprintf formatter "@ %s" packing;
+ self#emit_prop_code formatter;
+ Format.fprintf formatter "@ ()@ in@]@]"
+
+(* this one emits the properties which do not have their
+ default value; used by emit_init_code *)
+ method private emit_prop_code formatter =
+ let mandatory = self#get_mandatory_props in
+ List.iter self#emit_clean_proplist ~f:
+ begin fun (name, prop) ->
+ if List.mem name mandatory then () else
+ if prop#modified then
+ Format.fprintf formatter "@ ~%s:%s" prop#name prop#code
+ end
+
+(* this one emits the method returning this widget *)
+ method emit_method_code formatter =
+ Format.fprintf formatter "@ method %s = %s" name name;
+
+(* emits the code in the initializer part for this widget *)
+ method emit_initializer_code _ = ()
+
+(* for saving the project to a file. Used also by copy and cut *)
+ method private save_start formatter =
+ Format.fprintf formatter "@\n@[<2><%s name=%s>" classe name;
+ List.iter
+ ~f:(fun p -> Format.fprintf formatter
+ "@\n%s=\"%s\"" p (List.assoc p proplist)#get)
+ self#get_mandatory_props
+
+
+ method private save_end formatter =
+ Format.fprintf formatter "@]@\n</%s>" classe
+
+ method save formatter =
+ self#save_start formatter;
+ List.iter self#save_clean_proplist ~f:
+ (fun (name, prop) ->
+ if prop#modified then
+ Format.fprintf formatter "@\n%s=%s" name prop#save_code);
+ self#forall ~callback:(fun w -> w#save formatter);
+ self#save_end formatter
+
+
+ method private save_to_string string_ref =
+ let b = Buffer.create 80 in
+ let f = Format.formatter_of_buffer b in
+ self#save f;
+ Format.pp_print_flush f ();
+ string_ref := Buffer.contents b
+
+ method private copy_to_sel selection = self#save_to_string selection
+
+ method copy () = self#copy_to_sel selection
+
+ method private cut () =
+ self#copy ();
+ self#remove_me ()
+
+ method private paste () =
+ let lexbuf = Lexing.from_string !selection in
+ let node = Load_parser.widget Load_lexer.token lexbuf in
+ self#add_children node
+
+
+(* ML signal used when the name of the widget is changed *)
+ val name_changed : string signal = new signal
+ method connect = new tiwidget_signals ~signals:name_changed
+ method private call_name_changed = name_changed#call
+
+
+(* this is necessary because gtk_tree#remove deletes the tree
+ when removing the last item *)
+(* suppressed this in gtktree2
+ method new_tree () =
+ stree <- GTree2.tree;
+ tree_item#set_subtree stree;
+ tree_item#expand ()
+*)
+
+(* when full_menu is true we use the menu else the restricted menu *)
+ val mutable full_menu = true
+ method set_full_menu b = full_menu <- b
+
+(* the menu for this widget
+ This menu is recalculated when one clicks on the 3rd button.
+ There is nothing to do e.g. when the name of the widget changes,
+ it will change in the menu the next time. *)
+ method private menu ~time = self#restricted_menu ~time
+
+(* the restricted menu for this widget
+ used for containers when they are full *)
+ method private restricted_menu ~time =
+ let menu = GMenu.menu () in
+ let mi_remove = GMenu.menu_item ~packing:menu#append ~label:"remove" ()
+ and mi_cut = GMenu.menu_item ~packing:menu#append ~label:"Cut" ()
+ and mi_copy = GMenu.menu_item ~packing:menu#append ~label:"Copy" () in
+ mi_remove#connect#activate ~callback:self#remove_me;
+ mi_copy#connect#activate ~callback:self#copy;
+ mi_cut#connect#activate ~callback:self#cut;
+ menu#popup ~button:3 ~time
+
+(* changes all that depends on the name *)
+ method private set_new_name new_name =
+ if test_unique new_name then begin
+ Hashtbl.remove widget_map name;
+ Hashtbl.add widget_map ~key:new_name
+ ~data:(self : #tiwidget0 :> tiwidget0);
+ if (classe = "radio_button") then begin
+ radio_button_pool := new_name ::
+ (list_remove !radio_button_pool ~f:(fun x -> x = name));
+ List.iter
+ ~f:(fun x -> Propwin.update (Hashtbl.find widget_map x) false)
+ !radio_button_pool
+ end;
+ label#set_text new_name;
+ let old_name = name in
+ name <- new_name;
+(* Propwin.change_name old_name new_name; *)
+ name_list :=
+ new_name :: (list_remove !name_list ~f:(fun n -> n=old_name));
+ begin match self#parent with
+ | None -> ()
+ | Some p -> p#change_name_in_proplist old_name new_name
+ end;
+ self#call_name_changed new_name;
+ true
+ end
+ else begin
+ message_name ();
+ Propwin.update self true;
+ false
+ end
+
+
+(* moves the present tiw up in his parents' children list *)
+(* does something only when the parent is a box *)
+ method child_up = fun _ -> ()
+
+ method up () = match parent with
+ | None -> ()
+ | Some t -> t#child_up (self : #tiwidget0 :> tiwidget0)
+
+ method child_down = fun _ -> ()
+
+ method down () = match parent with
+ | None -> ()
+ | Some t -> t#child_down (self : #tiwidget0 :> tiwidget0)
+
+
+(* get the next tiwidget in the tree (used with Down arrow) *)
+ method next =
+ if children <> [] then fst (List.hd children)
+ else begin
+ match parent with
+ | None -> raise Not_found
+ | Some p -> p#next_child (self : #tiwidget0 :> tiwidget0)
+ end
+
+ method next_child child =
+ let _, tl = cut_list ~item:child (List.map ~f:fst children) in
+ match tl with
+ | ch :: next :: _ -> next
+ | ch :: [] -> begin
+ match parent with
+ | None -> raise Not_found
+ | Some p -> p#next_child (self : #tiwidget0 :> tiwidget0)
+ end
+ | _ -> failwith "next_child"
+
+(* get the last child of the last child ... of our last child.
+ Used by prev. *)
+ method last =
+ if children = [] then (self : #tiwidget0 :> tiwidget0)
+ else (fst (List.hd (List.rev children)))#last
+
+(* get the previous tiwidget in the tree (used with Up arrow) *)
+ method prev =
+ match parent with
+ | None -> raise Not_found
+ | Some p ->
+ let hd, _ = cut_list ~item:(self : #tiwidget0 :> tiwidget0)
+ (List.map ~f:fst p#children) in
+ match hd with
+ | [] -> p
+ | h :: _ -> h#last
+
+ initializer
+ Hashtbl.add widget_map ~key:name ~data:(self : #tiwidget0 :> tiwidget0);
+ name_list := name :: !name_list;
+ parent_tree#insert tree_item ~pos;
+ tree_item#set_subtree stree;
+ tree_item#add label#coerce;
+ tree_item#expand ();
+
+ proplist <- proplist @
+ [ "name",
+ new prop_string ~name:"name" ~init:name ~set:self#set_new_name;
+ "width", new prop_int ~name:"width" ~init:"-2"
+ ~set:(fun v -> widget#misc#set_geometry ~width:v (); true);
+ "height", new prop_int ~name:"height" ~init:"-2"
+ ~set:(fun v -> widget#misc#set_geometry ~height:v (); true) ];
+
+ self#add_signal name_changed;
+
+ tree_item#event#connect#button_press ~callback:
+ (fun ev -> match GdkEvent.get_type ev with
+ | `BUTTON_PRESS ->
+ if GdkEvent.Button.button ev = 1 then begin
+ parent_window#change_selected
+ (self : #tiwidget0 :> tiwidget0);
+ end
+ else if GdkEvent.Button.button ev = 3 then begin
+ if full_menu
+ then self#menu ~time:(GdkEvent.Button.time ev)
+ else self#restricted_menu ~time:(GdkEvent.Button.time ev);
+ end;
+ GtkSignal.stop_emit ();
+ true
+ | _ -> false);
+ ()
+end
+