1use std::ffi::OsStr;
13use std::fs;
14use std::path::PathBuf;
15
16use ::wallet::onchain::PublicNetwork;
17use ::wallet::psbt::Psbt;
18use bitcoin::consensus::Decodable;
19use bitcoin::psbt::PartiallySignedTransaction;
20use bpro::{FileDocument, Wallet};
21use gladis::Gladis;
22use gtk::{ApplicationWindow, ResponseType};
23use relm::{init, Relm, StreamHandle, Update, Widget};
24
25use super::{Msg, ViewModel, Widgets};
26use crate::view::launch::Page;
27use crate::view::{about, error_dlg, file_create_dlg, file_open_dlg, psbt, settings, wallet};
28
29pub struct Component {
34 model: ViewModel,
35 widgets: Widgets,
36 stream: StreamHandle<Msg>,
37 wallet_settings: relm::Component<settings::Component>,
38 wallets: Vec<relm::Component<wallet::Component>>,
40 psbts: Vec<relm::Component<psbt::Component>>,
41 about: relm::Component<about::Component>,
42 wallet_count: usize,
43 window_count: usize,
44}
45
46impl Component {
47 fn open_file(&mut self, path: PathBuf) -> bool {
48 match path.extension().and_then(OsStr::to_str) {
49 Some("mcw") => self.open_wallet(path),
50 _ => self.open_psbt(path, default!()),
51 }
52 }
53
54 fn open_wallet(&mut self, path: PathBuf) -> bool {
55 match Wallet::read_file(&path) {
56 Err(err) => {
57 error_dlg(
58 self.widgets.as_root(),
59 "Error opening wallet",
60 &format!("Unable to open wallet file {}", path.display()),
61 Some(&err.to_string()),
62 );
63 false
64 }
65 Ok(wallet) => {
66 let wallet = init::<wallet::Component>((wallet, path))
67 .expect("unable to instantiate wallet settings");
68 self.window_count += 1;
69 wallet.emit(wallet::Msg::RegisterLauncher(self.stream.clone()));
70 self.wallets.push(wallet);
71 true
72 }
73 }
74 }
75
76 fn open_psbt(&mut self, path: PathBuf, network: Option<PublicNetwork>) -> bool {
77 let mut file = match fs::File::open(&path) {
78 Ok(file) => file,
79 Err(err) => {
80 error_dlg(
81 self.widgets.as_root(),
82 "Error opening PSBT",
83 &path.display().to_string(),
84 Some(&err.to_string()),
85 );
86 return false;
87 }
88 };
89 let psbt = match PartiallySignedTransaction::consensus_decode(&mut file) {
90 Ok(psbt) => psbt.into(),
91 Err(err) => {
92 error_dlg(
93 self.widgets.as_root(),
94 "Invalid PSBT file",
95 &path.display().to_string(),
96 Some(&err.to_string()),
97 );
98 return false;
99 }
100 };
101
102 let comppnent = init::<psbt::Component>(psbt::ModelParam::Open(
103 path,
104 psbt,
105 network.unwrap_or(PublicNetwork::Mainnet),
106 ))
107 .expect("unable to instantiate wallet settings");
108 self.window_count += 1;
109 comppnent.emit(psbt::Msg::RegisterLauncher(self.stream.clone()));
110 self.psbts.push(comppnent);
111 true
112 }
113
114 fn create_psbt(&mut self, psbt: Psbt, network: PublicNetwork) {
115 let psbt = init::<psbt::Component>(psbt::ModelParam::Create(psbt, network))
116 .expect("unable to instantiate wallet settings");
117 self.window_count += 1;
118 psbt.emit(psbt::Msg::RegisterLauncher(self.stream.clone()));
119 self.psbts.push(psbt);
120 }
121}
122
123impl Update for Component {
124 type Model = ViewModel;
126 type ModelParam = ();
128 type Msg = Msg;
130
131 fn model(_relm: &Relm<Self>, _model: Self::ModelParam) -> Self::Model { ViewModel {} }
132
133 fn update(&mut self, event: Msg) {
134 match event {
135 Msg::Show => self.widgets.show(None),
136 Msg::ShowPage(page) => self.widgets.show(Some(page)),
137 Msg::Close => {
138 if self.window_count == 0 {
139 gtk::main_quit();
140 } else {
141 self.widgets.hide();
142 }
143 }
144 Msg::WalletClosed => {
145 self.window_count -= 1;
146 if self.window_count == 0 {
147 self.widgets.show(None);
148 }
149 }
151 Msg::PsbtClosed => {
152 self.window_count -= 1;
153 if self.window_count == 0 {
154 self.widgets.show(None);
155 }
156 }
158 Msg::Template(index) => {
159 if let Some(path) = file_create_dlg(
160 Some(self.widgets.as_root()),
161 "Create wallet",
162 "MyCitadel wallet",
163 "*.mcw",
164 &Wallet::file_name("citadel", self.wallet_count),
165 ) {
166 let template = self.widgets.template(index);
167 self.wallet_count += 1;
168 self.widgets.hide();
169 self.wallet_settings
170 .emit(settings::Msg::New(template, path));
171 }
172 }
173 Msg::Duplicate(settings, path) => {
174 if let Some(path) =
175 file_create_dlg(None, "Copy wallet", "MyCitadel wallet", "*.mcw", &path)
176 {
177 self.wallet_count += 1;
178 self.widgets.hide();
179 self.wallet_settings
180 .emit(settings::Msg::Duplicate(settings, path));
181 }
182 }
183 Msg::Import => {}
184 Msg::Wallet => {
185 if let Some(path) = file_open_dlg(None, "Open wallet", "MyCitadel wallet", "*.mcw")
186 {
187 self.widgets.hide();
188 if !self.open_wallet(path) {
189 self.widgets.show(None);
190 }
191 }
192 }
193 Msg::Psbt(network) => {
194 if let Some(path) = file_open_dlg(
195 None,
196 "Open PSBT",
197 "Partially signed bitcoin transaction",
198 "*.psbt",
199 ) {
200 self.widgets.hide();
201 if !self.open_psbt(path, network) {
202 self.widgets.show(None);
203 }
204 }
205 }
206 Msg::Recent => {
207 if let Some(path) = self.widgets.selected_recent() {
208 self.widgets.hide();
209 if !self.open_file(path) {
210 self.widgets.show(None);
211 }
212 }
213 }
214 Msg::About => self.about.emit(about::Msg::Show),
215 Msg::WalletCreated(path) => {
216 if !self.open_wallet(path) {
217 self.widgets.show(None);
218 }
219 }
220 Msg::CreatePsbt(psbt, network) => self.create_psbt(psbt, network),
221 }
222 }
223}
224
225impl Widget for Component {
226 type Root = ApplicationWindow;
228
229 fn root(&self) -> Self::Root { self.widgets.to_root() }
231
232 fn view(relm: &Relm<Self>, model: Self::Model) -> Self {
233 let glade_src = include_str!("launch.glade");
234 let widgets = Widgets::from_string(glade_src).expect("glade file broken");
235
236 widgets.init_ui();
237
238 let new_wallet =
239 init::<settings::Component>(()).expect("unable to instantiate wallet settings");
240 new_wallet.emit(settings::Msg::SetLauncher(relm.stream().clone()));
241 let about = init::<about::Component>(()).expect("unable to instantiate about settings");
242 about.emit(about::Msg::Response(ResponseType::Close));
243
244 widgets.connect(relm);
245 widgets.show(Some(Page::Template));
246
247 Component {
248 widgets,
249 model,
250 wallet_settings: new_wallet,
251 wallets: empty!(),
252 psbts: empty!(),
253 about,
254 stream: relm.stream().clone(),
255 wallet_count: 1,
256 window_count: 0,
257 }
258 }
259}