summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rust/src/interaction.rs394
1 files changed, 393 insertions, 1 deletions
diff --git a/rust/src/interaction.rs b/rust/src/interaction.rs
index 5915e67e..926a6e68 100644
--- a/rust/src/interaction.rs
+++ b/rust/src/interaction.rs
@@ -20,7 +20,9 @@ use std::ffi::CString;
use std::os::raw::c_void;
use std::path::PathBuf;
-use crate::string::BnString;
+use crate::binaryview::BinaryView;
+use crate::rc::Ref;
+use crate::string::{BnStr, BnString};
pub fn get_text_line_input(prompt: &str, title: &str) -> Option<String> {
let prompt = CString::new(prompt).unwrap();
@@ -138,6 +140,396 @@ pub fn show_message_box(
unsafe { BNShowMessageBox(title.as_ptr(), text.as_ptr(), buttons, icon) }
}
+pub enum FormResponses {
+ None,
+ String(String),
+ Integer(i64),
+ Address(u64),
+ Index(usize),
+}
+
+enum FormData {
+ Label {
+ _text: BnString,
+ },
+ Text {
+ _prompt: BnString,
+ _default: Option<BnString>,
+ },
+ Choice {
+ _prompt: BnString,
+ _choices: Vec<BnString>,
+ _raw: Vec<*const i8>,
+ },
+ File {
+ _prompt: BnString,
+ _ext: BnString,
+ _default: Option<BnString>,
+ },
+ FileSave {
+ _prompt: BnString,
+ _ext: BnString,
+ _default_name: BnString,
+ _default: Option<BnString>,
+ },
+}
+
+pub struct FormInputBuilder {
+ fields: Vec<BNFormInputField>,
+ data: Vec<FormData>,
+}
+
+impl<'a> FormInputBuilder {
+ pub fn new() -> Self {
+ Self {
+ fields: vec![],
+ data: vec![],
+ }
+ }
+
+ /// Form Field: Text output
+ pub fn label_field(mut self, text: &str) -> Self {
+ let text = BnString::new(text);
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::LabelFormField;
+ result.hasDefault = false;
+ result.prompt = text.as_ref().as_ptr() as *const i8;
+ self.fields.push(result);
+
+ self.data.push(FormData::Label { _text: text });
+ self
+ }
+
+ /// Form Field: Vertical spacing
+ pub fn seperator_field(mut self) -> Self {
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::SeparatorFormField;
+ result.hasDefault = false;
+ self.fields.push(result);
+ self
+ }
+
+ /// Form Field: Prompt for a string value
+ pub fn text_field(mut self, prompt: &str, default: Option<&str>) -> Self {
+ let prompt = BnString::new(prompt);
+ let default = if let Some(default) = default {
+ Some(BnString::new(default))
+ } else {
+ None
+ };
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::TextLineFormField;
+ result.prompt = prompt.as_ref().as_ptr() as *const i8;
+ result.hasDefault = default.is_some();
+ if let Some(ref default) = default {
+ result.stringDefault = default.as_ref().as_ptr() as *const i8;
+ }
+ self.fields.push(result);
+
+ self.data.push(FormData::Text {
+ _prompt: prompt,
+ _default: default,
+ });
+ self
+ }
+
+ /// Form Field: Prompt for multi-line string value
+ pub fn multiline_field(mut self, prompt: &str, default: Option<&str>) -> Self {
+ let prompt = BnString::new(prompt);
+ let default = if let Some(default) = default {
+ Some(BnString::new(default))
+ } else {
+ None
+ };
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::MultilineTextFormField;
+ result.prompt = prompt.as_ref().as_ptr() as *const i8;
+ result.hasDefault = default.is_some();
+ if let Some(ref default) = default {
+ result.stringDefault = default.as_ref().as_ptr() as *const i8;
+ }
+ self.fields.push(result);
+
+ self.data.push(FormData::Text {
+ _prompt: prompt,
+ _default: default,
+ });
+ self
+ }
+
+ /// Form Field: Prompt for an integer
+ pub fn integer_field(mut self, prompt: &str, default: Option<i64>) -> Self {
+ let prompt = BnString::new(prompt);
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::IntegerFormField;
+ result.prompt = prompt.as_ref().as_ptr() as *const i8;
+ result.hasDefault = default.is_some();
+ if let Some(default) = default {
+ result.intDefault = default;
+ }
+ self.fields.push(result);
+
+ self.data.push(FormData::Label { _text: prompt });
+ self
+ }
+
+ /// Form Field: Prompt for an address
+ pub fn address_field(
+ mut self,
+ prompt: &str,
+ view: Option<Ref<BinaryView>>,
+ current_address: Option<u64>,
+ default: Option<u64>,
+ ) -> Self {
+ let prompt = BnString::new(prompt);
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::AddressFormField;
+ result.prompt = prompt.as_ref().as_ptr() as *const i8;
+ if let Some(view) = view {
+ result.view = view.handle;
+ }
+ result.currentAddress = current_address.unwrap_or(0);
+ result.hasDefault = default.is_some();
+ if let Some(default) = default {
+ result.addressDefault = default;
+ }
+ self.fields.push(result);
+
+ self.data.push(FormData::Label { _text: prompt });
+ self
+ }
+
+ /// Form Field: Prompt for a choice from provided options
+ pub fn choice_field(mut self, prompt: &str, choices: &[&str], default: Option<usize>) -> Self {
+ let prompt = BnString::new(prompt);
+ let choices: Vec<BnString> = choices.iter().map(|&s| BnString::new(s)).collect();
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::ChoiceFormField;
+ result.prompt = prompt.as_ref().as_ptr() as *const i8;
+ let mut raw_choices: Vec<*const i8> = choices
+ .iter()
+ .map(|c| c.as_ref().as_ptr() as *const i8)
+ .collect();
+ result.choices = raw_choices.as_mut_ptr();
+ result.count = choices.len();
+ result.hasDefault = default.is_some();
+ if let Some(default) = default {
+ result.indexDefault = default;
+ }
+ self.fields.push(result);
+
+ self.data.push(FormData::Choice {
+ _prompt: prompt,
+ _choices: choices,
+ _raw: raw_choices,
+ });
+ self
+ }
+
+ /// Form Field: Prompt for file to open
+ pub fn open_file_field(
+ mut self,
+ prompt: &str,
+ ext: Option<&str>,
+ default: Option<&str>,
+ ) -> Self {
+ let prompt = BnString::new(prompt);
+ let ext = if let Some(ext) = ext {
+ BnString::new(ext)
+ } else {
+ BnString::new("")
+ };
+ let default = if let Some(default) = default {
+ Some(BnString::new(default))
+ } else {
+ None
+ };
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::OpenFileNameFormField;
+ result.prompt = prompt.as_ref().as_ptr() as *const i8;
+ result.ext = ext.as_ref().as_ptr() as *const i8;
+ result.hasDefault = default.is_some();
+ if let Some(ref default) = default {
+ result.stringDefault = default.as_ref().as_ptr() as *const i8;
+ }
+ self.fields.push(result);
+
+ self.data.push(FormData::File {
+ _prompt: prompt,
+ _ext: ext,
+ _default: default,
+ });
+ self
+ }
+
+ /// Form Field: Prompt for file to save to
+ pub fn save_file_field(
+ mut self,
+ prompt: &str,
+ ext: Option<&str>,
+ default_name: Option<&str>,
+ default: Option<&str>,
+ ) -> Self {
+ let prompt = BnString::new(prompt);
+ let ext = if let Some(ext) = ext {
+ BnString::new(ext)
+ } else {
+ BnString::new("")
+ };
+ let default_name = if let Some(default_name) = default_name {
+ BnString::new(default_name)
+ } else {
+ BnString::new("")
+ };
+ let default = if let Some(default) = default {
+ Some(BnString::new(default))
+ } else {
+ None
+ };
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::SaveFileNameFormField;
+ result.prompt = prompt.as_ref().as_ptr() as *const i8;
+ result.ext = ext.as_ref().as_ptr() as *const i8;
+ result.defaultName = default_name.as_ref().as_ptr() as *const i8;
+ result.hasDefault = default.is_some();
+ if let Some(ref default) = default {
+ result.stringDefault = default.as_ref().as_ptr() as *const i8;
+ }
+ self.fields.push(result);
+
+ self.data.push(FormData::FileSave {
+ _prompt: prompt,
+ _ext: ext,
+ _default_name: default_name,
+ _default: default,
+ });
+ self
+ }
+
+ /// Form Field: Prompt for directory name
+ pub fn directory_name_field(
+ mut self,
+ prompt: &str,
+ default_name: Option<&str>,
+ default: Option<&str>,
+ ) -> Self {
+ let prompt = BnString::new(prompt);
+ let default_name = if let Some(default_name) = default_name {
+ BnString::new(default_name)
+ } else {
+ BnString::new("")
+ };
+ let default = if let Some(default) = default {
+ Some(BnString::new(default))
+ } else {
+ None
+ };
+
+ let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
+ result.type_ = BNFormInputFieldType::DirectoryNameFormField;
+ result.prompt = prompt.as_ref().as_ptr() as *const i8;
+ result.defaultName = default_name.as_ref().as_ptr() as *const i8;
+ result.hasDefault = default.is_some();
+ if let Some(ref default) = default {
+ result.stringDefault = default.as_ref().as_ptr() as *const i8;
+ }
+ self.fields.push(result);
+
+ self.data.push(FormData::File {
+ _prompt: prompt,
+ _ext: default_name,
+ _default: default,
+ });
+ self
+ }
+
+ /// Prompts the user for a set of inputs specified in `fields` with given title.
+ /// The fields parameter is a list which can contain the following types:
+ ///
+ /// This API is flexible and works both in the UI via a pop-up dialog and on the command-line.
+ ///
+ /// ```
+ /// let responses = interaction::FormInputBuilder::new()
+ /// .text_field("First Name", None)
+ /// .text_field("Last Name", None)
+ /// .choice_field(
+ /// "Favorite Food",
+ /// &vec![
+ /// "Pizza",
+ /// "Also Pizza",
+ /// "Also Pizza",
+ /// "Yummy Pizza",
+ /// "Wrong Answer",
+ /// ],
+ /// Some(0),
+ /// )
+ /// .get_form_input("Form Title");
+ ///
+ /// let food = match responses[2] {
+ /// Index(0) => "Pizza",
+ /// Index(1) => "Also Pizza",
+ /// Index(2) => "Also Pizza",
+ /// Index(3) => "Wrong Answer",
+ /// _ => panic!("This person doesn't like pizza?!?"),
+ /// };
+ ///
+ /// let interaction::FormResponses::String(last_name) = responses[0];
+ /// let interaction::FormResponses::String(first_name) = responses[1];
+ ///
+ /// println!("{} {} likes {}", &first_name, &last_name, food);
+ /// ```
+
+
+ pub fn get_form_input(&mut self, title: &str) -> Vec<FormResponses> {
+ if unsafe {
+ BNGetFormInput(
+ self.fields.as_mut_ptr(),
+ self.fields.len(),
+ title.as_ptr() as *const _,
+ )
+ } {
+ let result = self
+ .fields
+ .iter()
+ .map(|form_field| match form_field.type_ {
+ BNFormInputFieldType::LabelFormField
+ | BNFormInputFieldType::SeparatorFormField => FormResponses::None,
+
+ BNFormInputFieldType::TextLineFormField
+ | BNFormInputFieldType::MultilineTextFormField
+ | BNFormInputFieldType::OpenFileNameFormField
+ | BNFormInputFieldType::SaveFileNameFormField
+ | BNFormInputFieldType::DirectoryNameFormField => {
+ FormResponses::String(unsafe { BnStr::from_raw(form_field.stringResult).to_string() })
+ }
+
+ BNFormInputFieldType::IntegerFormField => {
+ FormResponses::Integer(form_field.intResult)
+ }
+ BNFormInputFieldType::AddressFormField => {
+ FormResponses::Address(form_field.addressResult)
+ }
+ BNFormInputFieldType::ChoiceFormField => {
+ FormResponses::Index(form_field.indexResult)
+ }
+ })
+ .collect();
+ unsafe { BNFreeFormInputResults(self.fields.as_mut_ptr(), self.fields.len()) };
+ result
+ } else {
+ vec![]
+ }
+ }
+}
+
struct TaskContext<F: Fn(Box<dyn Fn(usize, usize) -> Result<(), ()>>)>(F);
pub fn run_progress_dialog<F: Fn(Box<dyn Fn(usize, usize) -> Result<(), ()>>)>(