use std::collections::HashMap;
use std::io::{Read, Write};
use std::process;
use std::sync::mpsc;
use std::sync::{Mutex, OnceLock};
use std::thread;
use super::font;
use super::toplevel;
use super::widget;
use once_cell::sync::Lazy;
#[derive(Debug)]
pub struct TkError {
message: String,
}
static TRACE_WISH: OnceLock<bool> = OnceLock::new();
fn tracing() -> bool {
*TRACE_WISH.get().unwrap_or(&false)
}
static mut WISH: OnceLock<process::Child> = OnceLock::new();
static mut OUTPUT: OnceLock<process::ChildStdout> = OnceLock::new();
static mut SENDER: OnceLock<mpsc::Sender<String>> = OnceLock::new();
pub(super) fn kill_wish() {
unsafe {
WISH.get_mut()
.unwrap()
.kill()
.expect("Wish was unexpectedly already finished");
}
}
pub fn tell_wish(msg: &str) {
if tracing() {
println!("wish: {}", msg);
}
unsafe {
SENDER.get_mut().unwrap().send(String::from(msg)).unwrap();
SENDER.get_mut().unwrap().send(String::from("\n")).unwrap();
}
}
pub fn ask_wish(msg: &str) -> String {
tell_wish(msg);
unsafe {
let mut input = [32; 10000]; if OUTPUT.get_mut().unwrap().read(&mut input).is_ok() {
if let Ok(input) = String::from_utf8(input.to_vec()) {
if tracing() {
println!("---: {:?}", &input.trim());
}
return input.trim().to_string();
}
}
}
panic!("Eval-wish failed to get a result");
}
static NEXT_ID: Lazy<Mutex<i64>> = Lazy::new(|| Mutex::new(0));
pub fn next_wid(parent: &str) -> String {
let mut nid = NEXT_ID.lock().unwrap();
*nid += 1;
if parent == "." {
format!(".r{}", nid)
} else {
format!("{}.r{}", parent, nid)
}
}
pub fn next_var() -> String {
let mut nid = NEXT_ID.lock().unwrap();
*nid += 1;
format!("::var{}", nid)
}
pub(super) fn current_id() -> i64 {
let nid = NEXT_ID.lock().unwrap();
*nid
}
type Callback0 = Box<(dyn Fn() + Send + 'static)>;
pub(super) fn mk_callback0<F>(f: F) -> Callback0
where
F: Fn() + Send + 'static,
{
Box::new(f) as Callback0
}
static CALLBACKS0: Lazy<Mutex<HashMap<String, Callback0>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(super) fn add_callback0(wid: &str, callback: Callback0) {
CALLBACKS0
.lock()
.unwrap()
.insert(String::from(wid), callback);
}
fn get_callback0(wid: &str) -> Option<Callback0> {
if let Some((_, command)) = CALLBACKS0.lock().unwrap().remove_entry(wid) {
Some(command)
} else {
None
}
}
fn eval_callback0(wid: &str) {
if let Some(command) = get_callback0(wid) {
command();
if !wid.contains("after") && !CALLBACKS0.lock().unwrap().contains_key(wid) {
add_callback0(wid, command);
}
} }
type Callback1Bool = Box<(dyn Fn(bool) + Send + 'static)>;
pub(super) fn mk_callback1_bool<F>(f: F) -> Callback1Bool
where
F: Fn(bool) + Send + 'static,
{
Box::new(f) as Callback1Bool
}
static CALLBACKS1BOOL: Lazy<Mutex<HashMap<String, Callback1Bool>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(super) fn add_callback1_bool(wid: &str, callback: Callback1Bool) {
CALLBACKS1BOOL
.lock()
.unwrap()
.insert(String::from(wid), callback);
}
fn get_callback1_bool(wid: &str) -> Option<Callback1Bool> {
if let Some((_, command)) = CALLBACKS1BOOL.lock().unwrap().remove_entry(wid) {
Some(command)
} else {
None
}
}
fn eval_callback1_bool(wid: &str, value: bool) {
if let Some(command) = get_callback1_bool(wid) {
command(value);
if !CALLBACKS1BOOL.lock().unwrap().contains_key(wid) {
add_callback1_bool(wid, command);
}
} }
type Callback1Event = Box<(dyn Fn(widget::TkEvent) + Send + 'static)>;
pub(super) fn mk_callback1_event<F>(f: F) -> Callback1Event
where
F: Fn(widget::TkEvent) + Send + 'static,
{
Box::new(f) as Callback1Event
}
static CALLBACKS1EVENT: Lazy<Mutex<HashMap<String, Callback1Event>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(super) fn add_callback1_event(wid: &str, callback: Callback1Event) {
CALLBACKS1EVENT
.lock()
.unwrap()
.insert(String::from(wid), callback);
}
fn get_callback1_event(wid: &str) -> Option<Callback1Event> {
if let Some((_, command)) = CALLBACKS1EVENT.lock().unwrap().remove_entry(wid) {
Some(command)
} else {
None
}
}
fn eval_callback1_event(wid: &str, value: widget::TkEvent) {
if let Some(command) = get_callback1_event(wid) {
command(value);
if !CALLBACKS1EVENT.lock().unwrap().contains_key(wid) {
add_callback1_event(wid, command);
}
} }
type Callback1Float = Box<(dyn Fn(f64) + Send + 'static)>;
pub(super) fn mk_callback1_float<F>(f: F) -> Callback1Float
where
F: Fn(f64) + Send + 'static,
{
Box::new(f) as Callback1Float
}
static CALLBACKS1FLOAT: Lazy<Mutex<HashMap<String, Callback1Float>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(super) fn add_callback1_float(wid: &str, callback: Callback1Float) {
CALLBACKS1FLOAT
.lock()
.unwrap()
.insert(String::from(wid), callback);
}
fn get_callback1_float(wid: &str) -> Option<Callback1Float> {
if let Some((_, command)) = CALLBACKS1FLOAT.lock().unwrap().remove_entry(wid) {
Some(command)
} else {
None
}
}
fn eval_callback1_float(wid: &str, value: f64) {
if let Some(command) = get_callback1_float(wid) {
command(value);
if !CALLBACKS1FLOAT.lock().unwrap().contains_key(wid) {
add_callback1_float(wid, command);
}
} }
type Callback1Font = Box<(dyn Fn(font::TkFont) + Send + 'static)>;
pub(super) fn mk_callback1_font<F>(f: F) -> Callback1Font
where
F: Fn(font::TkFont) + Send + 'static,
{
Box::new(f) as Callback1Font
}
static CALLBACKS1FONT: Lazy<Mutex<HashMap<String, Callback1Font>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(super) fn add_callback1_font(wid: &str, callback: Callback1Font) {
CALLBACKS1FONT
.lock()
.unwrap()
.insert(String::from(wid), callback);
}
fn get_callback1_font(wid: &str) -> Option<Callback1Font> {
if let Some((_, command)) = CALLBACKS1FONT.lock().unwrap().remove_entry(wid) {
Some(command)
} else {
None
}
}
fn eval_callback1_font(wid: &str, value: font::TkFont) {
if let Some(command) = get_callback1_font(wid) {
command(value);
if !CALLBACKS1FONT.lock().unwrap().contains_key(wid) {
add_callback1_font(wid, command);
}
} }
pub fn mainloop() {
unsafe {
loop {
let mut input = [32; 10000];
if OUTPUT.get_mut().unwrap().read(&mut input).is_ok() {
if let Ok(input) = String::from_utf8(input.to_vec()) {
if tracing() {
println!("Callback: {:?}", &input.trim());
}
if input.starts_with("clicked") {
if let Some(n) = input.find(&['\n', '\r']) {
let widget = &input[8..n];
eval_callback0(widget);
}
} else if input.starts_with("cb1b") {
let parts: Vec<&str> = input.split('-').collect();
let widget = parts[1].trim();
let value = parts[2].trim();
eval_callback1_bool(widget, value == "1");
} else if input.starts_with("cb1e") {
let parts: Vec<&str> = input.split(':').collect();
let widget_pattern = parts[1].trim();
let x = parts[2].parse::<i64>().unwrap_or(0);
let y = parts[3].parse::<i64>().unwrap_or(0);
let root_x = parts[4].parse::<i64>().unwrap_or(0);
let root_y = parts[5].parse::<i64>().unwrap_or(0);
let height = parts[6].parse::<i64>().unwrap_or(0);
let width = parts[7].parse::<i64>().unwrap_or(0);
let key_code = parts[8].parse::<u64>().unwrap_or(0);
let key_symbol = parts[9].parse::<String>().unwrap_or_default();
let mouse_button = parts[10].parse::<u64>().unwrap_or(0);
let event = widget::TkEvent {
x,
y,
root_x,
root_y,
height,
width,
key_code,
key_symbol,
mouse_button,
};
eval_callback1_event(widget_pattern, event);
} else if input.starts_with("cb1f") {
let parts: Vec<&str> = input.split('-').collect();
let widget = parts[1].trim();
let value = parts[2].trim().parse::<f64>().unwrap_or(0.0);
eval_callback1_float(widget, value);
} else if let Some(font) = input.strip_prefix("font") {
let font = font.trim();
if let Ok(font) = font.parse::<font::TkFont>() {
eval_callback1_font("font", font);
}
} else if input.starts_with("exit") {
kill_wish();
return; }
}
}
}
}
}
pub fn start_wish() -> Result<toplevel::TkTopLevel, TkError> {
start_with("wish")
}
pub fn start_with(wish: &str) -> Result<toplevel::TkTopLevel, TkError> {
if let Ok(_) = TRACE_WISH.set(false) {
start_tk_connection(wish)
} else {
return Err(TkError { message: String::from("Failed to set trace option") })
}
}
pub fn trace_with(wish: &str) -> Result<toplevel::TkTopLevel, TkError> {
if let Ok(_) = TRACE_WISH.set(true) {
start_tk_connection(wish)
} else {
return Err(TkError { message: String::from("Failed to set trace option") })
}
}
fn start_tk_connection(wish: &str)-> Result<toplevel::TkTopLevel, TkError> {
let err_msg = format!("Do not start {} twice", wish);
unsafe {
if let Ok(wish_process) = process::Command::new(wish)
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.spawn()
{
if WISH.set(wish_process).is_err() {
return Err(TkError { message: err_msg });
}
} else {
return Err(TkError {
message: format!("Failed to start {} process", wish),
});
};
let mut input = WISH.get_mut().unwrap().stdin.take().unwrap();
if OUTPUT
.set(WISH.get_mut().unwrap().stdout.take().unwrap())
.is_err()
{
return Err(TkError { message: err_msg });
}
input.write_all(b"catch {{ package require Plotchart }}\n").unwrap();
input
.write_all(b"wm protocol . WM_DELETE_WINDOW { puts stdout {exit} ; flush stdout } \n")
.unwrap();
input.write_all(b"option add *tearOff 0\n").unwrap();
input
.write_all(
b"proc font_choice {w font args} {
set res {font }
append res [font actual $font]
puts $res
flush stdout
}\n",
)
.unwrap();
input
.write_all(
b"proc scale_value {w value args} {
puts cb1f-$w-$value
flush stdout
}\n",
)
.unwrap();
let (sender, receiver) = mpsc::channel();
SENDER.set(sender).expect(&err_msg);
thread::spawn(move || loop {
let msg: Result<String, mpsc::RecvError> = receiver.recv();
if let Ok(msg) = msg {
input.write_all(msg.as_bytes()).unwrap();
input.write_all(b"\n").unwrap();
}
});
}
Ok(toplevel::TkTopLevel {
id: String::from("."),
})
}
pub fn end_wish() {
kill_wish();
process::exit(0);
}
pub(super) fn split_items(text: &str) -> Vec<String> {
let mut result: Vec<String> = vec![];
let mut remaining = text.trim();
while !remaining.is_empty() {
if let Some(start) = remaining.find('{') {
for word in remaining[..start].split_whitespace() {
result.push(String::from(word));
}
if let Some(end) = remaining.find('}') {
result.push(String::from(&remaining[start + 1..end]));
remaining = remaining[end + 1..].trim();
} else {
break; }
} else {
for word in remaining.split_whitespace() {
result.push(String::from(word));
}
break;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_items_1() {
let result = split_items("");
assert_eq!(0, result.len());
}
#[test]
fn split_items_2() {
let result = split_items("abc");
assert_eq!(1, result.len());
assert_eq!("abc", result[0]);
}
#[test]
fn split_items_3() {
let result = split_items(" abc def ");
assert_eq!(2, result.len());
assert_eq!("abc", result[0]);
assert_eq!("def", result[1]);
}
#[test]
fn split_items_4() {
let result = split_items("{abc def}");
assert_eq!(1, result.len());
assert_eq!("abc def", result[0]);
}
#[test]
fn split_items_5() {
let result = split_items("{abc def} xy_z {another}");
assert_eq!(3, result.len());
assert_eq!("abc def", result[0]);
assert_eq!("xy_z", result[1]);
assert_eq!("another", result[2]);
}
}