mail/listing.rs: move mail view to listing parent component
Instead of having a different widget to view mail in for each Listing (plain, threaded, compact, etc) use a single widget in the listing's parent type. This will help with making the listing logic more modular in future refactors to allow all combinations of listing/mail view/ thread view positions and layouts.pull/227/head
parent
5c9b3fb044
commit
575509f1ed
|
@ -173,8 +173,6 @@ theme_default
|
|||
.It
|
||||
error_message
|
||||
.It
|
||||
email_header
|
||||
.It
|
||||
highlight
|
||||
.It
|
||||
status.bar
|
||||
|
|
|
@ -1120,7 +1120,7 @@ Play sound file in notifications if possible.
|
|||
.Sh PAGER
|
||||
Default values are shown in parentheses.
|
||||
.Bl -tag -width 36n
|
||||
.It Ic headers_sticky Ar boolean
|
||||
.It Ic sticky_headers Ar boolean
|
||||
.Pq Em optional
|
||||
Always show headers when scrolling.
|
||||
.\" default value
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
#[pager]
|
||||
#filter = "COLUMNS=72 /usr/local/bin/pygmentize -l email"
|
||||
#pager_context = 0 # default, optional
|
||||
#headers_sticky = true # default, optional
|
||||
#sticky_headers = true # default, optional
|
||||
#
|
||||
#[notifications]
|
||||
#script = "notify-send"
|
||||
|
|
|
@ -32,6 +32,7 @@ use crate::{
|
|||
melib::text_processing::{TextProcessing, Truncate},
|
||||
terminal::boundaries::*,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub mod mail;
|
||||
pub use crate::mail::*;
|
||||
|
@ -163,6 +164,31 @@ pub trait Component: Display + Debug + Send + Sync {
|
|||
fn status(&self, _context: &Context) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &'static ComponentAttr {
|
||||
&ComponentAttr::DEFAULT
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
IndexMap::default()
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
IndexMap::default()
|
||||
}
|
||||
|
||||
fn realize(&self, parent: Option<ComponentId>, context: &mut Context) {
|
||||
log::debug!("Realizing id {} w/ parent {:?}", self.id(), &parent);
|
||||
context.realized.insert(self.id(), parent);
|
||||
}
|
||||
|
||||
fn unrealize(&self, context: &mut Context) {
|
||||
log::debug!("Unrealizing id {}", self.id());
|
||||
context.unrealized.insert(self.id());
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::ComponentUnrealize(self.id()));
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Box<dyn Component> {
|
||||
|
@ -205,4 +231,89 @@ impl Component for Box<dyn Component> {
|
|||
fn status(&self, context: &Context) -> String {
|
||||
(**self).status(context)
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &'static ComponentAttr {
|
||||
(**self).attributes()
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
(**self).children()
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
(**self).children_mut()
|
||||
}
|
||||
|
||||
fn realize(&self, parent: Option<ComponentId>, context: &mut Context) {
|
||||
(**self).realize(parent, context)
|
||||
}
|
||||
|
||||
fn unrealize(&self, context: &mut Context) {
|
||||
(**self).unrealize(context)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Attributes of a [`Component`] widget.
|
||||
///
|
||||
/// `ComponentAttr::DEFAULT` represents no attribute.
|
||||
pub struct ComponentAttr: u8 {
|
||||
/// Nothing special going on.
|
||||
const DEFAULT = 0;
|
||||
const HAS_ANIMATIONS = 1;
|
||||
const CONTAINER = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ComponentAttr {
|
||||
fn default() -> Self {
|
||||
Self::DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct ComponentPath {
|
||||
id: ComponentId,
|
||||
tail: SmallVec<[ComponentId; 8]>,
|
||||
}
|
||||
|
||||
impl ComponentPath {
|
||||
pub fn new(id: ComponentId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
tail: SmallVec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_front(&mut self, id: ComponentId) {
|
||||
self.tail.insert(0, self.id);
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
pub fn push_back(&mut self, id: ComponentId) {
|
||||
self.tail.push(id);
|
||||
}
|
||||
|
||||
pub fn resolve<'c>(&self, root: &'c dyn Component) -> Option<&'c dyn Component> {
|
||||
let mut cursor = root;
|
||||
for id in self.tail.iter().rev().chain(std::iter::once(&self.id)) {
|
||||
log::trace!("resolve cursor = {} next id is {}", cursor.id(), &id);
|
||||
if *id == cursor.id() {
|
||||
log::trace!("continue;");
|
||||
continue;
|
||||
}
|
||||
cursor = cursor.children().remove(id)?;
|
||||
}
|
||||
Some(cursor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn parent(&self) -> Option<&ComponentId> {
|
||||
self.tail.first()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn root(&self) -> Option<&ComponentId> {
|
||||
self.tail.last()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,10 +221,10 @@ impl Component for ContactManager {
|
|||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage("Saved.".into()),
|
||||
));
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
Some(false) => {
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
}
|
||||
self.set_dirty(true);
|
||||
|
@ -237,7 +237,7 @@ impl Component for ContactManager {
|
|||
ViewMode::ReadOnly => {
|
||||
if let &mut UIEvent::Input(Key::Esc) = event {
|
||||
if self.can_quit_cleanly(context) {
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -899,7 +899,9 @@ impl Component for ContactList {
|
|||
}
|
||||
} else {
|
||||
match event {
|
||||
UIEvent::ComponentKill(ref kill_id) if self.mode == ViewMode::View(*kill_id) => {
|
||||
UIEvent::ComponentUnrealize(ref kill_id)
|
||||
if self.mode == ViewMode::View(*kill_id) =>
|
||||
{
|
||||
self.mode = ViewMode::List;
|
||||
self.view.take();
|
||||
self.set_dirty(true);
|
||||
|
|
|
@ -1158,7 +1158,7 @@ impl Component for Composer {
|
|||
Flag::SEEN,
|
||||
) {
|
||||
Ok(job) => {
|
||||
let handle = context.job_executor.spawn_blocking(job);
|
||||
let handle = context.main_loop_handler.job_executor.spawn_blocking(job);
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::NewJob(
|
||||
|
@ -1208,26 +1208,29 @@ impl Component for Composer {
|
|||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
(ViewMode::Send(ref dialog), UIEvent::ComponentKill(ref id)) if *id == dialog.id() => {
|
||||
self.mode = ViewMode::Edit;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
(ViewMode::SelectRecipients(ref dialog), UIEvent::ComponentKill(ref id))
|
||||
(ViewMode::Send(ref dialog), UIEvent::ComponentUnrealize(ref id))
|
||||
if *id == dialog.id() =>
|
||||
{
|
||||
self.mode = ViewMode::Edit;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
(ViewMode::Discard(_, ref dialog), UIEvent::ComponentKill(ref id))
|
||||
(ViewMode::SelectRecipients(ref dialog), UIEvent::ComponentUnrealize(ref id))
|
||||
if *id == dialog.id() =>
|
||||
{
|
||||
self.mode = ViewMode::Edit;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
(ViewMode::Discard(_, ref dialog), UIEvent::ComponentUnrealize(ref id))
|
||||
if *id == dialog.id() =>
|
||||
{
|
||||
self.mode = ViewMode::Edit;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
#[cfg(feature = "gpgme")]
|
||||
(ViewMode::SelectEncryptKey(_, ref mut selector), UIEvent::ComponentKill(ref id))
|
||||
if *id == selector.id() =>
|
||||
{
|
||||
(
|
||||
ViewMode::SelectEncryptKey(_, ref mut selector),
|
||||
UIEvent::ComponentUnrealize(ref id),
|
||||
) if *id == selector.id() => {
|
||||
self.mode = ViewMode::Edit;
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
|
@ -2315,7 +2318,7 @@ pub fn send_draft_async(
|
|||
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
|
||||
let store_sent_mail = *account_settings!(context[account_hash].composing.store_sent_mail);
|
||||
let format_flowed = *account_settings!(context[account_hash].composing.format_flowed);
|
||||
let event_sender = context.sender.clone();
|
||||
let event_sender = context.main_loop_handler.sender.clone();
|
||||
#[cfg(feature = "gpgme")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
let mut filters_stack: Vec<
|
||||
|
|
|
@ -68,7 +68,10 @@ impl KeySelection {
|
|||
ctx.set_auto_key_locate(LocateKey::WKD | LocateKey::LOCAL)?;
|
||||
}
|
||||
let job = ctx.keylist(secret, Some(pattern.clone()))?;
|
||||
let handle = context.job_executor.spawn_specialized(job);
|
||||
let handle = context
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(job);
|
||||
let mut progress_spinner = ProgressSpinner::new(8, context);
|
||||
progress_spinner.start();
|
||||
Ok(KeySelection::LoadingKeys {
|
||||
|
|
|
@ -55,6 +55,7 @@ pub const DEFAULT_SNOOZED_FLAG: &str = "💤";
|
|||
pub struct RowsState<T> {
|
||||
pub selection: HashMap<EnvelopeHash, bool>,
|
||||
pub row_updates: SmallVec<[EnvelopeHash; 8]>,
|
||||
/// FIXME: env vec should have at least one element guaranteed
|
||||
pub thread_to_env: HashMap<ThreadHash, SmallVec<[EnvelopeHash; 8]>>,
|
||||
pub env_to_thread: HashMap<EnvelopeHash, ThreadHash>,
|
||||
pub thread_order: HashMap<ThreadHash, usize>,
|
||||
|
@ -412,6 +413,20 @@ struct AccountMenuEntry {
|
|||
}
|
||||
|
||||
pub trait MailListingTrait: ListingTrait {
|
||||
fn as_component(&self) -> &dyn Component
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self
|
||||
}
|
||||
|
||||
fn as_component_mut(&mut self) -> &mut dyn Component
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self
|
||||
}
|
||||
|
||||
fn perform_action(
|
||||
&mut self,
|
||||
context: &mut Context,
|
||||
|
@ -450,7 +465,10 @@ pub trait MailListingTrait: ListingTrait {
|
|||
));
|
||||
}
|
||||
Ok(fut) => {
|
||||
let handle = account.job_executor.spawn_specialized(fut);
|
||||
let handle = account
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(fut);
|
||||
account
|
||||
.insert_job(handle.job_id, JobRequest::SetFlags { env_hashes, handle });
|
||||
}
|
||||
|
@ -469,7 +487,10 @@ pub trait MailListingTrait: ListingTrait {
|
|||
));
|
||||
}
|
||||
Ok(fut) => {
|
||||
let handle = account.job_executor.spawn_specialized(fut);
|
||||
let handle = account
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(fut);
|
||||
account
|
||||
.insert_job(handle.job_id, JobRequest::SetFlags { env_hashes, handle });
|
||||
}
|
||||
|
@ -488,7 +509,10 @@ pub trait MailListingTrait: ListingTrait {
|
|||
));
|
||||
}
|
||||
Ok(fut) => {
|
||||
let handle = account.job_executor.spawn_specialized(fut);
|
||||
let handle = account
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(fut);
|
||||
account
|
||||
.insert_job(handle.job_id, JobRequest::SetFlags { env_hashes, handle });
|
||||
}
|
||||
|
@ -507,7 +531,10 @@ pub trait MailListingTrait: ListingTrait {
|
|||
));
|
||||
}
|
||||
Ok(fut) => {
|
||||
let handle = account.job_executor.spawn_specialized(fut);
|
||||
let handle = account
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(fut);
|
||||
account
|
||||
.insert_job(handle.job_id, JobRequest::SetFlags { env_hashes, handle });
|
||||
}
|
||||
|
@ -526,7 +553,10 @@ pub trait MailListingTrait: ListingTrait {
|
|||
));
|
||||
}
|
||||
Ok(fut) => {
|
||||
let handle = account.job_executor.spawn_specialized(fut);
|
||||
let handle = account
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(fut);
|
||||
account.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::DeleteMessages { env_hashes, handle },
|
||||
|
@ -551,7 +581,10 @@ pub trait MailListingTrait: ListingTrait {
|
|||
));
|
||||
}
|
||||
Ok(fut) => {
|
||||
let handle = account.job_executor.spawn_specialized(fut);
|
||||
let handle = account
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(fut);
|
||||
account.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Generic {
|
||||
|
@ -588,7 +621,10 @@ pub trait MailListingTrait: ListingTrait {
|
|||
));
|
||||
}
|
||||
Ok(fut) => {
|
||||
let handle = account.job_executor.spawn_specialized(fut);
|
||||
let handle = account
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(fut);
|
||||
account.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Generic {
|
||||
|
@ -667,7 +703,7 @@ pub trait MailListingTrait: ListingTrait {
|
|||
let _ = sender.send(r);
|
||||
Ok(())
|
||||
});
|
||||
let handle = account.job_executor.spawn_blocking(fut);
|
||||
let handle = account.main_loop_handler.job_executor.spawn_blocking(fut);
|
||||
let path = path.to_path_buf();
|
||||
account.insert_job(
|
||||
handle.job_id,
|
||||
|
@ -743,12 +779,27 @@ pub trait ListingTrait: Component {
|
|||
) {
|
||||
}
|
||||
fn unfocused(&self) -> bool;
|
||||
fn view_area(&self) -> Option<Area>;
|
||||
fn set_modifier_active(&mut self, _new_val: bool);
|
||||
fn set_modifier_command(&mut self, _new_val: Option<Modifier>);
|
||||
fn modifier_command(&self) -> Option<Modifier>;
|
||||
fn set_movement(&mut self, mvm: PageMovement);
|
||||
fn focus(&self) -> Focus;
|
||||
fn set_focus(&mut self, new_value: Focus, context: &mut Context);
|
||||
|
||||
fn kick_parent(&self, parent: ComponentId, msg: ListingMessage, context: &mut Context) {
|
||||
log::trace!(
|
||||
"kick_parent self is {} parent is {} msg is {:?}",
|
||||
self.id(),
|
||||
parent,
|
||||
&msg
|
||||
);
|
||||
context.replies.push_back(UIEvent::IntraComm {
|
||||
from: self.id(),
|
||||
to: parent,
|
||||
content: Box::new(msg),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -788,32 +839,13 @@ impl core::ops::DerefMut for ListingComponent {
|
|||
}
|
||||
|
||||
impl ListingComponent {
|
||||
fn set_style(&mut self, new_style: IndexStyle) {
|
||||
match new_style {
|
||||
IndexStyle::Plain => {
|
||||
if let Plain(_) = self {
|
||||
return;
|
||||
}
|
||||
*self = Plain(PlainListing::new(self.coordinates()));
|
||||
}
|
||||
IndexStyle::Threaded => {
|
||||
if let Threaded(_) = self {
|
||||
return;
|
||||
}
|
||||
*self = Threaded(ThreadListing::new(self.coordinates()));
|
||||
}
|
||||
IndexStyle::Compact => {
|
||||
if let Compact(_) = self {
|
||||
return;
|
||||
}
|
||||
*self = Compact(CompactListing::new(self.coordinates()));
|
||||
}
|
||||
IndexStyle::Conversations => {
|
||||
if let Conversations(_) = self {
|
||||
return;
|
||||
}
|
||||
*self = Conversations(ConversationsListing::new(self.coordinates()));
|
||||
}
|
||||
fn id(&self) -> ComponentId {
|
||||
match self {
|
||||
Compact(l) => l.as_component().id(),
|
||||
Plain(l) => l.as_component().id(),
|
||||
Threaded(l) => l.as_component().id(),
|
||||
Conversations(l) => l.as_component().id(),
|
||||
Offline(l) => l.as_component().id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -862,6 +894,7 @@ pub struct Listing {
|
|||
prev_ratio: usize,
|
||||
menu_width: WidgetWidth,
|
||||
focus: ListingFocus,
|
||||
view: Box<ThreadView>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Listing {
|
||||
|
@ -930,14 +963,20 @@ impl Component for Listing {
|
|||
if context.is_online(account_hash).is_err()
|
||||
&& !matches!(self.component, ListingComponent::Offline(_))
|
||||
{
|
||||
self.component.unrealize(context);
|
||||
self.component =
|
||||
Offline(OfflineListing::new((account_hash, MailboxHash::default())));
|
||||
self.component.realize(self.id().into(), context);
|
||||
}
|
||||
|
||||
if let Some(s) = self.status.as_mut() {
|
||||
s.draw(grid, area, context);
|
||||
} else {
|
||||
self.component.draw(grid, area, context);
|
||||
if self.component.unfocused() {
|
||||
self.view
|
||||
.draw(grid, self.component.view_area().unwrap_or(area), context);
|
||||
}
|
||||
}
|
||||
} else if right_component_width == 0 {
|
||||
self.draw_menu(grid, area, context);
|
||||
|
@ -950,14 +989,20 @@ impl Component for Listing {
|
|||
if context.is_online(account_hash).is_err()
|
||||
&& !matches!(self.component, ListingComponent::Offline(_))
|
||||
{
|
||||
self.component.unrealize(context);
|
||||
self.component =
|
||||
Offline(OfflineListing::new((account_hash, MailboxHash::default())));
|
||||
self.component.realize(self.id().into(), context);
|
||||
}
|
||||
if let Some(s) = self.status.as_mut() {
|
||||
s.draw(grid, (set_x(upper_left, mid + 1), bottom_right), context);
|
||||
} else {
|
||||
self.component
|
||||
.draw(grid, (set_x(upper_left, mid + 1), bottom_right), context);
|
||||
let area = (set_x(upper_left, mid + 1), bottom_right);
|
||||
self.component.draw(grid, area, context);
|
||||
if self.component.unfocused() {
|
||||
self.view
|
||||
.draw(grid, self.component.view_area().unwrap_or(area), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dirty = false;
|
||||
|
@ -1132,9 +1177,73 @@ impl Component for Listing {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
UIEvent::IntraComm {
|
||||
from,
|
||||
to,
|
||||
ref content,
|
||||
} if (*from, *to) == (self.component.id(), self.id()) => {
|
||||
match content.downcast_ref::<ListingMessage>().map(|msg| *msg) {
|
||||
None => {}
|
||||
Some(ListingMessage::FocusUpdate { new_value }) => {
|
||||
self.view.process_event(
|
||||
&mut UIEvent::VisibilityChange(!matches!(new_value, Focus::None)),
|
||||
context,
|
||||
);
|
||||
if matches!(new_value, Focus::Entry) {
|
||||
// Need to clear gap between sidebar and listing component, if any.
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
Some(ListingMessage::UpdateView) => {
|
||||
log::trace!("UpdateView");
|
||||
}
|
||||
Some(ListingMessage::OpenEntryUnderCursor {
|
||||
env_hash,
|
||||
thread_hash,
|
||||
show_thread,
|
||||
}) => {
|
||||
let (a, m) = self.component.coordinates();
|
||||
self.view.unrealize(context);
|
||||
self.view = Box::new(ThreadView::new(
|
||||
(a, m, env_hash),
|
||||
thread_hash,
|
||||
Some(env_hash),
|
||||
if show_thread {
|
||||
None
|
||||
} else {
|
||||
Some(ThreadViewFocus::MailView)
|
||||
},
|
||||
context,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "debug-tracing")]
|
||||
UIEvent::IntraComm {
|
||||
from,
|
||||
to,
|
||||
ref content,
|
||||
} => {
|
||||
if *from == self.component.id() || *to == self.id() {
|
||||
log::debug!(
|
||||
"BUG intracomm event: {:?} downcast content {:?}",
|
||||
event,
|
||||
content.downcast_ref::<ListingMessage>().map(|msg| *msg)
|
||||
);
|
||||
log::debug!(
|
||||
"BUG component is {} and self id is {}",
|
||||
self.component.id(),
|
||||
self.id()
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if self.component.unfocused() && self.view.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.focus == ListingFocus::Mailbox && self.status.is_some() {
|
||||
if let Some(s) = self.status.as_mut() {
|
||||
if s.process_event(event, context) {
|
||||
|
@ -1142,11 +1251,12 @@ impl Component for Listing {
|
|||
}
|
||||
}
|
||||
}
|
||||
if self.focus == ListingFocus::Mailbox
|
||||
&& self.status.is_none()
|
||||
&& self.component.process_event(event, context)
|
||||
{
|
||||
return true;
|
||||
if self.focus == ListingFocus::Mailbox && self.status.is_none() {
|
||||
if self.component.unfocused() && self.view.process_event(event, context) {
|
||||
return true;
|
||||
} else if self.component.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let shortcuts = self.shortcuts(context);
|
||||
|
@ -1336,19 +1446,19 @@ impl Component for Listing {
|
|||
match event {
|
||||
UIEvent::Action(ref action) => match action {
|
||||
Action::Listing(ListingAction::SetPlain) => {
|
||||
self.component.set_style(IndexStyle::Plain);
|
||||
self.set_style(IndexStyle::Plain, context);
|
||||
return true;
|
||||
}
|
||||
Action::Listing(ListingAction::SetThreaded) => {
|
||||
self.component.set_style(IndexStyle::Threaded);
|
||||
self.set_style(IndexStyle::Threaded, context);
|
||||
return true;
|
||||
}
|
||||
Action::Listing(ListingAction::SetCompact) => {
|
||||
self.component.set_style(IndexStyle::Compact);
|
||||
self.set_style(IndexStyle::Compact, context);
|
||||
return true;
|
||||
}
|
||||
Action::Listing(ListingAction::SetConversations) => {
|
||||
self.component.set_style(IndexStyle::Conversations);
|
||||
self.set_style(IndexStyle::Conversations, context);
|
||||
return true;
|
||||
}
|
||||
Action::Listing(ListingAction::Import(file_path, mailbox_path)) => {
|
||||
|
@ -1952,6 +2062,11 @@ impl Component for Listing {
|
|||
.as_ref()
|
||||
.map(Component::is_dirty)
|
||||
.unwrap_or_else(|| self.component.is_dirty())
|
||||
|| if self.component.unfocused() {
|
||||
self.view.is_dirty()
|
||||
} else {
|
||||
self.component.is_dirty()
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
|
@ -1960,6 +2075,9 @@ impl Component for Listing {
|
|||
s.set_dirty(value);
|
||||
} else {
|
||||
self.component.set_dirty(value);
|
||||
if self.component.unfocused() {
|
||||
self.view.set_dirty(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1972,6 +2090,9 @@ impl Component for Listing {
|
|||
let mut config_map = context.settings.shortcuts.listing.key_values();
|
||||
if self.focus != ListingFocus::Menu {
|
||||
config_map.remove("open_mailbox");
|
||||
if self.component.unfocused() {
|
||||
map.extend(self.view.shortcuts(context).into_iter());
|
||||
}
|
||||
}
|
||||
map.insert(Shortcuts::LISTING, config_map);
|
||||
|
||||
|
@ -1979,7 +2100,7 @@ impl Component for Listing {
|
|||
}
|
||||
|
||||
fn id(&self) -> ComponentId {
|
||||
self.component.id()
|
||||
self.id
|
||||
}
|
||||
|
||||
fn status(&self, context: &Context) -> String {
|
||||
|
@ -2022,6 +2143,38 @@ impl Component for Listing {
|
|||
MailboxStatus::Failed(_) | MailboxStatus::None => account[&mailbox_hash].status(),
|
||||
}
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
let mut ret = IndexMap::default();
|
||||
ret.insert(
|
||||
self.component.id(),
|
||||
match &self.component {
|
||||
Compact(l) => l.as_component(),
|
||||
Plain(l) => l.as_component(),
|
||||
Threaded(l) => l.as_component(),
|
||||
Conversations(l) => l.as_component(),
|
||||
Offline(l) => l.as_component(),
|
||||
},
|
||||
);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
let mut ret = IndexMap::default();
|
||||
ret.insert(
|
||||
self.component.id(),
|
||||
match &mut self.component {
|
||||
Compact(l) => l.as_component_mut(),
|
||||
Plain(l) => l.as_component_mut(),
|
||||
Threaded(l) => l.as_component_mut(),
|
||||
Conversations(l) => l.as_component_mut(),
|
||||
Offline(l) => l.as_component_mut(),
|
||||
},
|
||||
);
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Listing {
|
||||
|
@ -2059,18 +2212,23 @@ impl Listing {
|
|||
first_account_hash,
|
||||
MailboxHash::default(),
|
||||
))),
|
||||
view: Box::new(ThreadView::default()),
|
||||
accounts: account_entries,
|
||||
status: None,
|
||||
dirty: true,
|
||||
cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
|
||||
menu_cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
|
||||
menu_content: CellBuffer::new_with_context(0, 0, None, context),
|
||||
menu_scrollbar_show_timer: context.job_executor.clone().create_timer(
|
||||
menu_scrollbar_show_timer: context.main_loop_handler.job_executor.clone().create_timer(
|
||||
std::time::Duration::from_secs(0),
|
||||
std::time::Duration::from_millis(1200),
|
||||
),
|
||||
show_menu_scrollbar: ShowMenuScrollbar::Never,
|
||||
startup_checks_rate: RateLimit::new(2, 1000, context.job_executor.clone()),
|
||||
startup_checks_rate: RateLimit::new(
|
||||
2,
|
||||
1000,
|
||||
context.main_loop_handler.job_executor.clone(),
|
||||
),
|
||||
theme_default: conf::value(context, "theme_default"),
|
||||
id: ComponentId::default(),
|
||||
sidebar_divider: *account_settings!(
|
||||
|
@ -2084,6 +2242,7 @@ impl Listing {
|
|||
focus: ListingFocus::Mailbox,
|
||||
cmd_buf: String::with_capacity(4),
|
||||
};
|
||||
ret.component.realize(ret.id().into(), context);
|
||||
ret.change_account(context);
|
||||
ret
|
||||
}
|
||||
|
@ -2580,10 +2739,12 @@ impl Listing {
|
|||
|
||||
let index_style =
|
||||
mailbox_settings!(context[account_hash][mailbox_hash].listing.index_style);
|
||||
self.component.set_style(*index_style);
|
||||
self.set_style(*index_style, context);
|
||||
} else if !matches!(self.component, ListingComponent::Offline(_)) {
|
||||
self.component.unrealize(context);
|
||||
self.component =
|
||||
Offline(OfflineListing::new((account_hash, MailboxHash::default())));
|
||||
self.component.realize(self.id().into(), context);
|
||||
}
|
||||
self.status = None;
|
||||
context
|
||||
|
@ -2622,4 +2783,64 @@ impl Listing {
|
|||
fn is_menu_visible(&self) -> bool {
|
||||
!matches!(self.component.focus(), Focus::EntryFullscreen) && self.menu_visibility
|
||||
}
|
||||
|
||||
fn set_style(&mut self, new_style: IndexStyle, context: &mut Context) {
|
||||
let old = match new_style {
|
||||
IndexStyle::Plain => {
|
||||
if matches!(self.component, Plain(_)) {
|
||||
return;
|
||||
}
|
||||
let coordinates = self.component.coordinates();
|
||||
std::mem::replace(
|
||||
&mut self.component,
|
||||
Plain(PlainListing::new(self.id, coordinates)),
|
||||
)
|
||||
}
|
||||
IndexStyle::Threaded => {
|
||||
if matches!(self.component, Threaded(_)) {
|
||||
return;
|
||||
}
|
||||
let coordinates = self.component.coordinates();
|
||||
std::mem::replace(
|
||||
&mut self.component,
|
||||
Threaded(ThreadListing::new(self.id, coordinates, context)),
|
||||
)
|
||||
}
|
||||
IndexStyle::Compact => {
|
||||
if matches!(self.component, Compact(_)) {
|
||||
return;
|
||||
}
|
||||
let coordinates = self.component.coordinates();
|
||||
std::mem::replace(
|
||||
&mut self.component,
|
||||
Compact(CompactListing::new(self.id, coordinates)),
|
||||
)
|
||||
}
|
||||
IndexStyle::Conversations => {
|
||||
if matches!(self.component, Conversations(_)) {
|
||||
return;
|
||||
}
|
||||
let coordinates = self.component.coordinates();
|
||||
std::mem::replace(
|
||||
&mut self.component,
|
||||
Conversations(ConversationsListing::new(self.id, coordinates)),
|
||||
)
|
||||
}
|
||||
};
|
||||
old.unrealize(context);
|
||||
self.component.realize(self.id.into(), context);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ListingMessage {
|
||||
FocusUpdate {
|
||||
new_value: Focus,
|
||||
},
|
||||
OpenEntryUnderCursor {
|
||||
env_hash: EnvelopeHash,
|
||||
thread_hash: ThreadHash,
|
||||
show_thread: bool,
|
||||
},
|
||||
UpdateView,
|
||||
}
|
||||
|
|
|
@ -185,12 +185,13 @@ pub struct CompactListing {
|
|||
force_draw: bool,
|
||||
/// If `self.view` exists or not.
|
||||
focus: Focus,
|
||||
view: Box<ThreadView>,
|
||||
color_cache: ColorCache,
|
||||
|
||||
movement: Option<PageMovement>,
|
||||
modifier_active: bool,
|
||||
modifier_command: Option<Modifier>,
|
||||
view_area: Option<Area>,
|
||||
parent: ComponentId,
|
||||
id: ComponentId,
|
||||
}
|
||||
|
||||
|
@ -287,6 +288,7 @@ impl MailListingTrait for CompactListing {
|
|||
self.sort,
|
||||
&context.accounts[&self.cursor_pos.0].collection.envelopes,
|
||||
);
|
||||
drop(threads);
|
||||
|
||||
self.redraw_threads_list(
|
||||
context,
|
||||
|
@ -294,10 +296,22 @@ impl MailListingTrait for CompactListing {
|
|||
);
|
||||
|
||||
if !force && old_cursor_pos == self.new_cursor_pos {
|
||||
self.view.update(context);
|
||||
self.kick_parent(self.parent, ListingMessage::UpdateView, context);
|
||||
} else if self.unfocused() {
|
||||
if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
|
||||
self.view = Box::new(ThreadView::new(self.new_cursor_pos, thread, None, context));
|
||||
if let Some((thread_hash, env_hash)) = self
|
||||
.get_thread_under_cursor(self.cursor_pos.2)
|
||||
.and_then(|thread| self.rows.thread_to_env.get(&thread).map(|e| (thread, e[0])))
|
||||
{
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::OpenEntryUnderCursor {
|
||||
thread_hash,
|
||||
env_hash,
|
||||
show_thread: true,
|
||||
},
|
||||
context,
|
||||
);
|
||||
self.set_focus(Focus::Entry, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -564,7 +578,6 @@ impl ListingTrait for CompactListing {
|
|||
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.focus = Focus::None;
|
||||
self.view = Box::<ThreadView>::default();
|
||||
self.filtered_selection.clear();
|
||||
self.filtered_order.clear();
|
||||
self.filter_term.clear();
|
||||
|
@ -812,6 +825,10 @@ impl ListingTrait for CompactListing {
|
|||
);
|
||||
}
|
||||
|
||||
fn view_area(&self) -> Option<Area> {
|
||||
self.view_area
|
||||
}
|
||||
|
||||
fn unfocused(&self) -> bool {
|
||||
!matches!(self.focus, Focus::None)
|
||||
}
|
||||
|
@ -836,8 +853,6 @@ impl ListingTrait for CompactListing {
|
|||
fn set_focus(&mut self, new_value: Focus, context: &mut Context) {
|
||||
match new_value {
|
||||
Focus::None => {
|
||||
self.view
|
||||
.process_event(&mut UIEvent::VisibilityChange(false), context);
|
||||
self.dirty = true;
|
||||
/* If self.rows.row_updates is not empty and we exit a thread, the row_update
|
||||
* events will be performed but the list will not be drawn.
|
||||
|
@ -848,13 +863,17 @@ impl ListingTrait for CompactListing {
|
|||
Focus::Entry => {
|
||||
self.force_draw = true;
|
||||
self.dirty = true;
|
||||
self.view.set_dirty(true);
|
||||
}
|
||||
Focus::EntryFullscreen => {
|
||||
self.view.set_dirty(true);
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
self.focus = new_value;
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::FocusUpdate { new_value },
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
fn focus(&self) -> Focus {
|
||||
|
@ -870,7 +889,7 @@ impl fmt::Display for CompactListing {
|
|||
|
||||
impl CompactListing {
|
||||
pub const DESCRIPTION: &'static str = "compact listing";
|
||||
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
|
||||
pub fn new(parent: ComponentId, coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
|
||||
Box::new(CompactListing {
|
||||
cursor_pos: (coordinates.0, MailboxHash::default(), 0),
|
||||
new_cursor_pos: (coordinates.0, coordinates.1, 0),
|
||||
|
@ -889,11 +908,12 @@ impl CompactListing {
|
|||
rows: RowsState::default(),
|
||||
dirty: true,
|
||||
force_draw: true,
|
||||
view: Box::<ThreadView>::default(),
|
||||
color_cache: ColorCache::default(),
|
||||
movement: None,
|
||||
modifier_active: false,
|
||||
modifier_command: None,
|
||||
view_area: None,
|
||||
parent,
|
||||
id: ComponentId::default(),
|
||||
})
|
||||
}
|
||||
|
@ -1440,12 +1460,13 @@ impl CompactListing {
|
|||
|
||||
impl Component for CompactListing {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.is_dirty() {
|
||||
if matches!(self.focus, Focus::EntryFullscreen) {
|
||||
self.view_area = area.into();
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.focus, Focus::EntryFullscreen) {
|
||||
return self.view.draw(grid, area, context);
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.unfocused() {
|
||||
|
@ -1681,7 +1702,7 @@ impl Component for CompactListing {
|
|||
return;
|
||||
}
|
||||
|
||||
self.view.draw(grid, area, context);
|
||||
self.view_area = area.into();
|
||||
}
|
||||
self.dirty = false;
|
||||
}
|
||||
|
@ -1711,10 +1732,6 @@ impl Component for CompactListing {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
if self.unfocused() && self.view.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.length > 0 {
|
||||
match *event {
|
||||
UIEvent::Input(ref k)
|
||||
|
@ -1722,9 +1739,21 @@ impl Component for CompactListing {
|
|||
&& (shortcut!(k == shortcuts[Shortcuts::LISTING]["open_entry"])
|
||||
|| shortcut!(k == shortcuts[Shortcuts::LISTING]["focus_right"])) =>
|
||||
{
|
||||
if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
|
||||
self.view =
|
||||
Box::new(ThreadView::new(self.cursor_pos, thread, None, context));
|
||||
if let Some((thread_hash, env_hash)) = self
|
||||
.get_thread_under_cursor(self.cursor_pos.2)
|
||||
.and_then(|thread| {
|
||||
self.rows.thread_to_env.get(&thread).map(|e| (thread, e[0]))
|
||||
})
|
||||
{
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::OpenEntryUnderCursor {
|
||||
thread_hash,
|
||||
env_hash,
|
||||
show_thread: true,
|
||||
},
|
||||
context,
|
||||
);
|
||||
self.set_focus(Focus::Entry, context);
|
||||
}
|
||||
return true;
|
||||
|
@ -1837,7 +1866,7 @@ impl Component for CompactListing {
|
|||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
|
||||
UIEvent::EnvelopeRename(_, ref new_hash) => {
|
||||
let account = &context.accounts[&self.cursor_pos.0];
|
||||
let threads = account.collection.get_threads(self.cursor_pos.1);
|
||||
if !account.collection.contains_key(new_hash) {
|
||||
|
@ -1855,13 +1884,8 @@ impl Component for CompactListing {
|
|||
}
|
||||
|
||||
self.set_dirty(true);
|
||||
|
||||
if self.unfocused() {
|
||||
self.view
|
||||
.process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
|
||||
}
|
||||
}
|
||||
UIEvent::EnvelopeRemove(ref _env_hash, ref thread_hash) => {
|
||||
UIEvent::EnvelopeRemove(_, ref thread_hash) => {
|
||||
if self.rows.thread_order.contains_key(thread_hash) {
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
|
@ -1885,11 +1909,6 @@ impl Component for CompactListing {
|
|||
}
|
||||
|
||||
self.set_dirty(true);
|
||||
|
||||
if self.unfocused() {
|
||||
self.view
|
||||
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
|
||||
}
|
||||
}
|
||||
UIEvent::ChangeMode(UIMode::Normal) => {
|
||||
self.set_dirty(true);
|
||||
|
@ -1926,6 +1945,7 @@ impl Component for CompactListing {
|
|||
) {
|
||||
Ok(job) => {
|
||||
let handle = context.accounts[&self.cursor_pos.0]
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(job);
|
||||
self.search_job = Some((filter_term.to_string(), handle));
|
||||
|
@ -1948,6 +1968,7 @@ impl Component for CompactListing {
|
|||
) {
|
||||
Ok(job) => {
|
||||
let mut handle = context.accounts[&self.cursor_pos.0]
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(job);
|
||||
if let Ok(Some(search_result)) = try_recv_timeout!(&mut handle.chan) {
|
||||
|
@ -2011,24 +2032,17 @@ impl Component for CompactListing {
|
|||
fn is_dirty(&self) -> bool {
|
||||
match self.focus {
|
||||
Focus::None => self.dirty,
|
||||
Focus::Entry => self.dirty || self.view.is_dirty(),
|
||||
Focus::EntryFullscreen => self.view.is_dirty(),
|
||||
Focus::Entry => self.dirty,
|
||||
Focus::EntryFullscreen => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
self.dirty = value;
|
||||
if self.unfocused() {
|
||||
self.view.set_dirty(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
let mut map = if self.unfocused() {
|
||||
self.view.shortcuts(context)
|
||||
} else {
|
||||
ShortcutMaps::default()
|
||||
};
|
||||
let mut map = ShortcutMaps::default();
|
||||
|
||||
map.insert(
|
||||
Shortcuts::LISTING,
|
||||
|
|
|
@ -113,14 +113,15 @@ pub struct ConversationsListing {
|
|||
/// If we must redraw on next redraw event
|
||||
dirty: bool,
|
||||
force_draw: bool,
|
||||
/// If `self.view` exists or not.
|
||||
/// If `self.view` is visible or not.
|
||||
focus: Focus,
|
||||
view: ThreadView,
|
||||
color_cache: ColorCache,
|
||||
|
||||
movement: Option<PageMovement>,
|
||||
modifier_active: bool,
|
||||
modifier_command: Option<Modifier>,
|
||||
view_area: Option<Area>,
|
||||
parent: ComponentId,
|
||||
id: ComponentId,
|
||||
}
|
||||
|
||||
|
@ -204,6 +205,7 @@ impl MailListingTrait for ConversationsListing {
|
|||
self.sort,
|
||||
&context.accounts[&self.cursor_pos.0].collection.envelopes,
|
||||
);
|
||||
drop(threads);
|
||||
|
||||
self.redraw_threads_list(
|
||||
context,
|
||||
|
@ -212,10 +214,22 @@ impl MailListingTrait for ConversationsListing {
|
|||
|
||||
if !force && old_cursor_pos == self.new_cursor_pos && old_mailbox_hash == self.cursor_pos.1
|
||||
{
|
||||
self.view.update(context);
|
||||
self.kick_parent(self.parent, ListingMessage::UpdateView, context);
|
||||
} else if self.unfocused() {
|
||||
if let Some(thread_group) = self.get_thread_under_cursor(self.cursor_pos.2) {
|
||||
self.view = ThreadView::new(self.new_cursor_pos, thread_group, None, context);
|
||||
if let Some((thread_hash, env_hash)) = self
|
||||
.get_thread_under_cursor(self.cursor_pos.2)
|
||||
.and_then(|thread| self.rows.thread_to_env.get(&thread).map(|e| (thread, e[0])))
|
||||
{
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::OpenEntryUnderCursor {
|
||||
thread_hash,
|
||||
env_hash,
|
||||
show_thread: true,
|
||||
},
|
||||
context,
|
||||
);
|
||||
self.set_focus(Focus::Entry, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -377,7 +391,6 @@ impl ListingTrait for ConversationsListing {
|
|||
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.focus = Focus::None;
|
||||
self.view = ThreadView::default();
|
||||
self.filtered_selection.clear();
|
||||
self.filtered_order.clear();
|
||||
self.filter_term.clear();
|
||||
|
@ -556,6 +569,10 @@ impl ListingTrait for ConversationsListing {
|
|||
);
|
||||
}
|
||||
|
||||
fn view_area(&self) -> Option<Area> {
|
||||
self.view_area
|
||||
}
|
||||
|
||||
fn unfocused(&self) -> bool {
|
||||
!matches!(self.focus, Focus::None)
|
||||
}
|
||||
|
@ -580,8 +597,6 @@ impl ListingTrait for ConversationsListing {
|
|||
fn set_focus(&mut self, new_value: Focus, context: &mut Context) {
|
||||
match new_value {
|
||||
Focus::None => {
|
||||
self.view
|
||||
.process_event(&mut UIEvent::VisibilityChange(false), context);
|
||||
self.dirty = true;
|
||||
/* If self.rows.row_updates is not empty and we exit a thread, the row_update
|
||||
* events will be performed but the list will not be drawn.
|
||||
|
@ -592,13 +607,15 @@ impl ListingTrait for ConversationsListing {
|
|||
Focus::Entry => {
|
||||
self.force_draw = true;
|
||||
self.dirty = true;
|
||||
self.view.set_dirty(true);
|
||||
}
|
||||
Focus::EntryFullscreen => {
|
||||
self.view.set_dirty(true);
|
||||
}
|
||||
Focus::EntryFullscreen => {}
|
||||
}
|
||||
self.focus = new_value;
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::FocusUpdate { new_value },
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
fn focus(&self) -> Focus {
|
||||
|
@ -615,7 +632,7 @@ impl fmt::Display for ConversationsListing {
|
|||
impl ConversationsListing {
|
||||
//const PADDING_CHAR: char = ' '; //░';
|
||||
|
||||
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
|
||||
pub fn new(parent: ComponentId, coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
|
||||
Box::new(Self {
|
||||
cursor_pos: (coordinates.0, MailboxHash::default(), 0),
|
||||
new_cursor_pos: (coordinates.0, coordinates.1, 0),
|
||||
|
@ -631,11 +648,12 @@ impl ConversationsListing {
|
|||
dirty: true,
|
||||
force_draw: true,
|
||||
focus: Focus::None,
|
||||
view: ThreadView::default(),
|
||||
color_cache: ColorCache::default(),
|
||||
movement: None,
|
||||
modifier_active: false,
|
||||
modifier_command: None,
|
||||
view_area: None,
|
||||
parent,
|
||||
id: ComponentId::default(),
|
||||
})
|
||||
}
|
||||
|
@ -969,12 +987,13 @@ impl ConversationsListing {
|
|||
|
||||
impl Component for ConversationsListing {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.is_dirty() {
|
||||
if matches!(self.focus, Focus::EntryFullscreen) {
|
||||
self.view_area = area.into();
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.focus, Focus::EntryFullscreen) {
|
||||
return self.view.draw(grid, area, context);
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (upper_left, bottom_right) = area;
|
||||
|
@ -1228,7 +1247,7 @@ impl Component for ConversationsListing {
|
|||
);
|
||||
clear_area(grid, gap_area, self.color_cache.theme_default);
|
||||
context.dirty_areas.push_back(gap_area);
|
||||
self.view.draw(grid, entry_area, context);
|
||||
self.view_area = entry_area.into();
|
||||
}
|
||||
self.dirty = false;
|
||||
}
|
||||
|
@ -1258,10 +1277,6 @@ impl Component for ConversationsListing {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
if self.unfocused() && self.view.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.length > 0 {
|
||||
match *event {
|
||||
UIEvent::Input(ref k)
|
||||
|
@ -1269,8 +1284,21 @@ impl Component for ConversationsListing {
|
|||
&& (shortcut!(k == shortcuts[Shortcuts::LISTING]["open_entry"])
|
||||
|| shortcut!(k == shortcuts[Shortcuts::LISTING]["focus_right"])) =>
|
||||
{
|
||||
if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
|
||||
self.view = ThreadView::new(self.cursor_pos, thread, None, context);
|
||||
if let Some((thread_hash, env_hash)) = self
|
||||
.get_thread_under_cursor(self.cursor_pos.2)
|
||||
.and_then(|thread| {
|
||||
self.rows.thread_to_env.get(&thread).map(|e| (thread, e[0]))
|
||||
})
|
||||
{
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::OpenEntryUnderCursor {
|
||||
thread_hash,
|
||||
env_hash,
|
||||
show_thread: true,
|
||||
},
|
||||
context,
|
||||
);
|
||||
self.set_focus(Focus::Entry, context);
|
||||
}
|
||||
return true;
|
||||
|
@ -1336,13 +1364,6 @@ impl Component for ConversationsListing {
|
|||
}
|
||||
|
||||
self.set_dirty(true);
|
||||
|
||||
if self.unfocused() {
|
||||
self.view.process_event(
|
||||
&mut UIEvent::EnvelopeRename(*old_hash, *new_hash),
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
UIEvent::EnvelopeRemove(ref _env_hash, ref thread_hash) => {
|
||||
if self.rows.thread_order.contains_key(thread_hash) {
|
||||
|
@ -1368,11 +1389,6 @@ impl Component for ConversationsListing {
|
|||
}
|
||||
|
||||
self.set_dirty(true);
|
||||
|
||||
if self.unfocused() {
|
||||
self.view
|
||||
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
|
||||
}
|
||||
}
|
||||
UIEvent::Action(ref action) => match action {
|
||||
Action::SubSort(field, order) if !self.unfocused() => {
|
||||
|
@ -1464,6 +1480,7 @@ impl Component for ConversationsListing {
|
|||
) {
|
||||
Ok(job) => {
|
||||
let handle = context.accounts[&self.cursor_pos.0]
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(job);
|
||||
self.search_job = Some((filter_term.to_string(), handle));
|
||||
|
@ -1533,24 +1550,17 @@ impl Component for ConversationsListing {
|
|||
fn is_dirty(&self) -> bool {
|
||||
match self.focus {
|
||||
Focus::None => self.dirty,
|
||||
Focus::Entry => self.dirty || self.view.is_dirty(),
|
||||
Focus::EntryFullscreen => self.view.is_dirty(),
|
||||
Focus::Entry => self.dirty,
|
||||
Focus::EntryFullscreen => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
if self.unfocused() {
|
||||
self.view.set_dirty(value);
|
||||
}
|
||||
self.dirty = value;
|
||||
}
|
||||
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
let mut map = if self.unfocused() {
|
||||
self.view.shortcuts(context)
|
||||
} else {
|
||||
ShortcutMaps::default()
|
||||
};
|
||||
let mut map = ShortcutMaps::default();
|
||||
|
||||
map.insert(
|
||||
Shortcuts::LISTING,
|
||||
|
|
|
@ -82,6 +82,10 @@ impl ListingTrait for OfflineListing {
|
|||
|
||||
fn draw_list(&mut self, _: &mut CellBuffer, _: Area, _: &mut Context) {}
|
||||
|
||||
fn view_area(&self) -> Option<Area> {
|
||||
None
|
||||
}
|
||||
|
||||
fn unfocused(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
|
|
@ -141,13 +141,14 @@ pub struct PlainListing {
|
|||
/// If we must redraw on next redraw event
|
||||
dirty: bool,
|
||||
force_draw: bool,
|
||||
/// If `self.view` exists or not.
|
||||
/// If view is visible or not.
|
||||
focus: Focus,
|
||||
view: MailView,
|
||||
color_cache: ColorCache,
|
||||
movement: Option<PageMovement>,
|
||||
modifier_active: bool,
|
||||
modifier_command: Option<Modifier>,
|
||||
view_area: Option<Area>,
|
||||
parent: ComponentId,
|
||||
id: ComponentId,
|
||||
}
|
||||
|
||||
|
@ -261,11 +262,21 @@ impl MailListingTrait for PlainListing {
|
|||
drop(env_lck);
|
||||
|
||||
if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
|
||||
let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash);
|
||||
if !force && old_cursor_pos == self.new_cursor_pos {
|
||||
self.view.update(temp, context);
|
||||
self.kick_parent(self.parent, ListingMessage::UpdateView, context);
|
||||
} else if self.unfocused() {
|
||||
self.view = MailView::new(temp, None, None, context);
|
||||
let thread_hash = self.rows.env_to_thread[&env_hash];
|
||||
self.force_draw = true;
|
||||
self.dirty = true;
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::OpenEntryUnderCursor {
|
||||
thread_hash,
|
||||
env_hash,
|
||||
show_thread: false,
|
||||
},
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -304,7 +315,6 @@ impl ListingTrait for PlainListing {
|
|||
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.focus = Focus::None;
|
||||
self.view = MailView::default();
|
||||
self.filtered_selection.clear();
|
||||
self.filtered_order.clear();
|
||||
self.filter_term.clear();
|
||||
|
@ -544,6 +554,10 @@ impl ListingTrait for PlainListing {
|
|||
);
|
||||
}
|
||||
|
||||
fn view_area(&self) -> Option<Area> {
|
||||
self.view_area
|
||||
}
|
||||
|
||||
fn unfocused(&self) -> bool {
|
||||
!matches!(self.focus, Focus::None)
|
||||
}
|
||||
|
@ -568,8 +582,7 @@ impl ListingTrait for PlainListing {
|
|||
fn set_focus(&mut self, new_value: Focus, context: &mut Context) {
|
||||
match new_value {
|
||||
Focus::None => {
|
||||
self.view
|
||||
.process_event(&mut UIEvent::VisibilityChange(false), context);
|
||||
//self.view .process_event(&mut UIEvent::VisibilityChange(false), context);
|
||||
self.dirty = true;
|
||||
/* If self.rows.row_updates is not empty and we exit a thread, the row_update
|
||||
* events will be performed but the list will not be drawn.
|
||||
|
@ -578,20 +591,33 @@ impl ListingTrait for PlainListing {
|
|||
self.force_draw = true;
|
||||
}
|
||||
Focus::Entry => {
|
||||
if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
|
||||
let temp = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
|
||||
self.view = MailView::new(temp, None, None, context);
|
||||
if let Some((thread_hash, env_hash)) = self
|
||||
.get_env_under_cursor(self.cursor_pos.2)
|
||||
.map(|env_hash| (self.rows.env_to_thread[&env_hash], env_hash))
|
||||
{
|
||||
self.force_draw = true;
|
||||
self.dirty = true;
|
||||
self.view.set_dirty(true);
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::OpenEntryUnderCursor {
|
||||
thread_hash,
|
||||
env_hash,
|
||||
show_thread: false,
|
||||
},
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
Focus::EntryFullscreen => {
|
||||
self.dirty = true;
|
||||
self.view.set_dirty(true);
|
||||
}
|
||||
}
|
||||
self.focus = new_value;
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::FocusUpdate { new_value },
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
fn focus(&self) -> Focus {
|
||||
|
@ -606,7 +632,7 @@ impl fmt::Display for PlainListing {
|
|||
}
|
||||
|
||||
impl PlainListing {
|
||||
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
|
||||
pub fn new(parent: ComponentId, coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
|
||||
Box::new(PlainListing {
|
||||
cursor_pos: (AccountHash::default(), MailboxHash::default(), 0),
|
||||
new_cursor_pos: (coordinates.0, coordinates.1, 0),
|
||||
|
@ -623,11 +649,12 @@ impl PlainListing {
|
|||
dirty: true,
|
||||
force_draw: true,
|
||||
focus: Focus::None,
|
||||
view: MailView::default(),
|
||||
color_cache: ColorCache::default(),
|
||||
movement: None,
|
||||
modifier_active: false,
|
||||
modifier_command: None,
|
||||
view_area: None,
|
||||
parent,
|
||||
id: ComponentId::default(),
|
||||
})
|
||||
}
|
||||
|
@ -1097,12 +1124,13 @@ impl PlainListing {
|
|||
|
||||
impl Component for PlainListing {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.is_dirty() {
|
||||
if matches!(self.focus, Focus::EntryFullscreen) {
|
||||
self.view_area = area.into();
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.focus, Focus::EntryFullscreen) {
|
||||
return self.view.draw(grid, area, context);
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.focus, Focus::None) {
|
||||
|
@ -1340,7 +1368,7 @@ impl Component for PlainListing {
|
|||
return;
|
||||
}
|
||||
|
||||
self.view.draw(grid, area, context);
|
||||
self.view_area = area.into();
|
||||
}
|
||||
self.dirty = false;
|
||||
}
|
||||
|
@ -1370,10 +1398,6 @@ impl Component for PlainListing {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
if self.unfocused() && self.view.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.length > 0 {
|
||||
match *event {
|
||||
UIEvent::Input(ref k)
|
||||
|
@ -1481,11 +1505,6 @@ impl Component for PlainListing {
|
|||
}
|
||||
|
||||
self.set_dirty(true);
|
||||
|
||||
if self.unfocused() {
|
||||
self.view
|
||||
.process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
|
||||
}
|
||||
}
|
||||
UIEvent::EnvelopeUpdate(ref env_hash) => {
|
||||
let account = &context.accounts[&self.cursor_pos.0];
|
||||
|
@ -1500,11 +1519,6 @@ impl Component for PlainListing {
|
|||
|
||||
self.rows.row_updates.push(*env_hash);
|
||||
self.set_dirty(true);
|
||||
|
||||
if self.unfocused() {
|
||||
self.view
|
||||
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
|
||||
}
|
||||
}
|
||||
UIEvent::ChangeMode(UIMode::Normal) => {
|
||||
self.set_dirty(true);
|
||||
|
@ -1539,6 +1553,7 @@ impl Component for PlainListing {
|
|||
) {
|
||||
Ok(job) => {
|
||||
let handle = context.accounts[&self.cursor_pos.0]
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(job);
|
||||
self.search_job = Some((filter_term.to_string(), handle));
|
||||
|
@ -1583,23 +1598,16 @@ impl Component for PlainListing {
|
|||
fn is_dirty(&self) -> bool {
|
||||
match self.focus {
|
||||
Focus::None => self.dirty,
|
||||
Focus::Entry | Focus::EntryFullscreen => self.view.is_dirty(),
|
||||
Focus::Entry | Focus::EntryFullscreen => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
self.dirty = value;
|
||||
if self.unfocused() {
|
||||
self.view.set_dirty(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
let mut map = if self.unfocused() {
|
||||
self.view.shortcuts(context)
|
||||
} else {
|
||||
ShortcutMaps::default()
|
||||
};
|
||||
let mut map = ShortcutMaps::default();
|
||||
|
||||
map.insert(
|
||||
Shortcuts::LISTING,
|
||||
|
|
|
@ -102,8 +102,8 @@ macro_rules! row_attr {
|
|||
}};
|
||||
}
|
||||
|
||||
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the
|
||||
/// `Envelope` content in a `MailView`.
|
||||
/// A list of all mail ([`Envelope`](melib::Envelope)s) in a `Mailbox`. On `\n` it opens the
|
||||
/// [`Envelope`](melib::Envelope) content in a [`MailView`].
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadListing {
|
||||
/// (x, y, z): x is accounts, y is mailboxes, z is index inside a mailbox.
|
||||
|
@ -126,13 +126,14 @@ pub struct ThreadListing {
|
|||
/// If we must redraw on next redraw event
|
||||
dirty: bool,
|
||||
force_draw: bool,
|
||||
/// If `self.view` is focused or not.
|
||||
/// If `self.view` is visible or not.
|
||||
focus: Focus,
|
||||
initialised: bool,
|
||||
view: Option<Box<MailView>>,
|
||||
initialized: bool,
|
||||
modifier_active: bool,
|
||||
modifier_command: Option<Modifier>,
|
||||
movement: Option<PageMovement>,
|
||||
view_area: Option<Area>,
|
||||
parent: ComponentId,
|
||||
id: ComponentId,
|
||||
}
|
||||
|
||||
|
@ -171,6 +172,7 @@ impl MailListingTrait for ThreadListing {
|
|||
/// mailbox the user has chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context, _force: bool) {
|
||||
self.set_dirty(true);
|
||||
self.initialized = true;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
|
@ -425,13 +427,14 @@ impl ListingTrait for ThreadListing {
|
|||
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.focus = Focus::None;
|
||||
self.view = None;
|
||||
self.rows.clear();
|
||||
self.initialised = false;
|
||||
self.initialized = false;
|
||||
}
|
||||
|
||||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0
|
||||
if !self.initialized
|
||||
|| self.cursor_pos.1 != self.new_cursor_pos.1
|
||||
|| self.cursor_pos.0 != self.new_cursor_pos.0
|
||||
{
|
||||
self.refresh_mailbox(context, false);
|
||||
}
|
||||
|
@ -608,6 +611,10 @@ impl ListingTrait for ThreadListing {
|
|||
let _account = &context.accounts[&self.cursor_pos.0];
|
||||
}
|
||||
|
||||
fn view_area(&self) -> Option<Area> {
|
||||
self.view_area
|
||||
}
|
||||
|
||||
fn unfocused(&self) -> bool {
|
||||
!matches!(self.focus, Focus::None)
|
||||
}
|
||||
|
@ -632,7 +639,6 @@ impl ListingTrait for ThreadListing {
|
|||
fn set_focus(&mut self, new_value: Focus, context: &mut Context) {
|
||||
match new_value {
|
||||
Focus::None => {
|
||||
self.view = None;
|
||||
self.dirty = true;
|
||||
/* If self.rows.row_updates is not empty and we exit a thread, the row_update
|
||||
* events will be performed but the list will not be drawn.
|
||||
|
@ -641,29 +647,34 @@ impl ListingTrait for ThreadListing {
|
|||
self.force_draw = true;
|
||||
}
|
||||
Focus::Entry => {
|
||||
if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
|
||||
if let Some((thread_hash, env_hash)) = self
|
||||
.get_env_under_cursor(self.cursor_pos.2)
|
||||
.map(|env_hash| (self.rows.env_to_thread[&env_hash], env_hash))
|
||||
{
|
||||
self.force_draw = true;
|
||||
self.dirty = true;
|
||||
let coordinates = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
|
||||
|
||||
if let Some(ref mut v) = self.view {
|
||||
v.update(coordinates, context);
|
||||
} else {
|
||||
self.view = Some(Box::new(MailView::new(coordinates, None, None, context)));
|
||||
}
|
||||
|
||||
if let Some(ref mut s) = self.view {
|
||||
s.set_dirty(true);
|
||||
}
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::OpenEntryUnderCursor {
|
||||
thread_hash,
|
||||
env_hash,
|
||||
show_thread: false,
|
||||
},
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
Focus::EntryFullscreen => {
|
||||
if let Some(ref mut s) = self.view {
|
||||
s.set_dirty(true);
|
||||
}
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
self.focus = new_value;
|
||||
self.kick_parent(
|
||||
self.parent,
|
||||
ListingMessage::FocusUpdate { new_value },
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
fn focus(&self) -> Focus {
|
||||
|
@ -678,26 +689,31 @@ impl fmt::Display for ThreadListing {
|
|||
}
|
||||
|
||||
impl ThreadListing {
|
||||
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
|
||||
pub fn new(
|
||||
parent: ComponentId,
|
||||
coordinates: (AccountHash, MailboxHash),
|
||||
context: &mut Context,
|
||||
) -> Box<Self> {
|
||||
Box::new(ThreadListing {
|
||||
cursor_pos: (coordinates.0, MailboxHash::default(), 0),
|
||||
new_cursor_pos: (coordinates.0, coordinates.1, 0),
|
||||
length: 0,
|
||||
sort: (Default::default(), Default::default()),
|
||||
subsort: (Default::default(), Default::default()),
|
||||
color_cache: ColorCache::default(),
|
||||
color_cache: ColorCache::new(context, IndexStyle::Threaded),
|
||||
data_columns: DataColumns::default(),
|
||||
rows: RowsState::default(),
|
||||
search_job: None,
|
||||
dirty: true,
|
||||
force_draw: true,
|
||||
focus: Focus::None,
|
||||
view: None,
|
||||
initialised: false,
|
||||
initialized: false,
|
||||
movement: None,
|
||||
modifier_active: false,
|
||||
modifier_command: None,
|
||||
view_area: None,
|
||||
parent,
|
||||
id: ComponentId::default(),
|
||||
search_job: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1026,6 +1042,11 @@ impl ThreadListing {
|
|||
|
||||
impl Component for ThreadListing {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if matches!(self.focus, Focus::EntryFullscreen) {
|
||||
self.view_area = area.into();
|
||||
return;
|
||||
}
|
||||
|
||||
let (upper_left, bottom_right) = area;
|
||||
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
|
||||
|
||||
|
@ -1210,12 +1231,6 @@ impl Component for ThreadListing {
|
|||
return;
|
||||
}
|
||||
|
||||
if matches!(self.focus, Focus::EntryFullscreen) {
|
||||
if let Some(v) = self.view.as_mut() {
|
||||
return v.draw(grid, area, context);
|
||||
}
|
||||
}
|
||||
|
||||
if !self.unfocused() {
|
||||
self.dirty = false;
|
||||
/* Draw the entire list */
|
||||
|
@ -1290,27 +1305,7 @@ impl Component for ThreadListing {
|
|||
.push_back((set_y(upper_left, mid), set_y(bottom_right, mid)));
|
||||
}
|
||||
|
||||
if !self.dirty {
|
||||
if let Some(v) = self.view.as_mut() {
|
||||
v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
|
||||
let coordinates = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
|
||||
|
||||
if let Some(ref mut v) = self.view {
|
||||
v.update(coordinates, context);
|
||||
} else {
|
||||
self.view = Some(Box::new(MailView::new(coordinates, None, None, context)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(v) = self.view.as_mut() {
|
||||
v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
|
||||
}
|
||||
|
||||
self.view_area = (set_y(upper_left, mid + 1), bottom_right).into();
|
||||
self.dirty = false;
|
||||
}
|
||||
}
|
||||
|
@ -1340,12 +1335,6 @@ impl Component for ThreadListing {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(ref mut v) = self.view {
|
||||
if !matches!(self.focus, Focus::None) && v.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
match *event {
|
||||
UIEvent::ConfigReload { old_settings: _ } => {
|
||||
self.color_cache = ColorCache::new(context, IndexStyle::Threaded);
|
||||
|
@ -1411,15 +1400,6 @@ impl Component for ThreadListing {
|
|||
}
|
||||
|
||||
self.set_dirty(true);
|
||||
|
||||
if self.unfocused() {
|
||||
if let Some(v) = self.view.as_mut() {
|
||||
v.process_event(
|
||||
&mut UIEvent::EnvelopeRename(*old_hash, *new_hash),
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
UIEvent::EnvelopeRemove(ref env_hash, _) => {
|
||||
if self.rows.contains_env(*env_hash) {
|
||||
|
@ -1437,12 +1417,6 @@ impl Component for ThreadListing {
|
|||
}
|
||||
|
||||
self.set_dirty(true);
|
||||
|
||||
if self.unfocused() {
|
||||
if let Some(v) = self.view.as_mut() {
|
||||
v.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
UIEvent::ChangeMode(UIMode::Normal) => {
|
||||
self.set_dirty(true);
|
||||
|
@ -1500,6 +1474,7 @@ impl Component for ThreadListing {
|
|||
) {
|
||||
Ok(job) => {
|
||||
let handle = context.accounts[&self.cursor_pos.0]
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(job);
|
||||
self.search_job = Some((filter_term.to_string(), handle));
|
||||
|
@ -1547,27 +1522,17 @@ impl Component for ThreadListing {
|
|||
fn is_dirty(&self) -> bool {
|
||||
match self.focus {
|
||||
Focus::None => self.dirty,
|
||||
Focus::Entry => self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false),
|
||||
Focus::EntryFullscreen => self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false),
|
||||
Focus::Entry => self.dirty,
|
||||
Focus::EntryFullscreen => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
if let Some(p) = self.view.as_mut() {
|
||||
p.set_dirty(value);
|
||||
};
|
||||
self.dirty = value;
|
||||
}
|
||||
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
let mut map = if self.unfocused() {
|
||||
self.view
|
||||
.as_ref()
|
||||
.map(|p| p.shortcuts(context))
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
ShortcutMaps::default()
|
||||
};
|
||||
let mut map = ShortcutMaps::default();
|
||||
|
||||
map.insert(
|
||||
Shortcuts::LISTING,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -24,6 +24,8 @@ use std::{
|
|||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use melib::xdg_utils::query_default_app;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use melib::{Envelope, Error, Mail, Result};
|
||||
|
||||
use super::{EnvelopeView, MailView, ViewSettings};
|
||||
use crate::{jobs::JoinHandle, mailbox_settings, Component, Context, ShortcutMaps, UIEvent};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum PendingReplyAction {
|
||||
Reply,
|
||||
ReplyToAuthor,
|
||||
ReplyToAll,
|
||||
ForwardAttachment,
|
||||
ForwardInline,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MailViewState {
|
||||
Init {
|
||||
pending_action: Option<PendingReplyAction>,
|
||||
},
|
||||
LoadingBody {
|
||||
handle: JoinHandle<Result<Vec<u8>>>,
|
||||
pending_action: Option<PendingReplyAction>,
|
||||
},
|
||||
Error {
|
||||
err: Error,
|
||||
},
|
||||
Loaded {
|
||||
bytes: Vec<u8>,
|
||||
env: Box<Envelope>,
|
||||
env_view: Box<EnvelopeView>,
|
||||
stack: Vec<Box<dyn Component>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl MailViewState {
|
||||
pub fn load_bytes(self_: &mut MailView, bytes: Vec<u8>, context: &mut Context) {
|
||||
let Some(coordinates) = self_.coordinates else { return; };
|
||||
let account = &mut context.accounts[&coordinates.0];
|
||||
if account
|
||||
.collection
|
||||
.get_env(coordinates.2)
|
||||
.other_headers()
|
||||
.is_empty()
|
||||
{
|
||||
let _ = account
|
||||
.collection
|
||||
.get_env_mut(coordinates.2)
|
||||
.populate_headers(&bytes);
|
||||
}
|
||||
let env = Box::new(account.collection.get_env(coordinates.2).clone());
|
||||
let env_view = Box::new(EnvelopeView::new(
|
||||
Mail {
|
||||
envelope: *env.clone(),
|
||||
bytes: bytes.clone(),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
Some(ViewSettings {
|
||||
theme_default: crate::conf::value(context, "theme_default"),
|
||||
body_theme: crate::conf::value(context, "mail.view.body"),
|
||||
env_view_shortcuts: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.shortcuts
|
||||
.envelope_view
|
||||
)
|
||||
.key_values(),
|
||||
pager_filter: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pager.filter
|
||||
)
|
||||
.clone(),
|
||||
html_filter: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pager.html_filter
|
||||
)
|
||||
.clone(),
|
||||
url_launcher: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pager.url_launcher
|
||||
)
|
||||
.clone(),
|
||||
auto_choose_multipart_alternative: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.pager
|
||||
.auto_choose_multipart_alternative
|
||||
)
|
||||
.is_true(),
|
||||
expand_headers: false,
|
||||
sticky_headers: *mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pager.sticky_headers
|
||||
),
|
||||
show_date_in_my_timezone: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.pager
|
||||
.show_date_in_my_timezone
|
||||
)
|
||||
.is_true(),
|
||||
show_extra_headers: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.pager
|
||||
.show_extra_headers
|
||||
)
|
||||
.clone(),
|
||||
auto_verify_signatures: *mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.pgp
|
||||
.auto_verify_signatures
|
||||
),
|
||||
auto_decrypt: *mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pgp.auto_decrypt
|
||||
),
|
||||
}),
|
||||
context.main_loop_handler.clone(),
|
||||
));
|
||||
self_.state = MailViewState::Loaded {
|
||||
env,
|
||||
bytes,
|
||||
env_view,
|
||||
stack: vec![],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
matches!(self, Self::Loaded { ref env_view, .. } if env_view.is_dirty())
|
||||
}
|
||||
|
||||
pub fn set_dirty(&mut self, dirty: bool) {
|
||||
if let Self::Loaded {
|
||||
ref mut env_view, ..
|
||||
} = self
|
||||
{
|
||||
env_view.set_dirty(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
if let Self::Loaded { ref env_view, .. } = self {
|
||||
env_view.shortcuts(context)
|
||||
} else {
|
||||
ShortcutMaps::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
if let Self::Loaded {
|
||||
ref mut env_view, ..
|
||||
} = self
|
||||
{
|
||||
env_view.process_event(event, context)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MailViewState {
|
||||
fn default() -> Self {
|
||||
MailViewState::Init {
|
||||
pending_action: None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,6 +37,15 @@ struct ThreadEntry {
|
|||
hidden: bool,
|
||||
heading: String,
|
||||
timestamp: UnixTimestamp,
|
||||
mailview: Box<MailView>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub enum ThreadViewFocus {
|
||||
#[default]
|
||||
None,
|
||||
Thread,
|
||||
MailView,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
@ -46,11 +55,9 @@ pub struct ThreadView {
|
|||
expanded_pos: usize,
|
||||
new_expanded_pos: usize,
|
||||
reversed: bool,
|
||||
coordinates: (AccountHash, MailboxHash, usize),
|
||||
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
||||
thread_group: ThreadHash,
|
||||
mailview: MailView,
|
||||
show_mailview: bool,
|
||||
show_thread: bool,
|
||||
focus: ThreadViewFocus,
|
||||
entries: Vec<ThreadEntry>,
|
||||
visible_entries: Vec<Vec<usize>>,
|
||||
indentation_colors: [ThemeAttribute; 6],
|
||||
|
@ -64,24 +71,24 @@ pub struct ThreadView {
|
|||
|
||||
impl ThreadView {
|
||||
/*
|
||||
* coordinates: (account index, mailbox_hash, root set thread_node index)
|
||||
* expanded_hash: optional position of expanded entry when we render the
|
||||
* threadview. Default expanded message is the last one.
|
||||
* context: current context
|
||||
* @coordinates: (account index, mailbox_hash, root set thread_node index)
|
||||
* @expanded_hash: optional position of expanded entry when we render the
|
||||
* ThreadView.
|
||||
* default: expanded message is the last one.
|
||||
* @context: current context
|
||||
*/
|
||||
pub fn new(
|
||||
coordinates: (AccountHash, MailboxHash, usize),
|
||||
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
||||
thread_group: ThreadHash,
|
||||
expanded_hash: Option<ThreadNodeHash>,
|
||||
context: &Context,
|
||||
expanded_hash: Option<EnvelopeHash>,
|
||||
focus: Option<ThreadViewFocus>,
|
||||
context: &mut Context,
|
||||
) -> Self {
|
||||
let mut view = ThreadView {
|
||||
reversed: false,
|
||||
coordinates,
|
||||
thread_group,
|
||||
mailview: MailView::default(),
|
||||
show_mailview: true,
|
||||
show_thread: true,
|
||||
focus: focus.unwrap_or_default(),
|
||||
entries: Vec::new(),
|
||||
cursor_pos: 1,
|
||||
new_cursor_pos: 0,
|
||||
|
@ -103,7 +110,7 @@ impl ThreadView {
|
|||
view
|
||||
}
|
||||
|
||||
pub fn update(&mut self, context: &Context) {
|
||||
pub fn update(&mut self, context: &mut Context) {
|
||||
if self.entries.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
@ -122,7 +129,7 @@ impl ThreadView {
|
|||
None
|
||||
};
|
||||
|
||||
let expanded_hash = old_expanded_entry.as_ref().map(|e| e.index.1);
|
||||
let expanded_hash = old_expanded_entry.as_ref().map(|e| e.msg_hash);
|
||||
self.initiate(expanded_hash, context);
|
||||
|
||||
let mut old_cursor = 0;
|
||||
|
@ -165,18 +172,25 @@ impl ThreadView {
|
|||
self.set_dirty(true);
|
||||
}
|
||||
|
||||
fn initiate(&mut self, expanded_hash: Option<ThreadNodeHash>, context: &Context) {
|
||||
fn initiate(&mut self, expanded_hash: Option<EnvelopeHash>, context: &mut Context) {
|
||||
#[inline(always)]
|
||||
fn make_entry(
|
||||
i: (usize, ThreadNodeHash, usize),
|
||||
account_hash: AccountHash,
|
||||
mailbox_hash: MailboxHash,
|
||||
msg_hash: EnvelopeHash,
|
||||
seen: bool,
|
||||
timestamp: UnixTimestamp,
|
||||
context: &mut Context,
|
||||
) -> ThreadEntry {
|
||||
let (ind, _, _) = i;
|
||||
ThreadEntry {
|
||||
index: i,
|
||||
indentation: ind,
|
||||
mailview: Box::new(MailView::new(
|
||||
Some((account_hash, mailbox_hash, msg_hash)),
|
||||
context,
|
||||
)),
|
||||
msg_hash,
|
||||
seen,
|
||||
dirty: true,
|
||||
|
@ -186,36 +200,43 @@ impl ThreadView {
|
|||
}
|
||||
}
|
||||
|
||||
let account = &context.accounts[&self.coordinates.0];
|
||||
let threads = account.collection.get_threads(self.coordinates.1);
|
||||
let collection = context.accounts[&self.coordinates.0].collection.clone();
|
||||
let threads = collection.get_threads(self.coordinates.1);
|
||||
|
||||
if !threads.groups.contains_key(&self.thread_group) {
|
||||
return;
|
||||
}
|
||||
let (account_hash, mailbox_hash, _) = self.coordinates;
|
||||
|
||||
let thread_iter = threads.thread_group_iter(self.thread_group);
|
||||
self.entries.clear();
|
||||
for (line, (ind, thread_node_hash)) in thread_iter.enumerate() {
|
||||
let entry = if let Some(msg_hash) = threads.thread_nodes()[&thread_node_hash].message()
|
||||
{
|
||||
let env_ref = account.collection.get_env(msg_hash);
|
||||
let (is_seen, timestamp) = {
|
||||
let env_ref = collection.get_env(msg_hash);
|
||||
(env_ref.is_seen(), env_ref.timestamp)
|
||||
};
|
||||
make_entry(
|
||||
(ind, thread_node_hash, line),
|
||||
account_hash,
|
||||
mailbox_hash,
|
||||
msg_hash,
|
||||
env_ref.is_seen(),
|
||||
env_ref.timestamp,
|
||||
is_seen,
|
||||
timestamp,
|
||||
context,
|
||||
)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
self.entries.push(entry);
|
||||
match expanded_hash {
|
||||
Some(expanded_hash) if expanded_hash == thread_node_hash => {
|
||||
Some(expanded_hash) if expanded_hash == entry.msg_hash => {
|
||||
self.new_expanded_pos = self.entries.len().saturating_sub(1);
|
||||
self.expanded_pos = self.new_expanded_pos + 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.entries.push(entry);
|
||||
}
|
||||
if expanded_hash.is_none() {
|
||||
self.new_expanded_pos = self
|
||||
|
@ -712,18 +733,21 @@ impl ThreadView {
|
|||
.set_bg(theme_default.bg);
|
||||
}
|
||||
|
||||
match (self.show_mailview, self.show_thread) {
|
||||
(true, true) => {
|
||||
match self.focus {
|
||||
ThreadViewFocus::None => {
|
||||
self.draw_list(
|
||||
grid,
|
||||
(set_y(upper_left, y), set_x(bottom_right, mid - 1)),
|
||||
context,
|
||||
);
|
||||
let upper_left = (mid + 1, get_y(upper_left) + y - 1);
|
||||
self.mailview
|
||||
.draw(grid, (upper_left, bottom_right), context);
|
||||
self.entries[self.new_expanded_pos].mailview.draw(
|
||||
grid,
|
||||
(upper_left, bottom_right),
|
||||
context,
|
||||
);
|
||||
}
|
||||
(false, true) => {
|
||||
ThreadViewFocus::Thread => {
|
||||
clear_area(
|
||||
grid,
|
||||
((mid + 1, get_y(upper_left) + y - 1), bottom_right),
|
||||
|
@ -731,8 +755,10 @@ impl ThreadView {
|
|||
);
|
||||
self.draw_list(grid, (set_y(upper_left, y), bottom_right), context);
|
||||
}
|
||||
(_, false) => {
|
||||
self.mailview.draw(grid, area, context);
|
||||
ThreadViewFocus::MailView => {
|
||||
self.entries[self.new_expanded_pos]
|
||||
.mailview
|
||||
.draw(grid, area, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -820,8 +846,8 @@ impl ThreadView {
|
|||
);
|
||||
let (width, height) = self.content.size();
|
||||
|
||||
match (self.show_mailview, self.show_thread) {
|
||||
(true, true) => {
|
||||
match self.focus {
|
||||
ThreadViewFocus::None => {
|
||||
let area = (set_y(upper_left, y), set_y(bottom_right, mid));
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
@ -841,7 +867,7 @@ impl ThreadView {
|
|||
);
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
(false, true) => {
|
||||
ThreadViewFocus::Thread => {
|
||||
let area = (set_y(upper_left, y), bottom_right);
|
||||
let upper_left = upper_left!(area);
|
||||
|
||||
|
@ -859,11 +885,11 @@ impl ThreadView {
|
|||
);
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
(_, false) => { /* show only envelope */ }
|
||||
ThreadViewFocus::MailView => { /* show only envelope */ }
|
||||
}
|
||||
|
||||
match (self.show_mailview, self.show_thread) {
|
||||
(true, true) => {
|
||||
match self.focus {
|
||||
ThreadViewFocus::None => {
|
||||
let area = (set_y(upper_left, mid), set_y(bottom_right, mid));
|
||||
context.dirty_areas.push_back(area);
|
||||
for x in get_x(upper_left)..=get_x(bottom_right) {
|
||||
|
@ -874,15 +900,20 @@ impl ThreadView {
|
|||
}
|
||||
let area = (set_y(upper_left, y), set_y(bottom_right, mid - 1));
|
||||
self.draw_list(grid, area, context);
|
||||
self.mailview
|
||||
.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
|
||||
self.entries[self.new_expanded_pos].mailview.draw(
|
||||
grid,
|
||||
(set_y(upper_left, mid + 1), bottom_right),
|
||||
context,
|
||||
);
|
||||
}
|
||||
(false, true) => {
|
||||
ThreadViewFocus::Thread => {
|
||||
self.dirty = true;
|
||||
self.draw_list(grid, (set_y(upper_left, y), bottom_right), context);
|
||||
}
|
||||
(_, false) => {
|
||||
self.mailview.draw(grid, area, context);
|
||||
ThreadViewFocus::MailView => {
|
||||
self.entries[self.new_expanded_pos]
|
||||
.mailview
|
||||
.draw(grid, area, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -971,16 +1002,12 @@ impl Component for ThreadView {
|
|||
/* If user has selected another mail to view, change to it */
|
||||
if self.new_expanded_pos != self.expanded_pos {
|
||||
self.expanded_pos = self.new_expanded_pos;
|
||||
let coordinates = (
|
||||
self.coordinates.0,
|
||||
self.coordinates.1,
|
||||
self.entries[self.current_pos()].msg_hash,
|
||||
);
|
||||
self.mailview.update(coordinates, context);
|
||||
}
|
||||
|
||||
if self.entries.len() == 1 {
|
||||
self.mailview.draw(grid, area, context);
|
||||
self.entries[self.new_expanded_pos]
|
||||
.mailview
|
||||
.draw(grid, area, context);
|
||||
} else if total_cols >= self.content.size().0 + 74 {
|
||||
self.draw_vert(grid, area, context);
|
||||
} else {
|
||||
|
@ -998,7 +1025,13 @@ impl Component for ThreadView {
|
|||
return true;
|
||||
}
|
||||
|
||||
if self.show_mailview && self.mailview.process_event(event, context) {
|
||||
if matches!(
|
||||
self.focus,
|
||||
ThreadViewFocus::None | ThreadViewFocus::MailView
|
||||
) && self.entries[self.new_expanded_pos]
|
||||
.mailview
|
||||
.process_event(event, context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1035,34 +1068,45 @@ impl Component for ThreadView {
|
|||
self.movement = Some(PageMovement::PageDown(1));
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEvent::Input(ref key) if *key == Key::Home => {
|
||||
UIEvent::Input(ref k) if shortcut!(k == shortcuts[Shortcuts::GENERAL]["home_page"]) => {
|
||||
self.movement = Some(PageMovement::Home);
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEvent::Input(ref key) if *key == Key::End => {
|
||||
UIEvent::Input(ref k) if shortcut!(k == shortcuts[Shortcuts::GENERAL]["end_page"]) => {
|
||||
self.movement = Some(PageMovement::End);
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEvent::Input(Key::Char('\n')) => {
|
||||
UIEvent::Input(ref k)
|
||||
if shortcut!(k == shortcuts[Shortcuts::GENERAL]["open_entry"]) =>
|
||||
{
|
||||
if self.entries.len() < 2 {
|
||||
return true;
|
||||
}
|
||||
self.new_expanded_pos = self.current_pos();
|
||||
self.show_mailview = true;
|
||||
self.expanded_pos = self.current_pos();
|
||||
if matches!(self.focus, ThreadViewFocus::Thread) {
|
||||
self.focus = ThreadViewFocus::None;
|
||||
}
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::THREAD_VIEW]["toggle_mailview"]) =>
|
||||
{
|
||||
self.show_mailview = !self.show_mailview;
|
||||
self.focus = match self.focus {
|
||||
ThreadViewFocus::None | ThreadViewFocus::MailView => ThreadViewFocus::Thread,
|
||||
ThreadViewFocus::Thread => ThreadViewFocus::None,
|
||||
};
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::THREAD_VIEW]["toggle_threadview"]) =>
|
||||
{
|
||||
self.show_thread = !self.show_thread;
|
||||
self.focus = match self.focus {
|
||||
ThreadViewFocus::None | ThreadViewFocus::Thread => ThreadViewFocus::MailView,
|
||||
ThreadViewFocus::MailView => ThreadViewFocus::None,
|
||||
};
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
|
@ -1070,7 +1114,7 @@ impl Component for ThreadView {
|
|||
if shortcut!(key == shortcuts[Shortcuts::THREAD_VIEW]["reverse_thread_order"]) =>
|
||||
{
|
||||
self.reversed = !self.reversed;
|
||||
let expanded_hash = self.entries[self.expanded_pos].index.1;
|
||||
let expanded_hash = self.entries[self.expanded_pos].msg_hash;
|
||||
self.initiate(Some(expanded_hash), context);
|
||||
self.dirty = true;
|
||||
return true;
|
||||
|
@ -1107,7 +1151,7 @@ impl Component for ThreadView {
|
|||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Resize => {
|
||||
UIEvent::Resize | UIEvent::VisibilityChange(true) => {
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
|
||||
|
@ -1116,31 +1160,35 @@ impl Component for ThreadView {
|
|||
if e.msg_hash == *old_hash {
|
||||
e.msg_hash = *new_hash;
|
||||
let seen: bool = account.collection.get_env(*new_hash).is_seen();
|
||||
if seen != e.seen {
|
||||
self.dirty = true;
|
||||
}
|
||||
e.seen = seen;
|
||||
e.mailview.process_event(
|
||||
&mut UIEvent::EnvelopeRename(*old_hash, *new_hash),
|
||||
context,
|
||||
);
|
||||
self.set_dirty(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.mailview
|
||||
.process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
|
||||
}
|
||||
UIEvent::EnvelopeUpdate(ref env_hash) => {
|
||||
let account = &context.accounts[&self.coordinates.0];
|
||||
for e in self.entries.iter_mut() {
|
||||
if e.msg_hash == *env_hash {
|
||||
let seen: bool = account.collection.get_env(*env_hash).is_seen();
|
||||
if seen != e.seen {
|
||||
self.dirty = true;
|
||||
}
|
||||
e.seen = seen;
|
||||
e.mailview
|
||||
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
|
||||
self.set_dirty(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.mailview
|
||||
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
|
||||
}
|
||||
_ => {
|
||||
if self.mailview.process_event(event, context) {
|
||||
if self
|
||||
.entries
|
||||
.iter_mut()
|
||||
.any(|entry| entry.mailview.process_event(event, context))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1149,20 +1197,41 @@ impl Component for ThreadView {
|
|||
}
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty || (self.show_mailview && self.mailview.is_dirty())
|
||||
self.dirty
|
||||
|| (!matches!(self.focus, ThreadViewFocus::Thread)
|
||||
&& !self.entries.is_empty()
|
||||
&& self.entries[self.new_expanded_pos].mailview.is_dirty())
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
self.dirty = value;
|
||||
self.mailview.set_dirty(value);
|
||||
self.entries[self.new_expanded_pos]
|
||||
.mailview
|
||||
.set_dirty(value);
|
||||
}
|
||||
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
let mut map = self.mailview.shortcuts(context);
|
||||
let mut map = self.entries[self.new_expanded_pos]
|
||||
.mailview
|
||||
.shortcuts(context);
|
||||
|
||||
map.insert(
|
||||
Shortcuts::GENERAL,
|
||||
mailbox_settings!(
|
||||
context[self.coordinates.0][&self.coordinates.1]
|
||||
.shortcuts
|
||||
.general
|
||||
)
|
||||
.key_values(),
|
||||
);
|
||||
map.insert(
|
||||
Shortcuts::THREAD_VIEW,
|
||||
context.settings.shortcuts.thread_view.key_values(),
|
||||
mailbox_settings!(
|
||||
context[self.coordinates.0][&self.coordinates.1]
|
||||
.shortcuts
|
||||
.thread_view
|
||||
)
|
||||
.key_values(),
|
||||
);
|
||||
|
||||
map
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use melib::{attachment_types::Charset, pgp::DecryptionMetadata, Attachment, Error, Result};
|
||||
|
||||
use crate::{
|
||||
conf::shortcuts::EnvelopeViewShortcuts,
|
||||
jobs::{JobId, JoinHandle},
|
||||
ShortcutMap, ThemeAttribute, UIDialog,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ViewSettings {
|
||||
pub pager_filter: Option<String>,
|
||||
pub html_filter: Option<String>,
|
||||
pub url_launcher: Option<String>,
|
||||
pub expand_headers: bool,
|
||||
pub theme_default: ThemeAttribute,
|
||||
pub env_view_shortcuts: ShortcutMap,
|
||||
/// `"mail.view.body"`
|
||||
pub body_theme: ThemeAttribute,
|
||||
pub auto_choose_multipart_alternative: bool,
|
||||
pub sticky_headers: bool,
|
||||
pub show_date_in_my_timezone: bool,
|
||||
pub show_extra_headers: Vec<String>,
|
||||
pub auto_verify_signatures: bool,
|
||||
pub auto_decrypt: bool,
|
||||
}
|
||||
|
||||
impl Default for ViewSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theme_default: Default::default(),
|
||||
body_theme: Default::default(),
|
||||
pager_filter: None,
|
||||
html_filter: None,
|
||||
url_launcher: None,
|
||||
env_view_shortcuts: EnvelopeViewShortcuts::default().key_values(),
|
||||
auto_choose_multipart_alternative: true,
|
||||
expand_headers: false,
|
||||
sticky_headers: false,
|
||||
show_date_in_my_timezone: false,
|
||||
show_extra_headers: vec![],
|
||||
auto_verify_signatures: true,
|
||||
auto_decrypt: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum LinkKind {
|
||||
Url,
|
||||
Email,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct Link {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub kind: self::LinkKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub 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)]
|
||||
pub enum Source {
|
||||
Decoded,
|
||||
Raw,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Default)]
|
||||
pub enum ViewMode {
|
||||
#[default]
|
||||
Normal,
|
||||
Url,
|
||||
Attachment(usize),
|
||||
Source(Source),
|
||||
Subview,
|
||||
}
|
||||
|
||||
macro_rules! is_variant {
|
||||
($n:ident, $($var:tt)+) => {
|
||||
#[inline]
|
||||
pub fn $n(&self) -> bool {
|
||||
matches!(self, Self::$($var)*)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ViewMode {
|
||||
is_variant! { is_normal, Normal }
|
||||
is_variant! { is_url, Url }
|
||||
is_variant! { is_attachment, Attachment(_) }
|
||||
is_variant! { is_source, Source(_) }
|
||||
is_variant! { is_subview, Subview }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AttachmentDisplay {
|
||||
Alternative {
|
||||
inner: Box<Attachment>,
|
||||
shown_display: usize,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
},
|
||||
InlineText {
|
||||
inner: Box<Attachment>,
|
||||
comment: Option<String>,
|
||||
text: String,
|
||||
},
|
||||
InlineOther {
|
||||
inner: Box<Attachment>,
|
||||
},
|
||||
Attachment {
|
||||
inner: Box<Attachment>,
|
||||
},
|
||||
SignedPending {
|
||||
inner: Box<Attachment>,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
job_id: JobId,
|
||||
},
|
||||
SignedFailed {
|
||||
inner: Box<Attachment>,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
error: Error,
|
||||
},
|
||||
SignedUnverified {
|
||||
inner: Box<Attachment>,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
},
|
||||
SignedVerified {
|
||||
inner: Box<Attachment>,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
description: String,
|
||||
},
|
||||
EncryptedPending {
|
||||
inner: Box<Attachment>,
|
||||
handle: JoinHandle<Result<(DecryptionMetadata, Vec<u8>)>>,
|
||||
},
|
||||
EncryptedFailed {
|
||||
inner: Box<Attachment>,
|
||||
error: Error,
|
||||
},
|
||||
EncryptedSuccess {
|
||||
inner: Box<Attachment>,
|
||||
plaintext: Box<Attachment>,
|
||||
plaintext_display: Vec<AttachmentDisplay>,
|
||||
description: String,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{fs::File, io::Write, os::unix::fs::PermissionsExt, path::Path};
|
||||
|
||||
use melib::Result;
|
||||
|
||||
pub fn save_attachment(path: &Path, bytes: &[u8]) -> Result<()> {
|
||||
let mut f = File::create(path)?;
|
||||
let mut permissions = f.metadata()?.permissions();
|
||||
permissions.set_mode(0o600); // Read/write for owner only.
|
||||
f.set_permissions(permissions)?;
|
||||
f.write_all(bytes)?;
|
||||
f.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn desktop_exec_to_command(command: &str, path: String, is_url: bool) -> String {
|
||||
/* Purge unused field codes */
|
||||
let command = command
|
||||
.replace("%i", "")
|
||||
.replace("%c", "")
|
||||
.replace("%k", "");
|
||||
if command.contains("%f") {
|
||||
command.replacen("%f", &path.replace(' ', "\\ "), 1)
|
||||
} else if command.contains("%F") {
|
||||
command.replacen("%F", &path.replace(' ', "\\ "), 1)
|
||||
} else if command.contains("%u") || command.contains("%U") {
|
||||
let from_pattern = if command.contains("%u") { "%u" } else { "%U" };
|
||||
if is_url {
|
||||
command.replacen(from_pattern, &path, 1)
|
||||
} else {
|
||||
command.replacen(
|
||||
from_pattern,
|
||||
&format!("file://{}", path).replace(' ', "\\ "),
|
||||
1,
|
||||
)
|
||||
}
|
||||
} else if is_url {
|
||||
format!("{} {}", command, path)
|
||||
} else {
|
||||
format!("{} {}", command, path.replace(' ', "\\ "))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_desktop_exec() {
|
||||
assert_eq!(
|
||||
"ristretto /tmp/file".to_string(),
|
||||
desktop_exec_to_command("ristretto %F", "/tmp/file".to_string(), false)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/lib/firefox-esr/firefox-esr file:///tmp/file".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/lib/firefox-esr/firefox-esr %u",
|
||||
"/tmp/file".to_string(),
|
||||
false
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/lib/firefox-esr/firefox-esr www.example.com".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/lib/firefox-esr/firefox-esr %u",
|
||||
"www.example.com".to_string(),
|
||||
true
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/bin/vlc --started-from-file www.example.com".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/bin/vlc --started-from-file %U",
|
||||
"www.example.com".to_string(),
|
||||
true
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"zathura --fork file:///tmp/file".to_string(),
|
||||
desktop_exec_to_command("zathura --fork %U", "file:///tmp/file".to_string(), true)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -50,7 +50,11 @@ mod dbus {
|
|||
impl DbusNotifications {
|
||||
pub fn new(context: &Context) -> Self {
|
||||
DbusNotifications {
|
||||
rate_limit: RateLimit::new(1000, 1000, context.job_executor.clone()),
|
||||
rate_limit: RateLimit::new(
|
||||
1000,
|
||||
1000,
|
||||
context.main_loop_handler.job_executor.clone(),
|
||||
),
|
||||
id: ComponentId::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -795,6 +795,57 @@ impl Component for StatusBar {
|
|||
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
||||
self.container.can_quit_cleanly(context)
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &'static ComponentAttr {
|
||||
&ComponentAttr::CONTAINER
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
let mut ret = IndexMap::default();
|
||||
ret.insert(self.container.id(), &self.container as _);
|
||||
ret.insert(self.ex_buffer.id(), &self.ex_buffer as _);
|
||||
ret.insert(self.progress_spinner.id(), &self.progress_spinner as _);
|
||||
ret
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
IndexMap::default()
|
||||
}
|
||||
|
||||
fn realize(&self, parent: Option<ComponentId>, context: &mut Context) {
|
||||
context.realized.insert(self.id(), parent);
|
||||
log::debug!(
|
||||
"Realizing statusbar id {} w/ parent {:?}",
|
||||
self.id(),
|
||||
&parent
|
||||
);
|
||||
log::debug!(
|
||||
"Realizing statusbar container id {} w/ parent {:?}",
|
||||
self.container.id(),
|
||||
&self.id
|
||||
);
|
||||
self.container.realize(self.id().into(), context);
|
||||
log::debug!(
|
||||
"Realizing progress_spinner container id {} w/ parent {:?}",
|
||||
self.progress_spinner.id(),
|
||||
&self.id
|
||||
);
|
||||
log::debug!(
|
||||
"Realizing ex_buffer container id {} w/ parent {:?}",
|
||||
self.ex_buffer.id(),
|
||||
&self.id
|
||||
);
|
||||
self.progress_spinner.realize(self.id().into(), context);
|
||||
self.ex_buffer.realize(self.id().into(), context);
|
||||
}
|
||||
|
||||
fn unrealize(&self, context: &mut Context) {
|
||||
log::debug!("Unrealizing id {}", self.id());
|
||||
context.unrealized.insert(self.id());
|
||||
self.container.unrealize(context);
|
||||
self.progress_spinner.unrealize(context);
|
||||
self.ex_buffer.unrealize(context);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -837,6 +888,7 @@ impl Tabbed {
|
|||
.extend(ret.shortcuts(context).into_iter());
|
||||
ret
|
||||
}
|
||||
|
||||
fn draw_tabs(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
@ -911,7 +963,8 @@ impl Tabbed {
|
|||
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
pub fn add_component(&mut self, new: Box<dyn Component>) {
|
||||
pub fn add_component(&mut self, new: Box<dyn Component>, context: &mut Context) {
|
||||
new.realize(self.id().into(), context);
|
||||
self.children.push(new);
|
||||
}
|
||||
}
|
||||
|
@ -1386,7 +1439,7 @@ impl Component for Tabbed {
|
|||
return true;
|
||||
}
|
||||
UIEvent::Action(Tab(New(ref mut e))) if e.is_some() => {
|
||||
self.add_component(e.take().unwrap());
|
||||
self.add_component(e.take().unwrap(), context);
|
||||
self.children[self.cursor_pos]
|
||||
.process_event(&mut UIEvent::VisibilityChange(false), context);
|
||||
self.cursor_pos = self.children.len() - 1;
|
||||
|
@ -1415,6 +1468,7 @@ impl Component for Tabbed {
|
|||
if let Some(c_idx) = self.children.iter().position(|x| x.id() == *id) {
|
||||
self.children[c_idx]
|
||||
.process_event(&mut UIEvent::VisibilityChange(false), context);
|
||||
self.children[c_idx].unrealize(context);
|
||||
self.children.remove(c_idx);
|
||||
self.cursor_pos = 0;
|
||||
self.set_dirty(true);
|
||||
|
@ -1555,6 +1609,41 @@ impl Component for Tabbed {
|
|||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &'static ComponentAttr {
|
||||
&ComponentAttr::CONTAINER
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
let mut ret = IndexMap::default();
|
||||
for c in &self.children {
|
||||
ret.insert(c.id(), c as _);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
IndexMap::default()
|
||||
}
|
||||
|
||||
fn realize(&self, parent: Option<ComponentId>, context: &mut Context) {
|
||||
log::debug!("Realizing Tabbed id {} w/ parent {:?}", self.id(), &parent);
|
||||
context.realized.insert(self.id(), parent);
|
||||
for c in &self.children {
|
||||
log::debug!("Realizing child id {} w/ parent {:?}", c.id(), &self.id);
|
||||
c.realize(self.id().into(), context);
|
||||
}
|
||||
}
|
||||
|
||||
fn unrealize(&self, context: &mut Context) {
|
||||
log::debug!("Unrealizing id {}", self.id());
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::ComponentUnrealize(self.id()));
|
||||
for c in &self.children {
|
||||
c.unrealize(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -124,7 +124,7 @@ impl<T: 'static + PartialEq + Debug + Clone + Sync + Send> Component for UIDialo
|
|||
self.done = true;
|
||||
if let Some(event) = self.done() {
|
||||
context.replies.push_back(event);
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ impl<T: 'static + PartialEq + Debug + Clone + Sync + Send> Component for UIDialo
|
|||
self.done = true;
|
||||
if let Some(event) = self.done() {
|
||||
context.replies.push_back(event);
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ impl<T: 'static + PartialEq + Debug + Clone + Sync + Send> Component for UIDialo
|
|||
self.done = true;
|
||||
if let Some(event) = self.done() {
|
||||
context.replies.push_back(event);
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ impl<T: 'static + PartialEq + Debug + Clone + Sync + Send> Component for UIDialo
|
|||
self.done = true;
|
||||
if let Some(event) = self.done() {
|
||||
context.replies.push_back(event);
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -450,7 +450,7 @@ impl Component for UIConfirmationDialog {
|
|||
self.done = true;
|
||||
if let Some(event) = self.done() {
|
||||
context.replies.push_back(event);
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -486,7 +486,7 @@ impl Component for UIConfirmationDialog {
|
|||
self.done = true;
|
||||
if let Some(event) = self.done() {
|
||||
context.replies.push_back(event);
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -497,7 +497,7 @@ impl Component for UIConfirmationDialog {
|
|||
self.done = true;
|
||||
if let Some(event) = self.done() {
|
||||
context.replies.push_back(event);
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -508,7 +508,7 @@ impl Component for UIConfirmationDialog {
|
|||
self.done = true;
|
||||
if let Some(event) = self.done() {
|
||||
context.replies.push_back(event);
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,12 @@ pub struct Pager {
|
|||
colors: ThemeAttribute,
|
||||
initialised: bool,
|
||||
show_scrollbar: bool,
|
||||
/// At the last draw, were the visible columns plus horizontal cursor less than total width?
|
||||
/// Used to decide whether to accept `scroll_right` key events.
|
||||
cols_lt_width: bool,
|
||||
/// At the last draw, were the visible rows plus vertical cursor less than total height?
|
||||
/// Used to decide whether to accept `scroll_down` key events.
|
||||
rows_lt_height: bool,
|
||||
filtered_content: Option<(String, Result<CellBuffer>)>,
|
||||
text_lines: Vec<String>,
|
||||
line_breaker: LineBreakText,
|
||||
|
@ -525,9 +531,6 @@ impl Component for Pager {
|
|||
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
let (mut cols, mut rows) = (width!(area), height!(area));
|
||||
if cols < 2 || rows < 2 {
|
||||
return;
|
||||
}
|
||||
let (has_more_lines, (width, height)) = if self.filtered_content.is_some() {
|
||||
(false, (self.width, self.height))
|
||||
} else {
|
||||
|
@ -536,6 +539,14 @@ impl Component for Pager {
|
|||
(self.line_breaker.width().unwrap_or(cols), self.height),
|
||||
)
|
||||
};
|
||||
|
||||
self.cols_lt_width = cols + self.cursor.0 < width;
|
||||
self.rows_lt_height = rows + self.cursor.1 < height;
|
||||
|
||||
if cols < 2 || rows < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.show_scrollbar && rows < height {
|
||||
cols -= 1;
|
||||
rows -= 1;
|
||||
|
@ -655,42 +666,32 @@ impl Component for Pager {
|
|||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::PAGER]["scroll_up"]) =>
|
||||
if shortcut!(key == shortcuts[Shortcuts::PAGER]["scroll_up"])
|
||||
&& self.cursor.1 > 0 =>
|
||||
{
|
||||
self.movement = Some(PageMovement::Up(1));
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::PAGER]["scroll_down"]) =>
|
||||
if shortcut!(key == shortcuts[Shortcuts::PAGER]["scroll_down"])
|
||||
&& self.rows_lt_height =>
|
||||
{
|
||||
self.movement = Some(PageMovement::Down(1));
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["home_page"]) =>
|
||||
{
|
||||
self.movement = Some(PageMovement::Home);
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["end_page"]) =>
|
||||
{
|
||||
self.movement = Some(PageMovement::End);
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) =>
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"])
|
||||
&& self.cursor.0 > 0 =>
|
||||
{
|
||||
self.movement = Some(PageMovement::Left(1));
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) =>
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"])
|
||||
&& dbg!(self.cols_lt_width) =>
|
||||
{
|
||||
self.movement = Some(PageMovement::Right(1));
|
||||
self.dirty = true;
|
||||
|
@ -708,6 +709,20 @@ impl Component for Pager {
|
|||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["home_page"]) =>
|
||||
{
|
||||
self.movement = Some(PageMovement::Home);
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["end_page"]) =>
|
||||
{
|
||||
self.movement = Some(PageMovement::End);
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::ChangeMode(UIMode::Normal) => {
|
||||
self.dirty = true;
|
||||
}
|
||||
|
@ -802,7 +817,10 @@ impl Component for Pager {
|
|||
}
|
||||
UIEvent::Resize => {
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::VisibilityChange(true) => {
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::VisibilityChange(false) => {
|
||||
context
|
||||
|
|
|
@ -1122,6 +1122,7 @@ impl ProgressSpinner {
|
|||
.unwrap_or(0);
|
||||
let interval = Self::KINDS[kind].0;
|
||||
let timer = context
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.clone()
|
||||
.create_timer(interval, interval);
|
||||
|
|
|
@ -37,7 +37,6 @@ use std::{
|
|||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use crossbeam::channel::Sender;
|
||||
use futures::{
|
||||
future::FutureExt,
|
||||
stream::{Stream, StreamExt},
|
||||
|
@ -57,9 +56,9 @@ use smallvec::SmallVec;
|
|||
use super::{AccountConf, FileMailboxConf};
|
||||
use crate::{
|
||||
command::actions::AccountAction,
|
||||
jobs::{JobExecutor, JobId, JoinHandle},
|
||||
jobs::{JobId, JoinHandle},
|
||||
types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate, Notification},
|
||||
StatusEvent, ThreadEvent,
|
||||
MainLoopHandler, StatusEvent, ThreadEvent,
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
|
@ -177,10 +176,9 @@ pub struct Account {
|
|||
pub settings: AccountConf,
|
||||
pub backend: Arc<RwLock<Box<dyn MailBackend>>>,
|
||||
|
||||
pub job_executor: Arc<JobExecutor>,
|
||||
pub main_loop_handler: MainLoopHandler,
|
||||
pub active_jobs: HashMap<JobId, JobRequest>,
|
||||
pub active_job_instants: BTreeMap<std::time::Instant, JobId>,
|
||||
pub sender: Sender<ThreadEvent>,
|
||||
pub event_queue: VecDeque<(MailboxHash, RefreshEvent)>,
|
||||
pub backend_capabilities: MailBackendCapabilities,
|
||||
}
|
||||
|
@ -434,8 +432,7 @@ impl Account {
|
|||
name: String,
|
||||
mut settings: AccountConf,
|
||||
map: &Backends,
|
||||
job_executor: Arc<JobExecutor>,
|
||||
sender: Sender<ThreadEvent>,
|
||||
main_loop_handler: MainLoopHandler,
|
||||
event_consumer: BackendEventConsumer,
|
||||
) -> Result<Self> {
|
||||
let s = settings.clone();
|
||||
|
@ -490,18 +487,20 @@ impl Account {
|
|||
if let Ok(mailboxes_job) = backend.mailboxes() {
|
||||
if let Ok(online_job) = backend.is_online() {
|
||||
let handle = if backend.capabilities().is_async {
|
||||
job_executor.spawn_specialized(online_job.then(|_| mailboxes_job))
|
||||
main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(online_job.then(|_| mailboxes_job))
|
||||
} else {
|
||||
job_executor.spawn_blocking(online_job.then(|_| mailboxes_job))
|
||||
main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(online_job.then(|_| mailboxes_job))
|
||||
};
|
||||
let job_id = handle.job_id;
|
||||
active_jobs.insert(job_id, JobRequest::Mailboxes { handle });
|
||||
active_job_instants.insert(std::time::Instant::now(), job_id);
|
||||
sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::NewJob(job_id),
|
||||
)))
|
||||
.unwrap();
|
||||
main_loop_handler.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::NewJob(job_id),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -509,15 +508,13 @@ impl Account {
|
|||
if settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
|
||||
let db_path = match crate::sqlite3::db_path() {
|
||||
Err(err) => {
|
||||
sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Error with setting up an sqlite3 search database for account \
|
||||
main_loop_handler.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Error with setting up an sqlite3 search database for account \
|
||||
`{}`: {}",
|
||||
name, err
|
||||
)),
|
||||
)))
|
||||
.unwrap();
|
||||
name, err
|
||||
)),
|
||||
)));
|
||||
None
|
||||
}
|
||||
Ok(path) => Some(path),
|
||||
|
@ -529,11 +526,9 @@ impl Account {
|
|||
one will be created.",
|
||||
name
|
||||
);
|
||||
sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Action(
|
||||
(name.clone(), AccountAction::ReIndex).into(),
|
||||
)))
|
||||
.unwrap();
|
||||
main_loop_handler.send(ThreadEvent::UIEvent(UIEvent::Action(
|
||||
(name.clone(), AccountAction::ReIndex).into(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -553,8 +548,7 @@ impl Account {
|
|||
sent_mailbox: Default::default(),
|
||||
collection: backend.collection(),
|
||||
settings,
|
||||
sender,
|
||||
job_executor,
|
||||
main_loop_handler,
|
||||
active_jobs,
|
||||
active_job_instants,
|
||||
event_queue: VecDeque::with_capacity(8),
|
||||
|
@ -643,15 +637,14 @@ impl Account {
|
|||
&self.name,
|
||||
missing_mailbox,
|
||||
);
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Account `{}` mailbox `{}` configured but not present in account's \
|
||||
mailboxes. Is it misspelled?",
|
||||
&self.name, missing_mailbox,
|
||||
)),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
}
|
||||
if !mailbox_conf_hash_set.is_empty() {
|
||||
let mut mailbox_comma_sep_list_string = mailbox_entries
|
||||
|
@ -671,14 +664,13 @@ impl Account {
|
|||
&self.name,
|
||||
mailbox_comma_sep_list_string,
|
||||
);
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Account `{}` has the following mailboxes: [{}]",
|
||||
&self.name, mailbox_comma_sep_list_string,
|
||||
)),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
}
|
||||
|
||||
let mut tree: Vec<MailboxNode> = Vec::new();
|
||||
|
@ -697,16 +689,19 @@ impl Account {
|
|||
if let Ok(mailbox_job) = self.backend.write().unwrap().fetch(*h) {
|
||||
let mailbox_job = mailbox_job.into_future();
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(mailbox_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(mailbox_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(mailbox_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(mailbox_job)
|
||||
};
|
||||
let job_id = handle.job_id;
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::NewJob(job_id),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
self.active_jobs.insert(
|
||||
job_id,
|
||||
JobRequest::Fetch {
|
||||
|
@ -770,7 +765,8 @@ impl Account {
|
|||
);
|
||||
}
|
||||
Ok(job) => {
|
||||
let handle = self.job_executor.spawn_blocking(job);
|
||||
let handle =
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job);
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Generic {
|
||||
|
@ -816,7 +812,8 @@ impl Account {
|
|||
)
|
||||
}) {
|
||||
Ok(job) => {
|
||||
let handle = self.job_executor.spawn_blocking(job);
|
||||
let handle =
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job);
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Generic {
|
||||
|
@ -868,7 +865,8 @@ impl Account {
|
|||
);
|
||||
}
|
||||
Ok(job) => {
|
||||
let handle = self.job_executor.spawn_blocking(job);
|
||||
let handle =
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job);
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Generic {
|
||||
|
@ -908,11 +906,13 @@ impl Account {
|
|||
};
|
||||
#[cfg(feature = "sqlite3")]
|
||||
if self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
|
||||
let handle = self.job_executor.spawn_blocking(crate::sqlite3::insert(
|
||||
(*envelope).clone(),
|
||||
self.backend.clone(),
|
||||
self.name.clone(),
|
||||
));
|
||||
let handle = self.main_loop_handler.job_executor.spawn_blocking(
|
||||
crate::sqlite3::insert(
|
||||
(*envelope).clone(),
|
||||
self.backend.clone(),
|
||||
self.name.clone(),
|
||||
),
|
||||
);
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Generic {
|
||||
|
@ -1027,8 +1027,7 @@ impl Account {
|
|||
Some(format!("{} watcher exited with error", &self.name)),
|
||||
e.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
)));
|
||||
*/
|
||||
self.watch();
|
||||
return Some(Notification(
|
||||
|
@ -1057,24 +1056,26 @@ impl Account {
|
|||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()?;
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!("Running command {}", refresh_command)),
|
||||
)))
|
||||
.unwrap();
|
||||
self.sender
|
||||
)));
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Fork(
|
||||
crate::ForkType::Generic(child),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
return Ok(());
|
||||
}
|
||||
let refresh_job = self.backend.write().unwrap().refresh(mailbox_hash);
|
||||
if let Ok(refresh_job) = refresh_job {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(refresh_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(refresh_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(refresh_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(refresh_job)
|
||||
};
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
|
@ -1096,9 +1097,9 @@ impl Account {
|
|||
match self.backend.read().unwrap().watch() {
|
||||
Ok(fut) => {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(fut)
|
||||
self.main_loop_handler.job_executor.spawn_specialized(fut)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(fut)
|
||||
self.main_loop_handler.job_executor.spawn_blocking(fut)
|
||||
};
|
||||
self.active_jobs
|
||||
.insert(handle.job_id, JobRequest::Watch { handle });
|
||||
|
@ -1107,14 +1108,13 @@ impl Account {
|
|||
if e.kind == ErrorKind::NotSupported || e.kind == ErrorKind::NotImplemented => {
|
||||
}
|
||||
Err(e) => {
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Account `{}` watch action returned error: {}",
|
||||
&self.name, e
|
||||
)),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1174,9 +1174,13 @@ impl Account {
|
|||
Ok(mailbox_job) => {
|
||||
let mailbox_job = mailbox_job.into_future();
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(mailbox_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(mailbox_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(mailbox_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(mailbox_job)
|
||||
};
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
|
@ -1192,9 +1196,8 @@ impl Account {
|
|||
.and_modify(|entry| {
|
||||
entry.status = MailboxStatus::Failed(err);
|
||||
});
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(mailbox_hash)))
|
||||
.unwrap();
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(mailbox_hash)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1266,9 +1269,9 @@ impl Account {
|
|||
.save(bytes.to_vec(), mailbox_hash, flags)?;
|
||||
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(job)
|
||||
self.main_loop_handler.job_executor.spawn_specialized(job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(job)
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job)
|
||||
};
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
|
@ -1335,11 +1338,14 @@ impl Account {
|
|||
}
|
||||
#[cfg(feature = "smtp")]
|
||||
SendMail::Smtp(conf) => {
|
||||
let handle = self.job_executor.spawn_specialized(async move {
|
||||
let mut smtp_connection =
|
||||
melib::smtp::SmtpConnection::new_connection(conf).await?;
|
||||
smtp_connection.mail_transaction(&message, None).await
|
||||
});
|
||||
let handle = self
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(async move {
|
||||
let mut smtp_connection =
|
||||
melib::smtp::SmtpConnection::new_connection(conf).await?;
|
||||
smtp_connection.mail_transaction(&message, None).await
|
||||
});
|
||||
if complete_in_background {
|
||||
self.insert_job(handle.job_id, JobRequest::SendMessageBackground { handle });
|
||||
return Ok(None);
|
||||
|
@ -1357,9 +1363,9 @@ impl Account {
|
|||
.submit(message.into_bytes(), None, None)?;
|
||||
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(job)
|
||||
self.main_loop_handler.job_executor.spawn_specialized(job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(job)
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::SendMessageBackground { handle });
|
||||
return Ok(None);
|
||||
|
@ -1478,9 +1484,9 @@ impl Account {
|
|||
.unwrap()
|
||||
.create_mailbox(path.to_string())?;
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(job)
|
||||
self.main_loop_handler.job_executor.spawn_specialized(job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(job)
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::CreateMailbox { path, handle });
|
||||
Ok(())
|
||||
|
@ -1493,9 +1499,9 @@ impl Account {
|
|||
let mailbox_hash = self.mailbox_by_path(&path)?;
|
||||
let job = self.backend.write().unwrap().delete_mailbox(mailbox_hash)?;
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(job)
|
||||
self.main_loop_handler.job_executor.spawn_specialized(job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(job)
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job)
|
||||
};
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
|
@ -1514,9 +1520,9 @@ impl Account {
|
|||
.unwrap()
|
||||
.set_mailbox_subscription(mailbox_hash, true)?;
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(job)
|
||||
self.main_loop_handler.job_executor.spawn_specialized(job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(job)
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job)
|
||||
};
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
|
@ -1536,9 +1542,9 @@ impl Account {
|
|||
.unwrap()
|
||||
.set_mailbox_subscription(mailbox_hash, false)?;
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(job)
|
||||
self.main_loop_handler.job_executor.spawn_specialized(job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(job)
|
||||
self.main_loop_handler.job_executor.spawn_blocking(job)
|
||||
};
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
|
@ -1587,9 +1593,13 @@ impl Account {
|
|||
let online_job = self.backend.read().unwrap().is_online();
|
||||
if let Ok(online_job) = online_job {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(online_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(online_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(online_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(online_job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
|
||||
}
|
||||
|
@ -1643,11 +1653,10 @@ impl Account {
|
|||
}
|
||||
|
||||
pub fn process_event(&mut self, job_id: &JobId) -> bool {
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::JobFinished(*job_id),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
|
||||
if let Some(mut job) = self.active_jobs.remove(job_id) {
|
||||
match job {
|
||||
|
@ -1655,32 +1664,36 @@ impl Account {
|
|||
if let Ok(Some(mailboxes)) = handle.chan.try_recv() {
|
||||
if let Err(err) = mailboxes.and_then(|mailboxes| self.init(mailboxes)) {
|
||||
if err.kind.is_authentication() {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!("{}: authentication error", &self.name)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
self.is_online = Err(err);
|
||||
return true;
|
||||
}
|
||||
let mailboxes_job = self.backend.read().unwrap().mailboxes();
|
||||
if let Ok(mailboxes_job) = mailboxes_job {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(mailboxes_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(mailboxes_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(mailboxes_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(mailboxes_job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::Mailboxes { handle });
|
||||
};
|
||||
} else {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::AccountStatusChange(
|
||||
self.hash,
|
||||
Some("Loaded mailboxes.".into()),
|
||||
)))
|
||||
.unwrap();
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1705,40 +1718,38 @@ impl Account {
|
|||
.and_modify(|entry| {
|
||||
entry.status = MailboxStatus::Available;
|
||||
});
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((
|
||||
self.hash,
|
||||
mailbox_hash,
|
||||
))))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::MailboxUpdate((self.hash, mailbox_hash)),
|
||||
));
|
||||
return true;
|
||||
}
|
||||
Ok(Some((Some(Err(err)), _))) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!("{}: could not fetch mailbox", &self.name)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
self.mailbox_entries
|
||||
.entry(mailbox_hash)
|
||||
.and_modify(|entry| {
|
||||
entry.status = MailboxStatus::Failed(err);
|
||||
});
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((
|
||||
self.hash,
|
||||
mailbox_hash,
|
||||
))))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::MailboxUpdate((self.hash, mailbox_hash)),
|
||||
));
|
||||
return true;
|
||||
}
|
||||
Ok(Some((Some(Ok(payload)), rest))) => {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(rest.into_future())
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(rest.into_future())
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(rest.into_future())
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(rest.into_future())
|
||||
};
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
|
@ -1756,29 +1767,22 @@ impl Account {
|
|||
.merge(envelopes, mailbox_hash, self.sent_mailbox)
|
||||
{
|
||||
for f in updated_mailboxes {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((
|
||||
self.hash, f,
|
||||
))))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::MailboxUpdate((self.hash, f)),
|
||||
));
|
||||
}
|
||||
}
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((
|
||||
self.hash,
|
||||
mailbox_hash,
|
||||
))))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::MailboxUpdate((self.hash, mailbox_hash)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
JobRequest::IsOnline { ref mut handle, .. } => {
|
||||
if let Ok(Some(is_online)) = handle.chan.try_recv() {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
|
||||
self.hash, None,
|
||||
)))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::AccountStatusChange(self.hash, None),
|
||||
));
|
||||
if is_online.is_ok() {
|
||||
if self.is_online.is_err()
|
||||
&& !self
|
||||
|
@ -1798,9 +1802,13 @@ impl Account {
|
|||
let online_job = self.backend.read().unwrap().is_online();
|
||||
if let Ok(online_job) = online_job {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(online_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(online_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(online_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(online_job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
|
||||
};
|
||||
|
@ -1829,11 +1837,9 @@ impl Account {
|
|||
.is_authentication())
|
||||
{
|
||||
self.is_online = Ok(());
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
|
||||
self.hash, None,
|
||||
)))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::AccountStatusChange(self.hash, None),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(Some(Err(err))) => {
|
||||
|
@ -1841,31 +1847,32 @@ impl Account {
|
|||
let online_job = self.backend.read().unwrap().is_online();
|
||||
if let Ok(online_job) = online_job {
|
||||
let handle = if self.backend_capabilities.is_async {
|
||||
self.job_executor.spawn_specialized(online_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized(online_job)
|
||||
} else {
|
||||
self.job_executor.spawn_blocking(online_job)
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(online_job)
|
||||
};
|
||||
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
|
||||
};
|
||||
}
|
||||
self.is_online = Err(err);
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
|
||||
self.hash, None,
|
||||
)))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::AccountStatusChange(self.hash, None),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
JobRequest::SetFlags { ref mut handle, .. } => {
|
||||
if let Ok(Some(Err(err))) = handle.chan.try_recv() {
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
Some(format!("{}: could not set flag", &self.name)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
)));
|
||||
}
|
||||
}
|
||||
JobRequest::SaveMessage {
|
||||
|
@ -1882,7 +1889,7 @@ impl Account {
|
|||
"Message was stored in {} so that you can restore it manually.",
|
||||
file.path.display()
|
||||
);
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
Some(format!("{}: could not save message", &self.name)),
|
||||
format!(
|
||||
|
@ -1890,31 +1897,28 @@ impl Account {
|
|||
file.path.display()
|
||||
),
|
||||
Some(crate::types::NotificationType::Info),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
)));
|
||||
}
|
||||
}
|
||||
JobRequest::SendMessage => {}
|
||||
JobRequest::SendMessageBackground { ref mut handle, .. } => {
|
||||
if let Ok(Some(Err(err))) = handle.chan.try_recv() {
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
Some("Could not send message".to_string()),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
)));
|
||||
}
|
||||
}
|
||||
JobRequest::DeleteMessages { ref mut handle, .. } => {
|
||||
if let Ok(Some(Err(err))) = handle.chan.try_recv() {
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
Some(format!("{}: could not delete message", &self.name)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
)));
|
||||
}
|
||||
}
|
||||
JobRequest::CreateMailbox {
|
||||
|
@ -1925,24 +1929,21 @@ impl Account {
|
|||
if let Ok(Some(r)) = handle.chan.try_recv() {
|
||||
match r {
|
||||
Err(err) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!(
|
||||
"{}: could not create mailbox {}",
|
||||
&self.name, path
|
||||
)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok((mailbox_hash, mut mailboxes)) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxCreate((
|
||||
self.hash,
|
||||
mailbox_hash,
|
||||
))))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::MailboxCreate((self.hash, mailbox_hash)),
|
||||
));
|
||||
let mut new = FileMailboxConf::default();
|
||||
new.mailbox_conf.subscribe = super::ToggleFlag::InternalVal(true);
|
||||
new.mailbox_conf.usage = if mailboxes[&mailbox_hash].special_usage()
|
||||
|
@ -2013,21 +2014,18 @@ impl Account {
|
|||
Err(_) => { /* canceled */ }
|
||||
Ok(None) => {}
|
||||
Ok(Some(Err(err))) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!("{}: could not delete mailbox", &self.name)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(Some(Ok(mut mailboxes))) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxDelete((
|
||||
self.hash,
|
||||
mailbox_hash,
|
||||
))))
|
||||
.unwrap();
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::MailboxDelete((self.hash, mailbox_hash)),
|
||||
));
|
||||
if let Some(pos) =
|
||||
self.mailboxes_order.iter().position(|&h| h == mailbox_hash)
|
||||
{
|
||||
|
@ -2069,13 +2067,13 @@ impl Account {
|
|||
);
|
||||
// FIXME remove from settings as well
|
||||
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!("{}: mailbox deleted successfully", &self.name)),
|
||||
String::new(),
|
||||
Some(crate::types::NotificationType::Info),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2085,28 +2083,28 @@ impl Account {
|
|||
Err(_) => { /* canceled */ }
|
||||
Ok(None) => {}
|
||||
Ok(Some(Err(err))) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!(
|
||||
"{}: could not set mailbox permissions",
|
||||
&self.name
|
||||
)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(Some(Ok(_))) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!(
|
||||
"{}: mailbox permissions set successfully",
|
||||
&self.name
|
||||
)),
|
||||
String::new(),
|
||||
Some(crate::types::NotificationType::Info),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2119,16 +2117,16 @@ impl Account {
|
|||
Err(_) => { /* canceled */ }
|
||||
Ok(None) => {}
|
||||
Ok(Some(Err(err))) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!(
|
||||
"{}: could not set mailbox subscription",
|
||||
&self.name
|
||||
)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(Some(Ok(()))) if self.mailbox_entries.contains_key(mailbox_hash) => {
|
||||
self.mailbox_entries.entry(*mailbox_hash).and_modify(|m| {
|
||||
|
@ -2139,8 +2137,8 @@ impl Account {
|
|||
};
|
||||
let _ = m.ref_mailbox.set_is_subscribed(*new_value);
|
||||
});
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!(
|
||||
"{}: `{}` has been {}subscribed.",
|
||||
&self.name,
|
||||
|
@ -2149,8 +2147,8 @@ impl Account {
|
|||
)),
|
||||
String::new(),
|
||||
Some(crate::types::NotificationType::Info),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(Some(Ok(()))) => {}
|
||||
}
|
||||
|
@ -2162,13 +2160,13 @@ impl Account {
|
|||
self.watch();
|
||||
} else {
|
||||
//TODO: relaunch watch job with ratelimit for failure
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!("{}: watch thread failed", &self.name)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2180,34 +2178,33 @@ impl Account {
|
|||
} => {
|
||||
match handle.chan.try_recv() {
|
||||
Ok(Some(Err(err))) => {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!("{}: {} failed", &self.name, name,)),
|
||||
err.to_string(),
|
||||
Some(crate::types::NotificationType::Error(err.kind)),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(Some(Ok(()))) if on_finish.is_none() => {
|
||||
if log_level <= LogLevel::INFO {
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification(
|
||||
Some(format!("{}: {} succeeded", &self.name, name,)),
|
||||
String::new(),
|
||||
Some(crate::types::NotificationType::Info),
|
||||
)))
|
||||
.expect("Could not send event on main channel");
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(_) => { /* canceled */ }
|
||||
Ok(Some(Ok(()))) | Ok(None) => {}
|
||||
}
|
||||
if on_finish.is_some() {
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Callback(
|
||||
on_finish.take().unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2221,20 +2218,18 @@ impl Account {
|
|||
self.active_jobs.insert(job_id, job);
|
||||
self.active_job_instants
|
||||
.insert(std::time::Instant::now(), job_id);
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::NewJob(job_id),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn cancel_job(&mut self, job_id: JobId) -> Option<JobRequest> {
|
||||
if let Some(req) = self.active_jobs.remove(&job_id) {
|
||||
self.sender
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||
StatusEvent::JobCanceled(job_id),
|
||||
)))
|
||||
.unwrap();
|
||||
)));
|
||||
Some(req)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
use super::*;
|
||||
use melib::HeaderName;
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PagerSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "pager-context")] # [serde (default)] pub pager_context : Option < usize > , # [doc = " Stop at the end instead of displaying next mail."] # [doc = " Default: false"] # [serde (alias = "pager-stop")] # [serde (default)] pub pager_stop : Option < bool > , # [doc = " Always show headers when scrolling."] # [doc = " Default: true"] # [serde (alias = "headers-sticky")] # [serde (default)] pub headers_sticky : Option < bool > , # [doc = " The height of the pager in mail view, in percent."] # [doc = " Default: 80"] # [serde (alias = "pager-ratio")] # [serde (default)] pub pager_ratio : Option < usize > , # [doc = " A command to pipe mail output through for viewing in pager."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub filter : Option < Option < String > > , # [doc = " A command to pipe html output before displaying it in a pager"] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-filter")] # [serde (default)] pub html_filter : Option < Option < String > > , # [doc = " Respect \"format=flowed\""] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Split long lines that would overflow on the x axis."] # [doc = " Default: true"] # [serde (alias = "split-long-lines")] # [serde (default)] pub split_long_lines : Option < bool > , # [doc = " Minimum text width in columns."] # [doc = " Default: 80"] # [serde (alias = "minimum-width")] # [serde (default)] pub minimum_width : Option < usize > , # [doc = " Choose `text/html` alternative if `text/plain` is empty in"] # [doc = " `multipart/alternative` attachments."] # [doc = " Default: true"] # [serde (alias = "auto-choose-multipart-alternative")] # [serde (default)] pub auto_choose_multipart_alternative : Option < ToggleFlag > , # [doc = " Show Date: in my timezone"] # [doc = " Default: true"] # [serde (alias = "show-date-in-my-timezone")] # [serde (default)] pub show_date_in_my_timezone : Option < ToggleFlag > , # [doc = " A command to launch URLs with. The URL will be given as the first"] # [doc = " argument of the command. Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub url_launcher : Option < Option < String > > , # [doc = " A command to open html files."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-open")] # [serde (default)] pub html_open : Option < Option < String > > , # [doc = " Extra headers to display, if present, in the default header preamble."] # [doc = " Default: []"] # [serde (alias = "show-extra-headers")] # [serde (default)] pub show_extra_headers : Option < Vec < String > > } impl Default for PagerSettingsOverride { fn default () -> Self { PagerSettingsOverride { pager_context : None , pager_stop : None , headers_sticky : None , pager_ratio : None , filter : None , html_filter : None , format_flowed : None , split_long_lines : None , minimum_width : None , auto_choose_multipart_alternative : None , show_date_in_my_timezone : None , url_launcher : None , html_open : None , show_extra_headers : None } } }
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PagerSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "pager-context")] # [serde (default)] pub pager_context : Option < usize > , # [doc = " Stop at the end instead of displaying next mail."] # [doc = " Default: false"] # [serde (alias = "pager-stop")] # [serde (default)] pub pager_stop : Option < bool > , # [doc = " Always show headers when scrolling."] # [doc = " Default: true"] # [serde (alias = "sticky-headers" , alias = "headers-sticky" , alias = "headers_sticky")] # [serde (default)] pub sticky_headers : Option < bool > , # [doc = " The height of the pager in mail view, in percent."] # [doc = " Default: 80"] # [serde (alias = "pager-ratio")] # [serde (default)] pub pager_ratio : Option < usize > , # [doc = " A command to pipe mail output through for viewing in pager."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub filter : Option < Option < String > > , # [doc = " A command to pipe html output before displaying it in a pager"] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-filter")] # [serde (default)] pub html_filter : Option < Option < String > > , # [doc = " Respect \"format=flowed\""] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Split long lines that would overflow on the x axis."] # [doc = " Default: true"] # [serde (alias = "split-long-lines")] # [serde (default)] pub split_long_lines : Option < bool > , # [doc = " Minimum text width in columns."] # [doc = " Default: 80"] # [serde (alias = "minimum-width")] # [serde (default)] pub minimum_width : Option < usize > , # [doc = " Choose `text/html` alternative if `text/plain` is empty in"] # [doc = " `multipart/alternative` attachments."] # [doc = " Default: true"] # [serde (alias = "auto-choose-multipart-alternative")] # [serde (default)] pub auto_choose_multipart_alternative : Option < ToggleFlag > , # [doc = " Show Date: in my timezone"] # [doc = " Default: true"] # [serde (alias = "show-date-in-my-timezone")] # [serde (default)] pub show_date_in_my_timezone : Option < ToggleFlag > , # [doc = " A command to launch URLs with. The URL will be given as the first"] # [doc = " argument of the command. Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub url_launcher : Option < Option < String > > , # [doc = " A command to open html files."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-open")] # [serde (default)] pub html_open : Option < Option < String > > , # [doc = " Extra headers to display, if present, in the default header preamble."] # [doc = " Default: []"] # [serde (alias = "show-extra-headers")] # [serde (default)] pub show_extra_headers : Option < Vec < String > > } impl Default for PagerSettingsOverride { fn default () -> Self { PagerSettingsOverride { pager_context : None , pager_stop : None , sticky_headers : None , pager_ratio : None , filter : None , html_filter : None , format_flowed : None , split_long_lines : None , minimum_width : None , auto_choose_multipart_alternative : None , show_date_in_my_timezone : None , url_launcher : None , html_open : None , show_extra_headers : None } } }
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ListingSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "context-lines")] # [serde (default)] pub context_lines : Option < usize > , # [doc = "Show auto-hiding scrollbar in accounts sidebar menu."] # [doc = "Default: True"] # [serde (default)] pub show_menu_scrollbar : Option < bool > , # [doc = " Datetime formatting passed verbatim to strftime(3)."] # [doc = " Default: %Y-%m-%d %T"] # [serde (alias = "datetime-fmt")] # [serde (default)] pub datetime_fmt : Option < Option < String > > , # [doc = " Show recent dates as `X {minutes,hours,days} ago`, up to 7 days."] # [doc = " Default: true"] # [serde (alias = "recent-dates")] # [serde (default)] pub recent_dates : Option < bool > , # [doc = " Show only envelopes that match this query"] # [doc = " Default: None"] # [serde (default)] pub filter : Option < Option < Query > > , # [serde (alias = "index-style")] # [serde (default)] pub index_style : Option < IndexStyle > , # [doc = "Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling : Option < Option < String > > , # [doc = "Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling : Option < Option < String > > , # [doc = "Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling_leaf : Option < Option < String > > , # [doc = "Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling_leaf : Option < Option < String > > , # [doc = "Default: ' '"] # [serde (default)] pub sidebar_divider : Option < char > , # [doc = "Default: 90"] # [serde (default)] pub sidebar_ratio : Option < usize > , # [doc = " Flag to show if thread entry contains unseen mail."] # [doc = " Default: \"●\""] # [serde (default)] pub unseen_flag : Option < Option < String > > , # [doc = " Flag to show if thread has been snoozed."] # [doc = " Default: \"💤\""] # [serde (default)] pub thread_snoozed_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry has been selected."] # [doc = " Default: \"☑\u{fe0f}\""] # [serde (default)] pub selected_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry contains attachments."] # [doc = " Default: \"📎\""] # [serde (default)] pub attachment_flag : Option < Option < String > > , # [doc = " Should threads with differentiating Subjects show a list of those"] # [doc = " subjects on the entry title?"] # [doc = " Default: \"true\""] # [serde (default)] pub thread_subject_pack : Option < bool > } impl Default for ListingSettingsOverride { fn default () -> Self { ListingSettingsOverride { context_lines : None , show_menu_scrollbar : None , datetime_fmt : None , recent_dates : None , filter : None , index_style : None , sidebar_mailbox_tree_has_sibling : None , sidebar_mailbox_tree_no_sibling : None , sidebar_mailbox_tree_has_sibling_leaf : None , sidebar_mailbox_tree_no_sibling_leaf : None , sidebar_divider : None , sidebar_ratio : None , unseen_flag : None , thread_snoozed_flag : None , selected_flag : None , attachment_flag : None , thread_subject_pack : None } } }
|
||||
|
||||
|
|
|
@ -41,8 +41,14 @@ pub struct PagerSettings {
|
|||
|
||||
/// Always show headers when scrolling.
|
||||
/// Default: true
|
||||
#[serde(default = "true_val", alias = "headers-sticky")]
|
||||
pub headers_sticky: bool,
|
||||
#[serde(
|
||||
default = "true_val",
|
||||
alias = "sticky-headers",
|
||||
/* deprecated names */
|
||||
alias = "headers-sticky",
|
||||
alias = "headers_sticky"
|
||||
)]
|
||||
pub sticky_headers: bool,
|
||||
|
||||
/// The height of the pager in mail view, in percent.
|
||||
/// Default: 80
|
||||
|
@ -117,7 +123,7 @@ impl Default for PagerSettings {
|
|||
Self {
|
||||
pager_context: 0,
|
||||
pager_stop: false,
|
||||
headers_sticky: true,
|
||||
sticky_headers: true,
|
||||
pager_ratio: 80,
|
||||
filter: None,
|
||||
html_filter: None,
|
||||
|
@ -141,7 +147,7 @@ impl DotAddressable for PagerSettings {
|
|||
match *field {
|
||||
"pager_context" => self.pager_context.lookup(field, tail),
|
||||
"pager_stop" => self.pager_stop.lookup(field, tail),
|
||||
"headers_sticky" => self.headers_sticky.lookup(field, tail),
|
||||
"sticky_headers" => self.sticky_headers.lookup(field, tail),
|
||||
"pager_ratio" => self.pager_ratio.lookup(field, tail),
|
||||
"filter" => self.filter.lookup(field, tail),
|
||||
"html_filter" => self.html_filter.lookup(field, tail),
|
||||
|
|
|
@ -251,7 +251,6 @@ const DEFAULT_KEYS: &[&str] = &[
|
|||
"text.error",
|
||||
"text.highlight",
|
||||
"error_message",
|
||||
"email_header",
|
||||
"highlight",
|
||||
"status.bar",
|
||||
"status.command_bar",
|
||||
|
@ -1316,8 +1315,6 @@ impl Default for Themes {
|
|||
add!("text.highlight", dark = { fg: Color::Blue, bg: "theme_default", attrs: Attr::REVERSE }, light = { fg: Color::Blue, bg: "theme_default", attrs: Attr::REVERSE });
|
||||
|
||||
/* rest */
|
||||
add!("email_header", dark = { fg: Color::Byte(33), bg: Color::Default, attrs: Attr::DEFAULT }, light = { fg: Color::Byte(33), bg: Color::Default, attrs: Attr::DEFAULT });
|
||||
|
||||
add!("highlight", dark = { fg: Color::Byte(240), bg: Color::Byte(237), attrs: Attr::BOLD }, light = { fg: Color::Byte(240), bg: Color::Byte(237), attrs: Attr::BOLD });
|
||||
|
||||
add!("status.bar", dark = { fg: Color::Byte(123), bg: Color::Byte(26) }, light = { fg: Color::Byte(123), bg: Color::Byte(26) });
|
||||
|
|
|
@ -265,11 +265,13 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
sender,
|
||||
receiver.clone(),
|
||||
)?;
|
||||
let main_loop_handler = state.context.main_loop_handler.clone();
|
||||
state.register_component(Box::new(EnvelopeView::new(
|
||||
wrapper,
|
||||
None,
|
||||
None,
|
||||
AccountHash::default(),
|
||||
None,
|
||||
main_loop_handler,
|
||||
)));
|
||||
} else {
|
||||
state = State::new(None, sender, receiver.clone())?;
|
||||
|
|
145
src/state.rs
145
src/state.rs
|
@ -34,10 +34,10 @@
|
|||
//! for user input, observe folders for file changes etc. The relevant struct is
|
||||
//! [`ThreadEvent`].
|
||||
|
||||
use std::{env, os::unix::io::RawFd, sync::Arc, thread};
|
||||
use std::{collections::BTreeSet, env, os::unix::io::RawFd, sync::Arc, thread};
|
||||
|
||||
use crossbeam::channel::{unbounded, Receiver, Sender};
|
||||
use indexmap::IndexMap;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use melib::{
|
||||
backends::{AccountHash, BackendEvent, BackendEventConsumer, Backends, RefreshEvent},
|
||||
UnixTimestamp,
|
||||
|
@ -102,6 +102,21 @@ impl InputHandler {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MainLoopHandler {
|
||||
pub sender: Sender<ThreadEvent>,
|
||||
pub job_executor: Arc<JobExecutor>,
|
||||
}
|
||||
|
||||
impl MainLoopHandler {
|
||||
#[inline]
|
||||
pub fn send(&self, event: ThreadEvent) {
|
||||
if let Err(err) = self.sender.send(event) {
|
||||
log::error!("Could not send event to main loop: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A context container for loaded settings, accounts, UI changes, etc.
|
||||
pub struct Context {
|
||||
pub accounts: IndexMap<AccountHash, Account>,
|
||||
|
@ -112,10 +127,11 @@ pub struct Context {
|
|||
|
||||
/// Events queue that components send back to the state
|
||||
pub replies: VecDeque<UIEvent>,
|
||||
pub sender: Sender<ThreadEvent>,
|
||||
pub realized: IndexMap<ComponentId, Option<ComponentId>>,
|
||||
pub unrealized: IndexSet<ComponentId>,
|
||||
pub main_loop_handler: MainLoopHandler,
|
||||
receiver: Receiver<ThreadEvent>,
|
||||
input_thread: InputHandler,
|
||||
pub job_executor: Arc<JobExecutor>,
|
||||
pub children: Vec<std::process::Child>,
|
||||
|
||||
pub temp_files: Vec<File>,
|
||||
|
@ -196,8 +212,10 @@ impl Context {
|
|||
name,
|
||||
account_conf,
|
||||
&backends,
|
||||
job_executor.clone(),
|
||||
sender.clone(),
|
||||
MainLoopHandler {
|
||||
job_executor: job_executor.clone(),
|
||||
sender: sender.clone(),
|
||||
},
|
||||
BackendEventConsumer::new(Arc::new(
|
||||
move |account_hash: AccountHash, ev: BackendEvent| {
|
||||
sender
|
||||
|
@ -219,8 +237,9 @@ impl Context {
|
|||
settings,
|
||||
dirty_areas: VecDeque::with_capacity(0),
|
||||
replies: VecDeque::with_capacity(0),
|
||||
realized: IndexMap::default(),
|
||||
unrealized: IndexSet::default(),
|
||||
temp_files: Vec::new(),
|
||||
job_executor,
|
||||
children: vec![],
|
||||
|
||||
input_thread: InputHandler {
|
||||
|
@ -230,7 +249,10 @@ impl Context {
|
|||
control,
|
||||
state_tx: sender.clone(),
|
||||
},
|
||||
sender,
|
||||
main_loop_handler: MainLoopHandler {
|
||||
job_executor,
|
||||
sender,
|
||||
},
|
||||
receiver,
|
||||
}
|
||||
}
|
||||
|
@ -244,8 +266,9 @@ pub struct State {
|
|||
draw_rate_limit: RateLimit,
|
||||
child: Option<ForkType>,
|
||||
pub mode: UIMode,
|
||||
overlay: Vec<Box<dyn Component>>,
|
||||
components: Vec<Box<dyn Component>>,
|
||||
overlay: IndexMap<ComponentId, Box<dyn Component>>,
|
||||
components: IndexMap<ComponentId, Box<dyn Component>>,
|
||||
component_tree: IndexMap<ComponentId, ComponentPath>,
|
||||
pub context: Box<Context>,
|
||||
timer: thread::JoinHandle<()>,
|
||||
|
||||
|
@ -337,8 +360,10 @@ impl State {
|
|||
n.to_string(),
|
||||
a_s.clone(),
|
||||
&backends,
|
||||
job_executor.clone(),
|
||||
sender.clone(),
|
||||
MainLoopHandler {
|
||||
job_executor: job_executor.clone(),
|
||||
sender: sender.clone(),
|
||||
},
|
||||
BackendEventConsumer::new(Arc::new(
|
||||
move |account_hash: AccountHash, ev: BackendEvent| {
|
||||
sender
|
||||
|
@ -388,8 +413,9 @@ impl State {
|
|||
}),
|
||||
child: None,
|
||||
mode: UIMode::Normal,
|
||||
components: Vec::with_capacity(8),
|
||||
overlay: Vec::new(),
|
||||
components: IndexMap::default(),
|
||||
overlay: IndexMap::default(),
|
||||
component_tree: IndexMap::default(),
|
||||
timer,
|
||||
draw_rate_limit: RateLimit::new(1, 3, job_executor.clone()),
|
||||
display_messages: SmallVec::new(),
|
||||
|
@ -404,8 +430,9 @@ impl State {
|
|||
settings,
|
||||
dirty_areas: VecDeque::with_capacity(5),
|
||||
replies: VecDeque::with_capacity(5),
|
||||
realized: IndexMap::default(),
|
||||
unrealized: IndexSet::default(),
|
||||
temp_files: Vec::new(),
|
||||
job_executor,
|
||||
children: vec![],
|
||||
|
||||
input_thread: InputHandler {
|
||||
|
@ -415,7 +442,10 @@ impl State {
|
|||
control,
|
||||
state_tx: sender.clone(),
|
||||
},
|
||||
sender,
|
||||
main_loop_handler: MainLoopHandler {
|
||||
job_executor,
|
||||
sender,
|
||||
},
|
||||
receiver,
|
||||
}),
|
||||
};
|
||||
|
@ -480,7 +510,7 @@ impl State {
|
|||
}
|
||||
|
||||
pub fn sender(&self) -> Sender<ThreadEvent> {
|
||||
self.context.sender.clone()
|
||||
self.context.main_loop_handler.sender.clone()
|
||||
}
|
||||
|
||||
pub fn restore_input(&mut self) {
|
||||
|
@ -759,7 +789,7 @@ impl State {
|
|||
),
|
||||
);
|
||||
copy_area(&mut self.screen.overlay_grid, &self.screen.grid, area, area);
|
||||
self.overlay.get_mut(0).unwrap().draw(
|
||||
self.overlay.get_index_mut(0).unwrap().1.draw(
|
||||
&mut self.screen.overlay_grid,
|
||||
area,
|
||||
&mut self.context,
|
||||
|
@ -809,11 +839,12 @@ impl State {
|
|||
ref context,
|
||||
..
|
||||
} = self;
|
||||
components.iter_mut().all(|c| c.can_quit_cleanly(context))
|
||||
components.values_mut().all(|c| c.can_quit_cleanly(context))
|
||||
}
|
||||
|
||||
pub fn register_component(&mut self, component: Box<dyn Component>) {
|
||||
self.components.push(component);
|
||||
component.realize(None, &mut self.context);
|
||||
self.components.insert(component.id(), component);
|
||||
}
|
||||
|
||||
/// Convert user commands to actions/method calls.
|
||||
|
@ -885,7 +916,11 @@ impl State {
|
|||
}
|
||||
match crate::sqlite3::index(&mut self.context, account_index) {
|
||||
Ok(job) => {
|
||||
let handle = self.context.job_executor.spawn_blocking(job);
|
||||
let handle = self
|
||||
.context
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_blocking(job);
|
||||
self.context.accounts[account_index].active_jobs.insert(
|
||||
handle.job_id,
|
||||
crate::conf::accounts::JobRequest::Generic {
|
||||
|
@ -963,6 +998,7 @@ impl State {
|
|||
}
|
||||
Quit => {
|
||||
self.context
|
||||
.main_loop_handler
|
||||
.sender
|
||||
.send(ThreadEvent::Input((
|
||||
self.context.settings.shortcuts.general.quit.clone(),
|
||||
|
@ -989,7 +1025,7 @@ impl State {
|
|||
UIEvent::Command(cmd) => {
|
||||
if let Ok(action) = parse_command(cmd.as_bytes()) {
|
||||
if action.needs_confirmation() {
|
||||
self.overlay.push(Box::new(UIConfirmationDialog::new(
|
||||
let new = Box::new(UIConfirmationDialog::new(
|
||||
"You sure?",
|
||||
vec![(true, "yes".to_string()), (false, "no".to_string())],
|
||||
true,
|
||||
|
@ -1000,7 +1036,9 @@ impl State {
|
|||
))
|
||||
})),
|
||||
&self.context,
|
||||
)));
|
||||
));
|
||||
|
||||
self.overlay.insert(new.id(), new);
|
||||
} else if let Action::ReloadConfiguration = action {
|
||||
match Settings::new().and_then(|new_settings| {
|
||||
let old_accounts = self
|
||||
|
@ -1114,6 +1152,7 @@ impl State {
|
|||
}
|
||||
UIEvent::ChangeMode(m) => {
|
||||
self.context
|
||||
.main_loop_handler
|
||||
.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
|
||||
.unwrap();
|
||||
|
@ -1164,13 +1203,7 @@ impl State {
|
|||
self.display_messages_pos = self.display_messages.len() - 1;
|
||||
self.redraw();
|
||||
}
|
||||
UIEvent::ComponentKill(ref id) if self.overlay.iter().any(|c| c.id() == *id) => {
|
||||
let pos = self.overlay.iter().position(|c| c.id() == *id).unwrap();
|
||||
self.overlay.remove(pos);
|
||||
}
|
||||
UIEvent::FinishedUIDialog(ref id, ref mut results)
|
||||
if self.overlay.iter().any(|c| c.id() == *id) =>
|
||||
{
|
||||
UIEvent::FinishedUIDialog(ref id, ref mut results) if self.overlay.contains_key(id) => {
|
||||
if let Some(ref mut action @ Some(_)) = results.downcast_mut::<Option<Action>>() {
|
||||
self.exec_command(action.take().unwrap());
|
||||
|
||||
|
@ -1182,7 +1215,7 @@ impl State {
|
|||
return;
|
||||
}
|
||||
UIEvent::GlobalUIDialog(dialog) => {
|
||||
self.overlay.push(dialog);
|
||||
self.overlay.insert(dialog.id(), dialog);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -1195,12 +1228,60 @@ impl State {
|
|||
} = self;
|
||||
|
||||
/* inform each component */
|
||||
for c in overlay.iter_mut().chain(components.iter_mut()) {
|
||||
for c in overlay.values_mut().chain(components.values_mut()) {
|
||||
if c.process_event(&mut event, context) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((id, parent)) = self.context.realized.pop() {
|
||||
match parent {
|
||||
None => {
|
||||
self.component_tree.insert(id, ComponentPath::new(id));
|
||||
}
|
||||
Some(parent) if self.component_tree.contains_key(&parent) => {
|
||||
let mut v = self.component_tree[&parent].clone();
|
||||
v.push_front(id);
|
||||
if let Some(p) = v.root() {
|
||||
assert_eq!(
|
||||
v.resolve(&self.components[p] as &dyn Component)
|
||||
.unwrap()
|
||||
.id(),
|
||||
id
|
||||
);
|
||||
}
|
||||
self.component_tree.insert(id, v);
|
||||
}
|
||||
Some(parent) if !self.context.realized.contains_key(&parent) => {
|
||||
log::debug!(
|
||||
"BUG: component_realize new_id = {:?} parent = {:?} but component_tree \
|
||||
does not include parent, skipping.",
|
||||
id,
|
||||
parent
|
||||
);
|
||||
self.component_tree.insert(id, ComponentPath::new(id));
|
||||
}
|
||||
Some(_) => {
|
||||
let from_index = self.context.realized.len();
|
||||
self.context.realized.insert(id, parent);
|
||||
self.context.realized.move_index(from_index, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some(id) = self.context.unrealized.pop() {
|
||||
let mut to_delete = BTreeSet::new();
|
||||
for (desc, _) in self.component_tree.iter().filter(|(_, path)| {
|
||||
path.parent()
|
||||
.map(|p| self.context.unrealized.contains(p) || *p == id)
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
to_delete.insert(*desc);
|
||||
}
|
||||
self.context.unrealized.extend(to_delete.into_iter());
|
||||
self.component_tree.remove(&id);
|
||||
self.components.remove(&id);
|
||||
}
|
||||
|
||||
if !self.context.replies.is_empty() {
|
||||
let replies: smallvec::SmallVec<[UIEvent; 8]> =
|
||||
self.context.replies.drain(0..).collect();
|
||||
|
|
19
src/types.rs
19
src/types.rs
|
@ -38,13 +38,13 @@ mod helpers;
|
|||
|
||||
use std::{borrow::Cow, fmt, sync::Arc};
|
||||
|
||||
pub use helpers::*;
|
||||
use melib::{
|
||||
backends::{AccountHash, BackendEvent, MailboxHash},
|
||||
EnvelopeHash, RefreshEvent, ThreadHash,
|
||||
};
|
||||
use nix::unistd::Pid;
|
||||
|
||||
pub use self::helpers::*;
|
||||
use super::{
|
||||
command::Action,
|
||||
jobs::{JobExecutor, JobId, TimerId},
|
||||
|
@ -52,6 +52,8 @@ use super::{
|
|||
};
|
||||
use crate::components::{Component, ComponentId, ScrollUpdate};
|
||||
|
||||
pub type UIMessage = Box<dyn 'static + std::any::Any + Send + Sync>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StatusEvent {
|
||||
DisplayMessage(String),
|
||||
|
@ -70,15 +72,13 @@ pub enum StatusEvent {
|
|||
/// between our threads to the main process.
|
||||
#[derive(Debug)]
|
||||
pub enum ThreadEvent {
|
||||
/// User input.
|
||||
Input((Key, Vec<u8>)),
|
||||
/// User input and input as raw bytes.
|
||||
Input((Key, Vec<u8>)),
|
||||
/// A watched Mailbox has been refreshed.
|
||||
RefreshMailbox(Box<RefreshEvent>),
|
||||
UIEvent(UIEvent),
|
||||
/// A thread has updated some of its information
|
||||
Pulse,
|
||||
//Decode { _ }, // For gpg2 signature check
|
||||
JobFinished(JobId),
|
||||
}
|
||||
|
||||
|
@ -126,9 +126,7 @@ pub enum UIEvent {
|
|||
CmdInput(Key),
|
||||
InsertInput(Key),
|
||||
EmbedInput((Key, Vec<u8>)),
|
||||
//Quit?
|
||||
Resize,
|
||||
/// Force redraw.
|
||||
Fork(ForkType),
|
||||
ChangeMailbox(usize),
|
||||
ChangeMode(UIMode),
|
||||
|
@ -140,7 +138,7 @@ pub enum UIEvent {
|
|||
MailboxDelete((AccountHash, MailboxHash)),
|
||||
MailboxCreate((AccountHash, MailboxHash)),
|
||||
AccountStatusChange(AccountHash, Option<Cow<'static, str>>),
|
||||
ComponentKill(ComponentId),
|
||||
ComponentUnrealize(ComponentId),
|
||||
BackendEvent(AccountHash, BackendEvent),
|
||||
StartupCheck(MailboxHash),
|
||||
RefreshEvent(Box<RefreshEvent>),
|
||||
|
@ -150,6 +148,11 @@ pub enum UIEvent {
|
|||
Contacts(ContactEvent),
|
||||
Compose(ComposeEvent),
|
||||
FinishedUIDialog(ComponentId, UIMessage),
|
||||
IntraComm {
|
||||
from: ComponentId,
|
||||
to: ComponentId,
|
||||
content: UIMessage,
|
||||
},
|
||||
Callback(CallbackFn),
|
||||
GlobalUIDialog(Box<dyn Component>),
|
||||
Timer(TimerId),
|
||||
|
@ -374,8 +377,6 @@ pub enum ComposeEvent {
|
|||
SetReceipients(Vec<melib::Address>),
|
||||
}
|
||||
|
||||
pub type UIMessage = Box<dyn 'static + std::any::Any + Send + Sync>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
//use super::*;
|
||||
|
|
Loading…
Reference in New Issue