use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::BTreeSet;
use chrono::{DateTime, Datelike, NaiveDate, Utc};
use glib::subclass::prelude::*;
use gtk::prelude::*;
use gtk::subclass::prelude::ListModelImpl;
use gtk::{gio, glib};
use crate::model::{
Signer, SigsReq, SpendingCondition, TimelockDuration, TimelockReq, TimelockedSigs,
};
#[derive(Debug)]
pub struct ConditionInner {
sigs_all: RefCell<bool>,
sigs_at_least: RefCell<bool>,
sigs_any: RefCell<bool>,
sigs_no: RefCell<u32>,
lock_none: RefCell<bool>,
lock_older: RefCell<bool>,
lock_after: RefCell<bool>,
period_years: RefCell<bool>,
period_months: RefCell<bool>,
period_weeks: RefCell<bool>,
period_days: RefCell<bool>,
period_span: RefCell<u32>,
after_day: RefCell<u32>,
after_month: RefCell<u32>,
after_year: RefCell<u32>,
}
impl Default for ConditionInner {
fn default() -> Self {
ConditionInner {
sigs_all: RefCell::new(true),
sigs_at_least: RefCell::new(false),
sigs_any: RefCell::new(false),
sigs_no: RefCell::new(2),
lock_none: RefCell::new(true),
lock_older: RefCell::new(false),
lock_after: RefCell::new(false),
period_years: RefCell::new(true),
period_months: RefCell::new(false),
period_weeks: RefCell::new(false),
period_days: RefCell::new(false),
period_span: RefCell::new(1),
after_day: RefCell::new(1),
after_month: RefCell::new(1),
after_year: RefCell::new(2025),
}
}
}
impl From<&ConditionInner> for SigsReq {
fn from(inner: &ConditionInner) -> Self {
match (
*inner.sigs_all.borrow(),
*inner.sigs_at_least.borrow(),
*inner.sigs_any.borrow(),
) {
(true, false, false) => SigsReq::All,
(_, true, false) => SigsReq::AtLeast(*inner.sigs_no.borrow() as u16),
(_, _, true) => SigsReq::Any,
_ => unreachable!("ConditionInner internal inconsistency in sig requirements"),
}
}
}
impl From<&ConditionInner> for TimelockReq {
fn from(inner: &ConditionInner) -> Self {
match (
*inner.lock_none.borrow(),
*inner.lock_after.borrow(),
*inner.lock_older.borrow(),
) {
(true, false, false) => TimelockReq::Anytime,
(_, true, false) => {
let date = NaiveDate::from_ymd(
*inner.after_year.borrow() as i32,
*inner.after_month.borrow(),
*inner.after_day.borrow(),
);
TimelockReq::AfterDate(DateTime::from_utc(date.and_hms(0, 0, 0), Utc))
}
(_, _, true) => {
let offset = *inner.period_span.borrow();
let duration = match (
*inner.period_years.borrow(),
*inner.period_months.borrow(),
*inner.period_weeks.borrow(),
*inner.period_days.borrow(),
) {
(true, false, false, false) => TimelockDuration::Years(offset as u8),
(_, true, false, false) => TimelockDuration::Months(offset as u8),
(_, _, true, false) => TimelockDuration::Weeks(offset as u8),
(_, _, _, true) => TimelockDuration::Days(offset as u8),
_ => unreachable!(
"ConditionInner internal inconsistency in relative timelock \
requirements\n{:#?}",
inner
),
};
TimelockReq::AfterPeriod(duration)
}
_ => unreachable!(
"ConditionInner internal inconsistency in timelock requirements\n{:#?}",
inner
),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for ConditionInner {
const NAME: &'static str = "Condition";
type Type = Condition;
type ParentType = glib::Object;
}
impl ObjectImpl for ConditionInner {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
let flag = glib::ParamFlags::READWRITE;
vec![
glib::ParamSpecBoolean::new("sigs-all", "SigsAll", "SigsAll", true, flag),
glib::ParamSpecBoolean::new(
"sigs-at-least",
"SigsAtLeast",
"SigsAtLeast",
false,
flag,
),
glib::ParamSpecBoolean::new("sigs-any", "SigsAny", "SigsAny", false, flag),
glib::ParamSpecUInt::new("sigs-no", "SigsNo", "SigsNo", 1, 100, 2, flag),
glib::ParamSpecBoolean::new("lock-none", "LockNone", "LockNone", true, flag),
glib::ParamSpecBoolean::new("lock-after", "LockAfter", "LockAfter", false, flag),
glib::ParamSpecBoolean::new("lock-older", "LockOlder", "LockOlder", false, flag),
glib::ParamSpecBoolean::new(
"period-years",
"PeriodYears",
"PeriodYears",
true,
flag,
),
glib::ParamSpecBoolean::new(
"period-months",
"PeriodMonths",
"PeriodMonths",
false,
flag,
),
glib::ParamSpecBoolean::new(
"period-weeks",
"PeriodWeeks",
"PeriodWeeks",
false,
flag,
),
glib::ParamSpecBoolean::new("period-days", "PeriodDays", "PeriodDays", false, flag),
glib::ParamSpecUInt::new(
"period-span",
"PeriodSpan",
"PeriodSpan",
1,
100,
1,
flag,
),
glib::ParamSpecUInt::new("after-day", "AfterDay", "AfterDay", 1, 31, 1, flag),
glib::ParamSpecUInt::new("after-month", "AfterMonth", "AfterMonth", 1, 12, 1, flag),
glib::ParamSpecUInt::new(
"after-year",
"AfterYear",
"AfterYear",
2022,
2222,
2025,
flag,
),
]
});
PROPERTIES.as_ref()
}
fn set_property(
&self,
_obj: &Self::Type,
_id: usize,
value: &glib::Value,
pspec: &glib::ParamSpec,
) {
match pspec.name() {
"sigs-all" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.sigs_all.replace(value);
}
"sigs-at-least" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.sigs_at_least.replace(value);
}
"sigs-any" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.sigs_any.replace(value);
}
"sigs-no" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.sigs_no.replace(value);
}
"lock-none" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.lock_none.replace(value);
}
"lock-after" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.lock_after.replace(value);
}
"lock-older" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.lock_older.replace(value);
}
"period-years" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.period_years.replace(value);
}
"period-months" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.period_months.replace(value);
}
"period-weeks" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.period_weeks.replace(value);
}
"period-days" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.period_days.replace(value);
}
"period-span" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.period_span.replace(value);
}
"after-day" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.after_day.replace(value);
}
"after-month" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.after_month.replace(value);
}
"after-year" => {
let value = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.after_year.replace(value);
}
_ => unimplemented!(),
}
}
fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"sigs-all" => self.sigs_all.borrow().to_value(),
"sigs-at-least" => self.sigs_at_least.borrow().to_value(),
"sigs-any" => self.sigs_any.borrow().to_value(),
"sigs-no" => self.sigs_no.borrow().to_value(),
"lock-none" => self.lock_none.borrow().to_value(),
"lock-after" => self.lock_after.borrow().to_value(),
"lock-older" => self.lock_older.borrow().to_value(),
"period-years" => self.period_years.borrow().to_value(),
"period-months" => self.period_months.borrow().to_value(),
"period-weeks" => self.period_weeks.borrow().to_value(),
"period-days" => self.period_days.borrow().to_value(),
"period-span" => self.period_span.borrow().to_value(),
"after-day" => self.after_day.borrow().to_value(),
"after-month" => self.after_month.borrow().to_value(),
"after-year" => self.after_year.borrow().to_value(),
_ => unimplemented!(),
}
}
}
impl ConditionInner {
pub fn sigs_req(&self) -> SigsReq {
if *self.sigs_all.borrow() {
SigsReq::All
} else if *self.sigs_any.borrow() {
SigsReq::Any
} else {
SigsReq::AtLeast(*self.sigs_no.borrow() as u16)
}
}
}
glib::wrapper! {
pub struct Condition(ObjectSubclass<ConditionInner>);
}
impl Default for Condition {
fn default() -> Self { glib::Object::new(&[]).expect("Failed to create row data") }
}
impl From<&Condition> for SpendingCondition {
fn from(condition: &Condition) -> Self {
let condition = condition.imp().borrow();
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::from(condition),
timelock: TimelockReq::from(condition),
})
}
}
impl Condition {
pub fn sigs_req(&self) -> SigsReq {
if self.property("sigs-all") {
SigsReq::All
} else if self.property("sigs-any") {
SigsReq::Any
} else {
SigsReq::AtLeast(self.property::<u32>("sigs-no") as u16)
}
}
}
#[derive(Debug, Default)]
pub struct SpendingModelInner {
pub conditions: RefCell<Vec<Condition>>,
pub max_sigs: RefCell<u16>,
}
#[glib::object_subclass]
impl ObjectSubclass for SpendingModelInner {
const NAME: &'static str = "SpendingModel";
type Type = SpendingModel;
type ParentType = glib::Object;
type Interfaces = (gio::ListModel,);
}
impl ObjectImpl for SpendingModelInner {}
impl ListModelImpl for SpendingModelInner {
fn item_type(&self, _list_model: &Self::Type) -> glib::Type { Condition::static_type() }
fn n_items(&self, _list_model: &Self::Type) -> u32 { self.conditions.borrow().len() as u32 }
fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
self.conditions
.borrow()
.get(position as usize)
.map(|o| o.clone().upcast::<glib::Object>())
}
}
glib::wrapper! {
pub struct SpendingModel(ObjectSubclass<SpendingModelInner>) @implements gio::ListModel;
}
impl SpendingModel {
#[allow(clippy::new_without_default)]
pub fn new() -> SpendingModel {
glib::Object::new(&[]).expect("Failed to create SpendingModel")
}
pub fn refresh(&self, signers: BTreeSet<Signer>) {
let imp = self.imp();
*imp.max_sigs.borrow_mut() = signers.len() as u16;
}
pub fn append(&self, obj: &Condition) {
let imp = self.imp();
let index = {
let mut data = imp.conditions.borrow_mut();
data.push(obj.clone());
data.len() - 1
};
self.items_changed(index as u32, 0, 1);
}
pub fn clear(&self) {
let imp = self.imp();
let n = self.n_items();
imp.conditions.borrow_mut().clear();
self.items_changed(0, n, 0);
}
pub fn remove(&self, index: u32) {
let imp = self.imp();
imp.conditions.borrow_mut().remove(index as usize);
self.items_changed(index, 1, 0);
}
pub fn spending_conditions(&self) -> Vec<(u8, SpendingCondition)> {
let imp = self.imp();
imp.conditions
.borrow()
.iter()
.map(SpendingCondition::from)
.enumerate()
.map(|(d, c)| (d as u8 + 1, c))
.collect()
}
pub fn push_condition(&self, sc: &SpendingCondition) {
let cond = Condition::default();
cond.set_property(
"sigs-all",
matches!(
sc,
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::All,
..
})
),
);
cond.set_property(
"sigs-at-least",
matches!(
sc,
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::AtLeast(_),
..
})
),
);
cond.set_property(
"sigs-any",
matches!(
sc,
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::Any,
..
})
),
);
match sc {
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::AtLeast(no),
..
}) => Some(no),
_ => None,
}
.map(|no| cond.set_property("sigs-no", *no as u32));
cond.set_property(
"lock-none",
matches!(
sc,
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::Anytime,
..
})
),
);
cond.set_property(
"lock-older",
matches!(
sc,
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::AfterPeriod(_),
..
})
),
);
cond.set_property(
"lock-after",
matches!(
sc,
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::AfterDate(_),
..
})
),
);
match sc {
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::AfterPeriod(datetime),
..
}) => Some(datetime),
_ => None,
}
.map(|duration| {
let span = match duration {
TimelockDuration::Days(span)
| TimelockDuration::Weeks(span)
| TimelockDuration::Months(span)
| TimelockDuration::Years(span) => *span,
};
cond.set_property("period-span", span as u32);
cond.set_property(
"period-years",
matches!(duration, TimelockDuration::Years(_)),
);
cond.set_property(
"period-months",
matches!(duration, TimelockDuration::Months(_)),
);
cond.set_property(
"period-weeks",
matches!(duration, TimelockDuration::Weeks(_)),
);
cond.set_property("period-days", matches!(duration, TimelockDuration::Days(_)));
});
match sc {
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::AfterDate(datetime),
..
}) => Some(datetime),
_ => None,
}
.map(|datetime| {
cond.set_property("after-day", datetime.day());
cond.set_property("after-month", datetime.month());
cond.set_property("after-year", datetime.year() as u32);
});
self.append(&cond);
}
pub fn reset_conditions(&self, conditions: &BTreeSet<(u8, SpendingCondition)>) {
self.clear();
for (_, condition) in conditions {
self.push_condition(condition);
}
}
}