1 use glib::clone;
2 use gtk::prelude::*;
3 use gtk::TextBuffer;
4 use std::str::FromStr;
5 use std::sync::Arc;
6 use url::Url;
7 use std::convert::TryInto;
8 
9 extern crate textwrap;
10 use textwrap::fill;
11 
12 use crate::absolute_url::AbsoluteUrl;
13 use crate::colors::*;
14 use crate::gemini::link::Link as GeminiLink;
15 use crate::gopher::link::Link as GopherLink;
16 use crate::gui::Gui;
17 use crate::protocols::{Finger, Gemini, Gopher};
18 
19 
gemini_content( gui: &Arc<Gui>, content: Vec<Result<crate::gemini::parser::TextElement, crate::gemini::parser::ParseError>>, ) -> TextBuffer20 pub fn gemini_content(
21     gui: &Arc<Gui>,
22     content: Vec<Result<crate::gemini::parser::TextElement, crate::gemini::parser::ParseError>>,
23 ) -> TextBuffer {
24     let content_view = gui.content_view();
25     let buffer = content_view.get_buffer().unwrap();
26 
27     let mut mono_toggle = false;
28     let font_family = crate::settings::get_gemini_text_font_family();
29 
30     for el in content {
31         match el {
32             Ok(crate::gemini::parser::TextElement::MonoText(_text)) => {
33                 mono_toggle = !mono_toggle;
34             }
35             Ok(crate::gemini::parser::TextElement::H1(header)) => {
36                 let mut end_iter = buffer.get_end_iter();
37                 if mono_toggle {
38                     buffer.insert_markup(&mut end_iter, &mono_span(header));
39                 } else {
40                   buffer.insert_markup(
41                       &mut end_iter,
42                       &format!(
43                           "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
44                           crate::settings::get_h1_color(),
45                           crate::settings::get_gemini_h1_font_size(),
46                           crate::settings::get_gemini_h1_font_family(),
47                           crate::settings::get_gemini_h1_font_style(),
48                           crate::settings::get_h1_character(),
49                           header
50                       ),
51                   );
52                 }
53             }
54             Ok(crate::gemini::parser::TextElement::H2(header)) => {
55                 let mut end_iter = buffer.get_end_iter();
56                 if mono_toggle {
57                     buffer.insert_markup(&mut end_iter, &mono_span(header));
58                } else {
59                   buffer.insert_markup(
60                       &mut end_iter,
61                       &format!(
62                           "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
63                           crate::settings::get_h2_color(),
64                           crate::settings::get_gemini_h2_font_size(),
65                           crate::settings::get_gemini_h2_font_family(),
66                           crate::settings::get_gemini_h2_font_style(),
67                           crate::settings::get_h2_character(),
68                           header
69                       ),
70                   );
71                 }
72             }
73             Ok(crate::gemini::parser::TextElement::H3(header)) => {
74                 let mut end_iter = buffer.get_end_iter();
75                 if mono_toggle {
76                     buffer.insert_markup(&mut end_iter, &mono_span(header));
77                 } else {
78                     buffer.insert_markup(
79                         &mut end_iter,
80                         &format!(
81                             "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
82                             crate::settings::get_h3_color(),
83                             crate::settings::get_gemini_h3_font_size(),
84                             crate::settings::get_gemini_h3_font_family(),
85                             crate::settings::get_gemini_h3_font_style(),
86                             crate::settings::get_h3_character(),
87                             header
88                         ),
89                     );
90                 }
91             }
92             Ok(crate::gemini::parser::TextElement::ListItem(item)) => {
93                 let mut end_iter = buffer.get_end_iter();
94                 if mono_toggle {
95                     buffer.insert_markup(&mut end_iter, &mono_span(item));
96                 } else {
97                     buffer.insert_markup(
98                       &mut end_iter,
99                       &format!(
100                           "<span foreground=\"{}\" size=\"{}\" font_family=\"{}\" style=\"{}\">{}{}</span>\n",
101                           crate::settings::get_list_color(),
102                           crate::settings::get_gemini_list_font_size(),
103                           crate::settings::get_gemini_list_font_family(),
104                           crate::settings::get_gemini_list_font_style(),
105                           crate::settings::get_list_character(),
106                           wrap_text(&item, &gui)
107                       ),
108                   );
109                 }
110             }
111             Ok(crate::gemini::parser::TextElement::Quote(text)) => {
112                 let mut end_iter = buffer.get_end_iter();
113                 if mono_toggle {
114                     buffer.insert_markup(&mut end_iter, &mono_span(text));
115                 } else {
116                     buffer.insert_markup(
117                         &mut end_iter,
118                         &format!(
119                             "<span foreground=\"{}\" background=\"{}\" font_family=\"{}\" size=\"{}\" style=\"{}\">{}</span>\n",
120                             crate::settings::get_gemini_quote_foreground_color(),
121                             crate::settings::get_gemini_quote_background_color(),
122                             crate::settings::get_gemini_quote_font_family(),
123                             crate::settings::get_gemini_quote_font_size(),
124                             crate::settings::get_gemini_quote_font_style(),
125                             wrap_text(&text, &gui)
126                         ),
127                     );
128                 }
129             }
130             Ok(crate::gemini::parser::TextElement::Text(text)) => {
131                 let mut end_iter = buffer.get_end_iter();
132                 if mono_toggle {
133                     buffer.insert_markup(&mut end_iter, &mono_span(colors::colorize(&text)));
134                 } else {
135                     buffer.insert_markup(
136                         &mut end_iter,
137                         &format!(
138                             "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
139                             crate::settings::get_text_color(),
140                             font_family,
141                             crate::settings::get_gemini_text_font_size(),
142                             wrap_text(&text, &gui)
143                         ),
144                     );
145                 }
146             }
147             Ok(crate::gemini::parser::TextElement::LinkItem(link_item)) => {
148                 if mono_toggle {
149                     let mut end_iter = buffer.get_end_iter();
150                     buffer.insert_markup(&mut end_iter, &mono_span(escape_text(&link_item)));
151                 } else {
152                     gemini_link(&gui, link_item);
153                 }
154             }
155             Err(_) => println!("Something failed."),
156         }
157     }
158     buffer
159 }
160 
gemini_text_content(gui: &Arc<Gui>, content: std::str::Lines) -> TextBuffer161 pub fn gemini_text_content(gui: &Arc<Gui>, content: std::str::Lines) -> TextBuffer {
162     let content_view = gui.content_view();
163     let buffer = content_view.get_buffer().unwrap();
164 
165     for line in content {
166         let mut end_iter = buffer.get_end_iter();
167         buffer.insert_markup(
168             &mut end_iter,
169             &format!(
170                 "<span foreground=\"{}\" font_family=\"monospace\">{}</span>\n",
171                 crate::settings::get_text_color(),
172                 colors::colorize(&line)
173             ),
174         );
175     }
176     buffer
177 }
178 
gopher_content( gui: &Arc<Gui>, content: Vec<Result<crate::gopher::parser::TextElement, crate::gopher::parser::ParseError>>, ) -> TextBuffer179 pub fn gopher_content(
180     gui: &Arc<Gui>,
181     content: Vec<Result<crate::gopher::parser::TextElement, crate::gopher::parser::ParseError>>,
182 ) -> TextBuffer {
183     let content_view = gui.content_view();
184     let buffer = content_view.get_buffer().unwrap();
185 
186     for el in content {
187         match el {
188             Ok(crate::gopher::parser::TextElement::Text(text)) => {
189                 let mut end_iter = buffer.get_end_iter();
190                 let text = colors::colorize(&text);
191 
192                 buffer.insert_markup(
193                     &mut end_iter,
194                     &format!(
195                         "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
196                         crate::settings::get_text_color(),
197                         crate::settings::get_gopher_font_family(),
198                         crate::settings::get_gopher_font_size(),
199                         text
200                     ),
201                 );
202             }
203             Ok(crate::gopher::parser::TextElement::LinkItem(link_item)) => {
204                 gopher_link(&gui, colors::cleanup(&link_item));
205             }
206             Ok(crate::gopher::parser::TextElement::ExternalLinkItem(link_item)) => {
207                 gopher_link(&gui, colors::cleanup(&link_item));
208             }
209             Ok(crate::gopher::parser::TextElement::Image(link_item)) => {
210                 gopher_link(&gui, link_item);
211             }
212             Ok(crate::gopher::parser::TextElement::Binary(link_item)) => {
213                 gopher_link(&gui, link_item);
214             }
215             Err(_) => println!("Something failed."),
216         }
217     }
218     buffer
219 }
220 
finger_content( gui: &Arc<Gui>, content: Vec<Result<crate::finger::parser::TextElement, crate::finger::parser::ParseError>>, ) -> TextBuffer221 pub fn finger_content(
222     gui: &Arc<Gui>,
223     content: Vec<Result<crate::finger::parser::TextElement, crate::finger::parser::ParseError>>,
224 ) -> TextBuffer {
225     let content_view = gui.content_view();
226     let buffer = content_view.get_buffer().unwrap();
227 
228     for el in content {
229         match el {
230             Ok(crate::finger::parser::TextElement::Text(text)) => {
231                 let mut end_iter = buffer.get_end_iter();
232 
233                 buffer.insert_markup(
234                     &mut end_iter,
235                     &format!(
236                         "<span foreground=\"{}\" font_family=\"{}\" size=\"{}\">{}</span>\n",
237                         crate::settings::get_text_color(),
238                         crate::settings::get_finger_font_family(),
239                         crate::settings::get_finger_font_size(),
240                         escape_text(&text)
241                     ),
242                 );
243             }
244             Err(_) => println!("Something failed."),
245         }
246     }
247     buffer
248 }
249 
gemini_link(gui: &Arc<Gui>, link_item: String)250 pub fn gemini_link(gui: &Arc<Gui>, link_item: String) {
251     match GeminiLink::from_str(&link_item) {
252         Ok(GeminiLink::Finger(url, label)) => {
253             let button_label = if label.is_empty() {
254                 url.clone().to_string()
255             } else {
256                 label
257             };
258             let finger_label = format!("{} [Finger]", button_label);
259             insert_button(&gui, url, finger_label);
260         }
261         Ok(GeminiLink::Gemini(url, label)) => {
262             insert_button(&gui, url, label);
263         }
264         Ok(GeminiLink::Gopher(url, label)) => {
265             let button_label = if label.is_empty() {
266                 url.clone().to_string()
267             } else {
268                 label
269             };
270             let gopher_label = format!("{} [Gopher]", button_label);
271             insert_button(&gui, url, gopher_label);
272         }
273         Ok(GeminiLink::Http(url, label)) => {
274             let button_label = if label.is_empty() {
275                 url.clone().to_string()
276             } else {
277                 label
278             };
279             let www_label = format!("{} [WWW]", button_label);
280 
281             insert_external_button(&gui, url, &www_label);
282         }
283         Ok(GeminiLink::Relative(url, label)) => {
284             let new_url = Gemini { source: url }.to_absolute_url().unwrap();
285             insert_button(&gui, new_url, label);
286         }
287         Ok(GeminiLink::Unknown(_, _)) => (),
288         Err(_) => (),
289     }
290 }
291 
gopher_link(gui: &Arc<Gui>, link_item: String)292 pub fn gopher_link(gui: &Arc<Gui>, link_item: String) {
293     match GopherLink::from_str(&link_item) {
294         Ok(GopherLink::Http(url, label)) => {
295             let button_label = if label.is_empty() {
296                 url.clone().to_string()
297             } else {
298                 label
299             };
300             let www_label = format!("{} [WWW]", button_label);
301 
302             insert_external_button(&gui, url, &www_label);
303         }
304         Ok(GopherLink::Gopher(url, label)) => {
305             let button_label = if label.is_empty() {
306                 url.clone().to_string()
307             } else {
308                 label
309             };
310             let gopher_label = format!("{} [Gopher]", button_label);
311             insert_button(&gui, url, gopher_label);
312         }
313         Ok(GopherLink::Image(url, label)) => {
314             let button_label = if label.is_empty() {
315                 url.clone().to_string()
316             } else {
317                 label
318             };
319             let image_label = format!("{} [Image]", button_label);
320             insert_gopher_file_button(&gui, url, image_label);
321         }
322         Ok(GopherLink::File(url, label)) => {
323             let button_label = if label.is_empty() {
324                 url.clone().to_string()
325             } else {
326                 label
327             };
328             let file_label = format!("{} [File]", button_label);
329             insert_gopher_file_button(&gui, url, file_label);
330         }
331         Ok(GopherLink::Gemini(url, label)) => {
332             insert_button(&gui, url, label);
333         }
334         Ok(GopherLink::Relative(url, label)) => {
335             let new_url = Gopher { source: url }.to_absolute_url().unwrap();
336             insert_button(&gui, new_url, label);
337         }
338         Ok(GopherLink::Ftp(url, label)) => {
339             let button_label = if label.is_empty() {
340                 url.clone().to_string()
341             } else {
342                 label
343             };
344             let ftp_label = format!("{} [FTP]", button_label);
345 
346             insert_external_button(&gui, url, &ftp_label);
347         }
348         Ok(GopherLink::Finger(url, label)) => {
349             let button_label = if label.is_empty() {
350                 url.clone().to_string()
351             } else {
352                 label
353             };
354             let finger_label = format!("{} [Finger]", button_label);
355 
356             insert_external_button(&gui, url, &finger_label);
357         }
358         Ok(GopherLink::Unknown(_, _)) => (),
359         Err(_) => (),
360     }
361 }
362 
insert_button(gui: &Arc<Gui>, url: Url, label: String)363 pub fn insert_button(gui: &Arc<Gui>, url: Url, label: String) {
364     let content_view = gui.content_view();
365     let buffer = content_view.get_buffer().unwrap();
366 
367     let button_label = if label.is_empty() {
368         url.clone().to_string()
369     } else {
370         label
371     };
372 
373     let button = gtk::Button::new_with_label(&button_label);
374     button.set_tooltip_text(Some(&url.to_string()));
375 
376     button.connect_clicked(clone!(@weak gui => move |_| {
377         match url.scheme() {
378             "finger" => crate::visit_url(&gui, Finger { source: url.to_string() }),
379             "gemini" => crate::visit_url(&gui, Gemini { source: url.to_string() }),
380             "gopher" => crate::visit_url(&gui, Gopher { source: url.to_string() }),
381             _ => ()
382         }
383     }));
384 
385     let mut start_iter = buffer.get_end_iter();
386     let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
387     content_view.add_child_at_anchor(&button, &anchor);
388     let mut end_iter = buffer.get_end_iter();
389     buffer.insert(&mut end_iter, "\n");
390 }
391 
insert_gopher_file_button(gui: &Arc<Gui>, url: Url, label: String)392 pub fn insert_gopher_file_button(gui: &Arc<Gui>, url: Url, label: String) {
393     let content_view = gui.content_view();
394     let buffer = content_view.get_buffer().unwrap();
395 
396     let button_label = if label.is_empty() {
397         url.clone().to_string()
398     } else {
399         label
400     };
401 
402     let button = gtk::Button::new_with_label(&button_label);
403     button.set_tooltip_text(Some(&url.to_string()));
404 
405     button.connect_clicked(move |_| {
406         let (_meta, content) = crate::gopher::client::get_data(Gopher {
407             source: url.to_string(),
408         })
409         .unwrap();
410         crate::client::download(content);
411     });
412 
413     let mut start_iter = buffer.get_end_iter();
414     let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
415     content_view.add_child_at_anchor(&button, &anchor);
416     let mut end_iter = buffer.get_end_iter();
417     buffer.insert(&mut end_iter, "\n");
418 }
419 
insert_external_button(gui: &Arc<Gui>, url: Url, label: &str)420 pub fn insert_external_button(gui: &Arc<Gui>, url: Url, label: &str) {
421     let content_view = gui.content_view();
422     let buffer = content_view.get_buffer().unwrap();
423 
424     let button = gtk::Button::new_with_label(&label);
425     button.set_tooltip_text(Some(&url.to_string()));
426 
427     button.connect_clicked(move |_| {
428         open::that(url.to_string()).unwrap();
429     });
430 
431     let mut start_iter = buffer.get_end_iter();
432     let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
433     content_view.add_child_at_anchor(&button, &anchor);
434     let mut end_iter = buffer.get_end_iter();
435     buffer.insert(&mut end_iter, "\n");
436 }
437 
wrap_text(str: &str, gui: &Arc<Gui>) -> String438 fn wrap_text(str: &str, gui: &Arc<Gui>) -> String {
439     fill(&escape_text(str), width(&gui))
440 }
441 
escape_text(str: &str) -> String442 fn escape_text(str: &str) -> String {
443     String::from(glib::markup_escape_text(&str).as_str())
444 }
445 
mono_span(text: String) -> String446 fn mono_span(text: String) -> String {
447     format!(
448         "<span foreground=\"{}\" font_family=\"monospace\" size=\"{}\">{}</span>\n",
449         crate::settings::get_text_color(),
450         crate::settings::get_gemini_text_font_size(),
451         text
452     )
453 }
454 
width(gui: &Arc<Gui>) -> usize455 fn width(gui: &Arc<Gui>) -> usize {
456     let (win_width, _) = gtk::ApplicationWindow::get_size(gui.window());
457     let calculated_width = (win_width / 10).try_into().unwrap();
458     std::cmp::min(calculated_width, crate::settings::max_width().unwrap_or(std::usize::MAX))
459 }
460