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