//! Statische Forms-Typinformation. Namen werden beim Übersetzen in diese //! Tabellenindizes aufgelöst; die Laufzeit muss keine Strings durchsuchen. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ObjectClass { Form, CheckBox, ComboBox, CommandButton, DirListBox, DriveListBox, FileListBox, Frame, HScrollBar, Label, ListBox, Menu, OptionButton, PictureBox, TextBox, Timer, VScrollBar, Screen, Spin, } impl ObjectClass { pub const ALL: [Self; 19] = [ Self::Form, Self::CheckBox, Self::ComboBox, Self::CommandButton, Self::DirListBox, Self::DriveListBox, Self::FileListBox, Self::Frame, Self::HScrollBar, Self::Label, Self::ListBox, Self::Menu, Self::OptionButton, Self::PictureBox, Self::TextBox, Self::Timer, Self::VScrollBar, Self::Screen, Self::Spin, ]; pub fn name(self) -> &'static str { match self { Self::Form => "FORM", Self::CheckBox => "CHECKBOX", Self::ComboBox => "COMBOBOX", Self::CommandButton => "COMMANDBUTTON", Self::DirListBox => "DIRLISTBOX", Self::DriveListBox => "DRIVELISTBOX", Self::FileListBox => "FILELISTBOX", Self::Frame => "FRAME", Self::HScrollBar => "HSCROLLBAR", Self::Label => "LABEL", Self::ListBox => "LISTBOX", Self::Menu => "MENU", Self::OptionButton => "OPTIONBUTTON", Self::PictureBox => "PICTUREBOX", Self::TextBox => "TEXTBOX", Self::Timer => "TIMER", Self::VScrollBar => "VSCROLLBAR", Self::Screen => "SCREEN", Self::Spin => "SPIN", } } pub fn display_name(self) -> &'static str { match self { Self::Form => "Form", Self::CheckBox => "CheckBox", Self::ComboBox => "ComboBox", Self::CommandButton => "CommandButton", Self::DirListBox => "DirListBox", Self::DriveListBox => "DriveListBox", Self::FileListBox => "FileListBox", Self::Frame => "Frame", Self::HScrollBar => "HScrollBar", Self::Label => "Label", Self::ListBox => "ListBox", Self::Menu => "Menu", Self::OptionButton => "OptionButton", Self::PictureBox => "PictureBox", Self::TextBox => "TextBox", Self::Timer => "Timer", Self::VScrollBar => "VScrollBar", Self::Screen => "Screen", Self::Spin => "Spin", } } pub fn parse(name: &str) -> Option { Self::ALL .into_iter() .find(|c| c.name().eq_ignore_ascii_case(name)) } pub fn id(self) -> u8 { Self::ALL.iter().position(|c| *c == self).unwrap() as u8 } pub fn from_id(id: u8) -> Option { Self::ALL.get(id as usize).copied() } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PropertyType { Integer, Single, String, Boolean, Object, IntegerArray, } #[derive(Debug, Clone, Copy, PartialEq)] pub enum PropertyDefault { Integer(i32), Single(f32), String(&'static str), Boolean(bool), Empty, } #[derive(Debug, Clone, Copy, PartialEq)] pub struct PropertySpec { pub name: &'static str, pub ty: PropertyType, pub default: PropertyDefault, pub min: Option, pub max: Option, pub writable: bool, } const fn int(name: &'static str, default: i32) -> PropertySpec { PropertySpec { name, ty: PropertyType::Integer, default: PropertyDefault::Integer(default), min: None, max: None, writable: true, } } const fn range(name: &'static str, default: i32, min: i32, max: i32) -> PropertySpec { PropertySpec { name, ty: PropertyType::Integer, default: PropertyDefault::Integer(default), min: Some(min), max: Some(max), writable: true, } } const fn boolp(name: &'static str, default: bool) -> PropertySpec { PropertySpec { name, ty: PropertyType::Boolean, default: PropertyDefault::Boolean(default), min: None, max: None, writable: true, } } const fn string(name: &'static str, default: &'static str) -> PropertySpec { PropertySpec { name, ty: PropertyType::String, default: PropertyDefault::String(default), min: None, max: None, writable: true, } } const fn ro(name: &'static str, ty: PropertyType) -> PropertySpec { PropertySpec { name, ty, default: PropertyDefault::Empty, min: None, max: None, writable: false, } } /// Eigenschaftstabelle in stabiler Reihenfolge. Der Vektor ist klein und wird /// nur beim Übersetzen bzw. Erzeugen eines Objekts aufgebaut. pub fn properties(class: ObjectClass) -> Vec { use ObjectClass::*; let mut p = Vec::new(); if !matches!(class, Timer | Menu | Screen) { p.extend([ range("BACKCOLOR", 7, 0, 15), boolp("ENABLED", true), range("HEIGHT", 1, 1, 254), range("LEFT", 0, 0, 254), range("MOUSEPOINTER", 0, 0, 12), ro("PARENT", PropertyType::Object), string("TAG", ""), range("TOP", 0, 0, 254), boolp("VISIBLE", true), range("WIDTH", 1, 1, 254), ]); } if !matches!(class, Form | Timer | Menu | Screen) { p.push(int("INDEX", 0)); p.push(int("TABINDEX", 0)); } if !matches!(class, Form | Frame | Label | Timer | Menu | Screen) { p.push(boolp("TABSTOP", true)); } if !matches!(class, Form | Timer | Menu | Screen) { p.push(string("CTLNAME", "")); } if matches!( class, Form | TextBox | ListBox | ComboBox | CheckBox | OptionButton | Label | HScrollBar | VScrollBar | PictureBox ) { p.push(range("FORECOLOR", 0, 0, 15)); } if matches!( class, Form | CommandButton | TextBox | ListBox | ComboBox | CheckBox | OptionButton | Frame | Label | HScrollBar | VScrollBar | PictureBox ) { p.push(range("DRAGMODE", 0, 0, 1)); } match class { Form => p.extend([ boolp("AUTOREDRAW", false), range("BORDERSTYLE", 2, 0, 6), string("CAPTION", ""), boolp("CONTROLBOX", true), int("CURRENTX", 0), int("CURRENTY", 0), string("FORMNAME", ""), range("FORMTYPE", 0, 0, 1), boolp("MAXBUTTON", true), boolp("MINBUTTON", true), ro("SCALEHEIGHT", PropertyType::Integer), ro("SCALEWIDTH", PropertyType::Integer), range("WINDOWSTATE", 0, 0, 2), ]), CommandButton => p.extend([ boolp("CANCEL", false), string("CAPTION", ""), boolp("DEFAULT", false), boolp("VALUE", false), ]), TextBox => p.extend([ range("BORDERSTYLE", 1, 0, 1), boolp("MULTILINE", false), range("SCROLLBARS", 0, 0, 3), int("SELLENGTH", 0), int("SELSTART", 0), string("SELTEXT", ""), string("TEXT", ""), ]), ListBox => p.extend([ ro("LIST", PropertyType::String), ro("LISTCOUNT", PropertyType::Integer), int("LISTINDEX", -1), boolp("SORTED", false), string("TEXT", ""), ]), ComboBox => p.extend([ ro("LIST", PropertyType::String), ro("LISTCOUNT", PropertyType::Integer), int("LISTINDEX", -1), boolp("SORTED", false), int("SELLENGTH", 0), int("SELSTART", 0), string("SELTEXT", ""), range("STYLE", 0, 0, 2), string("TEXT", ""), ]), CheckBox => p.extend([string("CAPTION", ""), range("VALUE", 0, 0, 2)]), OptionButton => p.extend([string("CAPTION", ""), range("VALUE", 0, -1, 1)]), Frame => p.push(string("CAPTION", "")), Label => p.extend([ range("ALIGNMENT", 0, 0, 2), boolp("AUTOSIZE", false), range("BORDERSTYLE", 0, 0, 2), string("CAPTION", ""), ]), HScrollBar | VScrollBar => p.extend([ ro("ATTACHED", PropertyType::Boolean), range("LARGECHANGE", 1, 1, 32767), range("MIN", 0, -32768, 32767), range("MAX", 32767, -32768, 32767), range("SMALLCHANGE", 1, 1, 32767), range("VALUE", 0, -32768, 32767), ]), Spin => { p.extend([ ro("BORDERSTYLE", PropertyType::Integer), range("INTERVAL", 250, 0, 65535), range("MIN", 0, -32768, 32767), range("MAX", 32767, -32768, 32767), range("STYLE", 0, 0, 1), range("VALUE", 0, -32768, 32767), ]); for name in ["HEIGHT", "WIDTH"] { if let Some(property) = p.iter_mut().find(|property| property.name == name) { property.writable = false; } } } PictureBox => p.extend([ boolp("AUTOREDRAW", false), range("BORDERSTYLE", 1, 0, 2), int("CURRENTX", 0), int("CURRENTY", 0), ro("SCALEHEIGHT", PropertyType::Integer), ro("SCALEWIDTH", PropertyType::Integer), ]), Timer => p.extend([ string("CTLNAME", ""), boolp("ENABLED", true), int("INDEX", 0), range("INTERVAL", 0, 0, 65535), ro("PARENT", PropertyType::Object), string("TAG", ""), ]), Menu => p.extend([ string("CAPTION", ""), boolp("CHECKED", false), string("CTLNAME", ""), boolp("ENABLED", true), int("INDEX", 0), ro("PARENT", PropertyType::Object), boolp("SEPARATOR", false), string("TAG", ""), boolp("VISIBLE", true), string("SHORTCUT", ""), ]), DirListBox => p.extend([ ro("LIST", PropertyType::String), ro("LISTCOUNT", PropertyType::Integer), int("LISTINDEX", -1), string("PATH", ""), string("TEXT", ""), ]), DriveListBox => p.extend([ ro("LIST", PropertyType::String), ro("LISTCOUNT", PropertyType::Integer), int("LISTINDEX", -1), string("DRIVE", ""), string("TEXT", ""), ]), FileListBox => p.extend([ ro("LIST", PropertyType::String), ro("LISTCOUNT", PropertyType::Integer), int("LISTINDEX", -1), string("FILENAME", ""), string("PATH", ""), string("PATTERN", "*.*"), boolp("ARCHIVE", true), boolp("HIDDEN", false), boolp("NORMAL", true), boolp("READONLY", true), boolp("SYSTEM", false), string("TEXT", ""), ]), Screen => p.extend([ ro("ACTIVECONTROL", PropertyType::Object), ro("ACTIVEFORM", PropertyType::Object), boolp("AUTOREDRAW", false), PropertySpec { name: "CONTROLPANEL", ty: PropertyType::IntegerArray, default: PropertyDefault::Integer(0), min: Some(0), max: Some(15), writable: true, }, ro("HEIGHT", PropertyType::Integer), ro("WIDTH", PropertyType::Integer), range("MOUSEPOINTER", 0, 0, 12), ]), } if class == Form { if let Some(visible) = p.iter_mut().find(|p| p.name == "VISIBLE") { visible.default = PropertyDefault::Boolean(false); } } p } pub fn property(class: ObjectClass, name: &str) -> Option<(u16, PropertySpec)> { properties(class) .into_iter() .enumerate() .find(|(_, p)| p.name.eq_ignore_ascii_case(name)) .map(|(i, p)| (i as u16, p)) } pub fn methods(class: ObjectClass) -> &'static [&'static str] { use ObjectClass::*; match class { Form => &[ "CLS", "DRAG", "HIDE", "LOAD", "MOVE", "PRINT", "PRINTFORM", "REFRESH", "SHOW", "TEXTHEIGHT", "TEXTWIDTH", "UNLOAD", ], ListBox | ComboBox => &[ "ADDITEM", "DRAG", "MOVE", "REFRESH", "REMOVEITEM", "SETFOCUS", ], CommandButton | TextBox | CheckBox | OptionButton => { &["DRAG", "MOVE", "REFRESH", "SETFOCUS"] } Frame | Label | HScrollBar | VScrollBar => &["DRAG", "MOVE", "REFRESH"], Spin => &["DRAG", "REFRESH", "SETFOCUS"], PictureBox => &[ "CLS", "DRAG", "MOVE", "PRINT", "REFRESH", "SETFOCUS", "TEXTHEIGHT", "TEXTWIDTH", ], Screen => &["HIDE", "SHOW"], _ => &[], } } pub fn method_is_implemented(class: ObjectClass, name: &str) -> bool { methods(class).contains(&name) } pub fn method_arity(class: ObjectClass, name: &str) -> Option<(usize, usize)> { match (class, name) { (ObjectClass::Form, "SHOW") => Some((0, 1)), (ObjectClass::Form, "HIDE" | "LOAD" | "UNLOAD") | (ObjectClass::Screen, "HIDE" | "SHOW") => Some((0, 0)), (_, "REFRESH" | "SETFOCUS" | "CLS" | "PRINTFORM") => Some((0, 0)), (_, "DRAG") => Some((1, 1)), (_, "MOVE") => Some((2, 4)), (ObjectClass::ListBox | ObjectClass::ComboBox, "ADDITEM") => Some((1, 2)), (ObjectClass::ListBox | ObjectClass::ComboBox, "REMOVEITEM") => Some((1, 1)), (ObjectClass::Form | ObjectClass::PictureBox, "PRINT") => Some((0, usize::MAX)), (ObjectClass::Form | ObjectClass::PictureBox, "TEXTWIDTH" | "TEXTHEIGHT") => Some((1, 1)), _ => None, } } pub fn method_return_type(class: ObjectClass, name: &str) -> Option { match (class, name) { (ObjectClass::Form | ObjectClass::PictureBox, "TEXTWIDTH" | "TEXTHEIGHT") => { Some(PropertyType::Integer) } _ => None, } } pub fn events(class: ObjectClass) -> &'static [&'static str] { use ObjectClass::*; match class { Form => &[ "CLICK", "DBLCLICK", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOAD", "LOSTFOCUS", "MOUSEDOWN", "MOUSEMOVE", "MOUSEUP", "PAINT", "RESIZE", "UNLOAD", ], CommandButton => &[ "CLICK", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", ], TextBox => &[ "CHANGE", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", ], ListBox => &[ "CLICK", "DBLCLICK", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", "MOUSEDOWN", "MOUSEMOVE", "MOUSEUP", ], ComboBox => &[ "CHANGE", "CLICK", "DBLCLICK", "DRAGDROP", "DRAGOVER", "DROPDOWN", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", "MOUSEDOWN", "MOUSEMOVE", "MOUSEUP", ], CheckBox => &[ "CLICK", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", ], OptionButton => &[ "CLICK", "DBLCLICK", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", ], Frame => &["DRAGDROP", "DRAGOVER"], Label => &[ "CHANGE", "CLICK", "DBLCLICK", "DRAGDROP", "DRAGOVER", "MOUSEDOWN", "MOUSEMOVE", "MOUSEUP", ], HScrollBar | VScrollBar => &[ "CHANGE", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", ], Spin => &[ "CUSTOM", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", ], PictureBox => &[ "CLICK", "DBLCLICK", "DRAGDROP", "DRAGOVER", "GOTFOCUS", "KEYDOWN", "KEYPRESS", "KEYUP", "LOSTFOCUS", "MOUSEDOWN", "MOUSEMOVE", "MOUSEUP", "PAINT", ], Timer => &["TIMER"], Menu => &["CLICK"], DirListBox => &["CHANGE", "CLICK", "DBLCLICK", "PATHCHANGE"], DriveListBox => &["CHANGE"], FileListBox => &["CLICK", "DBLCLICK", "PATTERNCHANGE", "PATHCHANGE"], Screen => &[], } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EventParamType { Integer, Single, Control, } pub fn event_params(event: &str) -> Option<&'static [(&'static str, EventParamType)]> { use EventParamType::*; Some(match event.to_ascii_uppercase().as_str() { "KEYDOWN" | "KEYUP" => &[("KEYCODE", Integer), ("SHIFT", Integer)], "KEYPRESS" => &[("KEYASCII", Integer)], "MOUSEDOWN" | "MOUSEMOVE" | "MOUSEUP" => &[ ("BUTTON", Integer), ("SHIFT", Integer), ("X", Single), ("Y", Single), ], "DRAGDROP" => &[("SOURCE", Control), ("X", Single), ("Y", Single)], "DRAGOVER" => &[ ("SOURCE", Control), ("X", Single), ("Y", Single), ("STATE", Integer), ], "UNLOAD" => &[("CANCEL", Integer)], "CUSTOM" => &[("EVENTTYPE", Integer)], "CLICK" | "DBLCLICK" | "CHANGE" | "DROPDOWN" | "GOTFOCUS" | "LOSTFOCUS" | "LOAD" | "PAINT" | "RESIZE" | "TIMER" | "PATHCHANGE" | "PATTERNCHANGE" => &[], _ => return None, }) } #[derive(Debug, Clone)] pub struct FormObject { pub name: String, pub class: ObjectClass, pub parent_form: Option, pub array: bool, } #[derive(Debug, Clone, Default)] pub struct FormCatalog { pub objects: Vec, } impl FormCatalog { pub fn add( &mut self, name: impl Into, class: ObjectClass, parent_form: Option<&str>, array: bool, ) -> u16 { let id = self.objects.len() as u16; self.objects.push(FormObject { name: name.into().to_uppercase(), class, parent_form: parent_form.map(str::to_uppercase), array, }); id } pub fn find(&self, name: &str) -> Option<(u16, &FormObject)> { self.objects .iter() .enumerate() .find(|(_, o)| o.name.eq_ignore_ascii_case(name)) .map(|(i, o)| (i as u16, o)) } pub fn belongs_to(&self, object: &FormObject, form: &str) -> bool { let mut parent = object.parent_form.as_deref(); for _ in 0..self.objects.len() { let Some(name) = parent else { return false }; if name.eq_ignore_ascii_case(form) { return true; } parent = self .find(name) .and_then(|(_, object)| object.parent_form.as_deref()); } false } }