use crate::{
helper::{as_ref, chars_to_string, to_c_string},
try_gp_internal, Result,
};
use std::{
ffi, fmt,
os::raw::{c_int, c_void},
};
#[derive(Debug, PartialEq, Clone)]
pub enum WidgetValue {
Text(String),
Range(f32),
Toggle(Option<bool>),
Menu(String),
Date(c_int),
}
#[derive(Debug, PartialEq, Clone)]
pub enum WidgetType {
Window,
Section,
Text,
Range {
min: f32,
max: f32,
increment: f32,
},
Toggle,
Menu {
choices: Vec<String>,
radio: bool,
},
Button,
Date,
}
pub struct WidgetIterator<'a> {
parent_widget: &'a Widget,
count: usize,
index: usize,
}
pub struct Widget {
pub(crate) inner: *mut libgphoto2_sys::CameraWidget,
}
impl Drop for Widget {
fn drop(&mut self) {
unsafe {
libgphoto2_sys::gp_widget_unref(self.inner);
}
}
}
impl fmt::Debug for Widget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Widget")
.field("id", &self.id().ok())
.field("name", &self.name().ok())
.field("label", &self.label().ok())
.field("readonly", &self.readonly().ok())
.field("widget_type", &self.widget_type().ok())
.field(
"value",
&match self.value() {
Ok((Some(value), _)) => Some(value),
_ => None,
},
)
.field("children", &self.children_iter().map(|iter| iter.collect::<Vec<Widget>>()))
.finish()
}
}
as_ref!(Widget -> libgphoto2_sys::CameraWidget, *self.inner);
impl Widget {
pub(crate) fn new_owned(widget: *mut libgphoto2_sys::CameraWidget) -> Self {
Self { inner: widget }
}
pub(crate) fn new_shared(widget: *mut libgphoto2_sys::CameraWidget) -> Self {
unsafe {
libgphoto2_sys::gp_widget_ref(widget);
}
Self::new_owned(widget)
}
pub fn readonly(&self) -> Result<bool> {
try_gp_internal!(gp_widget_get_readonly(self.inner, &out readonly));
Ok(readonly == 1)
}
pub fn label(&self) -> Result<String> {
try_gp_internal!(gp_widget_get_label(self.inner, &out label));
Ok(chars_to_string(label))
}
pub fn name(&self) -> Result<String> {
try_gp_internal!(gp_widget_get_name(self.inner, &out name));
Ok(chars_to_string(name))
}
pub fn id(&self) -> Result<i32> {
try_gp_internal!(gp_widget_get_id(self.inner, &out id));
Ok(id)
}
pub fn info(&self) -> Result<String> {
try_gp_internal!(gp_widget_get_info(self.inner, &out info));
Ok(chars_to_string(info))
}
pub fn children_iter(&self) -> Result<WidgetIterator<'_>> {
Ok(WidgetIterator { parent_widget: self, count: self.children_count()?, index: 0 })
}
pub fn children_count(&self) -> Result<usize> {
try_gp_internal!(let count = gp_widget_count_children(self.inner));
Ok(count as usize)
}
pub fn get_child(&self, index: usize) -> Result<Widget> {
try_gp_internal!(gp_widget_get_child(self.inner, index as c_int, &out child));
Ok(Self::new_shared(child))
}
pub fn get_child_by_id(&self, id: usize) -> Result<Widget> {
try_gp_internal!(gp_widget_get_child_by_id(self.inner, id as c_int, &out child));
Ok(Self::new_shared(child))
}
pub fn get_child_by_label(&self, label: &str) -> Result<Widget> {
try_gp_internal!(gp_widget_get_child_by_label(self.inner, to_c_string!(label), &out child));
Ok(Self::new_shared(child))
}
pub fn get_child_by_name(&self, name: &str) -> Result<Widget> {
try_gp_internal!(gp_widget_get_child_by_name(self.inner, to_c_string!(name), &out child));
Ok(Self::new_shared(child))
}
pub fn widget_type(&self) -> Result<WidgetType> {
use libgphoto2_sys::CameraWidgetType;
try_gp_internal!(gp_widget_get_type(self.inner, &out widget_type));
Ok(match widget_type {
CameraWidgetType::GP_WIDGET_WINDOW => WidgetType::Window,
CameraWidgetType::GP_WIDGET_SECTION => WidgetType::Section,
CameraWidgetType::GP_WIDGET_TEXT => WidgetType::Text,
CameraWidgetType::GP_WIDGET_RANGE => {
try_gp_internal!(gp_widget_get_range(self.inner, &out min, &out max, &out increment));
WidgetType::Range { min, max, increment }
}
CameraWidgetType::GP_WIDGET_TOGGLE => WidgetType::Toggle,
CameraWidgetType::GP_WIDGET_MENU | CameraWidgetType::GP_WIDGET_RADIO => {
try_gp_internal!(let choice_count = gp_widget_count_choices(self.inner));
let mut choices = Vec::with_capacity(choice_count as usize);
for choice_i in 0..choice_count {
try_gp_internal!(gp_widget_get_choice(self.inner, choice_i, &out choice));
choices.push(chars_to_string(choice));
}
WidgetType::Menu { choices, radio: widget_type == CameraWidgetType::GP_WIDGET_RADIO }
}
CameraWidgetType::GP_WIDGET_BUTTON => WidgetType::Button,
CameraWidgetType::GP_WIDGET_DATE => WidgetType::Date,
})
}
fn raw_value<T>(&self) -> Result<T> {
try_gp_internal!(gp_widget_get_value(self.inner, &out value as *mut T as *mut c_void));
Ok(value)
}
fn str_value(&self) -> Result<String> {
Ok(chars_to_string(self.raw_value()?))
}
pub fn value(&self) -> Result<(Option<WidgetValue>, WidgetType)> {
let widget_type = self.widget_type()?;
Ok((
match widget_type {
WidgetType::Window | WidgetType::Button | WidgetType::Section => None,
WidgetType::Text => Some(WidgetValue::Text(self.str_value()?)),
WidgetType::Range { .. } => Some(WidgetValue::Range(self.raw_value()?)),
WidgetType::Toggle => Some(WidgetValue::Toggle(match self.raw_value::<c_int>()? {
1 => Some(true),
2 => None,
_ => Some(false),
})),
WidgetType::Date => Some(WidgetValue::Date(self.raw_value()?)),
WidgetType::Menu { .. } => Some(WidgetValue::Menu(self.str_value()?)),
},
widget_type,
))
}
pub fn set_value(&mut self, value: WidgetValue) -> Result<()> {
let self_type = self.widget_type()?;
match self_type {
WidgetType::Window => Err("Window has no value")?,
WidgetType::Section => Err("Section has no value")?,
WidgetType::Button => Err("Button has no value")?,
WidgetType::Text => {
if let WidgetValue::Text(text) = value {
try_gp_internal!(gp_widget_set_value(self.inner, to_c_string!(text).cast::<c_void>()));
} else {
Err("Expected value to be a string")?;
}
}
WidgetType::Range { min, max, .. } => {
if let WidgetValue::Range(range_value) = value {
if (range_value < min) || (range_value > max) {
Err("Value out of range")?;
}
try_gp_internal!(gp_widget_set_value(
self.inner,
&range_value as *const f32 as *const c_void
));
} else {
Err("Expected value to be Range")?;
}
}
WidgetType::Toggle => {
if let WidgetValue::Toggle(optional_toggle) = value {
if let Some(toggle_value) = optional_toggle {
try_gp_internal!(gp_widget_set_value(
self.inner,
&(toggle_value as c_int) as *const c_int as *const c_void
));
} else {
Err("A toggle cannot be set to an unknown state")?;
}
} else {
Err("Expected value to be Toggle")?;
}
}
WidgetType::Date => {
if let WidgetValue::Date(unix_date) = value {
try_gp_internal!(gp_widget_set_value(
self.inner,
&unix_date as *const c_int as *const c_void
));
} else {
Err("Expected value to be Date")?;
}
}
WidgetType::Menu { choices, .. } => {
if let WidgetValue::Menu(choice) = value {
if !choices.contains(&choice) {
Err("Choice not in choices")?;
}
try_gp_internal!(gp_widget_set_value(self.inner, to_c_string!(choice).cast::<c_void>()));
} else {
Err("Expected value to be Menu")?;
}
}
}
Ok(())
}
}
impl<'a> Iterator for WidgetIterator<'a> {
type Item = Widget;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.count {
None
} else {
let child = self.parent_widget.get_child(self.index).ok();
self.index += 1;
child
}
}
}