## generate-tree-gui-g.pkg
## (C) 2000, Albert Ludwigs Universität Freiburg
## Author: bu
# Compiled by:
#
src/lib/tk/src/toolkit/sources.sublib# ***************************************************************************
# A generic graphical user interface.
# including a navigation pad, but still generic
# in the construction area.
#
# See <a href=file:../../doc/manual.html>the documentation</a> for more
# details.
# "tests+examples/tsimpleinst.pkg" contains a small example of how to
# use this package.
# **************************************************************************
### "To business that we love,
### we rise betime
### and go to't with delight."
###
### -- William Shakespeare
api Tgen_Gui_Api {
#
include api Generated_Gui; # Generated_Gui is from
src/lib/tk/src/toolkit/generated-gui.api #
package tree_obj: Ptree_Part_Class; # Ptree_Part_Class is from
src/lib/tk/src/toolkit/tree_object_class.api create_folder: tk::Coordinate -> Void;
};
generic package generate_tree_gui_g (package appl: Application;) # Application is from
src/lib/tk/src/toolkit/appl.api# : Tgen_Gui_Api # XXX BUGGO FIXME why is api not used?
{
stipulate
include package tk;
include package basic_utilities;
herein
default_printmode= { mode => print::long,
printdepth => 100,
height => NULL,
width => NULL }; # the value is temporary
fun debugmsg msg = debug::print 11 ("GenGUI: " + msg);
fun bool2string TRUE => "TRUE";
bool2string FALSE => "FALSE"; end;
# ************************************************************************
#
# Pumping the data-model of APPL (the object_class) on object_class_trees
#
# ************************************************************************
package m: (weak) Part_Class # Part_Class is from
src/lib/tk/src/toolkit/object_class.api = appl; # Take just appl, reduce it to its
# Objectclass, and bind it to m
# I'm not that sure if folder names should remain strings.
# We must assure uniqueness here - and the string-solution
# would require that update-operations check this constraint
# as invariant. Alternative: unique key's...
# I'll think about it
package n: (weak) Folder_Info # Folder_Info is from
src/lib/tk/src/toolkit/tree_object_class.api =
package {
Node_Info = (Ref (String), ((tk::Coordinate,
tk::Anchor_Kind)));
Subnode_Info = (tk::Coordinate, tk::Anchor_Kind);
fun ord_node ((x, _), (y, _)) = string::compare (*x,*y);
fun string_of_name_node (s, _) _ = *s;
fun rename_node s (t, _) = (t:=s);
fun reset_name_node (s, _) = (s:="...");
};
package tree_obj
=
object_to_tree_object_g (package n = n also m = m;);
fun name2string x = tree_obj::string_of_name
(tree_obj::path2name x)
default_printmode;
include package tree_obj; /* From now on, we'll have treeobjects as objects */
# ************************************************************************
#
# TGengui-State
#
# ************************************************************************
exception GENERATE_GUI_FN String;
# the state comprises a focus and a list of (tree)-objects
Gui_State = (Path, List( Part_Ilk ));
New_Part= Part_Ilk; # Distinction no longer necessary
root = ([]:List( Node_Info ), NULL: Null_Or( basic::Part_Ilk ));
gui_state = REF (root,[]:List( Part_Ilk ));
folder_id = REF (0);
fun select_object_from_guistate path
=
select_from_path (snd *gui_state) path;
fun eq x y
=
case (ord (x, y))
EQUAL => TRUE;
_ => FALSE;
esac;
# Merging into state is useful to keep orders of objects whenever possible.
# Keeps the presentation og navi-board smooth - notepad::state() yields
# the state in quite arbitrary orders . . .
fun merge s' s''
=
{ b = (list::map_partial_fn (\\ x => list::find (\\ y => eq x y; end ) s''; end ) s');
c = (list::filter (\\ x => not (list::exists (\\ y => eq x y; end ) s'); end ) s'');
b @ c;
};
fun update_object_in_guistate ([], NULL) objs
=>
gui_state := (fst *gui_state, merge (snd *gui_state) objs);
update_object_in_guistate path [obj]
=>
{ my (p, objs) = *gui_state;
gui_state:=(p, update_at_path objs path obj);
# except INCONSIST_PATH => ()
};
end;
# hooks, refreshManagement; ah, the joys of linear visibility
my
elim_ob_hook = REF( NULL: Null_Or( Part_Ilk -> Void ) ); my
intro_ob_hook
=
REF (NULL: Null_Or( (Part_Ilk, ((tk::Coordinate, tk::Anchor_Kind))) -> Void) );
my
refresh_lab_hook = REF (NULL: Null_Or( (Void -> Void) )); my
sync_hook = REF (NULL: Null_Or( (Bool -> Void) )); my
sync_back_hook = REF (NULL: Null_Or( (Void -> Void) )); my
refocus_hook = REF (NULL: Null_Or( (tree_obj::Path -> Void) ));
/* used in order to delay sync's, for example for drag-Drop-Actions.
For efficiency reasons. Could be made easily mor eager . . .
*/ my
force_refresh = REF FALSE;
# ************************************************************************
#
# The common state: the clipboard
#
# ************************************************************************
# The application clipboard-class (appl::clipboard) is a flat clipboard -
# it must be adapted to the above clipboard with tree-objects.
package canvas_clipboard = appl::clipboard; /* external clipboard for
communication with ca */
Objectlist = Void -> List( Part_Ilk );
package notepad_navigator_clipboard = clipboard_g ( Part = Objectlist;);
/* internal clipboard for communication
between navigation and notepad */
fun treeobj2obj x
=
if (is_folder x ) list::cat (list::map treeobj2obj (snd (get_folder x)));
else [fst (get_content x)];fi;
package clipboard # sort of "joined clipboard"
=
package { Part = notepad_navigator_clipboard::Part;
exception EMPTY = notepad_navigator_clipboard::EMPTY;
fun put it ev cb
=
{ notepad_navigator_clipboard::put it ev cb;
canvas_clipboard::put (appl::cb_objects_abs
(\\ ()=> list::cat (list::map
treeobj2obj (it())); end )) ev cb;};
fun get ev
=
if (notepad_navigator_clipboard::is_empty ev)
\\ ()
=
(list::map
(\\ x = content (x, ((10, 10), CENTER)))
(appl::cb_objects_rep (canvas_clipboard::get ev)())
)
except
canvas_clipboard::EMPTY = raise exception EMPTY;
else
notepad_navigator_clipboard::get ev;
fi;
fun copy ev
=
if (notepad_navigator_clipboard::is_empty ev)
\\ ()
=
list::map
(\\ x
=
content (x, ((10, 10), CENTER))
)
(appl::cb_objects_rep
(canvas_clipboard::copy ev)()
)
except
canvas_clipboard::EMPTY
=
raise exception EMPTY;
else
notepad_navigator_clipboard::copy ev;
fi;
fun is_empty ev
=
notepad_navigator_clipboard::is_empty ev
and
canvas_clipboard::is_empty ev;
};
# ************************************************************************
#
# Instantiate Navigation Board
#
# ************************************************************************
# the navigationboard
package navi_board_actions: (weak) Treelist_Callbacks # Treelist_Callbacks is from
src/lib/tk/src/toolkit/tree-list-g.pkg =
package {
Part_Ilk = tree_obj::Part_Ilk;
Node_Info = tree_obj::Node_Info;
Subnode_Info = tree_obj::Subnode_Info;
Path = tree_obj::Path;
fun content_label_action { path, was, cc } =
uw::enter_line { title=>"enter label:",
prompt=>"", default=>was, width=>30,
cc=> \\ s => { cc s; the *refresh_lab_hook ();}; end };
fun objtree_change_notifier { changed_at: Path }
=
{ debugmsg ( "general change notifier at :" $
(name2string changed_at) + "\n");
the *sync_back_hook ();};
fun focus_change_notifier { changed_at: List( Path ) } =
{ debugmsg ( "notifier activated at :" +
(string::cat
(map name2string changed_at)) +
"\n");
the *refocus_hook (hd (changed_at));};
fun open_close_notifier { is_open: Bool,
changed_at: List( Path ) } =
{ debugmsg ( "open/close notifier activated at :" +
(string::cat
(map name2string changed_at)) +
" is_open:" +
(bool2string is_open) +
"\n");
# sync just for subtree . . . >>>
if (is_prefix (hd changed_at, fst *gui_state))
the *sync_hook FALSE;
fi;
};
fun error_action s = (uw::error "ERROR" );
};
package navi_board
=
tree_list_g (package s = package {
package m = tree_obj;
package a = navi_board_actions;
Objlist = Void -> List( Part_Ilk );
package clipboard = clipboard;
};);
my _ =
(.width (navi_board::my_config) := 120);
fun navi_board window objs
=
navi_board::create_canvas objs;
# ************************************************************************
#
# Instantiate notepad_g
#
# ************************************************************************
# the construction area frame widget id
ca_frame_id = make_tagged_widget_id("ca");
# the widget id of the canvas all the items are placed on
# flag indicating wether the construction area is currently open
ca_open = REF (NULL: Null_Or( Part_Ilk ));
# And a function to check that
fun is_open ob = case *ca_open
NULL => FALSE;
THE ob2 => case (ord (ob, ob2))
EQUAL => TRUE;
_ => FALSE; esac; esac;
fun fst (x, _) = x;
fun snd (_, x) = x;
fun justx x y = x;
# split: (Node_Info * List( Part_Ilk ) -> X) *
# (Data::Part_Ilk * Subnode_Info -> X)
# -> Part_Ilk -> X
fun split (f, g) obj
=
if (is_folder obj ) f (get_folder obj);
else g (get_content obj);fi;
fun object2newobject ob
=
(ob, split (snd o fst, snd) ob);
fun newobject2object (ob, pos)
=
split (\\((n, _), ol)=>folder((n, pos), ol); end,
\\ (ob, _)=>content (ob, pos); end ) ob;
# This could be done generically:
my
label_action
=
(\\ { obj, cc } =>
(if (is_folder obj )
uw::enter_line { title=>"Renaming folder",
default=>"",
prompt=>"Please enter new name: ",
width=> 20,
cc=> \\ txt => { rename_node txt (fst (get_folder obj));
force_refresh:=TRUE; # shut up optimize !!!
navi_board::refresh_label();
};
end
};
else appl::label_action { obj => fst (get_content obj),
cc => (\\ txt => { cc txt;
navi_board::refresh_label();
};
end
)
};
fi
);
end
);
fun is_constructed ot
=
if (is_folder_type ot) FALSE;
else appl::is_constructed (get_content_type ot);
fi;
Mode = Null_Or( appl::Mode );
fun mode ot
=
if (is_folder_type ot) NULL;
else THE (appl::mode (get_content_type ot));
fi;
fun modes ot
=
if (is_folder_type ot) [];
else map THE (appl::modes (get_content_type ot));
fi;
# much better ...
fun objlist_type [] => NULL;
objlist_type [a] => THE (part_type a);
objlist_type (a . r) =>
if (list::exists (\\ oo => not (part_type a == part_type oo); end ) r ) NULL;
else THE (part_type a);fi;
end;
fun mode_name NULL => "Folder";
mode_name (THE x) => appl::mode_name x;
end;
fun set_mode (ob, m)
=
split (justx (), \\ (ob', _) => appl::set_mode (ob', the m); end ) ob;
my
outline = split (justx FALSE, appl::outline o fst);
fun delete ob
=
{ fun rem ob ob_s
=
list::filter (\\ x => not (ord (x, ob) == EQUAL); end ) ob_s;
ss = rem ob (snd *gui_state);
p = fst *gui_state;
split (justx (), appl::delete o fst) ob; # logical delete in application
case p
#
([], NULL) => { gui_state:=(p, ss);
navi_board::upd_guistate p (ss);
navi_board::refresh p;};
(m, NULL) => ({ my (n, ss) = get_folder (select_from_path
(snd *gui_state) p);
nu_fol = [folder (n, rem ob ss)];
update_object_in_guistate p nu_fol;
navi_board::upd_guistate p nu_fol;
navi_board::refresh p;
});
esac;
};
fun create_folder (x, y)
=
{ folder_id:= *folder_id + 1;
fol = folder((REF("Folder " + (int::to_string *folder_id)),
((x, y), CENTER)), /* will be overwritten by
placing . . . */
[]);
(the *intro_ob_hook) (object2newobject fol); /* Doesn't work well
does not acticate
"findNicePlace" for
some reason XXX BUGGO FIXME */
(the *sync_hook) TRUE;
};
fun show_folder x
=
{ my ((nm, _), ol) = get_folder x;
tx = "This is a folder with " +
(int::to_string (length ol)) + " subjects";
uw::display { title=> *nm, width=> 40, height=> 20,
text=> string_to_livetext tx, cc=> \\ _ => (); end };
};
fun stat_folder t_o
=
show_folder t_o; # preliminary HACK !
fun std_ops ot
=
if (is_folder_type ot )
[ (show_folder, "Show"),
(stat_folder, "Info")
];
else
map (\\ (f, s) = (split (justx(), f o fst), s))
(appl::std_ops (get_content_type ot));
fi;
fun mon_ops ot
=
if (is_folder_type ot)
[]; # so far
else
fun conv (h)(ob, c)(f)
=
h (fst (get_content ob), c)
(\\ (x, y)=(f (content (x, y), y)));
map (\\ (h, s)= (conv h, s))
(appl::mon_ops (get_content_type ot));
fi;
fun bin_ops (ot1, ot2)
=
if (is_folder_type ot1)
# moving >>>
({ fun hhh (ob, coord, ob_s, f) = { apply (the *elim_ob_hook) ob_s;
# <<< delete items from Notepad
the *sync_hook TRUE;
# <<< sync notepad with state and redisplay
/* <<< extremely EXPENSIVE. Could be replaced
by something that just deletes CITEMS --
commenting out results just in WARNINGS like:
WARNING: Exception CANVAS_ITEM: item: anocid115xIcon
not found */
# update substate in gui_state und nv-board
{ pp = join_path((fst *gui_state),
(name_of ob));
my (n, ss) = get_folder (select_from_path
(snd *gui_state)
(pp));
update_object_in_guistate pp [folder (n, ss@ob_s)];
navi_board::upd_guistate pp [folder (n, ss@ob_s)];
navi_board::refresh pp;
/* replace tree-object on note-pad -
otherwise correspondence of all three
states violated (data-invariant) and
sync will not work in future >>> */
(the *elim_ob_hook) ob;
(the *intro_ob_hook)
(folder (n, ss@ob_s), (coord, CENTER));
}
;};
THE hhh; });
else ({ fun loc_refresh (p as ([], NULL)) ob => ({ ss =snd *gui_state;
gui_state:=(p, ss @ [ob]);
navi_board::upd_guistate p (ss @ [ob]);
navi_board::refresh p;
});
loc_refresh (p as (m, NULL)) ob => ({ my (n, ss) = get_folder
(select_from_path
(snd *gui_state)
(p));
nu_fol = [folder (n, ss @ [ob])];
update_object_in_guistate p nu_fol;
navi_board::upd_guistate p nu_fol;
navi_board::refresh p;
}); end;
prior = split (justx NULL, THE o fst);
# filtering folder objects
fun hhh (h)(ob, c, ob_s, f) = h (fst (get_content ob), c,
list::map_partial_fn prior ob_s,
(\\ (x, y)=>{ loc_refresh (fst *gui_state)
(content (x, y));
f (content (x, y), y);}; end ));
if (is_folder_type ot2)
NULL; # Dragging folders on basic objs: not considered useful!
else
case (appl::bin_ops (get_content_type ot1, get_content_type ot2))
THE f => THE (hhh f);
NULL => NULL;
esac;
fi;
});fi;
fun open_con_area { window, obj, replace_object_action, outline_object_action }
=
{
# id of the window holding the con/area widgets
cawin = if (appl::conf::one_window)
window;
else make_window_id ();
fi;
# Event_Callbacks for the con area while open:
fun ca_enter wsp ev
=
{ dropobs = appl::cb_objects_rep (appl::clipboard::copy ev)();
oot = appl::objlist_type dropobs;
case oot
THE ot => appl::area_ops ot wsp dropobs;
NULL => ();
esac;
}
except
appl::clipboard::EMPTY => (); end ;
fun ca_namings wsp
=
[EVENT_CALLBACK (ENTER, ca_enter wsp)];
# Event_Callbacks for the con/area while closed:
ca_closed_namings
=
[EVENT_CALLBACK (ENTER, k0)];
fun close_con_area nu_ob
=
{ ca_open := NULL;
replace_object_action nu_ob;
if (appl::conf::one_window)
apply (delete_widget o get_widget_id)
(get_subwidgets (get_widget ca_frame_id));
else
close_window cawin;
fi;
};
if ( (is_constructed (part_type obj))
and not (null_or::not_null *ca_open)
)
# get the con/area widgets from the application:
my (bobj, _) = get_content obj; /* Folder are never
constrcution objects...*/
my (wsp, wwidgs, init) = appl::area_open (cawin, bobj,
close_con_area);
# Add con/area event_callbacks to widgets:
wwidgs = map (\\ w=> update_widget_event_callbacks w
((ca_namings wsp)@
(get_widget_event_callbacks w)); end )
wwidgs;
outline_object_action ();
# set flag
ca_open := THE obj;
if appl::conf::one_window
apply (add_widget window ca_frame_id) wwidgs;
add_event_callbacks ca_frame_id (ca_namings wsp);
init ();
else
open_window
(make_window
{
window_id => cawin,
traits => [WINDOW_TITLE (appl::conf::ca_title
(appl::string_of_name
(appl::name_of bobj)
(default_printmode))),
WIDE_HIGH_X_Y (THE (appl::conf::ca_width,
appl::conf::ca_height),
appl::conf::ca_xy),
WINDOW_GROUP window],
subwidgets => PACKED wwidgs,
event_callbacks => [], init
}
);
fi;
else
debugmsg "Not a primary object, or ConArea already open.";fi;
};
fun object_action { obj, window,
outline_object_action,
replace_object_action }
=
{ fun name2path_app (p, m) a
=
(a @ p, m);
if (is_folder_type (part_type obj))
the *refocus_hook
(name2path_app (name_of obj)
(fst (fst *gui_state)));
else
open_con_area
{
obj,
window,
outline_object_action,
replace_object_action
=>
\\ x
=
replace_object_action (content (x, ((10, 10), CENTER)))
};
fi;
};
package notepadappl =
package {
include package tree_obj;
Mode = Mode;
mode = mode;
modes= modes;
mode_name = mode_name;
set_mode = set_mode;
outline = outline;
std_ops = std_ops;
mon_ops = mon_ops;
bin_ops = bin_ops;
delete = delete;
New_Part = (Part_Ilk, ((tk::Coordinate, tk::Anchor_Kind)));
objlist_type = objlist_type;
is_constructed = is_constructed;
object_action = object_action;
label_action = label_action;
create_actions = [(\\ { pos=>(x, y), tag=>str } => create_folder (x, y); end, "folder")];
is_locked_object = is_open;
fun init () = map (\\ x=> (content x, snd x); end ) (appl::init());
package conf = appl::conf;
Objectlist = Void -> List( Part_Ilk );
package clipboard = clipboard;
};
package notepad = notepad_g (package appl = notepadappl;);
# ************************************************************************
#
# sync'ing and refresh
#
# ************************************************************************
# sync - routines = synchronize distributed state + refresh.
# implemented unidirectional for efficiency reasons
# here: sync notepad-content with gui_state, and gui_state with
# navi-board-state.
fun sync_notepad_with_guistate refresh_naviboard
=
{ path = fst *gui_state;
my () = debugmsg("sync: path >" + (name2string path) + "<\n");
case path
#
# Sync at root position:
#
([], NULL) => { update_object_in_guistate path (map newobject2object (notepad::state()));
navi_board::upd_guistate path (snd *gui_state);
debugmsg "sync root refresh\n";
};
# Sync somewhere deep inside:
#
(_, NULL) => { (get_folder (select_object_from_guistate path))
->
(n, s');
s'' = map newobject2object (notepad::state());
fol = folder (n, merge s' s'');
update_object_in_guistate path [fol];
navi_board::upd_guistate path [fol];
debugmsg "sync inside refresh\n";
};
esac;
if refresh_naviboard navi_board::refresh path; fi;
};
fun sync_naviboard_into_guistate ()
=
{ my (ap, st) = *gui_state;
my () = gui_state := (ap, navi_board::get_guistate());
newobs = case ap
([], NULL) => snd *gui_state;
(_, NULL) => snd (get_folder(
select_object_from_guistate
ap));
esac;
notepad::init (map object2newobject newobs);
};
/* necessary for sync'ing name-changes and moves from
the naviboard with the notepad (done since names are shared)
and rearrangements within the naviboard. Thats not implemented
so far. . . */
/*
fun create_folder (x, y)
=
(let my () = folder_id:= *folder_id + 1;
fol = Folder((REF("Folder " + (int::to_string *folder_id)),
((x, y), CENTER)), /* will be overwritten by
placing . . . */
[])
in notepad::intro (object2newobject fol); /* doesn't work well
does not acticate
"findNicePlace" for
some reason */
sync_notepad_with_guistate TRUE
end);
*/
fun refocus_notepad (path as (m, NULL))
=>
{ (*gui_state) -> (act_path, act_state);
#
debugmsg("focus: act_path " + (name2string act_path) +
"\n path " + (name2string path) + "\n");
fun do_it ()
=
{ sync_notepad_with_guistate TRUE;
#
gui_state := (path, snd *gui_state);
{ newobs =
case path
([], NULL) => snd *gui_state;
(_, NULL) => snd (get_folder(
select_object_from_guistate
path)); esac;
notepad::init (map object2newobject newobs);
};
};
case (ord_path (path, act_path))
#
EQUAL => (if *force_refresh do_it(); fi);
_ => do_it();
esac;
force_refresh := FALSE;
};
refocus_notepad (m, THE _ )
=>
refocus_notepad (m, NULL);
end;
# ************************************************************************
#
# export
#
# ************************************************************************
fun main_wid window
=
{ ass_area
=
FRAME
{ widget_id=> make_widget_id(),
subwidgets => PACKED [ notepad::main_wid window ],
packing_hints => [EXPAND TRUE, FILL XY, PACK_AT RIGHT],
traits => [],
event_callbacks=> []
};
navi = FRAME { widget_id=> make_widget_id(),
subwidgets => PACKED [navi_board window []],
packing_hints => [ EXPAND TRUE, FILL XY, PACK_AT LEFT ],
traits => [], event_callbacks=> [] };
ass_area_nnavi = FRAME { widget_id=> make_widget_id(),
subwidgets => PACKED [ass_area, navi],
packing_hints => [ EXPAND TRUE, FILL XY, PACK_AT TOP ],
traits => [],
event_callbacks=> [] };
if (appl::conf::one_window)
FRAME {
widget_id=> make_widget_id(),
subwidgets => PACKED [ass_area_nnavi,
FRAME { widget_id=>ca_frame_id,
subwidgets => PACKED [],
packing_hints => [ FILL ONLY_X, PACK_AT BOTTOM],
traits=> [HEIGHT appl::conf::ca_height],
event_callbacks => [] }
],
packing_hints=> [],
event_callbacks=> [],
traits=> []
};
else
ass_area_nnavi;
fi;
};
# my init: gui_state -> Void
# Call that as init action of main window
fun init ((s, NULL), objs)
=>
{ fun distinct eq [] => TRUE;
distinct eq (a . r) => (not (list::exists (eq a) r))
and (distinct eq r);
end;
# too naiv. should be recursively
fun eq x y
=
case (ord (x, y))
EQUAL => TRUE;
_ => FALSE;
esac;
my () = { elim_ob_hook := THE notepad::elim;
intro_ob_hook := THE notepad::intro;
refresh_lab_hook := THE (\\ _ => notepad::init (
notepad::state()); end );
/* does a complete refresh; a more
local refresh of labels would increase
EFFICIENCY here */
sync_hook := THE sync_notepad_with_guistate;
sync_back_hook := THE sync_naviboard_into_guistate;
refocus_hook := THE refocus_notepad;
};
if (distinct eq objs)
gui_state:=((s, NULL), objs);
navi_board::upd_guistate (s, NULL) objs;
navi_board::refresh (s, NULL);
if (null s) # focus at root
notepad::init (map object2newobject objs);
else refocus_notepad (s, NULL);
fi;
ca_open := NULL;
appl::area_init();
else
raise exception GENERATE_GUI_FN "generate_tree_gui_g init: objects must be distinct";
fi;
};
init ((s, THE _), objs) =>
raise exception GENERATE_GUI_FN "generate_tree_gui_g init: path must point to folder"; end;
# my state: Void -> gui_state
# This is the initial state which only has those objects as given
# by the application's init() function (see above).
fun state ()
=
{ sync_notepad_with_guistate TRUE;
*gui_state;
};
# introduce (not "create" really) a new object into the
# manipulation area
# my intro: New_Part -> Void
fun intro obj = notepad::intro (object2newobject obj);
# NOT YET: probably sync with state and naviboard necessary !!!
# my initial_state: Void -> gui_state
fun initial_state () = (([], NULL), map content (appl::init()));
# Builds a (flat) gui_state just from appl::init
# Quite old-fashioned, I guess.
# Resynchronize all icons, e.g. if objects have changed their mode.
# (Unfortunately, we cinnae change icons of single objects, since
# we can't identify objects...)
#
# my redisplay_icons: (Part_Ilk -> Bool) -> Void
redisplay_icons = notepad::redisplay_icons;
end;
};