mirror of
https://github.com/FliegendeWurst/inboxid.git
synced 2024-11-09 10:50:40 +00:00
browse: custom mail part view
This commit is contained in:
parent
c54f42adf5
commit
3de317af35
@ -1,8 +1,9 @@
|
|||||||
#![feature(internal_output_capture)]
|
#![feature(internal_output_capture)]
|
||||||
|
|
||||||
use std::{cell::RefCell, cmp, collections::{HashMap, HashSet}, env, fmt::Display, io, sync::{Arc, atomic::{AtomicBool, Ordering}}};
|
use std::{cell::RefCell, cmp, collections::{HashMap, HashSet}, env, fmt::Display, io, sync::{Arc, atomic::{AtomicBool, Ordering}}};
|
||||||
|
use std::result::Result as StdResult;
|
||||||
|
|
||||||
use cursive::{Cursive, Vec2, WrapMethod, view::ViewWrapper};
|
use cursive::{Cursive, Vec2, WrapMethod, traits::Finder, view::ViewWrapper};
|
||||||
use cursive::align::HAlign;
|
use cursive::align::HAlign;
|
||||||
use cursive::event::{Event, Key};
|
use cursive::event::{Event, Key};
|
||||||
use cursive::traits::Identifiable;
|
use cursive::traits::Identifiable;
|
||||||
@ -13,7 +14,7 @@ use inboxid::*;
|
|||||||
use io::Write;
|
use io::Write;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::error;
|
use log::error;
|
||||||
use mailparse::{MailHeaderMap, ParsedMail};
|
use mailparse::{MailHeaderMap, ParsedMail, parse_mail};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use petgraph::{EdgeDirection, graph::{DiGraph, NodeIndex}, visit::{Dfs, IntoNodeReferences}};
|
use petgraph::{EdgeDirection, graph::{DiGraph, NodeIndex}, visit::{Dfs, IntoNodeReferences}};
|
||||||
use rusqlite::params;
|
use rusqlite::params;
|
||||||
@ -214,15 +215,8 @@ fn show_listing(mailbox: &str) -> Result<()> {
|
|||||||
siv.call_on_name("mail_info", |view: &mut MailInfoView| {
|
siv.call_on_name("mail_info", |view: &mut MailInfoView| {
|
||||||
view.set(item);
|
view.set(item);
|
||||||
});
|
});
|
||||||
siv.call_on_name("mail", |view: &mut TextView| {
|
siv.call_on_name("mail", |view: &mut MailPartView| {
|
||||||
let body = if mail.ctype.mimetype == "text/html" {
|
view.set_part(mail);
|
||||||
let html = mail.get_body().unwrap();
|
|
||||||
html2text::from_read(html.as_bytes(), 120)
|
|
||||||
// TODO: this logic is duplicated below + the actual width should be considered
|
|
||||||
} else {
|
|
||||||
mail.get_body().unwrap()
|
|
||||||
};
|
|
||||||
view.set_content(body);
|
|
||||||
});
|
});
|
||||||
siv.call_on_name("mail_scroller", |view: &mut MailScrollerView| {
|
siv.call_on_name("mail_scroller", |view: &mut MailScrollerView| {
|
||||||
view.scroll_to_top();
|
view.scroll_to_top();
|
||||||
@ -285,32 +279,33 @@ fn show_listing(mailbox: &str) -> Result<()> {
|
|||||||
});
|
});
|
||||||
let tree_resized = ResizedView::new(SizeConstraint::AtMost(120), SizeConstraint::Free, tree);
|
let tree_resized = ResizedView::new(SizeConstraint::AtMost(120), SizeConstraint::Free, tree);
|
||||||
let mail_info = MailInfoView::new().with_name("mail_info");
|
let mail_info = MailInfoView::new().with_name("mail_info");
|
||||||
let mail_content = TextView::new("").with_name("mail");
|
let mail_content = MailPartView::empty().with_name("mail");
|
||||||
static MAIL_FULLSCREEN: AtomicBool = AtomicBool::new(false);
|
static MAIL_FULLSCREEN: AtomicBool = AtomicBool::new(false);
|
||||||
let dummy = std::rc::Rc::new(RefCell::new(Some(OnEventView::new(TextView::new("dummy").with_name("dummy")).scrollable()))); // TODO dummy content
|
let dummy = std::rc::Rc::new(RefCell::new(Some(OnEventView::new(MailView::empty().with_name("dummy")).scrollable()))); // TODO dummy content
|
||||||
let dummy_ = dummy.clone();
|
let dummy_ = dummy.clone();
|
||||||
let mail_content = OnEventView::new(mail_content)
|
let mail_content = OnEventView::new(mail_content)
|
||||||
.on_event('f', move |s| {
|
.on_event('f', move |s| {
|
||||||
let dummy__ = dummy_.clone();
|
let dummy__ = dummy_.clone();
|
||||||
if MAIL_FULLSCREEN.load(Ordering::SeqCst) {
|
if MAIL_FULLSCREEN.load(Ordering::SeqCst) {
|
||||||
let layer = s.pop_layer().unwrap();
|
let layer = s.pop_layer().unwrap();
|
||||||
if let Ok(textview) = layer.downcast::<ResizedView<ScrollView<OnEventView<NamedView<TextView>>>>>() {
|
if let Ok(textview) = layer.downcast::<ResizedView<MailScrollerView>>() {
|
||||||
let mut it = textview.into_inner().unwrap_or_else(|_| panic!("?"));
|
let mut it = textview.into_inner().unwrap_or_else(|_| panic!("?"));
|
||||||
it.set_show_scrollbars(true);
|
it.set_show_scrollbars(true);
|
||||||
it.get_inner_mut().get_inner_mut().get_mut().set_wrap_method(WrapMethod::XiUnicode);
|
it.get_inner_mut().get_inner_mut().get_mut().set_wrap_method(WrapMethod::XiUnicode);
|
||||||
dummy__.borrow_mut().replace(it);
|
dummy__.borrow_mut().replace(it);
|
||||||
s.call_on_name("mail_scroller", move |this: &mut ScrollView<OnEventView<NamedView<TextView>>>| {
|
s.call_on_name("mail_scroller", move |this: &mut MailScrollerView| {
|
||||||
std::mem::swap(dummy__.borrow_mut().as_mut().unwrap(), this);
|
std::mem::swap(dummy__.borrow_mut().as_mut().unwrap(), this);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
MAIL_FULLSCREEN.store(false, Ordering::SeqCst);
|
MAIL_FULLSCREEN.store(false, Ordering::SeqCst);
|
||||||
} else {
|
} else {
|
||||||
s.call_on_name("mail_scroller", move |this: &mut ScrollView<OnEventView<NamedView<TextView>>>| {
|
s.call_on_name("mail_scroller", move |this: &mut MailScrollerView| {
|
||||||
std::mem::swap(dummy__.borrow_mut().as_mut().unwrap(), this);
|
std::mem::swap(dummy__.borrow_mut().as_mut().unwrap(), this);
|
||||||
});
|
});
|
||||||
let mut it = dummy_.borrow_mut().take().unwrap();
|
let mut it = dummy_.borrow_mut().take().unwrap();
|
||||||
it.set_show_scrollbars(false);
|
it.set_show_scrollbars(false);
|
||||||
it.get_inner_mut().get_inner_mut().get_mut().set_wrap_method(WrapMethod::Newlines);
|
it.get_inner_mut().get_inner_mut().get_mut().set_wrap_method(WrapMethod::Newlines);
|
||||||
|
eprintln!("adding fullscreen layer!");
|
||||||
s.add_fullscreen_layer(ResizedView::with_full_screen(it));
|
s.add_fullscreen_layer(ResizedView::with_full_screen(it));
|
||||||
MAIL_FULLSCREEN.store(true, Ordering::SeqCst);
|
MAIL_FULLSCREEN.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
@ -322,14 +317,8 @@ fn show_listing(mailbox: &str) -> Result<()> {
|
|||||||
let mail = siv.call_on_name("part_select", |tree: &mut TreeView<MailPart>| {
|
let mail = siv.call_on_name("part_select", |tree: &mut TreeView<MailPart>| {
|
||||||
tree.borrow_item(row).unwrap().part
|
tree.borrow_item(row).unwrap().part
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
siv.call_on_name("mail", |view: &mut TextView| {
|
siv.call_on_name("mail", |view: &mut MailView| {
|
||||||
let body = if mail.ctype.mimetype == "text/html" {
|
view.set_part(mail);
|
||||||
let html = mail.get_body().unwrap();
|
|
||||||
html2text::from_read(html.as_bytes(), 120)
|
|
||||||
} else {
|
|
||||||
mail.get_body().unwrap()
|
|
||||||
};
|
|
||||||
view.set_content(body);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
mail_part_select.set_on_submit(|siv, _row| {
|
mail_part_select.set_on_submit(|siv, _row| {
|
||||||
@ -411,7 +400,7 @@ fn show_listing(mailbox: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MailScrollerView = ScrollView<OnEventView<NamedView<MailView>>>;
|
type MailScrollerView = ScrollView<OnEventView<NamedView<MailView>>>;
|
||||||
type MailView = TextView;
|
type MailView = MailPartView;
|
||||||
type MailTreeView<'a> = TreeView<&'a EasyMail<'a>>;
|
type MailTreeView<'a> = TreeView<&'a EasyMail<'a>>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -435,6 +424,128 @@ impl From<&'static ParsedMail<'static>> for MailPart {
|
|||||||
|
|
||||||
impl TreeEntry for MailPart {}
|
impl TreeEntry for MailPart {}
|
||||||
|
|
||||||
|
struct MailPartView {
|
||||||
|
part: Option<&'static ParsedMail<'static>>,
|
||||||
|
wrap: WrapMethod,
|
||||||
|
text: Option<TextView>,
|
||||||
|
cached_size: Option<Vec2>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MailPartView {
|
||||||
|
fn empty() -> Self {
|
||||||
|
MailPartView {
|
||||||
|
part: None,
|
||||||
|
wrap: WrapMethod::XiUnicode,
|
||||||
|
text: None,
|
||||||
|
cached_size: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_wrap_method(&mut self, wrap: WrapMethod) {
|
||||||
|
if let Some(text) = self.text.as_mut() {
|
||||||
|
text.set_wrap_method(wrap);
|
||||||
|
}
|
||||||
|
self.wrap = wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_part(&mut self, part: &'static ParsedMail<'static>) {
|
||||||
|
self.part = Some(part);
|
||||||
|
self.text = None;
|
||||||
|
self.cached_size = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_text(&mut self, size: Vec2) {
|
||||||
|
if self.part.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let part = self.part.unwrap();
|
||||||
|
let body = if part.ctype.mimetype == "text/html" {
|
||||||
|
let html = part.get_body().unwrap();
|
||||||
|
eprintln!("HTML layout using {} width", size.x);
|
||||||
|
html2text::from_read(html.as_bytes(), size.x)
|
||||||
|
} else {
|
||||||
|
part.get_body().unwrap()
|
||||||
|
};
|
||||||
|
let mut text = TextView::new(body);
|
||||||
|
text.set_wrap_method(self.wrap);
|
||||||
|
self.text = Some(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for MailPartView {
|
||||||
|
fn draw(&self, printer: &cursive::Printer) {
|
||||||
|
eprintln!("drawing with {:?}", printer.output_size);
|
||||||
|
if let Some(text) = self.text.as_ref() {
|
||||||
|
text.draw(printer)
|
||||||
|
} else {
|
||||||
|
printer.print((0, 0), "missing text cache");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&mut self, given_size: Vec2) {
|
||||||
|
eprintln!("layout called with {:?}", given_size);
|
||||||
|
if self.cached_size.is_some() && self.cached_size != Some(given_size) {
|
||||||
|
self.setup_text(given_size);
|
||||||
|
}
|
||||||
|
self.cached_size = Some(given_size);
|
||||||
|
if let Some(text) = self.text.as_mut() {
|
||||||
|
text.layout(given_size);
|
||||||
|
} else if self.part.is_some() {
|
||||||
|
self.setup_text(given_size);
|
||||||
|
self.text.as_mut().unwrap().layout(given_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_relayout(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
|
||||||
|
// just use all of the available space
|
||||||
|
if let Some(text) = self.text.as_mut() {
|
||||||
|
let text_constraint = text.required_size(constraint);
|
||||||
|
let (x, y) = text_constraint.pair();
|
||||||
|
(x.max(constraint.x), y.max(constraint.y)).into()
|
||||||
|
} else {
|
||||||
|
constraint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(&mut self, ev: Event) -> cursive::event::EventResult {
|
||||||
|
if let Some(text) = self.text.as_mut() {
|
||||||
|
text.on_event(ev)
|
||||||
|
} else {
|
||||||
|
cursive::event::EventResult::Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_on_any<'a>(&mut self, sel: &cursive::view::Selector<'_>, cb: cursive::event::AnyCb<'a>) {
|
||||||
|
if let Some(text) = self.text.as_mut() {
|
||||||
|
text.call_on_any(sel, cb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focus_view(&mut self, sel: &cursive::view::Selector<'_>) -> StdResult<(), cursive::view::ViewNotFound> {
|
||||||
|
if let Some(text) = self.text.as_mut() {
|
||||||
|
text.focus_view(sel)
|
||||||
|
} else {
|
||||||
|
Err(cursive::view::ViewNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_focus(&mut self, _source: cursive::direction::Direction) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn important_area(&self, view_size: Vec2) -> cursive::Rect {
|
||||||
|
if let Some(text) = self.text.as_ref() {
|
||||||
|
text.important_area(view_size)
|
||||||
|
} else {
|
||||||
|
cursive::Rect::from_size((0, 30), view_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct MailInfoView {
|
struct MailInfoView {
|
||||||
email: Option<&'static ParsedMail<'static>>
|
email: Option<&'static ParsedMail<'static>>
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user