PreviousUpNext

15.4.1337  src/lib/tk/src/toolkit/tree-list-g.pkg

## tree-list-g.pkg
## (C) 1999, Albert Ludwigs Universität Freiburg
## Author: bu

# Compiled by:
#     src/lib/tk/src/toolkit/sources.sublib



# ***************************************************************************
# A hierarchical Listbox -- tree-lie browsing and selection.
# **************************************************************************



###                    "The essence of mathematics lies in its freedom."
###
###                                               -- Georg Cantor 



api Treelist_Callbacks {

          Part_Ilk;     #  SML-necessity since no HO-generics 
       eqtype Node_Info;    #  SML-necessity since no HO-generics 
          Subnode_Info; #  SML-necessity since no HO-generics 
          Path;         #  SML-necessity since no HO-generics 

        content_label_action:  
           { path: Path, was: String, cc: String -> Void } -> Void;
           # fired whenever a content label is activated. 
           # Should be a modal action.

        focus_change_notifier:  
           { changed_at: List( Path ) } -> Void;
           # fired whenever a folder label or a folder icon is modified; 
           # should be used if tree_list_g is nonmodally coupled over  
           # gui_state with a notepad.

        objtree_change_notifier :
           { changed_at: Path } -> Void;
           # fired whenever the tree-package (gui_state) has been modified -
           # e.g. as a consequence of an internal drag-drop.
           # Used for rehresh`s of other views.

        open_close_notifier:  
           { is_open: Bool, changed_at: List( Path ) } -> Void;
           # fired whenever a folder label or a folder icon is opened; 
           # can be used if internal tree is incrementally .

        error_action:           String -> Void;
           # fired whenever illegal drag-drop-operations are attempted. 
           # Should be a modal action.
   };


api Join {              #  Only there for stupid SML-reasons 

      package  m:   Ptree_Part_Class;                           # Ptree_Part_Class      is from   src/lib/tk/src/toolkit/tree_object_class.api
      package  a:   Treelist_Callbacks;                         # Treelist_Callbacks    is from   src/lib/tk/src/toolkit/tree-list-g.pkg
      package  clipboard:  Clipboard;                           # Clipboard             is from   src/lib/tk/src/toolkit/clipboard-g.pkg
      sharing    a::Part_Ilk     ==   m::Part_Ilk; 
      sharing    clipboard::Part ==   m::Cb_Objects; 
      sharing    a::Node_Info    ==   m::Node_Info;
      sharing    a::Subnode_Info ==   m::Subnode_Info;
      sharing    a::Path         ==   m::Path;
   };

generic package tree_list_g (package  s:  Join;)                # Join                  is from   src/lib/tk/src/toolkit/tree-list-g.pkg
: (weak)
api {
    Scale   = MICRO | MINI | DEMO;
    Config      = { height:      Ref( Int ),             #  Default 300 
                       width:      Ref( Int ),             #  Default 400 
                       scrollbars: Ref( tk::Scrollbars_At ),#  Default NOWHERE
                       no_icons:   Ref( Bool ),            # no icons used;
                                                        # default FALSE
                       std_icons:  Ref( Bool ),            # use icons speci-
                                                        # fied in M or use 
                                                        # std-icons; 
                                                        # default TRUE
                       scale_factor: Ref( Scale )          # scales display,
                                                        # default MICRO
                      };
    my_config:     Config;

    create_canvas:  List( s::m::Part_Ilk ) -> tk::Widget;

    upd_guistate:  s::m::Path -> List( s::m::Part_Ilk ) -> Void;
    get_guistate:  Void -> List( s::m::Part_Ilk );
    refresh:       s::m::Path -> Void;
    refresh_label: Void -> Void;

    get_selected:  Void -> List( s::m::Part_Ilk );
    set_selected:  List( s::m::Path ) -> Void;
}

{

    include package   s;
    include package   tk;
    include package   global_configuration;

  #  *********************************************************************** 
  #                                                                          
  #  Configuration ...                                                       
  #                                                                          
  #  *********************************************************************** 


     Scale = MICRO | MINI | DEMO;       #  According to icons / filer / * 

     Config
        =
        {   height:         Ref( Int ),             #  Default 300 
            width:         Ref( Int ),             #  Default 400 
            scrollbars:    Ref( tk::Scrollbars_At ),#  Default NOWHERE 
            no_icons:      Ref( Bool ),            /* no icons used;
                                                   default FALSE */
            std_icons:     Ref( Bool ),            #  Default TRUE 
            scale_factor:  Ref(  Scale )            /* scales display,
                                                   default 1 */  
        };
                                                                                  my
    my_config
        =
        {    height        => REF (300),
             width        => REF (400),
             scrollbars   => REF (tk::AT_RIGHT),
             no_icons     => REF FALSE,
             std_icons    => REF TRUE,
             scale_factor => REF MICRO
        };

    fun debugmsg msg
        =
        debug::print 11 ("tree_list_g: " + msg);
                                                                                my
    default_printmode
        =
        {   mode => print::long,
            printdepth => 100,
            height => NULL,
            width => NULL
        };  #  the value is temporary 

    fun name2string x
        =
        m::string_of_name (m::path2name x) default_printmode;


    fun scale_to_string MICRO => "micro";
       scale_to_string MINI  => "mini";
       scale_to_string DEMO  => "demo"; end;


  #  Some Display Parameters 

  /* ****************************************************************

      <  in2  >    -
              *    ^
              *    | <hi
      <in1 >****** -   -
            *    *     ^
            * ** ****  |
            *    *     | <box_height
            ******     _
      <in3          >
            <    >box_width
     **************************************************************** */

                                                                                my
    box_height   = 9;                                                            my
    box_width    = 8;                                                            my
    box_w_middle = 4;                                                            my
    box_h_middle = 5;                                                            my
    icon_width   = 9; /* in realitaet 12 !!! */                                  my
    in1          = 4;                                                            my
    in2          = 9;                                                            my
    in3          = 12;                                                           my
    hi           = 9;

    # The crosshair for dragging an item:
                                                                             my
    drag_cursor = CURSOR (XCURSOR (make_cursor_name("fleur"), NULL));

    fun height n
        =
        coordinate (0, n * (hi+box_height));

  #  *********************************************************************** 
  #                                                                          
  #  The internal object-tree                                                
  #                                                                          
  #  *********************************************************************** 

  # The internal object tree contains not only the pure data-package
  # with labels, icons and object items, but also a decent abstraction of
  # the state of the canvas, i.e. which folders are displayed
  # open or closed ("is_open"), which ones are selected ("is_selct"), etc.
  #
  # For efficiency reasons, even more information is stored:
  # - namely hooks to redisplay functions for local labels
  # - and the CItems used in order to move substrees efficiently.
  #   (not yet implemented)
  #
  # However, there no real good reason for the fact, that I dicided to
  # implement obj_tree in its own in this class here instead of providing
  # a new instance of object_to_tree_object_g. Better patternmatch, and efficiency,
  # maybe. But the price is code duplicity for critical functions like update.


     Leaf_Type =   { lab:        (m::basic::Part_Ilk, m::Subnode_Info),
                        path:       m::Path,
                        icon:       Null_Or( Icon_Variety ), 
                        cids:       (Canvas_Item_Id, Canvas_Item_Id, Canvas_Item_Id, Canvas_Item_Id),
                        is_selct:   Ref( Bool ),
                        rd_hook:     Ref( Null_Or( Void_Callback ) )
                       };

     Folder_Type( A_obj_tree ) = 
                       { lab:        m::Node_Info,
                        path:       m::Path,
                        subtrees:   List( A_obj_tree ), 
                        icon:       Null_Or( Icon_Variety ), 
                        cids:       (Canvas_Item_Id, Canvas_Item_Id, Canvas_Item_Id ,
                                    Canvas_Item_Id, Canvas_Item_Id, Canvas_Item_Id, Canvas_Item_Id),
                        is_open:    Ref( Bool ),
                        is_selct:   Ref( Bool ),
                        rd_hook:     Ref( Null_Or( Void_Callback ) ) };

     Obj_Tree = LEAF      Leaf_Type
                  | FOLDER    Folder_Type( Obj_Tree );

    fun get_folder (folder x) = x;
    fun get_leaf   (leaf x)   = x;

    fun fst (x, y) = x;
    fun snd (x, y) = y;

    fun convert_ft ( { lab, path, icon, cids, is_selct, rd_hook, ... } 
                  : Folder_Type( A_obj_tree )) = 
        { my (x1, x2, x3, _, _, _, _) = cids; 
                { lab, path, icon, 
                 cids=>(x1, x2, x3),
                 is_selct,
                 rd_hook  => rd_hook };
        };


    fun length []             => 0;
       length ((leaf _) . rrr)  => 1 + length rrr;
       length ((folder { is_open, subtrees, ... } ) . rrr) => 
               1 + (if *is_open  length subtrees; else 0;fi) + (length rrr); end; 

    fun relabel (path, _) obs = 
        { fun rel p [] => [];
               rel p (a as (leaf { lab, path, icon, cids,
                       is_selct, rd_hook } ) . rrr) => 
                       leaf { lab, path=>m::path_abs (list::reverse p, snd (m::path_rep path)), 
                            icon, cids, is_selct,
                            rd_hook }
                       . (rel p rrr);
               rel p (a as (aob as (folder { lab, path, subtrees, icon, cids,
                                           is_open, is_selct, rd_hook } )) . rrr) => 
                       folder { lab, path => m::path_abs (list::reverse p, NULL),
                              subtrees => rel (lab . p) subtrees,
                              icon, cids,
                              is_open, is_selct,
                              rd_hook  => REF NULL } 
                       . (rel p rrr); end; 
          rel (list::reverse path) obs; };

    exception WRONG_UPDATE;

    fun get_subtrees (folder { subtrees, ... } ) => subtrees;
       get_subtrees _ => raise exception WRONG_UPDATE; end;

    fun update clean path ob [] => [];
       update clean path ob x =>
        {   fun upd path [] => [];
                  #  search for leaf on leaf >>> 
                 upd ([], THE x) ((aob as (leaf { lab, ... } )) . rrr) => 
                      (case (m::basic::ord (x, fst lab))   
                        EQUAL => { clean aob; ob . rrr;};
                       _     => aob . (upd ([], THE x) rrr); esac);
                  #  search for leaf on fold >>> 
                 upd  ([], THE x) (aaa . rrr) => aaa . upd ([], THE x) rrr; 
                  #  replace folder by folder content >>> 
                 upd  ([x], NULL)((aob as (folder { lab, path, subtrees, icon, cids,
                                                 is_open, is_selct, rd_hook } )) . rrr)=> 
                      (case (m::ord_node (x, lab))   
                        EQUAL => { apply clean subtrees; 
                                  folder { lab, path,
                                         subtrees=>get_subtrees ob /* !!! */,
                                         icon, cids, is_open,
                                         is_selct, rd_hook } . rrr;};
                       _     => aob . (upd ([x], NULL) rrr); esac);
                  #  search for folder on leaf >>> 
                 upd  ([x], NULL) (aaa . rrr) => aaa . upd ([x], NULL) rrr;   
                  #  Descending in folder >>> 
                 upd  (x . rrr, hhh)((aob as (folder { lab, path, subtrees, icon, cids,
                                               is_open, is_selct, rd_hook } )) . rrr') =>
                      (case (m::ord_node (x, lab))   
                        EQUAL => (folder { lab, path,
                                  subtrees=>upd (rrr, hhh) subtrees,
                                  icon, cids, is_open,
                                  is_selct, rd_hook } . rrr');
                       _     => aob . (upd (x . rrr, hhh) rrr'); esac); 
                 upd (x . rrr, hhh) (aaa . rrr') => aaa . upd (x . rrr, hhh) rrr'; end;

         upd (m::path_rep path) x; }; end;

    exception WRONG_INSERT;

    fun insert path obs [] => raise exception WRONG_INSERT;
       insert path obs x =>
        {   fun ins path [] => [];
                  #  search for leaf on leaf >>> 
                 ins ([], THE x) ((aob as (leaf { lab, ... } )) . rrr) =>
                      (case (m::basic::ord (x, fst lab))   
                        EQUAL => (aob . (relabel (m::path_rep path) obs)@rrr);
                       _     =>  aob . (ins ([], THE x) rrr); esac);
                  #  search for leaf on fold >>> 
                 ins  ([], THE x) (aaa . rrr) => aaa . ins ([], THE x) rrr;
                  #  replace folder by folder content >>> 
                 ins  ([x], NULL)((aob as (folder { lab, path, subtrees, icon, cids,
                                                 is_open, is_selct, rd_hook } )) . rrr)=>
                      (case (m::ord_node (x, lab))   
                        EQUAL => (aob . (relabel (m::path_rep path) obs)@rrr);
                       _     => aob . (ins ([x], NULL) rrr); esac);
                  #  search for folder on leaf >>> 
                 ins  ([x], NULL) (aaa . rrr) => aaa . ins ([x], NULL) rrr;
                  #  Descending in folder >>> 
                 ins  (x . rrr, hhh)((aob as (folder { lab, path, subtrees, icon, cids,
                                               is_open, is_selct, rd_hook } )) . rrr') =>
                      (case (m::ord_node (x, lab))   
                        EQUAL => (folder { lab, path,
                                  subtrees=>ins (rrr, hhh) subtrees,
                                  icon, cids, is_open,
                                  is_selct, rd_hook } . rrr');
                       _     => aob . (ins (x . rrr, hhh) rrr'); esac);
                 ins (x . rrr, hhh) (aaa . rrr') => aaa . ins (x . rrr, hhh) rrr'; end;

         ins (m::path_rep path) x; }; end;

    fun is_open_at _ [] => FALSE;
       is_open_at (a . rrr) ((leaf _) . rrr') => is_open_at (a . rrr) (rrr');
       is_open_at [a] ((folder { lab, is_open, subtrees, ... } ) . rrr') =>
           if *is_open    
              case (m::ord_node (a, lab))   
                EQUAL => TRUE;
               _     => FALSE; esac;
           else is_open_at [a] (rrr');fi;
       is_open_at (a . rrr) ((folder { lab, is_open, subtrees, ... } ) . rrr') =>
           if *is_open    
              case (m::ord_node (a, lab))   
                EQUAL => is_open_at (rrr) (subtrees);
               _     => FALSE; esac;
           else is_open_at (a . rrr) (rrr');fi; end; 


    fun cids_of [] => [];
       cids_of((leaf { cids=>(aaa, bbb, ccc, dddd), ... } ) . rrr)  => aaa . bbb . ccc . dddd . (cids_of rrr);
       cids_of((folder { cids=>(aaa, bbb, ccc, dddd, eee', fff, ggg), subtrees, ... } ) . rrr)  => 
               aaa . bbb . ccc . dddd . eee' . fff . ggg . (cids_of rrr)@(cids_of subtrees); end;

  #  *********************************************************************** 
  #                                                                          
  #  Conversion ...                                                          
  #                                                                          
  #  *********************************************************************** 

    fun gen_cids1 () = (make_canvas_item_id(), make_canvas_item_id(), make_canvas_item_id(), make_canvas_item_id());
    fun gen_cids2 () = (make_canvas_item_id(), make_canvas_item_id(), make_canvas_item_id(),
                        make_canvas_item_id(), make_canvas_item_id(), make_canvas_item_id(), make_canvas_item_id());


                                                                                my
    texticon_id   = make_image_id ();                                               my
    foldericon_id = make_image_id ();


    fun text_icon  ()
        =
        FILE_IMAGE((tk::get_lib_path())$
                                  "/icons/treelist/"$ 
                                  (scale_to_string *my_config.scale_factor) $
                                  "/text.gif", 
                                  texticon_id);
    fun folder_icon ()
        =
        FILE_IMAGE((tk::get_lib_path())$
                                  "/icons/treelist/"$
                                  (scale_to_string *my_config.scale_factor) $
                                  "/folder.gif", 
                                  foldericon_id);

    fun obj2obj_tree0 p obj
        = 
        if (m::is_folder obj ) 
              { my (h, s)       = m::get_folder obj;
                  p           = h . p;
                folder { lab      => h,
                         path     => m::path_abs (list::reverse p, NULL),
                         subtrees => map (obj2obj_tree0 p) s,
                         icon     => if   *my_config.no_icons  NULL;
                                    else THE (folder_icon ());fi, 
                         cids     => gen_cids2 (),
                         is_open  => REF FALSE, is_selct => REF FALSE, 
                         rd_hook  => REF NULL };
              };           
        else
             leaf { lab  => m::get_content obj, 
                    path => m::path_abs (list::reverse p, THE (fst (m::get_content obj))),
                    icon => if   *my_config.no_icons   NULL; 
                            elif *my_config.std_icons  THE (text_icon ());
                            else                       THE (icons::get_microlined_variety (m::icon (m::part_type obj)));
                            fi, 
                    cids => gen_cids1 (),

                    is_selct => REF FALSE,
                    rd_hook  => REF NULL
                  };
        fi;

                                                                                my
    obj2obj_tree = obj2obj_tree0 [];

    fun obj_tree2obj (folder { lab, subtrees, ... } )
        => 
        m::folder
            (lab, map obj_tree2obj subtrees);

       obj_tree2obj (leaf { lab, ... } )
        =>
        m::content lab; end;


  #  *********************************************************************** 
  #                                                                          
  #  Display related dagwalk ...                                            
  #                                                                          
  #  *********************************************************************** 

    fun clear_sel [] => ();
       clear_sel((leaf { is_selct, rd_hook, ... } ) . rrr)  => 
                { if *is_selct  { is_selct:=FALSE; the *rd_hook ();};
                 fi;
                 clear_sel rrr;};
       clear_sel((folder { is_selct, rd_hook, subtrees, ... } ) . rrr)  => 
                { if *is_selct  { is_selct:=FALSE; the *rd_hook ();};
                 fi;
                 clear_sel subtrees;
                 clear_sel rrr;}; end;

    fun deselect [] => FALSE;
       deselect ((leaf { is_selct, ... } ) . rrr)  => 
                  ({ h = *is_selct;
                       r = deselect rrr;
                     is_selct:=FALSE; h or r; });
       deselect ((folder { is_selct, subtrees, ... } ) . rrr) => 
                  ({ h  = *is_selct;
                       r  = deselect rrr;
                       r' = deselect subtrees;
                     is_selct:=FALSE; h or r or r'; }); end;

    fun set_sel_range aaa tree =
        { mark_mode = REF (0); /* 0 = init, 1 = fill-to-mode,
                                      2 = fill-from-mode, 3 = delete-mode */
            fun mlr [] => ();
               mlr ((leaf { cids=>(_, _, ccc, _), is_selct, rd_hook, ... } ) . rrr)  =>
                    { case *mark_mode   
                          0 => if (aaa == ccc)   mark_mode:=1;
                                                 is_selct:=TRUE;
                                                 the *rd_hook ();
                               elif *is_selct  mark_mode:=2;
                               fi;

                         1 =>  if (aaa == ccc)   mark_mode:=3;
                                                 is_selct:=TRUE;
                                                 the *rd_hook();

                               elif *is_selct    mark_mode:=3; 

                               else              is_selct:=TRUE;
                                                 the *rd_hook ();
                               fi;

                         2 =>  if (aaa == ccc )   mark_mode:=3;
                                                 is_selct:=TRUE;
                                                 the *rd_hook ();

                               elif *is_selct    (); 

                               else              is_selct:=TRUE;
                                                 the *rd_hook ();
                               fi;

                         3 =>  if   not *is_selct  (); 

                               else                is_selct:=FALSE;
                                                   the *rd_hook ();
                               fi;
                     esac;
                     mlr rrr;};

               mlr((folder { cids=>(_, _, _, _, _, ccc, _), is_open, is_selct,
                            rd_hook, subtrees, ... } ) . rrr)  =>
                    { case *mark_mode   
                          0 => if (aaa == ccc )  mark_mode:=1;
                                                 is_selct:=TRUE;
                                                 the *rd_hook ();

                               elif *is_selct    mark_mode:=2;
                               fi;

                          1 => if (aaa == ccc )   mark_mode:=3;
                                                  is_selct:=TRUE;
                                                  the *rd_hook ();

                               elif *is_selct     mark_mode:=3; 
                               else               is_selct:=TRUE;
                                                  the *rd_hook ();
                               fi;

                          2 => if (aaa == ccc )   mark_mode:=3;
                                                  is_selct:=TRUE;
                                                  the *rd_hook ();

                               elif *is_selct     (); 

                               else               is_selct:=TRUE;
                                                  the *rd_hook ();
                               fi;

                          3 => if (not *is_selct) (); 

                               else               is_selct:=FALSE;
                                                  the *rd_hook ();
                               fi;
                     esac;

                     if *is_open  mlr subtrees; fi;

                     mlr rrr;
                   };
              end;
         mlr tree; };




  #  *********************************************************************** 
  #                                                                          
  #  State ...                                                               
  #                                                                          
  #  *********************************************************************** 



    gui_state = REF ([]: List( Obj_Tree ));
    global_drag_drop_namings = REF([]:List( Event_Callback ));
    refresh_hook = REF( NULL:  Null_Or( s::m::Path -> Void ) );

  /*
    fun get_selected0 () =  
        let fun get_sel [] = []
               |get_sel((leaf { is_selct, lab, path, ... } ) . rrr)  = 
                       (if *is_selct then [(path, m::Content lab)] 
                        else []) @ (get_sel rrr)
               |get_sel((folder { is_selct, subtrees, lab, path, ... } ) . rrr)  = 
                       (if *is_selct 
                        then [(path, m::Folder (lab, map obj_tree2obj subtrees))] 
                        else get_sel subtrees) 
                       @ (get_sel rrr)
        in  get_sel *gui_state end;

   */
    fun get_selected0 ()
        =
        get_sel *gui_state
        where
            fun get_sel []
                    =>
                    [];

                get_sel ((a as leaf { is_selct, lab, path, ... } ) . rrr)
                    =>
                    if *is_selct  [ (path, a) ];
                    else          [           ];
                    fi
                    @
                    (get_sel rrr);

                get_sel ((a as folder { is_selct, subtrees, lab, path, ... } ) . rrr)
                    =>
                    if *is_selct   [(path, a)];
                    else           get_sel subtrees;
                    fi
                    @
                    (get_sel rrr);
            end;
        end;


    fun get_selected ()
        =
        map (obj_tree2obj o snd) (get_selected0());

    fun rem_selected []
            =>
            [];

        rem_selected ((a as (leaf { is_selct, lab, path, ... } )) . rrr)
            =>
            (if *is_selct  []; else [a];fi) @ (rem_selected rrr);

        rem_selected ((a as (folder { lab, path, subtrees, icon, cids, is_open, is_selct, rd_hook } )) . rrr)
            => 
            if *is_selct

                 [];
            else
                 [ folder { lab,
                            path,
                            icon,
                            cids, 
                            is_open,
                            is_selct,
                            rd_hook,
                            subtrees => rem_selected subtrees
                          }
                 ];
            fi
            @
            (rem_selected rrr);
    end;

    fun set_selected _  = ();   #  NOT YET IMPLEMETED 


    fun get_guistate () =  map obj_tree2obj *gui_state;

  #  *********************************************************************** 
  #                                                                          
  #  Drag-drop-control  ...                                                  
  #                                                                          
  #  *********************************************************************** 

  fun debugmsg x = print x;
  fun debugmsg x = ();

     Dragmodetype = INTERNAL #  from tl-canvas to tl-canvas 
                          | EXTERNAL #  from tl-canvas to extern 
                          | IMPORT;   #  from extern to tl-canvas 
    dragmode     = REF (NULL: Null_Or( Dragmodetype )); #  NULL:  don't know 

    fun press_grab_button   path can_id (ev: Tk_Event)
        = 
               { dragmode := NULL;
                debugmsg "drag:";
                debugmsg (name2string path);
                 debugmsg "\n";
               add_trait can_id [drag_cursor];};

    fun release_grab_button path can_id (TK_EVENT(_, _, x, y, _, _))
        = 
               { debugmsg ("release:");
                debugmsg (name2string path);

                case *dragmode
                   
                    NULL         => debugmsg ": none \n";
                    THE IMPORT   => debugmsg ": Import \n";
                    THE INTERNAL => debugmsg ": internal \n";
                    THE EXTERNAL => debugmsg ": external \n";
                esac;

                #  Dragmode := NULL; 
                add_trait can_id [CURSOR (NO_CURSOR)];};

    fun grabbed_motion can_id _
        = 
               { dragmode := THE (internal);
                debugmsg "motion \n";};

    fun leave_canvas can_id ev = 
               { case *dragmode    
                  THE (internal) => dragmode := THE (external);
                 _              => dragmode := NULL; esac; 
                debugmsg "leave: \n";
                { objs = get_selected();
                    fun remove objs = (print "   export objects \n");
                  clipboard::put (m::cb_objects_abs (\\() => objs; end )) ev 
                           (\\() => remove (objs); end );  }
               ;};  

    fun import_objects to_path objs ev = 
        { print "   include objects \n";
         if (clipboard::is_empty ev ) ();
         else { objs = (m::cb_objects_rep (clipboard::get ev))();
                  fun do_it x = ();
                do_it objs; };fi;};

    fun move_objects path intern canvas_id =
        { print "   move objects \n";
         # internal objects have been dragged into can Id.
         # This resulted in storing the objects in the
         # internal_release_buffer.
         { objs    = map snd (get_selected0());
             state'  = rem_selected *gui_state;
             state'' = insert path objs state';
          # first: delete everything from screen (including 
            # stuff not in nustate)
            apply (\\ x => (delete_canvas_item canvas_id x 
                       except CANVAS_ITEM _ => (); end ); end )
                (cids_of *gui_state);
            gui_state:=state'';
            the *refresh_hook (m::path_abs([], NULL));
            #  <<< removes again, but does not hurt <<< 
            a::objtree_change_notifier { changed_at=>m::path_abs([], NULL) };
            #  HACK! more precise: least common prefix of all paths . . . 
         };};


    #  enterCanvas atpath called_internally inCanvasId ... 
    fun enter_canvas path TRUE can_id ev
            => 
            {   debugmsg ("enter:1:");
                debugmsg (name2string path);

                case *dragmode
                   
                     THE EXTERNAL => (print (":ext \n"));
                     THE IMPORT   => { print (":imp \n");
                                     import_objects path TRUE ev;};
                     THE INTERNAL => { print (":int \n");
                                     move_objects path TRUE can_id;};
                     _            => print (":no \n");
               esac;

               dragmode := NULL;};                

       enter_canvas path FALSE can_id ev
           => 
           {   debugmsg ("enter:2:");
               debugmsg (name2string path);

               case *dragmode
                  
                    THE EXTERNAL => print (":ext \n");
                    THE INTERNAL => { print (":int \n");
                                     move_objects path FALSE can_id;};
                    THE IMPORT   => { print (":imp \n");
                                     import_objects path FALSE ev;};
                    _            => print (":no \n");
               esac;

               dragmode := NULL;
           };
    end;


    fun press_sel_button can_id _
        =
        ();


  #  *********************************************************************** 
  #                                                                          
  #  object-tree - drawing ...                                               
  #                                                                          
  #  *********************************************************************** 

    canvas_id = make_widget_id();

    fun cline (cid, c, cl, bl)
        =
        tk::CANVAS_LINE { citem_id=>cid, coords=>c, 
                                             traits=>cl, event_callbacks=>bl };

    # make_label:  generates a editable label for folders and basicobjects.
    # Lots of functionality for selection is provided - 
    # requiring additional information of the global tree, the surrounding
    # canvas-widget, the Canvas_Item_ID for delete-management.

    fun make_label is_selected rd_hook (gttxt, updtxt) pos aaa wid path
        = 
        # in order not to redraw the whole tree in case of a selection,
        # local redraw functions are provided here and stored in the 
        # obj_tree via the hooks. Thus, selection with global effects
        # can be implemented via evaluating the local redraw functions
        # on demand. This complicates the story a bit.

        { label_id  = make_widget_id();

            fun col_lab b
                =
                if b     BACKGROUND (*(colors::config.background_sel));
                        else BACKGROUND (*(colors::config.background));fi;

            fun relief_lab b
                =
                b ?? RELIEF SUNKEN
                  :: RELIEF FLAT;

            fun redraw _
                =
                (add_trait label_id [col_lab *is_selected,
                                            relief_lab *is_selected,
                                            TEXT (gttxt())]);

            fun hilite b _
                =
                if (not *is_selected)
                     add_trait label_id [col_lab (b)];
                fi;

            fun sel_action _
                =
                {   clear_sel *gui_state;
                    is_selected := TRUE; 
                    the *rd_hook ();
                };

            fun sel_range_action _
                =
                (set_sel_range aaa *gui_state);

            fun sel_group_elem_action _
                =
                { is_selected:= not *is_selected;
                                           the *rd_hook ();};

            fun activate _
                =
                {   updtxt (the *rd_hook);
                    redraw ()
                ;};

            fun lab b
                =
                LABEL { widget_id=>label_id,
                              packing_hints => [],
                              event_callbacks => [EVENT_CALLBACK (events::sel_elem_event(),
                                               \\ XX => { sel_action XX;
                                                         #  Drag-code >>> 
                                                         press_grab_button 
                                                            path wid XX;}; end ),
                              /*CONFLICT with:
                                        EVENT_CALLBACK (Events::drag_event(), 
                                               pressGrabButton path wid), */                                 /* Problem:  This conflict-resolution above
                                 assumes a particular configuration
                                 of sel_elem_event() and drag_event() */
            EVENT_CALLBACK (events::drop_event(), release_grab_button path wid),
            EVENT_CALLBACK (events::dd_motion_event(), grabbed_motion wid),                               
                                        EVENT_CALLBACK (events::sel_range_event(),
                                               sel_range_action),
                                        EVENT_CALLBACK (events::sel_group_elem_event(),
                                               sel_group_elem_action),
                                        EVENT_CALLBACK (events::activate_event(),
                                               activate),

                                        EVENT_CALLBACK (ENTER, \\ XX=>{ hilite TRUE XX;
                                                              enter_canvas path
                                                                FALSE wid XX;}; end ),
                                        EVENT_CALLBACK (LEAVE, hilite FALSE)
                                       ],
                              traits => [TEXT (gttxt ()),
                                       col_lab b, relief_lab b,
                                       FONT (tk::SANS_SERIF [tk::SMALL])]

                             };

            m = coordinate (icon_width + 4, 0);

            fun make_clab b
                =
                CANVAS_WIDGET {
                    citem_id => aaa,
                    coord => add_coordinates pos m, 
                    subwidgets => PACKED [lab b],
                    traits => [ANCHOR WEST], 
                    event_callbacks => []
                };
        
            rd_hook := THE redraw;
            make_clab *is_selected;
        };

    # mini-box: clickable symbol for folders; activation may result in opening
    # the folder by displaying the subtree. The symbol is drawn - not a gif.
    #    b: open/close status; pos: top-left start position of the drawing,
    #    aaa, bbb, ccc, dddd:  Canvas_Item_ID's stored here for systematic release,
    #    cmd: command for activation, path: info for activation and debugging:

    fun mini_box b pos (aaa, bbb, ccc, dddd) cmd path
        =
        {   fun cm _ = { a::open_close_notifier { is_open=> *b, changed_at => [path] };
                        #  Caution ! this may change the gui_state ! 
                        cmd();};
            bi = EVENT_CALLBACK (events::activate_event(), cm); 
           [CANVAS_BOX { citem_id=>aaa, coord1=>pos, 
                       coord2=>add_coordinates pos (coordinate (box_width, box_height)),
                       traits => [FILL_COLOR WHITE, OUTLINE BLACK], event_callbacks => [bi] },
            cline (bbb,[add_coordinates pos (coordinate (2, box_h_middle)),
                     add_coordinates pos (coordinate (box_width - 1, box_h_middle))],[],[bi]),
            cline (ccc,[add_coordinates pos (coordinate (box_width, box_h_middle)),
                     add_coordinates pos (coordinate (in3, box_h_middle))],[],[bi])] @
           (if *b  [];
            else [cline (dddd,[add_coordinates pos (coordinate (box_w_middle, 2)),
                           add_coordinates pos (coordinate (box_w_middle, box_width))],
                        [],[bi])];fi);
        };


    # icon_piece: clickable symbol for folders and basicobjects; 
    # activation may result in firing the activation fate. 
    # The symbol is a gif - either user-defined (i available), or standard.
    #    p: top-left start position of the drawing,
    #    path: info for activation and debugging

    fun icon_piece (THE i) citem_id p path hi wid
        =>
        { fun activate _
                =
                a::focus_change_notifier { changed_at => [path] }; 
         
            [   CANVAS_ICON {
                    citem_id,
                    coord    => add_coordinates p (add_coordinates (in3+1, box_h_middle) hi),
                    icon_variety => i,
                    traits  => [ANCHOR WEST],
                    event_callbacks => [    /* EVENT_CALLBACK (Events::sel_elem_event(), sel_action),
                                              EVENT_CALLBACK (Events::sel_range_event(), sel_range_action),
                                              EVENT_CALLBACK (Events::sel_group_elem_event(),
                                                            sel_group_elem_action),
                                            */
                                           EVENT_CALLBACK (events::drag_event(), press_grab_button path wid),
                                           EVENT_CALLBACK (events::drop_event(), release_grab_button path wid),
                                           EVENT_CALLBACK (events::activate_event(), activate),
                                           EVENT_CALLBACK (ENTER, enter_canvas path FALSE wid) 
                                      ]
                  }
            ];
         };

        icon_piece (NULL) citem_id p path hi wid => []; end;


    # folder_line: line in tree consisting of box, icon, and label (for folders).
    # All information from the context tree must be passed to the drawing 
    # functions of these subitems. 
    # The offset off produces a shift of the line level and a suitably prologued 
    # front vertex.

    fun folder_line is_open is_slct rdh icon p path 
                    off lab (aaa, bbb, ccc, dddd, eee', fff, ggg) cmd wid
        = 
        { p'  = add_coordinates p (coordinate (in1, 0));
            hi  = add_coordinates (coordinate (0, hi)) (height off);
            li  = cline (aaa, [p', add_coordinates p' hi],[],[]);
            p'' = add_coordinates p hi;
            p'''= add_coordinates p (add_coordinates (coordinate (in3, box_h_middle)) hi);
        
            li . mini_box is_open p'' (bbb, ccc, dddd, eee') cmd path @ 
            icon_piece icon ggg p path hi wid @ 
            [make_label is_slct rdh lab p''' fff wid path]; 
        }; 

    # object_line: line in tree consisting of front vertrex, icon, and label 
    # (for basicobjects).
    # All information from the context tree must be passed to the drawing 
    # functions of these subitems.  
    # The offset off produces a shift of the line level and a suitably 
    # prolongued front vertex.

    fun object_line is_open is_slct rdh icon p path off lab (aaa, bbb, ccc, dddd) wid
        = 
        { p'    = add_coordinates p (coordinate (in1, 0));
            hi    = add_coordinates (coordinate (0, hi)) (height off);
            hi_tot= add_coordinates hi (coordinate (0, box_height));
            p''   = add_coordinates (add_coordinates p' hi) 
                                 (coordinate (0, box_h_middle));
            p'''  = if is_open  add_coordinates p' hi_tot; else p'';fi;
            p'''' = add_coordinates p (add_coordinates (coordinate (in3, box_h_middle)) hi);
        
            [cline (aaa, [p', p'''],[],[]),
             cline (bbb, [p'', add_coordinates p'' (coordinate (in3-in1, 0))],[],[])] @
             icon_piece icon dddd p path hi wid @
            [make_label is_slct rdh lab p'''' ccc wid path];
        };

    fun diag1 maxcl p lab ccc
        =
        { s = m::basic::string_of_name (m::basic::name_of lab) maxcl;
            fun upd1 s lab ccc= (\\ s' => if (s==s' ) (); 
                                        else { m::basic::rename s' lab; ccc();};fi; end ); 
        
            a::content_label_action {
                path => p,
                was => s,
                cc => upd1 s lab ccc
            };
        };

    fun diag2 maxcl p lab ccc
        = 
        { s = m::string_of_name_node lab maxcl;
            fun upd2 s lab ccc= (\\ s' => if (s==s' ) (); 
                                        else { m::rename_node s' lab; ccc();};fi; end ); 
        
            a::content_label_action {
                path=>p,
                was=>s,
                cc=>upd2 s lab ccc
            };
        };

    #  placing a tree into a canvas - with all jingles . . . 
    fun place_tree pos wid tree
        = 
        { cl         = { mode=>print::short, printdepth=>1, 
                              height=>NULL, width=>NULL };          #  HACK ! 
            fun str1  lab  = (\\ () => m::basic::string_of_name 
                                          (m::basic::name_of lab) cl; end );
            fun str2  lab  = (\\ () => m::string_of_name_node lab cl; end );
            fun shift n p  = add_coordinates p (height n); 
            fun indent  p  = add_coordinates p (coordinate (in3 + (icon_width div 2) - 1, 0));
            fun beh1 p lab = (str1 lab, diag1 cl p lab);
            fun beh2 p lab = (str2 lab, diag2 cl p lab);
            fun open_cont p lt is_open_ref _ = 
                       { is_open_ref := not *is_open_ref;
                        (.is_selct (get_folder lt)) := deselect [lt];
                        refresh (m::path_rep p);};
            #  the core of the display algorithm: 
            fun pt p off ([]) => [];
               pt p off ((leaf { lab, icon, cids, is_selct, rd_hook, path, ... } ) . rrr)  => 
                        (object_line (not (null rrr)) is_selct rd_hook icon p path
                                      off (beh1 path (fst lab)) cids wid) @ 
                        (pt (shift (1+off) p) 0 rrr); 
               pt p off ((lt as folder { icon, lab, cids, is_selct,
                                        rd_hook, is_open, subtrees, path, ... } ) . rrr)=> 
                       if *is_open  
                            (folder_line is_open is_selct rd_hook icon p path
                                         off (beh2 path lab) 
                                         cids (open_cont path lt is_open) wid) @
                            (pt (indent (shift 1 p)) (0) subtrees) @
                            (pt (shift (1+off) p) (length subtrees) rrr);
                       else (folder_line is_open is_selct rd_hook icon p path
                                         off (beh2 path lab) 
                                         cids (open_cont path lt is_open) wid) @
                            (pt (shift (1+off) p) 0 rrr);fi; end;

       
           debugmsg "place_tree ... "; pt pos 0 tree;
       }

    also
    fun refresh ([], _)
        => 
        { apply (\\ x => (delete_canvas_item canvas_id x
                       except CANVAS_ITEM _ => (); end ); end ) 
             (cids_of *gui_state);
         #  gui_state := map (obj2obj_tree o obj_tree2obj) *gui_state; 
         apply (add_canvas_item canvas_id) 
             (place_tree (coordinate (10, 15)) canvas_id *gui_state)
        ;};

      refresh (p, NULL) =>  refresh ([], NULL); end;  
         /* correct, but inefficient. This refresh is used for internal
            use - i.e. redisplay for open-close-actions. */

    fun refresh_o ([], xxx')
            =>
            refresh ([], xxx');

        refresh_o (p, NULL)
            =>
            if (is_open_at p *gui_state)
                                                # Very simple heuristic 
                                                # to keep it smooth.
                  print "refresh full\n";
                  refresh ([], NULL);
            else
                 (print "refresh optimized\n");
            fi;
    end;

    refresh   =   (\\ p =  refresh   (m::path_rep p));
    refresh_o =   (\\ p =  refresh_o (m::path_rep p));


     fun refresh_label ()
         = 
         rl *gui_state
         where

            fun rl [] => ();

                rl ((leaf { rd_hook, ... } ) . rrr)
                    =>
                    {   the *rd_hook ();
                        rl rrr;
                    };

                rl ((folder { rd_hook, is_open, subtrees, ... } ) . rrr)
                    =>
                    {   the *rd_hook ();
                        if *is_open  rl subtrees; fi;
                        rl rrr;
                    };
            end;
        end;



     fun redisplay ()
         = 
        {   apply
                (\\ x = (delete_canvas_item canvas_id x 
                       except CANVAS_ITEM _ = ())) 
                (cids_of *gui_state); 

           #  <<< better: scratch everything from complete canvas ...
           #
           gui_state
               :=
               map (obj2obj_tree o obj_tree2obj)
                   *gui_state;

           apply
               (add_canvas_item canvas_id) 
               (place_tree (coordinate (10, 15)) canvas_id *gui_state);
        };

  #  *********************************************************************** 
  #                                                                          
  #  update access to gui_state ...                                          
  #                                                                          
  #  *********************************************************************** 

    /* merge maintains the internal data-package,
       as long as there are no differences to the
       analogous external package.  Maintaining
       means open/Close, cids, etc. Path is patched.
    */
    fun merge p rrr [] => rrr;
        merge p [] rrr => map (obj2obj_tree0 p) rrr;
        merge p ((aob as (leaf { lab, ... } )) . rrr) (a . rrr') => 
               if (m::is_folder a ) ((obj2obj_tree0 p a) . (merge p rrr rrr'));
               else (case (m::basic::ord (fst (m::get_content a), fst lab))   
                       EQUAL => aob . (merge p rrr rrr');
                      _ => (obj2obj_tree0 p a) . (merge p rrr rrr'); esac);fi;

        merge p ((aob as (folder { lab, path, subtrees, icon, cids,
                                is_open, is_selct, rd_hook } )) . rrr) (a . rrr') =>
               if (m::is_folder a ) 
                    { my (n, rrr'') = m::get_folder a;
                      (case (m::ord_node (n, lab))   
                           EQUAL => ((folder { lab, path=>m::path_abs (list::reverse p, NULL),
                                          subtrees => merge (n . p) subtrees rrr'',
                                          icon, cids, is_open,
                                          is_selct, rd_hook } ) 
                                    . (merge p rrr rrr'));
                          _ => (obj2obj_tree0 p a) . (merge p rrr rrr'); esac);
                    };
               else (obj2obj_tree0 p a) . (merge p rrr rrr');fi;
    end;






    fun upd_guistate (p as ([], NULL)) obs
        =>
                     { apply (\\ x => (delete_canvas_item canvas_id x
                           except CANVAS_ITEM _ => (); end ); end ) 
                          (cids_of *gui_state);
                      gui_state:= map obj2obj_tree obs;};  
       upd_guistate (p as (m, _)) [ob] =>
                      { fun clean dob = apply (\\ x => (delete_canvas_item canvas_id x
                                                       except CANVAS_ITEM _ => (); end ); end ) 
                                             (cids_of [dob]); 
                        gui_state:=update clean (m::path_abs p) 
                                             (obj2obj_tree0 (tl (reverse m)) ob) 
                                             *gui_state;
                      };
    end;

    upd_guistate = \\ p => \\ obs =>  upd_guistate (m::path_rep p) obs; end; end ;  


    fun canvas_event_callbacks can_id
        =  
          [ EVENT_CALLBACK (events::drag_event(), \\ _ => { clear_sel *gui_state;
                                                 dragmode:=NULL;
                                                 refresh_label ();}; end ),
            EVENT_CALLBACK (events::drop_event(), release_grab_button (m::path_abs([], NULL)) can_id),
            EVENT_CALLBACK (events::dd_motion_event(), grabbed_motion can_id),
            EVENT_CALLBACK (events::dd_leave_event(), leave_canvas can_id),
            #  EVENT_CALLBACK (Events::dd_enter_event(), enterCanvas([], NULL)TRUE canId), 
            #  <<< seems to have no effect . . . 
            #  EVENT_CALLBACK (ENTER, enterCanvas ([], NULL) FALSE canId) 
            EVENT_CALLBACK (LEAVE, leave_canvas can_id) 
          ];

  /* Events::drag_event()      = BUTTON_PRESS    (THE 1) 
     Events::drop_event()      = BUTTON_RELEASE  (THE 1) 
     Events::dd_motion_event() = MODIFIER_BUTTON (1, MOTION) 
     Events::dd_leave_event()  = MODIFIER_BUTTON (1, LEAVE)
     Events::dd_enter_event()  = MODIFIER_BUTTON (1, ENTER)   
   */

    fun create_canvas obj
        =
        { my () = { gui_state := map obj2obj_tree obj;
                      global_drag_drop_namings := canvas_event_callbacks canvas_id;
                      refresh_hook:=THE (refresh);};
        
            CANVAS {
                widget_id       => canvas_id,
                scrollbars      => *my_config.scrollbars,
                citems          => (place_tree (coordinate (10, 15))canvas_id *gui_state),

                packing_hints   => [PACK_AT TOP, FILL ONLY_X, EXPAND TRUE],
                event_callbacks => *global_drag_drop_namings,

                traits          => [   HEIGHT *my_config.height, 
                                      WIDTH *my_config.width, 
                                      RELIEF GROOVE, 
                                      BACKGROUND (*(colors::config.background))
                                  ]

            };
        };

                                                                                my
    refresh = refresh_o;   #  This optimized refresh is exported ... 

};


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext