browse: configurable unread message styling

This commit is contained in:
FliegendeWurst 2021-04-07 17:08:58 +02:00 committed by Arne Keller
parent b0e5a33e5b
commit ae055a3fa1
2 changed files with 86 additions and 15 deletions

View File

@ -3,10 +3,11 @@
use std::{array::IntoIter, cell::RefCell, cmp, collections::{HashMap, HashSet}, env, fmt::Display, io, sync::{Arc, Mutex}}; use std::{array::IntoIter, cell::RefCell, cmp, collections::{HashMap, HashSet}, env, fmt::Display, io, sync::{Arc, Mutex}};
use cursive::{Cursive, CursiveExt, Vec2}; use cursive::{Cursive, CursiveExt, Vec2};
use cursive::align::HAlign;
use cursive::event::{Event, Key}; use cursive::event::{Event, Key};
use cursive::traits::Identifiable; use cursive::traits::Identifiable;
use cursive::view::{Scrollable, SizeConstraint, View}; use cursive::view::{Scrollable, SizeConstraint, View};
use cursive::views::{Checkbox, LinearLayout, OnEventView, Panel, ResizedView, TextView}; use cursive::views::{Checkbox, LinearLayout, OnEventView, Panel, ResizedView, SelectView, TextView};
use cursive_tree_view::{Placement, TreeEntry, TreeView}; use cursive_tree_view::{Placement, TreeEntry, TreeView};
use inboxid::*; use inboxid::*;
use io::Write; use io::Write;
@ -261,9 +262,20 @@ fn show_listing(mailbox: &str) -> Result<()> {
.child(show_email_addresses) .child(show_email_addresses)
.child(TextView::new(" Show email addresses")) .child(TextView::new(" Show email addresses"))
); );
let mut style_select = SelectView::new().h_align(HAlign::Left);
let values = ["simple", "reverse", "bold", "italic", "strikethrough", "underline", "blink"];
for &x in &values {
style_select.add_item(x, x);
}
let current = style_to_str(&config.browse.unread_style);
style_select.set_selection(values.iter().position(|&x| x == current).unwrap());
style_select.set_on_select(|_s, style| {
CONFIG.get().unwrap().write().browse.unread_style = parse_effect(style).unwrap().into();
});
setup.add_child(ResizedView::new(SizeConstraint::AtLeast(28), SizeConstraint::Free, Panel::new(style_select).title("Unread message styling")));
} }
// most horrible hack // most horrible hack
let setup: Arc<RwLock<Option<Box<dyn View>>>> = Arc::new(RwLock::new(Some(Box::new(ResizedView::new(SizeConstraint::Full, SizeConstraint::Full, setup))))); let setup: Arc<RwLock<Option<Box<dyn View>>>> = Arc::new(RwLock::new(Some(Box::new(ResizedView::new(SizeConstraint::Free, SizeConstraint::Full, setup)))));
let setup2 = Arc::clone(&setup); let setup2 = Arc::clone(&setup);
let setup_view: ResizedView<LinearLayout> = *setup.write().take().unwrap().as_boxed_any().downcast().unwrap(); let setup_view: ResizedView<LinearLayout> = *setup.write().take().unwrap().as_boxed_any().downcast().unwrap();
let setup_view = OnEventView::new(setup_view) let setup_view = OnEventView::new(setup_view)
@ -274,7 +286,7 @@ fn show_listing(mailbox: &str) -> Result<()> {
error!("failed to save config {:?}", e); error!("failed to save config {:?}", e);
} }
}); });
*setup.write() = Some(Box::new(setup_view)); *setup.write() = Some(Box::new(Panel::new(setup_view).title("Settings")));
let setup2 = Arc::clone(&setup); let setup2 = Arc::clone(&setup);
siv.add_global_callback(Event::Key(Key::F2), move |s| { siv.add_global_callback(Event::Key(Key::F2), move |s| {

View File

@ -1,8 +1,8 @@
use std::{borrow::Cow, cmp, convert::{TryFrom, TryInto}, env, fmt::{Debug, Display}, fs, hash::Hash, io, net::TcpStream, ops::Deref, path::PathBuf}; use std::{borrow::Cow, convert::{TryFrom, TryInto}, env, fmt::{Debug, Display}, fs, hash::Hash, io, net::TcpStream, ops::Deref, path::PathBuf};
use anyhow::Context; use anyhow::Context;
use chrono::{DateTime, Local, NaiveDateTime, TimeZone}; use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
use cursive::{theme::Style, utils::span::{IndexedCow, IndexedSpan, SpannedString}}; use cursive::{theme::{Effect, Style}, utils::span::{IndexedCow, IndexedSpan, SpannedString}};
use cursive_tree_view::TreeEntry; use cursive_tree_view::TreeEntry;
use directories_next::ProjectDirs; use directories_next::ProjectDirs;
use imap::{Session, types::Flag}; use imap::{Session, types::Flag};
@ -14,6 +14,8 @@ use parking_lot::RwLock;
use petgraph::{Graph, graph::NodeIndex}; use petgraph::{Graph, graph::NodeIndex};
use rusqlite::{Connection, params}; use rusqlite::{Connection, params};
use rustls_connector::{RustlsConnector, rustls::{ClientSession, StreamOwned}}; use rustls_connector::{RustlsConnector, rustls::{ClientSession, StreamOwned}};
use serde::{Deserializer, Serializer};
use serde::de::Visitor;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
@ -209,13 +211,14 @@ impl TreeEntry for &EasyMail<'_> {
line.push(' '); line.push(' ');
line += &self.date_iso; line += &self.date_iso;
let style = if self.flags.contains('S') { Style::default() } else { CONFIG.get().unwrap().read().browse.unread_style };
let spans = vec![ let spans = vec![
IndexedSpan { IndexedSpan {
content: IndexedCow::Borrowed { content: IndexedCow::Borrowed {
start: 0, start: 0,
end: subj_len end: subj_len
}, },
attr: Style::default(), attr: style,
width: subj_len width: subj_len
}, },
IndexedSpan { IndexedSpan {
@ -223,7 +226,7 @@ impl TreeEntry for &EasyMail<'_> {
start: 0, start: 0,
end: 0 end: 0
}, },
attr: Style::default(), attr: style,
width: line.len() - subj_len - from.len() - self.date_iso.len() - 1 width: line.len() - subj_len - from.len() - self.date_iso.len() - 1
}, },
IndexedSpan { IndexedSpan {
@ -231,7 +234,7 @@ impl TreeEntry for &EasyMail<'_> {
start: line.len() - self.date_iso.len() - 1 - from.len(), start: line.len() - self.date_iso.len() - 1 - from.len(),
end: line.len() - self.date_iso.len() - 1 end: line.len() - self.date_iso.len() - 1
}, },
attr: Style::default(), attr: style,
width: from.len() width: from.len()
}, },
IndexedSpan { IndexedSpan {
@ -239,7 +242,7 @@ impl TreeEntry for &EasyMail<'_> {
start: 0, start: 0,
end: 0 end: 0
}, },
attr: Style::default(), attr: style,
width: 1 width: 1
}, },
IndexedSpan { IndexedSpan {
@ -247,7 +250,7 @@ impl TreeEntry for &EasyMail<'_> {
start: line.len() - self.date_iso.len(), start: line.len() - self.date_iso.len(),
end: line.len() end: line.len()
}, },
attr: Style::default(), attr: style,
width: self.date_iso.len() width: self.date_iso.len()
}, },
]; ];
@ -454,9 +457,7 @@ impl Config {
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
browse: Browse { browse: Browse::default()
show_email_addresses: false
}
} }
} }
} }
@ -465,13 +466,71 @@ impl Default for Config {
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Browse { pub struct Browse {
#[serde(default)] #[serde(default)]
pub show_email_addresses: bool pub show_email_addresses: bool,
#[serde(default = "default_unread_style")]
#[serde(deserialize_with = "deserialize_style")]
#[serde(serialize_with = "serialize_style")]
pub unread_style: Style,
} }
impl Default for Browse { impl Default for Browse {
fn default() -> Self { fn default() -> Self {
Self { Self {
show_email_addresses: Default::default() show_email_addresses: Default::default(),
unread_style: default_unread_style()
} }
} }
} }
pub fn style_to_str(x: &Style) -> &'static str {
match x.effects.iter().next() {
Some(x) => match x {
Effect::Simple => "simple",
Effect::Reverse => "reverse",
Effect::Bold => "bold",
Effect::Italic => "italic",
Effect::Strikethrough => "strikethrough",
Effect::Underline => "underline",
Effect::Blink => "blink"
},
None => "none"
}
}
fn serialize_style<S>(x: &Style, s: S) -> std::result::Result<S::Ok, S::Error> where S: Serializer {
s.serialize_str(style_to_str(x))
}
fn deserialize_style<'de, D>(de: D) -> std::result::Result<Style, D::Error> where D: Deserializer<'de> {
struct StrVisitor;
impl<'de> Visitor<'de> for StrVisitor {
type Value = Style;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("style specification")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> std::result::Result<Self::Value, E> {
parse_effect(v).map(Into::into).ok_or(serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self))
}
}
let vis = StrVisitor;
de.deserialize_str(vis)
}
pub fn parse_effect(effect: &str) -> Option<Effect> {
match effect {
"simple" => Some(Effect::Simple),
"reverse" => Some(Effect::Reverse),
"bold" => Some(Effect::Bold),
"italic" => Some(Effect::Italic),
"strikethrough" => Some(Effect::Strikethrough),
"underline" => Some(Effect::Underline),
"blink" => Some(Effect::Blink),
_ => None
}
}
fn default_unread_style() -> Style {
Effect::Reverse.into()
}