PreviousUpNext

15.4.1475  src/lib/x-kit/widget/old/basic/hostwindow.pkg

## hostwindow.pkg -- Pre-packaged management for the top-level window of an application.
#
#
#
# TODO: Allow mapping/unmapping of hostwindows
#       Cleanup and complete hostwindow resource usage  XXX BUGGO FIXME

# Compiled by:
#     src/lib/x-kit/widget/xkit-widget.sublib

# See also:
#     src/lib/x-kit/xclient/src/window/window-old.api





###      "You think you know when you learn,
###       are more sure when you can write,
###       even more when you can teach,
###       but certain when you can program."
###
###                            -- Alan Perlis


stipulate
    include package   threadkit;                # threadkit             is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
    package cmd =  commandline;                 # commandline           is from   src/lib/std/commandline.pkg
    #
    package g2d =  geometry2d;                  # geometry2d            is from   src/lib/std/2d/geometry2d.pkg
    package xc  =  xclient;                     # xclient               is from   src/lib/x-kit/xclient/xclient.pkg
    #
    package xtr =  xlogger;                     # xlogger               is from   src/lib/x-kit/xclient/src/stuff/xlogger.pkg
    #
    package wg  =  widget;                      # widget                is from   src/lib/x-kit/widget/old/basic/widget.pkg
    package wa  =  widget_attribute_old;        # widget_attribute_old  is from   src/lib/x-kit/widget/old/lib/widget-attribute-old.pkg
    package mr  =  xevent_mail_router;          # xevent_mail_router    is from   src/lib/x-kit/widget/old/basic/xevent-mail-router.pkg
    #
    tracing     =  logger::make_logtree_leaf { parent => xlogger::xkit_logging, name => "hostwindow::tracing", default => FALSE };
    trace       =  xtr::log_if  tracing 0;      # Conditionally write strings to tracing.log or whatever.
        #
        # To debug via tracelogging, annotate the code with lines like
        #
        #       trace {. sprintf "foo/top: bar d=%d" bar; };
herein

    package   hostwindow
    : (weak)  Hostwindow                                # Hostwindow            is from   src/lib/x-kit/widget/old/basic/hostwindow.api
    {
        Window_Manager_Hints
            =
            {    size_hints:  List( xc::Window_Manager_Size_Hint    ),
              nonsize_hints:  List( xc::Window_Manager_Nonsize_Hint )
                  #
#               class_hints:  Null_Or  { resource_class:  String, resource_name:  String }
            };

        fun make_window_manager_hints a
            =
            a;

        Plea_Mail = START
                  | DESTROY
                  | MAP       Bool
                  | WM_HINTS  Window_Manager_Hints
                  | WINDOW_OF (Mailslot( xc::Window ))
                  ;

        Hostwindow
            =
            HOSTWINDOW
              ( Mailslot( Plea_Mail ),
                Mailslot( Void )                # wm_window_delete_slot. Set when user clicks on windowframe close button.
              );

        fun set_size_hints
            {
              col_preference  as  wg::INT_PREFERENCE  xdim,
              row_preference  as  wg::INT_PREFERENCE  ydim
            }
            =
            do_inc() @ do_max() @ do_min()
            where
                fun min_size ()
                    =
                    {   minx =  wg::minimum_length  col_preference;
                        miny =  wg::minimum_length  row_preference;

                        { wide => int::max (1, minx),
                          high => int::max (1, miny)
                        };
                    };

                fun max_size ()
                    =
                    ( wg::maximum_length  col_preference,
                      wg::maximum_length  row_preference
                    );

                fun inc_size ()
                    =
                    ( xdim.step_by,
                      ydim.step_by
                    );

                maxx = 65535;

                fun do_inc ()
                    =
                    case (inc_size ())   
                        #
                        (1, 1) => [];
                        (x, 1) => [xc::HINT_PRESIZE_INC ({ wide=>x, high=>1 } )];
                        (1, y) => [xc::HINT_PRESIZE_INC ({ wide=>1, high=>y } )];
                        (x, y) => [xc::HINT_PRESIZE_INC ({ wide=>x, high=>y } )];
                    esac;

                fun do_min ()
                    =
                    {   minsz = min_size ();

                        [ xc::HINT_PMIN_SIZE   minsz,
                          xc::HINT_PBASE_SIZE  minsz
                        ];
                    };

                fun do_max ()
                    =
                    case (max_size ())
                        #
                        (NULL,  NULL ) =>  [];
                        (THE x, NULL ) =>  [ xc::HINT_PMAX_SIZE ({ wide=>x,    high => maxx } )];
                        (NULL,  THE y) =>  [ xc::HINT_PMAX_SIZE ({ wide=>maxx, high => y    } )];
                        (THE x, THE y) =>  [ xc::HINT_PMAX_SIZE ({ wide=>x,    high => y    } )];
                    esac;

            end;

    /* DEBUG
        setSizeHints = \\ arg => let
              pr = xlogger::pr1
              arglist = setSizeHints arg
              fun pritem (xc::HINT_PRESIZE_INC size) = pr("inc = "$(Db::sztos size)$"\n")
                | pritem (xc::HINT_PMAX_SIZE size) = pr("max = "$(Db::sztos size)$"\n")
                | pritem (xc::HINT_PMIN_SIZE size) = pr("min = "$(Db::sztos size)$"\n")
                | pritem _ = ()
              in
                apply pritem arglist;
                arglist
              end
    */

        Window_And_Icon_Names
            =
            { window_name:   Null_Or( String ),
              icon_name:     Null_Or( String )
            };


        fun pick_window_site (NULL, size)
                =>
                (g2d::point::zero, size);

            pick_window_site (THE ({ col, row, wide, high } ), { wide=>default_wide, high=>default_high } )
                =>
                ( { col, row },

                  { wide => if (wide > 0)  wide;  else default_wide;  fi,
                    high => if (high > 0)  high;  else default_high;  fi
                  }
                );
        end;


        fun make_hostwindow'
                make_simple_vs_transient_window                         # window creation fn, one of either 'simple' or 'transient' (see below).
                null_or_box                                             # Pixel size for window.
                ( widgettree:               wg::Widget,
                  null_or_background_rgb:   Null_Or( xc::Rgb ),         # Background color for window
                  wm_args:                  Window_And_Icon_Names
                )
            =
            {   root_window =  wg::root_window_of  widgettree;

                plea_slot = make_mailslot ();

                screen =  wg::screen_of  root_window;


                # Default window background color to white:
                #
                background_rgb
                    =
                    case null_or_background_rgb
                        #
                        THE rgb =>  rgb;
                        NULL    =>  xc::white;
                    esac;


                wm_window_delete_slot = make_mailslot ();               # This will be set when user clicks windowframe close button.


                # Advertise that we support the ICCCM WM_DELETE_WINDOW protocol.
                # This way, when a user clicks on the windowframe close button,
                # the window manager will send us a WM_DELETE_WINDOW ClientMessage
                # X event instead of just summarily killing our window and our
                # X connection;  this gives us time to close down gracefully
                # via our get_''close_window''_mailop facility.
                #
                fun set_protocols window
                    = 
                    xc::set_window_manager_protocols
                        #
                        window
                        #
                        [ (xc::make_atom (wg::xsession_of root_window) "WM_DELETE_WINDOW") ];


                fun create_window_and_enter_main_loop (hintlist, mapped, window_requestors)
                    =
                    {
                        (wg::size_preference_of  widgettree)
                            ->
                            size_preference as { col_preference, row_preference };

                        default_size
                            =
                            { wide =>  wg::preferred_length  col_preference,
                              high =>  wg::preferred_length  row_preference
                            };

                        my (upperleft, size) =   pick_window_site (null_or_box, default_size);

                        my (hostwindow, in_kidplug, null_or_wm_delete_window_slot)
                            =
                            make_simple_vs_transient_window  widgettree
                              {
                                site =>  { upperleft, size, border_thickness=>0 }: g2d::Window_Site,
                                #
                                background_color =>  (xc::rgb8_from_rgb  background_rgb),
                                border_color     =>  background_rgb                     #  not used 
                              };

                        fun give_hint { size_hints, nonsize_hints }
                            =
                            xc::set_window_manager_properties  hostwindow
                              {
                                size_hints,
                                nonsize_hints,
                                #
                                commandline_arguments =>  [],
                                window_name           =>  NULL,
                                icon_name             =>  NULL,
                                class_hints           =>  NULL
                              };

                                                                                        # commandline           is from   src/lib/std/commandline.pkg
                        xc::set_window_manager_properties  hostwindow
                          {
                            commandline_arguments =>  cmd::get_commandline_arguments(),
                            #
                            window_name           =>  wm_args.window_name,
                            icon_name             =>  wm_args.icon_name,
                            #
                            size_hints            =>  set_size_hints  size_preference,
                            #
                            nonsize_hints         =>  [],
                            class_hints           =>  NULL
                          };

                        apply give_hint (reverse hintlist);

                        set_protocols hostwindow;

                        (xc::make_widget_cable ())
                            ->
                            { kidplug =>  my_kidplug,
                              momplug =>  my_momplug
                            };

                        child_window
                            =
                            wg::make_child_window
                              ( hostwindow,
                                g2d::box::make (g2d::point::zero, size),
                                wg::args_of  widgettree
                              );

                        (xc::make_widget_cable ())
                            ->
                            { kidplug =>  ckidplug,
                              momplug =>  cmomplug as xc::MOMPLUG { from_kid', ... }
                            };

                        from_kid' =  wg::wrap_queue  from_kid';

                        (xc::ignore_mouse_and_keyboard  my_kidplug)
                            ->
                            xc::KIDPLUG { from_other', ... };

                        # A loop ignoring all input forever.
                        # We run this after we've officially died,
                        # to soak up any left-over input:
                        # 
                        fun zombie ()
                            =
                            for (;;) {
                                #
                                do_one_mailop [
                                    from_other'       ==>  (\\ _ = ()),
                                    take_from_mailslot'  plea_slot  ==>  (\\ _ = ()),
                                    from_kid'         ==>  (\\ _ = ())
                                ];
                            };

                        fun do_kid  xc::REQ_RESIZE
                                =>
                                {   (wg::size_preference_of  widgettree)
                                        ->
                                        bounds as { col_preference,
                                                    row_preference
                                                  };

                                    xc::set_window_manager_properties hostwindow
                                      {
                                        size_hints =>  set_size_hints  bounds,
                                        #
                                        nonsize_hints         =>  [],
                                        commandline_arguments =>  [],
                                        window_name           =>  NULL,
                                        icon_name             =>  NULL,
                                        class_hints           =>  NULL
                                      };                

                                      xc::resize_window
                                        hostwindow
                                        ( { wide =>  wg::preferred_length  col_preference,
                                            high =>  wg::preferred_length  row_preference
                                          }
                                        );
                                };

                           do_kid  xc::REQ_DESTRUCTION
                               =>
                               {    xc::destroy_window hostwindow;
                                    zombie ();                          # Never returns.
                               };
                        end;


                        fun do_other (xc::ETC_RESIZE ({ wide, high, ... }: g2d::Box))
                                => 
                                xc::resize_window child_window ({ wide, high } );

                            do_other  xc::ETC_OWN_DEATH      =>  zombie ();                     # Never returns.
                            do_other (xc::ETC_CHILD_DEATH _) =>  zombie ();                     # Never returns.
                            #
                            do_other (xc::ETC_REDRAW _)      =>  ();
                            do_other _                       =>  ();
                        end;

                        # First arg is un/mapped state;
                        # Second arg is un/map request:
                        # Return value is updated un/mapped state.
                        #
#                       fun map_top_window (FALSE, TRUE) => { xc::show_window     hostwindow;  TRUE;  };
#                           map_top_window (TRUE, FALSE) => { xc::withdraw_window hostwindow;  FALSE; };
#                           map_top_window (_, b) => b;                                                         # No-op.
#                       end;
                        fun map_top_window (FALSE, TRUE) => {
                                                              xc::show_window     hostwindow;
                                                              TRUE;
                                                            };
                            map_top_window (TRUE, FALSE) => {
                                                              xc::withdraw_window hostwindow;
                                                              FALSE;
                                                            };
                            map_top_window (_, b) => b;                                                         # No-op.
                        end;

                        # Do plea, return possibly updated 'mapped' value:
                        #
                        fun do_plea mapped
                            =
                            {
#                               \\ START                =>                                             mapped;
#                                  DESTROY              => { xc::destroy_window hostwindow;  zombie (); mapped; };              # zombie() never returns; the 'mapped' is just for type-correctness.
#                                  WM_HINTS hint        => { give_hint hint;                           mapped; };
#                                  WINDOW_OF reply_slot => { put_in_mailslot (reply_slot, hostwindow);             mapped; };   
#                                  MAP arg              =>   map_top_window (mapped, arg);
#                               end;
                                \\ START                => {
                                                                                                       mapped;
                                                           };
                                   DESTROY              => {
                                                             xc::destroy_window hostwindow;  zombie (); mapped; };              # zombie() never returns; the 'mapped' is just for type-correctness.
                                   WM_HINTS hint        => { give_hint hint;                           mapped; };
                                   WINDOW_OF reply_slot => { put_in_mailslot (reply_slot, hostwindow);             mapped; };   
                                   MAP arg              => {
                                                             map_top_window (mapped, arg);
                                                           };   
                                end;
                            };

                        # Handle requests from four directions:
                        #
                        #    o Resize commands etc from parent, ultimately from X server.
                        #    o WINDOW_DELETE       from parent, ultimately from X server.
                        #    o Resize requests etc from child.
                        #    o Pleas from client threads.
                        #
                        fun main_loop  mapped
                            =
                            {   do_one_mailop [
                                    #
                                    from_other'
                                         ==>
                                         do_other  o  xc::get_contents_of_envelope,

                                    case null_or_wm_delete_window_slot
                                        #
                                        THE input_wm_window_delete_slot
                                            =>
                                            take_from_mailslot'  input_wm_window_delete_slot
                                                ==>
                                                {.  put_in_mailslot (wm_window_delete_slot, ());  };
                                                        #
                                                        # input_wm_window_delete_slot   was created by   make_router   in
                                                        #
                                                        #     src/lib/x-kit/xclient/src/window/hostwindow-to-widget-router-old.pkg;
                                                        #
                                                        # which also 'set's it when a WM_DELETE_WINDOW ClientEvent is received.
                                                        #
                                                        # wm_window_delete_slot   was created in this file.
                                                        # It gets returned in the HOSTWINDOW values we return,
                                                        # and thus is the one made available via our   get_''close_window''_mailop
                                                        # call and ultimately used by the application programmer.
                                                        # Offhand, I do not see why we need both.       XXX BUGGO FIXME
                                        NULL => never';
                                    esac, 


                                    take_from_mailslot'  plea_slot
                                        ==>
                                        main_loop o (do_plea mapped),

                                    from_kid'
                                        ==>
                                        do_kid
                                ];

                                main_loop mapped; 
                            };

                        mr::route_pair (in_kidplug, my_momplug, cmomplug);

                        wg::realize_widget
                             widgettree
                             {
                               kidplug => ckidplug, 
                               window  => child_window,
                               window_size => size
                             };

                        xc::show_window child_window;

                        apply {. put_in_mailslot (#reply_slot, hostwindow); }
                              window_requestors;

                        main_loop (map_top_window (FALSE, mapped));
                    };                                          # fun create_window_and_enter_main_loop


                # Here we loop doing nothing much until sent our START
                # command, at which point we actually create our window
                # and enter the main loop:
                #
                fun ready_to_start_loop (state as (hintlist, mapped, window_requestors))
                    =
                    {
                        case (take_from_mailslot  plea_slot)
                            #
                            START                =>  create_window_and_enter_main_loop  state;
                            DESTROY              =>  ready_to_start_loop                state;
                            #
                            WM_HINTS hint        =>  ready_to_start_loop (hint ! hintlist,   mapped,                window_requestors);
                            MAP mapped           =>  ready_to_start_loop (       hintlist,   mapped,                window_requestors);
                            WINDOW_OF reply_slot =>  ready_to_start_loop (       hintlist,   mapped,   reply_slot ! window_requestors);
                        esac;
                    };

                xlogger::make_thread  "hostwindow main_loop"  {. ready_to_start_loop ([], TRUE, []); };

                HOSTWINDOW (plea_slot, wm_window_delete_slot);

            };                                  # fun make_hostwindow'

        stipulate

            fun simple
                    (widget: wg::Widget)
                    #
                    (g as { site:               g2d::Window_Site,
                            border_color:       xc::Rgb,
                            background_color:   xc::Rgb8
                          }
                    )
                =
                {
                    (xc::make_simple_top_window  (wg::screen_of  (wg::root_window_of  widget))  g)
                        ->
                        (window, in_kidplug, wm_window_delete_slot);

                    (window, in_kidplug, THE wm_window_delete_slot);
                };

            fun transient
                   (window: xc::Window)
                   (widget: wg::Widget)                                                                         # UNUSED in this fn.
                   (g as { site: g2d::Window_Site,  border_color: xc::Rgb,  background_color:  xc::Rgb8 })
                = 
                {
                    (xc::make_transient_window  window  g)
                        ->
                        (window', in_kidplug);

                    (window', in_kidplug, NULL);
                };
        herein

            fun make_hostwindow_at  box
                =
                make_hostwindow' simple (THE box);

            make_hostwindow = make_hostwindow' simple NULL;

            fun make_transient_hostwindow_at r w =  make_hostwindow' (transient w) (THE r);
            fun make_transient_hostwindow w      =  make_hostwindow' (transient w) NULL;

            attributes
                =
                [ (wa::title,        wa::STRING,    wa::NO_VAL),
                  (wa::icon_name,    wa::STRING,    wa::NO_VAL),
                  (wa::background,   wa::COLOR,     wa::NO_VAL)
                ];

            fun hostwindow (root_window, view, args) widgettree
                =
                {   attributes = wg::find_attribute (wg::attributes (view, attributes, args));

                    window_name = wa::get_string_opt (attributes wa::title);
                    icon_name   = wa::get_string_opt (attributes wa::icon_name);
                    color       = wa::get_color_opt  (attributes wa::background);

                    pos = NULL;                 # Fix to look up geometry.  XXX BUGGO FIXME

                    args = { window_name, icon_name };
                    make_hostwindow' simple pos (widgettree, color, args);
                }; 

        end;

        fun start_widgettree_running_in_hostwindow  (HOSTWINDOW (slot, wm_window_delete_slot))     =    put_in_mailslot (slot, START);
        fun destroy                                (HOSTWINDOW (slot, wm_window_delete_slot))     =    put_in_mailslot (slot, DESTROY);
        fun unmap                                  (HOSTWINDOW (slot, wm_window_delete_slot))     =    put_in_mailslot (slot, MAP FALSE);
        fun map                                    (HOSTWINDOW (slot, wm_window_delete_slot))     =    put_in_mailslot (slot, MAP TRUE);
        fun window_of                              (HOSTWINDOW (slot, wm_window_delete_slot))     =  {  reply_slot = make_mailslot ();
                                                                                                        put_in_mailslot (slot, WINDOW_OF reply_slot);
                                                                                                        take_from_mailslot reply_slot;
                                                                                                    };

        fun set_window_manager_hints
                (HOSTWINDOW (slot, wm_window_delete_slot))
                arg
            =
            put_in_mailslot (slot, WM_HINTS arg);

        fun get_''close_window''_mailop (HOSTWINDOW (slot, wm_window_delete_slot))
            =
            take_from_mailslot'  wm_window_delete_slot;

    };                  # package hostwindow 
end;                    # stipulate


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext