Add command to select charset encoding for email
Open dialog to select charset with `d`.pull/182/head
parent
939dc15e28
commit
d9c07def0f
|
@ -997,6 +997,10 @@ When active, it prepends an index next to each url that you can select by typing
|
||||||
View raw envelope source in a pager.
|
View raw envelope source in a pager.
|
||||||
.\" default value
|
.\" default value
|
||||||
.Pq Em M-r
|
.Pq Em M-r
|
||||||
|
.It Ic change_charset
|
||||||
|
Force attachment charset for decoding.
|
||||||
|
.\" default value
|
||||||
|
.Pq Em d
|
||||||
.El
|
.El
|
||||||
.sp
|
.sp
|
||||||
.Em thread-view
|
.Em thread-view
|
||||||
|
|
|
@ -32,6 +32,23 @@ use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::email::attachment_types::*;
|
use crate::email::attachment_types::*;
|
||||||
|
|
||||||
|
pub type Filter<'a> = Box<dyn FnMut(&Attachment, &mut Vec<u8>) + 'a>;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DecodeOptions<'att> {
|
||||||
|
pub filter: Option<Filter<'att>>,
|
||||||
|
pub force_charset: Option<Charset>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'att> From<Option<Charset>> for DecodeOptions<'att> {
|
||||||
|
fn from(force_charset: Option<Charset>) -> DecodeOptions<'att> {
|
||||||
|
Self {
|
||||||
|
filter: None,
|
||||||
|
force_charset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct AttachmentBuilder {
|
pub struct AttachmentBuilder {
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
|
@ -983,11 +1000,3 @@ impl Attachment {
|
||||||
pub fn interpret_format_flowed(_t: &str) -> String {
|
pub fn interpret_format_flowed(_t: &str) -> String {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Filter<'a> = Box<dyn FnMut(&Attachment, &mut Vec<u8>) + 'a>;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DecodeOptions<'att> {
|
|
||||||
pub filter: Option<Filter<'att>>,
|
|
||||||
pub force_charset: Option<Charset>,
|
|
||||||
}
|
|
||||||
|
|
|
@ -45,6 +45,23 @@ pub use self::envelope::*;
|
||||||
use linkify::LinkFinder;
|
use linkify::LinkFinder;
|
||||||
use xdg_utils::query_default_app;
|
use xdg_utils::query_default_app;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
enum ForceCharset {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Dialog(Box<UIDialog<Option<Charset>>>),
|
||||||
|
Forced(Charset),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Option<Charset>> for &ForceCharset {
|
||||||
|
fn into(self) -> Option<Charset> {
|
||||||
|
match self {
|
||||||
|
ForceCharset::Forced(val) => Some(*val),
|
||||||
|
ForceCharset::None | ForceCharset::Dialog(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
enum Source {
|
enum Source {
|
||||||
Decoded,
|
Decoded,
|
||||||
|
@ -160,6 +177,7 @@ pub struct MailView {
|
||||||
theme_default: ThemeAttribute,
|
theme_default: ThemeAttribute,
|
||||||
active_jobs: HashSet<JobId>,
|
active_jobs: HashSet<JobId>,
|
||||||
state: MailViewState,
|
state: MailViewState,
|
||||||
|
force_charset: ForceCharset,
|
||||||
|
|
||||||
cmd_buf: String,
|
cmd_buf: String,
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
|
@ -196,6 +214,74 @@ enum MailViewState {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MailViewState {
|
||||||
|
fn load_bytes(self_: &mut MailView, bytes: Vec<u8>, context: &mut Context) {
|
||||||
|
let account = &mut context.accounts[&self_.coordinates.0];
|
||||||
|
if account
|
||||||
|
.collection
|
||||||
|
.get_env(self_.coordinates.2)
|
||||||
|
.other_headers()
|
||||||
|
.is_empty()
|
||||||
|
{
|
||||||
|
let _ = account
|
||||||
|
.collection
|
||||||
|
.get_env_mut(self_.coordinates.2)
|
||||||
|
.populate_headers(&bytes);
|
||||||
|
}
|
||||||
|
let env = Box::new(account.collection.get_env(self_.coordinates.2).clone());
|
||||||
|
let body = Box::new(AttachmentBuilder::new(&bytes).build());
|
||||||
|
let display = MailView::attachment_to(
|
||||||
|
&body,
|
||||||
|
context,
|
||||||
|
self_.coordinates,
|
||||||
|
&mut self_.active_jobs,
|
||||||
|
(&self_.force_charset).into(),
|
||||||
|
);
|
||||||
|
let (paths, attachment_tree_s) = self_.attachment_displays_to_tree(&display);
|
||||||
|
self_.attachment_tree = attachment_tree_s;
|
||||||
|
self_.attachment_paths = paths;
|
||||||
|
let body_text = self_.attachment_displays_to_text(&display, context, true);
|
||||||
|
self_.state = MailViewState::Loaded {
|
||||||
|
display,
|
||||||
|
env,
|
||||||
|
body,
|
||||||
|
bytes,
|
||||||
|
body_text,
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redecode(self_: &mut MailView, context: &mut Context) {
|
||||||
|
let (new_display, new_body_text) =
|
||||||
|
if let MailViewState::Loaded { ref body, .. } = self_.state {
|
||||||
|
let new_display = MailView::attachment_to(
|
||||||
|
body,
|
||||||
|
context,
|
||||||
|
self_.coordinates,
|
||||||
|
&mut self_.active_jobs,
|
||||||
|
(&self_.force_charset).into(),
|
||||||
|
);
|
||||||
|
let (paths, attachment_tree_s) = self_.attachment_displays_to_tree(&new_display);
|
||||||
|
self_.attachment_tree = attachment_tree_s;
|
||||||
|
self_.attachment_paths = paths;
|
||||||
|
let body_text = self_.attachment_displays_to_text(&new_display, context, true);
|
||||||
|
(new_display, body_text)
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let MailViewState::Loaded {
|
||||||
|
ref mut display,
|
||||||
|
ref mut body_text,
|
||||||
|
..
|
||||||
|
} = self_.state
|
||||||
|
{
|
||||||
|
*display = new_display;
|
||||||
|
*body_text = new_body_text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
enum LinkKind {
|
enum LinkKind {
|
||||||
Url,
|
Url,
|
||||||
|
@ -228,6 +314,7 @@ impl Clone for MailView {
|
||||||
attachment_paths: self.attachment_paths.clone(),
|
attachment_paths: self.attachment_paths.clone(),
|
||||||
state: MailViewState::default(),
|
state: MailViewState::default(),
|
||||||
active_jobs: self.active_jobs.clone(),
|
active_jobs: self.active_jobs.clone(),
|
||||||
|
force_charset: ForceCharset::None,
|
||||||
..*self
|
..*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,7 +351,7 @@ impl MailView {
|
||||||
theme_default: crate::conf::value(context, "mail.view.body"),
|
theme_default: crate::conf::value(context, "mail.view.body"),
|
||||||
active_jobs: Default::default(),
|
active_jobs: Default::default(),
|
||||||
state: MailViewState::default(),
|
state: MailViewState::default(),
|
||||||
|
force_charset: ForceCharset::None,
|
||||||
cmd_buf: String::with_capacity(4),
|
cmd_buf: String::with_capacity(4),
|
||||||
id: ComponentId::new_v4(),
|
id: ComponentId::new_v4(),
|
||||||
};
|
};
|
||||||
|
@ -298,41 +385,7 @@ impl MailView {
|
||||||
if let Ok(Some(bytes_result)) = try_recv_timeout!(&mut handle.chan) {
|
if let Ok(Some(bytes_result)) = try_recv_timeout!(&mut handle.chan) {
|
||||||
match bytes_result {
|
match bytes_result {
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
if account
|
MailViewState::load_bytes(self, bytes, context);
|
||||||
.collection
|
|
||||||
.get_env(self.coordinates.2)
|
|
||||||
.other_headers()
|
|
||||||
.is_empty()
|
|
||||||
{
|
|
||||||
let _ = account
|
|
||||||
.collection
|
|
||||||
.get_env_mut(self.coordinates.2)
|
|
||||||
.populate_headers(&bytes);
|
|
||||||
}
|
|
||||||
let env = Box::new(
|
|
||||||
account.collection.get_env(self.coordinates.2).clone(),
|
|
||||||
);
|
|
||||||
let body = Box::new(AttachmentBuilder::new(&bytes).build());
|
|
||||||
let display = Self::attachment_to(
|
|
||||||
&body,
|
|
||||||
context,
|
|
||||||
self.coordinates,
|
|
||||||
&mut self.active_jobs,
|
|
||||||
);
|
|
||||||
let (paths, attachment_tree_s) =
|
|
||||||
self.attachment_displays_to_tree(&display);
|
|
||||||
self.attachment_tree = attachment_tree_s;
|
|
||||||
self.attachment_paths = paths;
|
|
||||||
let body_text =
|
|
||||||
self.attachment_displays_to_text(&display, context, true);
|
|
||||||
self.state = MailViewState::Loaded {
|
|
||||||
display,
|
|
||||||
env,
|
|
||||||
body,
|
|
||||||
bytes,
|
|
||||||
body_text,
|
|
||||||
links: vec![],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.state = MailViewState::Error { err };
|
self.state = MailViewState::Error { err };
|
||||||
|
@ -719,6 +772,7 @@ impl MailView {
|
||||||
context: &mut Context,
|
context: &mut Context,
|
||||||
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
||||||
active_jobs: &mut HashSet<JobId>,
|
active_jobs: &mut HashSet<JobId>,
|
||||||
|
force_charset: Option<Charset>,
|
||||||
) -> Vec<AttachmentDisplay> {
|
) -> Vec<AttachmentDisplay> {
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
fn rec(
|
fn rec(
|
||||||
|
@ -727,13 +781,14 @@ impl MailView {
|
||||||
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
||||||
acc: &mut Vec<AttachmentDisplay>,
|
acc: &mut Vec<AttachmentDisplay>,
|
||||||
active_jobs: &mut HashSet<JobId>,
|
active_jobs: &mut HashSet<JobId>,
|
||||||
|
force_charset: Option<Charset>,
|
||||||
) {
|
) {
|
||||||
if a.content_disposition.kind.is_attachment() || a.content_type == "message/rfc822" {
|
if a.content_disposition.kind.is_attachment() || a.content_type == "message/rfc822" {
|
||||||
acc.push(AttachmentDisplay::Attachment {
|
acc.push(AttachmentDisplay::Attachment {
|
||||||
inner: Box::new(a.clone()),
|
inner: Box::new(a.clone()),
|
||||||
});
|
});
|
||||||
} else if a.content_type().is_text_html() {
|
} else if a.content_type().is_text_html() {
|
||||||
let bytes = a.decode(Default::default());
|
let bytes = a.decode(force_charset.into());
|
||||||
let filter_invocation =
|
let filter_invocation =
|
||||||
mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter)
|
mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -788,7 +843,7 @@ impl MailView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if a.is_text() {
|
} else if a.is_text() {
|
||||||
let bytes = a.decode(Default::default());
|
let bytes = a.decode(force_charset.into());
|
||||||
acc.push(AttachmentDisplay::InlineText {
|
acc.push(AttachmentDisplay::InlineText {
|
||||||
inner: Box::new(a.clone()),
|
inner: Box::new(a.clone()),
|
||||||
comment: None,
|
comment: None,
|
||||||
|
@ -810,7 +865,7 @@ impl MailView {
|
||||||
if let Some(text_attachment_pos) =
|
if let Some(text_attachment_pos) =
|
||||||
parts.iter().position(|a| a.content_type == "text/plain")
|
parts.iter().position(|a| a.content_type == "text/plain")
|
||||||
{
|
{
|
||||||
let bytes = &parts[text_attachment_pos].decode(Default::default());
|
let bytes = &parts[text_attachment_pos].decode(force_charset.into());
|
||||||
if bytes.trim().is_empty()
|
if bytes.trim().is_empty()
|
||||||
&& mailbox_settings!(
|
&& mailbox_settings!(
|
||||||
context[coordinates.0][&coordinates.1]
|
context[coordinates.0][&coordinates.1]
|
||||||
|
@ -831,7 +886,14 @@ impl MailView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for a in parts {
|
for a in parts {
|
||||||
rec(a, context, coordinates, &mut display, active_jobs);
|
rec(
|
||||||
|
a,
|
||||||
|
context,
|
||||||
|
coordinates,
|
||||||
|
&mut display,
|
||||||
|
active_jobs,
|
||||||
|
force_charset,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
acc.push(AttachmentDisplay::Alternative {
|
acc.push(AttachmentDisplay::Alternative {
|
||||||
inner: Box::new(a.clone()),
|
inner: Box::new(a.clone()),
|
||||||
|
@ -846,7 +908,14 @@ impl MailView {
|
||||||
inner: Box::new(a.clone()),
|
inner: Box::new(a.clone()),
|
||||||
display: {
|
display: {
|
||||||
let mut v = vec![];
|
let mut v = vec![];
|
||||||
rec(&parts[0], context, coordinates, &mut v, active_jobs);
|
rec(
|
||||||
|
&parts[0],
|
||||||
|
context,
|
||||||
|
coordinates,
|
||||||
|
&mut v,
|
||||||
|
active_jobs,
|
||||||
|
force_charset,
|
||||||
|
);
|
||||||
v
|
v
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -869,7 +938,14 @@ impl MailView {
|
||||||
job_id: handle.job_id,
|
job_id: handle.job_id,
|
||||||
display: {
|
display: {
|
||||||
let mut v = vec![];
|
let mut v = vec![];
|
||||||
rec(&parts[0], context, coordinates, &mut v, active_jobs);
|
rec(
|
||||||
|
&parts[0],
|
||||||
|
context,
|
||||||
|
coordinates,
|
||||||
|
&mut v,
|
||||||
|
active_jobs,
|
||||||
|
force_charset,
|
||||||
|
);
|
||||||
v
|
v
|
||||||
},
|
},
|
||||||
handle,
|
handle,
|
||||||
|
@ -879,7 +955,14 @@ impl MailView {
|
||||||
inner: Box::new(a.clone()),
|
inner: Box::new(a.clone()),
|
||||||
display: {
|
display: {
|
||||||
let mut v = vec![];
|
let mut v = vec![];
|
||||||
rec(&parts[0], context, coordinates, &mut v, active_jobs);
|
rec(
|
||||||
|
&parts[0],
|
||||||
|
context,
|
||||||
|
coordinates,
|
||||||
|
&mut v,
|
||||||
|
active_jobs,
|
||||||
|
force_charset,
|
||||||
|
);
|
||||||
v
|
v
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -925,13 +1008,20 @@ impl MailView {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
for a in parts {
|
for a in parts {
|
||||||
rec(a, context, coordinates, acc, active_jobs);
|
rec(a, context, coordinates, acc, active_jobs, force_charset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rec(body, context, coordinates, &mut ret, active_jobs);
|
rec(
|
||||||
|
body,
|
||||||
|
context,
|
||||||
|
coordinates,
|
||||||
|
&mut ret,
|
||||||
|
active_jobs,
|
||||||
|
force_charset,
|
||||||
|
);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1655,12 +1745,46 @@ impl Component for MailView {
|
||||||
if let ViewMode::ContactSelector(ref mut s) = self.mode {
|
if let ViewMode::ContactSelector(ref mut s) = self.mode {
|
||||||
s.draw(grid, area, context);
|
s.draw(grid, area, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let ForceCharset::Dialog(ref mut s) = self.force_charset {
|
||||||
|
s.draw(grid, area, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
|
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
|
||||||
if self.coordinates.0.is_null() || self.coordinates.1.is_null() {
|
if self.coordinates.0.is_null() || self.coordinates.1.is_null() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match (&mut self.force_charset, &event) {
|
||||||
|
(ForceCharset::Dialog(selector), UIEvent::FinishedUIDialog(id, results))
|
||||||
|
if *id == selector.id() =>
|
||||||
|
{
|
||||||
|
self.force_charset =
|
||||||
|
if let Some(results) = results.downcast_ref::<Vec<Option<Charset>>>() {
|
||||||
|
if results.len() != 1 {
|
||||||
|
ForceCharset::None
|
||||||
|
} else if let Some(charset) = results[0] {
|
||||||
|
ForceCharset::Forced(charset)
|
||||||
|
} else {
|
||||||
|
ForceCharset::None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ForceCharset::None
|
||||||
|
};
|
||||||
|
MailViewState::redecode(self, context);
|
||||||
|
self.initialised = false;
|
||||||
|
self.set_dirty(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(ForceCharset::Dialog(selector), _) => {
|
||||||
|
if selector.process_event(event, context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
let shortcuts = self.get_shortcuts(context);
|
let shortcuts = self.get_shortcuts(context);
|
||||||
match (&mut self.mode, &mut event) {
|
match (&mut self.mode, &mut event) {
|
||||||
/*(ViewMode::Ansi(ref mut buf), _) => {
|
/*(ViewMode::Ansi(ref mut buf), _) => {
|
||||||
|
@ -1745,44 +1869,7 @@ impl Component for MailView {
|
||||||
Ok(None) => { /* something happened, perhaps a worker thread panicked */
|
Ok(None) => { /* something happened, perhaps a worker thread panicked */
|
||||||
}
|
}
|
||||||
Ok(Some(Ok(bytes))) => {
|
Ok(Some(Ok(bytes))) => {
|
||||||
if context.accounts[&self.coordinates.0]
|
MailViewState::load_bytes(self, bytes, context);
|
||||||
.collection
|
|
||||||
.get_env(self.coordinates.2)
|
|
||||||
.other_headers()
|
|
||||||
.is_empty()
|
|
||||||
{
|
|
||||||
let _ = context.accounts[&self.coordinates.0]
|
|
||||||
.collection
|
|
||||||
.get_env_mut(self.coordinates.2)
|
|
||||||
.populate_headers(&bytes);
|
|
||||||
}
|
|
||||||
let env = Box::new(
|
|
||||||
context.accounts[&self.coordinates.0]
|
|
||||||
.collection
|
|
||||||
.get_env(self.coordinates.2)
|
|
||||||
.clone(),
|
|
||||||
);
|
|
||||||
let body = Box::new(AttachmentBuilder::new(&bytes).build());
|
|
||||||
let display = Self::attachment_to(
|
|
||||||
&body,
|
|
||||||
context,
|
|
||||||
self.coordinates,
|
|
||||||
&mut self.active_jobs,
|
|
||||||
);
|
|
||||||
let (paths, attachment_tree_s) =
|
|
||||||
self.attachment_displays_to_tree(&display);
|
|
||||||
self.attachment_tree = attachment_tree_s;
|
|
||||||
self.attachment_paths = paths;
|
|
||||||
let body_text =
|
|
||||||
self.attachment_displays_to_text(&display, context, true);
|
|
||||||
self.state = MailViewState::Loaded {
|
|
||||||
bytes,
|
|
||||||
env,
|
|
||||||
body,
|
|
||||||
display,
|
|
||||||
links: vec![],
|
|
||||||
body_text,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Ok(Some(Err(err))) => {
|
Ok(Some(Err(err))) => {
|
||||||
self.state = MailViewState::Error { err };
|
self.state = MailViewState::Error { err };
|
||||||
|
@ -1855,6 +1942,7 @@ impl Component for MailView {
|
||||||
context,
|
context,
|
||||||
self.coordinates,
|
self.coordinates,
|
||||||
&mut self.active_jobs,
|
&mut self.active_jobs,
|
||||||
|
(&self.force_charset).into(),
|
||||||
);
|
);
|
||||||
*d = AttachmentDisplay::EncryptedSuccess {
|
*d = AttachmentDisplay::EncryptedSuccess {
|
||||||
inner: std::mem::replace(
|
inner: std::mem::replace(
|
||||||
|
@ -2700,6 +2788,55 @@ impl Component for MailView {
|
||||||
.push_back(UIEvent::Action(Tab(New(Some(Box::new(self.clone()))))));
|
.push_back(UIEvent::Action(Tab(New(Some(Box::new(self.clone()))))));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
UIEvent::Input(ref key)
|
||||||
|
if shortcut!(key == shortcuts[Shortcuts::ENVELOPE_VIEW]["change_charset"]) =>
|
||||||
|
{
|
||||||
|
let entries = vec![
|
||||||
|
(None, "default".to_string()),
|
||||||
|
(Some(Charset::Ascii), Charset::Ascii.to_string()),
|
||||||
|
(Some(Charset::UTF8), Charset::UTF8.to_string()),
|
||||||
|
(Some(Charset::UTF16), Charset::UTF16.to_string()),
|
||||||
|
(Some(Charset::ISO8859_1), Charset::ISO8859_1.to_string()),
|
||||||
|
(Some(Charset::ISO8859_2), Charset::ISO8859_2.to_string()),
|
||||||
|
(Some(Charset::ISO8859_3), Charset::ISO8859_3.to_string()),
|
||||||
|
(Some(Charset::ISO8859_4), Charset::ISO8859_4.to_string()),
|
||||||
|
(Some(Charset::ISO8859_5), Charset::ISO8859_5.to_string()),
|
||||||
|
(Some(Charset::ISO8859_6), Charset::ISO8859_6.to_string()),
|
||||||
|
(Some(Charset::ISO8859_7), Charset::ISO8859_7.to_string()),
|
||||||
|
(Some(Charset::ISO8859_8), Charset::ISO8859_8.to_string()),
|
||||||
|
(Some(Charset::ISO8859_10), Charset::ISO8859_10.to_string()),
|
||||||
|
(Some(Charset::ISO8859_13), Charset::ISO8859_13.to_string()),
|
||||||
|
(Some(Charset::ISO8859_14), Charset::ISO8859_14.to_string()),
|
||||||
|
(Some(Charset::ISO8859_15), Charset::ISO8859_15.to_string()),
|
||||||
|
(Some(Charset::ISO8859_16), Charset::ISO8859_16.to_string()),
|
||||||
|
(Some(Charset::Windows1250), Charset::Windows1250.to_string()),
|
||||||
|
(Some(Charset::Windows1251), Charset::Windows1251.to_string()),
|
||||||
|
(Some(Charset::Windows1252), Charset::Windows1252.to_string()),
|
||||||
|
(Some(Charset::Windows1253), Charset::Windows1253.to_string()),
|
||||||
|
(Some(Charset::GBK), Charset::GBK.to_string()),
|
||||||
|
(Some(Charset::GB2312), Charset::GB2312.to_string()),
|
||||||
|
(Some(Charset::GB18030), Charset::GB18030.to_string()),
|
||||||
|
(Some(Charset::BIG5), Charset::BIG5.to_string()),
|
||||||
|
(Some(Charset::ISO2022JP), Charset::ISO2022JP.to_string()),
|
||||||
|
(Some(Charset::EUCJP), Charset::EUCJP.to_string()),
|
||||||
|
(Some(Charset::KOI8R), Charset::KOI8R.to_string()),
|
||||||
|
(Some(Charset::KOI8U), Charset::KOI8U.to_string()),
|
||||||
|
];
|
||||||
|
self.force_charset = ForceCharset::Dialog(Box::new(Selector::new(
|
||||||
|
"select charset to force",
|
||||||
|
entries,
|
||||||
|
true,
|
||||||
|
Some(Box::new(
|
||||||
|
move |id: ComponentId, results: &[Option<Charset>]| {
|
||||||
|
Some(UIEvent::FinishedUIDialog(id, Box::new(results.to_vec())))
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
context,
|
||||||
|
)));
|
||||||
|
self.initialised = false;
|
||||||
|
self.dirty = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
@ -2709,13 +2846,8 @@ impl Component for MailView {
|
||||||
self.dirty
|
self.dirty
|
||||||
|| self.pager.is_dirty()
|
|| self.pager.is_dirty()
|
||||||
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
|| if let ViewMode::ContactSelector(ref s) = self.mode {
|
|| matches!(self.force_charset, ForceCharset::Dialog(ref s) if s.is_dirty())
|
||||||
s.is_dirty()
|
|| matches!(self.mode, ViewMode::ContactSelector(ref s) if s.is_dirty())
|
||||||
/*} else if let ViewMode::Ansi(ref r) = self.mode {
|
|
||||||
r.is_dirty()*/
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_dirty(&mut self, value: bool) {
|
fn set_dirty(&mut self, value: bool) {
|
||||||
|
|
|
@ -51,6 +51,7 @@ pub struct EnvelopeView {
|
||||||
mail: Mail,
|
mail: Mail,
|
||||||
|
|
||||||
_account_hash: AccountHash,
|
_account_hash: AccountHash,
|
||||||
|
force_charset: ForceCharset,
|
||||||
cmd_buf: String,
|
cmd_buf: String,
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
}
|
}
|
||||||
|
@ -73,6 +74,7 @@ impl EnvelopeView {
|
||||||
subview,
|
subview,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
mode: ViewMode::Normal,
|
mode: ViewMode::Normal,
|
||||||
|
force_charset: ForceCharset::None,
|
||||||
mail,
|
mail,
|
||||||
_account_hash,
|
_account_hash,
|
||||||
cmd_buf: String::with_capacity(4),
|
cmd_buf: String::with_capacity(4),
|
||||||
|
@ -122,7 +124,11 @@ impl EnvelopeView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
..Default::default()
|
force_charset: if let ForceCharset::Forced(val) = self.force_charset {
|
||||||
|
Some(val)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
.into_owned();
|
.into_owned();
|
||||||
match self.mode {
|
match self.mode {
|
||||||
|
@ -312,9 +318,42 @@ impl Component for EnvelopeView {
|
||||||
} else if let Some(p) = self.pager.as_mut() {
|
} else if let Some(p) = self.pager.as_mut() {
|
||||||
p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let ForceCharset::Dialog(ref mut s) = self.force_charset {
|
||||||
|
s.draw(grid, area, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||||
|
match (&mut self.force_charset, &event) {
|
||||||
|
(ForceCharset::Dialog(selector), UIEvent::FinishedUIDialog(id, results))
|
||||||
|
if *id == selector.id() =>
|
||||||
|
{
|
||||||
|
if let Some(results) = results.downcast_ref::<Vec<Option<Charset>>>() {
|
||||||
|
if results.len() != 1 {
|
||||||
|
self.force_charset = ForceCharset::None;
|
||||||
|
self.set_dirty(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if let Some(charset) = results[0] {
|
||||||
|
self.force_charset = ForceCharset::Forced(charset);
|
||||||
|
} else {
|
||||||
|
self.force_charset = ForceCharset::None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.force_charset = ForceCharset::None;
|
||||||
|
}
|
||||||
|
self.set_dirty(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(ForceCharset::Dialog(selector), _) => {
|
||||||
|
if selector.process_event(event, context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref mut sub) = self.subview {
|
if let Some(ref mut sub) = self.subview {
|
||||||
if sub.process_event(event, context) {
|
if sub.process_event(event, context) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -324,6 +363,7 @@ impl Component for EnvelopeView {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match *event {
|
match *event {
|
||||||
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) if !self.cmd_buf.is_empty() => {
|
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) if !self.cmd_buf.is_empty() => {
|
||||||
self.cmd_buf.clear();
|
self.cmd_buf.clear();
|
||||||
|
@ -521,15 +561,64 @@ impl Component for EnvelopeView {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
UIEvent::Input(Key::Char('d')) => {
|
||||||
|
let entries = vec![
|
||||||
|
(None, "default".to_string()),
|
||||||
|
(Some(Charset::Ascii), Charset::Ascii.to_string()),
|
||||||
|
(Some(Charset::UTF8), Charset::UTF8.to_string()),
|
||||||
|
(Some(Charset::UTF16), Charset::UTF16.to_string()),
|
||||||
|
(Some(Charset::ISO8859_1), Charset::ISO8859_1.to_string()),
|
||||||
|
(Some(Charset::ISO8859_2), Charset::ISO8859_2.to_string()),
|
||||||
|
(Some(Charset::ISO8859_3), Charset::ISO8859_3.to_string()),
|
||||||
|
(Some(Charset::ISO8859_4), Charset::ISO8859_4.to_string()),
|
||||||
|
(Some(Charset::ISO8859_5), Charset::ISO8859_5.to_string()),
|
||||||
|
(Some(Charset::ISO8859_6), Charset::ISO8859_6.to_string()),
|
||||||
|
(Some(Charset::ISO8859_7), Charset::ISO8859_7.to_string()),
|
||||||
|
(Some(Charset::ISO8859_8), Charset::ISO8859_8.to_string()),
|
||||||
|
(Some(Charset::ISO8859_10), Charset::ISO8859_10.to_string()),
|
||||||
|
(Some(Charset::ISO8859_13), Charset::ISO8859_13.to_string()),
|
||||||
|
(Some(Charset::ISO8859_14), Charset::ISO8859_14.to_string()),
|
||||||
|
(Some(Charset::ISO8859_15), Charset::ISO8859_15.to_string()),
|
||||||
|
(Some(Charset::ISO8859_16), Charset::ISO8859_16.to_string()),
|
||||||
|
(Some(Charset::Windows1250), Charset::Windows1250.to_string()),
|
||||||
|
(Some(Charset::Windows1251), Charset::Windows1251.to_string()),
|
||||||
|
(Some(Charset::Windows1252), Charset::Windows1252.to_string()),
|
||||||
|
(Some(Charset::Windows1253), Charset::Windows1253.to_string()),
|
||||||
|
(Some(Charset::GBK), Charset::GBK.to_string()),
|
||||||
|
(Some(Charset::GB2312), Charset::GB2312.to_string()),
|
||||||
|
(Some(Charset::GB18030), Charset::GB18030.to_string()),
|
||||||
|
(Some(Charset::BIG5), Charset::BIG5.to_string()),
|
||||||
|
(Some(Charset::ISO2022JP), Charset::ISO2022JP.to_string()),
|
||||||
|
(Some(Charset::EUCJP), Charset::EUCJP.to_string()),
|
||||||
|
(Some(Charset::KOI8R), Charset::KOI8R.to_string()),
|
||||||
|
(Some(Charset::KOI8U), Charset::KOI8U.to_string()),
|
||||||
|
];
|
||||||
|
self.force_charset = ForceCharset::Dialog(Box::new(Selector::new(
|
||||||
|
"select charset to force",
|
||||||
|
entries,
|
||||||
|
true,
|
||||||
|
Some(Box::new(
|
||||||
|
move |id: ComponentId, results: &[Option<Charset>]| {
|
||||||
|
Some(UIEvent::FinishedUIDialog(id, Box::new(results.to_vec())))
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
context,
|
||||||
|
)));
|
||||||
|
self.dirty = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty
|
self.dirty
|
||||||
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
|
|| matches!(self.force_charset, ForceCharset::Dialog(ref s) if s.is_dirty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_dirty(&mut self, value: bool) {
|
fn set_dirty(&mut self, value: bool) {
|
||||||
self.dirty = value;
|
self.dirty = value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -891,8 +891,8 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send> Selec
|
||||||
self.vertical_alignment,
|
self.vertical_alignment,
|
||||||
self.horizontal_alignment,
|
self.horizontal_alignment,
|
||||||
);
|
);
|
||||||
clear_area(grid, dialog_area, self.theme_default);
|
|
||||||
let inner_area = create_box(grid, dialog_area);
|
let inner_area = create_box(grid, dialog_area);
|
||||||
|
clear_area(grid, inner_area, self.theme_default);
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
&self.title,
|
&self.title,
|
||||||
grid,
|
grid,
|
||||||
|
|
|
@ -246,7 +246,8 @@ shortcut_key_values! { "envelope-view",
|
||||||
return_to_normal_view |> "Return to envelope if viewing raw source or attachment." |> Key::Char('r'),
|
return_to_normal_view |> "Return to envelope if viewing raw source or attachment." |> Key::Char('r'),
|
||||||
toggle_expand_headers |> "Expand extra headers (References and others)." |> Key::Char('h'),
|
toggle_expand_headers |> "Expand extra headers (References and others)." |> Key::Char('h'),
|
||||||
toggle_url_mode |> "Toggles url open mode." |> Key::Char('u'),
|
toggle_url_mode |> "Toggles url open mode." |> Key::Char('u'),
|
||||||
view_raw_source |> "View envelope source in a pager. (toggles between raw and decoded source)" |> Key::Alt('r')
|
view_raw_source |> "View envelope source in a pager. (toggles between raw and decoded source)" |> Key::Alt('r'),
|
||||||
|
change_charset |> "Force attachment charset for decoding." |> Key::Char('d')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue