## textentry.pkg
#
# See also:
#
src/lib/x-kit/widget/leaf/button.pkg#
src/lib/x-kit/widget/leaf/diamondbutton.pkg#
src/lib/x-kit/widget/leaf/roundbutton.pkg# Compiled by:
#
src/lib/x-kit/widget/xkit-widget.sublib# This package gets used in:
#
#
stipulate
include package threadkit; # threadkit is from
src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg include package geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg #
package chr = char; # char is from
src/lib/std/char.pkg package evt = gui_event_types; # gui_event_types is from
src/lib/x-kit/widget/gui/gui-event-types.pkg package g2p = gadget_to_pixmap; # gadget_to_pixmap is from
src/lib/x-kit/widget/theme/gadget-to-pixmap.pkg package gd = gui_displaylist; # gui_displaylist is from
src/lib/x-kit/widget/theme/gui-displaylist.pkg package gt = guiboss_types; # guiboss_types is from
src/lib/x-kit/widget/gui/guiboss-types.pkg package wt = widget_theme; # widget_theme is from
src/lib/x-kit/widget/theme/widget/widget-theme.pkg package wti = widget_theme_imp; # widget_theme_imp is from
src/lib/x-kit/widget/xkit/theme/widget/default/widget-theme-imp.pkg package r8 = rgb8; # rgb8 is from
src/lib/x-kit/xclient/src/color/rgb8.pkg package r64 = rgb; # rgb is from
src/lib/x-kit/xclient/src/color/rgb.pkg package wi = widget_imp; # widget_imp is from
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg package g2d = geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg package g2j = geometry2d_junk; # geometry2d_junk is from
src/lib/std/2d/geometry2d-junk.pkg package mtx = rw_matrix; # rw_matrix is from
src/lib/std/src/rw-matrix.pkg package pp = standard_prettyprinter; # standard_prettyprinter is from
src/lib/prettyprint/big/src/standard-prettyprinter.pkg package gtg = guiboss_to_guishim; # guiboss_to_guishim is from
src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg nb = log::note_on_stderr; # log is from
src/lib/std/src/log.pkgherein
package textentry
: Textentry # Textentry is from
src/lib/x-kit/widget/leaf/textentry.api {
App_To_Textentry
=
{ id: Id,
#
get_active: Void -> Bool,
get_state: Void -> String,
get_relief: Void -> wt::Relief, #
set_active_to: Bool -> Void,
set_state_to: String -> Void, # Also calls gadget_to_guiboss.needs_redraw_gadget_request(id);
set_relief_to: wt::Relief -> Void # Also calls gadget_to_guiboss.needs_redraw_gadget_request(id);
};
Redraw_Fn_Arg
=
REDRAW_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
frame_number: Int, # 1,2,3,... Purely for convenience of widget, guiboss-imp makes no use of this.
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Window rectangle in which to draw.
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
#
duration_in_seconds: Float, # If state has changed look-imp should call note_changed_gadget_foreground() before this time is up. Also useful for motionblur.
widget_to_guiboss: gt::Widget_To_Guiboss,
gadget_mode: gt::Gadget_Mode,
#
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
palette: wt::Gadget_Palette,
#
default_redraw_fn: Redraw_Fn,
#
relief: wt::Relief, # Is the widget outline a slope, a ridge, or a flat band?
have_keyboard_focus: Bool,
state: String,
#
fonts: List(String),
font_weight: Null_Or(wt::Font_Weight),
font_size: Null_Or(Int),
no_box: Bool,
margin: Int,
thick: Int
}
withtype
Redraw_Fn
=
Redraw_Fn_Arg
->
{ displaylist: gd::Gui_Displaylist,
point_in_gadget: Null_Or(g2d::Point -> Bool), #
pixels_high_min: Int,
pixels_wide_min: Int
}
;
Mouse_Click_Fn_Arg
=
MOUSE_CLICK_FN_ARG # Needs to be a sumtype because of recursive reference in default_mouse_click_fn.
{ id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event: gt::Mousebutton_Event, # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
button: evt::Mousebutton, # Which mousebutton was pressed/released.
point: g2d::Point, # Where the mouse was.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_click_fn: Mouse_Click_Fn,
#
relief: Ref(wt::Relief), # Is the widget outline a slope, a ridge, or a flat band?
have_keyboard_focus: Bool,
state: Ref(String),
#
notify_string_outs: Void -> Void, #
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Click_Fn = Mouse_Click_Fn_Arg -> Void;
Mouse_Drag_Fn_Arg
=
MOUSE_DRAG_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_drag_fn: Mouse_Drag_Fn,
#
relief: Ref(wt::Relief), # Is the widget outline a slope, a ridge, or a flat band?
have_keyboard_focus: Bool,
state: Ref(String),
#
notify_string_outs: Void -> Void, #
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Drag_Fn = Mouse_Drag_Fn_Arg -> Void;
Mouse_Transit_Fn_Arg # Note that buttons are always all up in a mouse-transit event -- otherwise it is a mouse-drag event.
=
MOUSE_TRANSIT_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_transit_fn: Mouse_Transit_Fn,
#
relief: Ref(wt::Relief), # Is the widget outline a slope, a ridge, or a flat band?
have_keyboard_focus: Bool,
state: Ref(String),
#
notify_string_outs: Void -> Void, #
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Transit_Fn = Mouse_Transit_Fn_Arg -> Void;
Key_Event_Fn_Arg
=
KEY_EVENT_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
keystroke: gt::Keystroke_Info, # Keystring etc for event.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
widget_to_guiboss: gt::Widget_To_Guiboss,
guiboss_to_widget: gt::Guiboss_To_Widget, # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_key_event_fn: Key_Event_Fn,
#
relief: Ref(wt::Relief), # Is the widget outline a slope, a ridge, or a flat band?
have_keyboard_focus: Bool,
state: Ref(String),
#
notify_string_outs: Void -> Void, #
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Key_Event_Fn = Key_Event_Fn_Arg -> Void;
Option = PIXELS_SQUARE Int
#
| PIXELS_HIGH_MIN Int
| PIXELS_WIDE_MIN Int
#
| PIXELS_HIGH_CUT Float
| PIXELS_WIDE_CUT Float
#
| INITIALLY_ACTIVE Bool
#
| BODY_COLOR rgb::Rgb
| BODY_COLOR_WITH_MOUSEFOCUS rgb::Rgb
| BODY_COLOR_WHEN_ON rgb::Rgb
| BODY_COLOR_WHEN_ON_WITH_MOUSEFOCUS rgb::Rgb
#
| ID Id
| DOC String
#
| RELIEF wt::Relief
# Should button boundary be drawn flat, raised, sunken, ridged or grooved?
| MARGIN Int
# How many pixels to inset button relative to its assigned window site. Default is 4.
| THICK Int
# Thickness of lines (well, polygons) forming button. Default is 5.
| NO_BOX
# Do not draw a box around button.
#
| TEXT String
# Text to draw inside button. Default is "".
#
| FONT_SIZE Int
# Show any text in this pointsize. Default is 12.
| FONTS List(String)
# Override theme font: Font to use for text label, e.g. "-*-courier-bold-r-*-*-20-*-*-*-*-*-*-*". We'll use the first font in list which is found on X server, else "9x15" (which X guarantees to have).
#
| ROMAN
# Show any text in plain font from widget-theme. This is the default.
| ITALIC
# Show any text in italic font from widget-theme.
| BOLD
# Show any text in bold font from widget-theme. NB: Text is either bold or italic, not both.
#
| REDRAW_FN Redraw_Fn
# Application-specific handler for widget redraw.
| MOUSE_CLICK_FN Mouse_Click_Fn
# Application-specific handler for mousebutton clicks.
| MOUSE_DRAG_FN Mouse_Drag_Fn
# Application-specific handler for mouse drags.
| MOUSE_TRANSIT_FN Mouse_Transit_Fn
# Application-specific handler for mouse crossings.
| KEY_EVENT_FN Key_Event_Fn
# Application-specific handler for keyboard input.
#
| STRING_OUT (String -> Void)
# Widget's current state will be sent to these fns each time state changes.
| PORTWATCHER (Null_Or(App_To_Textentry) -> Void)
# Widget's app port will be sent to these fns at widget startup.
| SITEWATCHER (Null_Or((Id,g2d::Box)) -> Void)
# Widget's site in window coordinates will be sent to these fns each time it changes.
; # To help prevent deadlock, watcher fns should be fast and nonblocking, typically just setting a var or entering something into a mailqueue.
fun process_options
( options: List(Option),
#
{ body_color,
body_color_with_mousefocus,
body_color_when_on,
body_color_when_on_with_mousefocus,
#
widget_id,
widget_doc,
#
relief,
margin,
thick,
no_box,
#
text,
#
fonts,
font_weight,
font_size,
#
redraw_fn,
mouse_click_fn,
mouse_drag_fn,
mouse_transit_fn,
key_event_fn,
#
initially_active,
#
widget_options,
#
portwatchers,
string_outs,
sitewatchers
}
)
=
{ my_body_color = REF body_color;
my_body_color_with_mousefocus = REF body_color_with_mousefocus;
my_body_color_when_on = REF body_color_when_on;
my_body_color_when_on_with_mousefocus = REF body_color_when_on_with_mousefocus;
#
my_widget_id = REF widget_id;
my_widget_doc = REF widget_doc;
#
my_relief = REF relief;
my_margin = REF margin;
my_thick = REF thick;
my_no_box = REF no_box;
#
my_text = REF text;
#
my_fonts = REF fonts;
my_font_weight = REF font_weight;
my_font_size = REF font_size;
#
my_redraw_fn = REF redraw_fn;
my_mouse_click_fn = REF mouse_click_fn;
my_mouse_drag_fn = REF mouse_drag_fn;
my_mouse_transit_fn = REF mouse_transit_fn;
my_key_event_fn = REF key_event_fn;
#
my_initially_active = REF initially_active;
#
my_widget_options = REF widget_options;
#
my_portwatchers = REF portwatchers;
my_string_outs = REF string_outs;
my_sitewatchers = REF sitewatchers;
#
apply do_option options
where
fun do_option (INITIALLY_ACTIVE b) => my_initially_active := b;
#
do_option (BODY_COLOR c) => my_body_color := THE c;
do_option (BODY_COLOR_WITH_MOUSEFOCUS c) => my_body_color_with_mousefocus := THE c;
do_option (BODY_COLOR_WHEN_ON c) => my_body_color_when_on := THE c;
do_option (BODY_COLOR_WHEN_ON_WITH_MOUSEFOCUS c) => my_body_color_when_on_with_mousefocus := THE c;
#
do_option (ID i) => my_widget_id := THE i;
do_option (DOC d) => my_widget_doc := d;
#
do_option (RELIEF r) => my_relief := r;
do_option (MARGIN i) => my_margin := i;
do_option (THICK i) => my_thick := i;
do_option (NO_BOX ) => my_no_box := TRUE;
#
do_option (TEXT t) => my_text := t;
#
do_option (FONTS t) => my_fonts := t;
#
do_option (ROMAN ) => my_font_weight := THE wt::ROMAN_FONT;
do_option (ITALIC ) => my_font_weight := THE wt::ITALIC_FONT;
do_option (BOLD ) => my_font_weight := THE wt::BOLD_FONT;
#
do_option (FONT_SIZE i) => my_font_size := THE i;
#
do_option (REDRAW_FN f) => my_redraw_fn := f;
do_option (MOUSE_CLICK_FN f) => my_mouse_click_fn := f;
do_option (MOUSE_DRAG_FN f) => my_mouse_drag_fn := THE f;
do_option (MOUSE_TRANSIT_FN f) => my_mouse_transit_fn := f;
do_option (KEY_EVENT_FN f) => my_key_event_fn := f;
#
do_option (PORTWATCHER c) => my_portwatchers := c ! *my_portwatchers;
do_option (STRING_OUT c) => my_string_outs := c ! *my_string_outs;
do_option (SITEWATCHER c) => my_sitewatchers := c ! *my_sitewatchers;
#
#
do_option (PIXELS_HIGH_MIN i) => my_widget_options := (wi::PIXELS_HIGH_MIN i) ! *my_widget_options;
do_option (PIXELS_WIDE_MIN i) => my_widget_options := (wi::PIXELS_WIDE_MIN i) ! *my_widget_options;
#
do_option (PIXELS_HIGH_CUT f) => my_widget_options := (wi::PIXELS_HIGH_CUT f) ! *my_widget_options;
do_option (PIXELS_WIDE_CUT f) => my_widget_options := (wi::PIXELS_WIDE_CUT f) ! *my_widget_options;
#
do_option (PIXELS_SQUARE i) => my_widget_options := (wi::PIXELS_HIGH_MIN i)
! (wi::PIXELS_WIDE_MIN i)
! (wi::PIXELS_HIGH_CUT 0.0)
! (wi::PIXELS_WIDE_CUT 0.0)
! *my_widget_options;
end;
end;
{ body_color => *my_body_color,
body_color_with_mousefocus => *my_body_color_with_mousefocus,
body_color_when_on => *my_body_color_when_on,
body_color_when_on_with_mousefocus => *my_body_color_when_on_with_mousefocus,
#
widget_id => *my_widget_id,
widget_doc => *my_widget_doc,
#
relief => *my_relief,
margin => *my_margin,
thick => *my_thick,
no_box => *my_no_box,
#
text => *my_text,
#
fonts => *my_fonts,
font_weight => *my_font_weight,
font_size => *my_font_size,
#
redraw_fn => *my_redraw_fn,
mouse_click_fn => *my_mouse_click_fn,
mouse_drag_fn => *my_mouse_drag_fn,
mouse_transit_fn => *my_mouse_transit_fn,
key_event_fn => *my_key_event_fn,
#
initially_active => *my_initially_active,
#
widget_options => *my_widget_options,
#
portwatchers => *my_portwatchers,
string_outs => *my_string_outs,
sitewatchers => *my_sitewatchers
};
};
fun default_redraw_fn (REDRAW_FN_ARG a) # Handle a guiboss request to redraw ourself.
=
{ font_size = a.font_size;
font_weight = a.font_weight;
fonts = a.fonts;
gadget_mode = a.gadget_mode;
margin = a.margin;
no_box = a.no_box;
palette = a.palette;
relief = a.relief;
site = a.site;
state = a.state;
theme = a.theme;
thick = a.thick;
have_keyboard_focus = a.have_keyboard_focus;
background_box = site;
background = [ gd::COLOR (palette.surround_color, [ gd::FILLED_BOXES [ background_box ]]) ];
inner_box = g2d::box::make_nested_box (background_box, margin); #
fun get_fontnames ()
=
{ font_size_to_use
=
case font_size THE i => i;
NULL => *theme.default_font_size;
esac;
fontname_to_use
=
case font_weight THE wt::ROMAN_FONT => *theme.get_roman_fontname font_size_to_use;
THE wt::ITALIC_FONT => *theme.get_italic_fontname font_size_to_use;
THE wt::BOLD_FONT => *theme.get_bold_fontname font_size_to_use;
NULL => *theme.get_roman_fontname font_size_to_use;
esac;
fontnames = fonts @ [ fontname_to_use, "9x15" ];
fontnames;
};
fun get_text_dimensions (text: String)
=
{ g = wti::get__guiboss_to_hostwindow theme;
#
font = g.get_font (get_fontnames ());
{ font_ascent => font.font_height.ascent,
font_descent => font.font_height.descent,
length_in_pixels => font.string_length_in_pixels text
};
};
fun make_text_displaylist
(
text: String,
text_box: g2d::Box
)
=
{ cursor_width = 4; # This probably should be an m-width or such.
text_indent = 3; # For readability, insert some space between frame and start of text.
#
text_dimensions = get_text_dimensions text;
fontnames = get_fontnames ();
box_corners = g2d::box::box_corners text_box;
#
(g2d::point::mean [ box_corners.upper_left, box_corners.lower_left ])
->
{ row, col };
# Indent text a bit from image and also
# center it properly vertically -- most
# fonts have ascent > descent:
#
row = row - text_dimensions.font_descent + ((text_dimensions.font_ascent + text_dimensions.font_descent) / 2);
col = col + text_indent; # For readability, insert some space between frame and start of text.
col = int::min (col, text_box.col + text_box.wide - text_dimensions.length_in_pixels - cursor_width); # Scroll text left when it reaches end of text_box.
draw_point = { row, col };
#
displaylist
=
[ gd::FONT ( fontnames,
[ gd::PUT_TEXT ( gd::TO_RIGHT_OF_POINT,
[ gd::TEXT (draw_point, text) ]
)
]
)
];
displaylist
=
if (not have_keyboard_focus)
#
displaylist;
else
cursor_box = { row => row - text_dimensions.font_ascent,
col => col + text_dimensions.length_in_pixels,
high => text_dimensions.font_ascent + text_dimensions.font_descent,
wide => cursor_width
};
cursorlist = [ gd::FILLED_BOXES [ cursor_box ] ];
displaylist @ cursorlist;
fi;
displaylist
=
[ gd::COLOR (palette.text_color, displaylist) ];
displaylist = [ gd::CLIP_TO (text_box, displaylist) ];
displaylist;
};
foreground = [ gd::COLOR (palette.body_color, [ gd::FILLED_POLYGON (g2d::box::to_points inner_box) ]) ]; # Interior of widget. We draw this first because 3D outline occupies same bounding box:
foreground = case no_box FALSE => foreground @ *theme.pictureframe palette { box => inner_box, thick, relief }; # 3-D outline for widget.
TRUE => foreground;
esac;
text_box = { row => inner_box.row + thick,
col => inner_box.col + thick,
high => inner_box.high - 2*thick,
wide => inner_box.wide - 2*thick
};
foreground = foreground @ make_text_displaylist (state, text_box); # Maybe incorporate text into button foreground.
fun point_in_gadget (point: g2d::Point)
=
g2d::point::in_box (point, inner_box);
point_in_gadget = THE point_in_gadget;
{ displaylist => background @ foreground,
point_in_gadget,
pixels_high_min => 0,
pixels_wide_min => 0
};
};
fun default_mouse_click_fn
(
MOUSE_CLICK_FN_ARG
{ id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event: gt::Mousebutton_Event, # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
button: evt::Mousebutton,
point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_click_fn: Mouse_Click_Fn,
#
relief: Ref(wt::Relief),
have_keyboard_focus: Bool,
state: Ref(String),
#
notify_string_outs: Void -> Void, #
needs_redraw_gadget_request: Void -> Void
}: Mouse_Click_Fn_Arg
)
=
{
case event
#
gt::MOUSEBUTTON_PRESS
=>
{ widget_to_guiboss.g.request_keyboard_focus id;
};
gt::MOUSEBUTTON_RELEASE
=>
{
};
esac;
();
};
fun default_mouse_transit_fn (MOUSE_TRANSIT_FN_ARG a)
=
{ case a.transit
#
gt::CAME => { a.needs_redraw_gadget_request (); # So button will lighten when mouse enters it.
#
};
gt::LEFT => { a.needs_redraw_gadget_request (); # So button will revert when mouse leaves it.
};
_ => ();
esac;
};
fun default_key_event_fn (KEY_EVENT_FN_ARG a)
=
{ key_event = a.keystroke.key_event;
keystring = a.keystroke.keystring;
keychar = a.keystroke.keychar;
state = a.state;
needs_redraw_gadget_request = a.needs_redraw_gadget_request;
notify_string_outs = a.notify_string_outs;
if (key_event == gt::KEY_PRESS) # Currently we ignore KEY_RELEASE.
#
if (char::is_print keychar)
#
state := *state + keystring;
needs_redraw_gadget_request ();
else
if (keychar == chr::ctrl_h
or keychar == chr::del
or keystring == "<backspace>"
or keystring == "<delete>")
#
if (string::length_in_bytes *state > 0)
#
state := string::substring (*state, 0, string::length_in_bytes *state - 1);
needs_redraw_gadget_request ();
fi;
elif (keychar == chr::ctrl_u)
#
if (string::length_in_bytes *state > 0)
#
state := "";
needs_redraw_gadget_request ();
fi;
elif (keychar == chr::return)
#
notify_string_outs ();
elif (keychar == chr::nul)
#
case keystring
#
("<Clear>"
| "<Home>")
=>
if (string::length_in_bytes *state > 0)
#
state := "";
needs_redraw_gadget_request ();
fi;
"<Left>"
=>
if (string::length_in_bytes *state > 0)
#
state := string::substring (*state, 0, string::length_in_bytes *state - 1);
needs_redraw_gadget_request ();
fi;
_ => ();
esac;
fi;
fi;
fi;
};
fun with (options: List(Option)) # PUBLIC. The point of the 'with' name is that GUI coders can write 'textentry::with { this => that, foo => bar, ... }.'
=
{
reliefref = REF wt::SUNKEN; #
textref = REF "";
#
(process_options
(
options,
#
{ body_color => NULL,
body_color_with_mousefocus => NULL,
body_color_when_on => NULL,
body_color_when_on_with_mousefocus => NULL,
#
widget_id => NULL,
widget_doc => "<textentry>",
#
relief => *reliefref,
margin => 4,
thick => 5,
no_box => FALSE,
#
text => *textref,
#
fonts => [],
font_weight => (THE wt::BOLD_FONT: Null_Or(wt::Font_Weight)),
font_size => (NULL: Null_Or(Int)),
#
redraw_fn => default_redraw_fn,
mouse_click_fn => default_mouse_click_fn,
mouse_drag_fn => NULL,
mouse_transit_fn => default_mouse_transit_fn,
key_event_fn => default_key_event_fn,
#
initially_active => TRUE,
#
widget_options => [],
#
portwatchers => [],
string_outs => [],
sitewatchers => []
}
) )
->
{ # These values are globally visible to the subsequenc fns, which can lock them in as needed.
body_color,
body_color_with_mousefocus,
body_color_when_on,
body_color_when_on_with_mousefocus,
#
widget_id,
widget_doc,
#
relief,
margin,
thick,
no_box,
#
text,
#
fonts,
font_weight,
font_size,
#
redraw_fn,
mouse_click_fn,
mouse_drag_fn,
mouse_transit_fn,
key_event_fn,
#
initially_active,
#
widget_options,
#
portwatchers,
string_outs,
sitewatchers
};
reliefref := relief;
textref := text;
#
#######################################
# Top of per-imp state variable section
#
widget_to_guiboss__global
=
REF (NULL: Null_Or((gt::Widget_To_Guiboss, Id)));
fun note_changed_gadget_activity (is_active: Bool)
=
case (*widget_to_guiboss__global)
#
THE (widget_to_guiboss, id) => widget_to_guiboss.g.note_changed_gadget_activity { id, is_active };
NULL => ();
esac;
fun needs_redraw_gadget_request ()
=
case (*widget_to_guiboss__global)
#
THE (widget_to_guiboss, id) => widget_to_guiboss.g.needs_redraw_gadget_request(id);
NULL => ();
esac;
last_known_site
=
REF ( { col => -1, wide => -1,
row => -1, high => -1
}: g2d::Box
);
have_keyboard_focus__global = REF FALSE;
button_active
=
REF initially_active;
exception SAVED_STATE { last_known_site: g2d::Box, # Here we're doing the usual hack of using Exception as an extensible datatype -- nothing to do with actually raising or trapping exceptions.
state: String,
button_active: Bool
};
fun note_site (id: Id, site: g2d::Box)
=
if(*last_known_site != site)
last_known_site := site;
#
apply tell_watcher sitewatchers
where
fun tell_watcher sitewatcher
=
sitewatcher (THE (id,site));
end;
fi;
fun notify_string_outs ()
=
apply tell_watcher string_outs
where
fun tell_watcher string_out
=
string_out *textref;
end;
fun note_state (t: String)
=
if(*textref != t)
textref := t;
#
notify_string_outs ();
fi;
#
# End of state variable section
###############################
#####################
# Top of port section
#
# Here we implement our App_To_Textentry port:
fun set_active_to (is_active: Bool)
=
{ button_active := is_active;
#
note_changed_gadget_activity is_active;
};
fun set_state_to (state: String)
=
{ note_state state;
#
needs_redraw_gadget_request ();
};
fun set_relief_to (relief: wt::Relief)
=
{
reliefref := relief;
#
needs_redraw_gadget_request ();
};
fun get_active ()
=
*button_active;
fun get_state ()
=
*textref;
fun get_relief ()
=
*reliefref;
#
# End of port section
#####################
###############################
# Top of widget hook fn section
#
# These fns get called by widget_imp logic, ultimately # widget_imp is from
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg # in response to user mouseclicks and keypresses etc:
fun startup_fn
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
widget_to_guiboss: gt::Widget_To_Guiboss,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue
}
=
{ widget_to_guiboss__global
:=
THE (widget_to_guiboss, id);
app_to_button
=
{ id,
#
get_active,
get_state,
get_relief,
#
set_active_to,
set_state_to,
set_relief_to
}
: App_To_Textentry
;
apply tell_watcher portwatchers # We do this here rather than (say) above this fn because we don't want the port in circulation until we're running.
where
fun tell_watcher portwatcher
=
portwatcher (THE app_to_button);
end;
();
};
fun shutdown_fn () # Return to widget_imp an exception packaging up our state; this will be returned to guiboss_imp, saved in the
= # Paused_Gui tree, and passed to our startup_fn when/if gui is restarted. This exception will never be raised;
{ apply tell_watcher portwatchers #
where
fun tell_watcher portwatcher
=
portwatcher NULL;
end;
apply tell_watcher sitewatchers
where
fun tell_watcher sitewatcher
=
sitewatcher NULL;
end;
case *widget_to_guiboss__global
#
THE (widget_to_guiboss, id)
=>
if *have_keyboard_focus__global
#
widget_to_guiboss.g.release_keyboard_focus id;
fi;
NULL => ();
esac;
};
fun initialize_gadget_fn
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
site: g2d::Box, # Window rectangle in which to draw.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
pass_font: List(String) -> Replyqueue
-> (evt::Font -> Void) -> Void, # Nonblocking version of next, for use in imps.
get_font: List(String) -> evt::Font, # Accepts a list of font names which are tried in order.
make_rw_pixmap: g2d::Size -> g2p::Gadget_To_Rw_Pixmap,
#
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
();
};
fun redraw_request_fn_wrapper
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
frame_number: Int, # 1,2,3,... Purely for convenience of widget-imp, guiboss-imp makes no use of this.
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Window rectangle in which to draw.
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
#
duration_in_seconds: Float, # If state has changed widget-imp should call redraw_gadget() before this time is up. Also useful for motionblur.
widget_to_guiboss: gt::Widget_To_Guiboss,
gadget_mode: gt::Gadget_Mode,
#
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void,
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
palette = *theme.current_gadget_colors { gadget_is_on => FALSE, # We're not a button, we don't have ON/OFF state. (But maybe click-to-focus should work like ON, if/when we implement it?)
gadget_mode,
popup_nesting_depth,
#
body_color,
body_color_when_on,
body_color_with_mousefocus,
body_color_when_on_with_mousefocus
};
text = *textref;
redraw_fn_arg
=
REDRAW_FN_ARG
{ id,
doc,
frame_number,
frame_indent_hint,
site,
popup_nesting_depth,
duration_in_seconds,
widget_to_guiboss,
gadget_mode,
theme,
do,
to,
palette,
#
default_redraw_fn,
#
state => *textref,
relief => *reliefref,
have_keyboard_focus => *have_keyboard_focus__global,
fonts,
font_weight,
font_size,
no_box,
margin,
thick
};
(redraw_fn redraw_fn_arg)
->
{ displaylist,
point_in_gadget,
pixels_high_min,
pixels_wide_min
};
widget_to_guiboss.g.redraw_gadget { id, site, displaylist, point_in_gadget };
};
fun mouse_click_fn_wrapper # This a callback we hand to
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg {
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event: gt::Mousebutton_Event, # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
button: evt::Mousebutton,
point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
mouse_click_fn_arg
=
MOUSE_CLICK_FN_ARG
{
id,
doc,
event,
button,
point,
widget_layout_hint,
frame_indent_hint,
site,
modifier_keys_state,
mousebuttons_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_click_fn,
#
state => textref, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
relief => reliefref,
have_keyboard_focus => *have_keyboard_focus__global,
#
notify_string_outs,
needs_redraw_gadget_request
};
mouse_click_fn mouse_click_fn_arg;
};
fun mouse_drag_fn_wrapper # This a callback we hand to
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg (
{ id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
)
=
{ note_site (id,site);
#
mouse_drag_fn_arg
=
MOUSE_DRAG_FN_ARG
{
id,
doc,
event_point,
start_point,
last_point,
widget_layout_hint,
frame_indent_hint,
site,
phase,
button,
modifier_keys_state,
mousebuttons_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_drag_fn => \\ _ = (), # Default drag behavior for buttons is to do absolutely nothing.
#
state => textref, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
relief => reliefref,
have_keyboard_focus => *have_keyboard_focus__global,
#
notify_string_outs,
needs_redraw_gadget_request
};
case mouse_drag_fn
#
THE mouse_drag_fn => mouse_drag_fn mouse_drag_fn_arg;
NULL => (); # We do not expect this case to happen: If mouse_drag_fn is NULL mouse_drag_fn_wrapper should not have been registered with widget-imp so we should never get called.
esac;
};
fun mouse_transit_fn_wrapper
#
( arg as
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
)
=
{ note_site (id,site);
#
mouse_transit_fn_arg
=
MOUSE_TRANSIT_FN_ARG
{
id,
doc,
event_point,
widget_layout_hint,
frame_indent_hint,
site,
transit,
modifier_keys_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_transit_fn, #
#
state => textref, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
relief => reliefref,
have_keyboard_focus => *have_keyboard_focus__global,
#
notify_string_outs,
needs_redraw_gadget_request
};
mouse_transit_fn mouse_transit_fn_arg;
();
};
fun key_event_fn_wrapper
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
keystroke: gt::Keystroke_Info, # Keystring etc for event.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
widget_to_guiboss: gt::Widget_To_Guiboss,
guiboss_to_widget: gt::Guiboss_To_Widget, # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
key_event_fn_arg
=
KEY_EVENT_FN_ARG
{
id,
doc,
keystroke,
widget_layout_hint,
frame_indent_hint,
site,
widget_to_guiboss,
guiboss_to_widget,
theme,
do,
to,
#
default_key_event_fn,
#
state => textref, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
relief => reliefref,
have_keyboard_focus => *have_keyboard_focus__global,
#
notify_string_outs,
needs_redraw_gadget_request
};
key_event_fn key_event_fn_arg;
();
};
fun note_keyboard_focus_fn_wrapper # Not really a wrapper because we don't currently allow clients to replace it, but it is structurally parallel with our other wrapper fns in that it gets handed to widget-imp.pkg.
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
have_keyboard_focus: Bool,
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ have_keyboard_focus__global
:=
have_keyboard_focus;
needs_redraw_gadget_request ();
};
#
# End of widget hook fn section
###############################
widget_options
=
case mouse_drag_fn
#
THE _ => (wi::MOUSE_DRAG_FN mouse_drag_fn_wrapper) ! widget_options; # Register for drag events only if we are going to use them.
NULL => widget_options;
esac;
widget_options
=
case widget_id
#
THE id => (wi::ID id) ! widget_options; #
NULL => widget_options;
esac;
widget_options
=
[ wi::STARTUP_FN startup_fn, # We always register for these five because our base behavior depends on them.
wi::SHUTDOWN_FN shutdown_fn,
wi::INITIALIZE_GADGET_FN initialize_gadget_fn,
wi::REDRAW_REQUEST_FN redraw_request_fn_wrapper,
wi::MOUSE_CLICK_FN mouse_click_fn_wrapper,
wi::MOUSE_TRANSIT_FN mouse_transit_fn_wrapper,
wi::KEY_EVENT_FN key_event_fn_wrapper,
wi::NOTE_KEYBOARD_FOCUS_FN note_keyboard_focus_fn_wrapper,
wi::DOC widget_doc
]
@
widget_options
;
make_widget_fn = wi::make_widget_start_fn widget_options;
gt::WIDGET make_widget_fn; # So caller can write guiplan = gt::ROW [ frame::with [...], frame::with [...], ... ];
}; # PUBLIC
};
end;