mirror of
https://github.com/FliegendeWurst/inboxid.git
synced 2024-11-08 10:20:39 +00:00
browse: configurable unread message styling
This commit is contained in:
parent
b0e5a33e5b
commit
ae055a3fa1
@ -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| {
|
||||||
|
83
src/lib.rs
83
src/lib.rs
@ -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()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user