ui: add set_seen shortcut in {Compact,Conversation}

Shortcut sets an entire thread as seen.
embed
Manos Pitsidianakis 2019-09-20 09:21:12 +03:00
parent fada0ffce1
commit fb7b038ee1
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
3 changed files with 131 additions and 66 deletions

View File

@ -417,10 +417,25 @@ impl ThreadNode {
self.has_unseen
}
pub fn set_has_unseen(&mut self, new_val: bool) {
self.has_unseen = new_val;
}
pub fn len(&self) -> usize {
self.len
}
pub fn date(&self) -> UnixTimestamp {
self.date
}
pub fn datetime(&self) -> chrono::DateTime<chrono::Utc> {
use chrono::{TimeZone, Utc};
use std::convert::TryInto;
Utc.timestamp(self.date.try_into().unwrap_or(0), 0)
}
pub fn is_empty(&self) -> bool {
self.parent.is_none() && self.message.is_none() && self.children.is_empty()
}
@ -575,12 +590,12 @@ impl<'a> Iterator for RootIterator<'a> {
}
}
fn find_ref(buf: &FnvHashMap<ThreadHash, ThreadNode>, h: ThreadHash) -> ThreadHash {
pub fn find_thread_group(buf: &FnvHashMap<ThreadHash, ThreadNode>, h: ThreadHash) -> ThreadHash {
if buf[&h].thread_group == h {
return h;
}
let p = buf[&h].thread_group;
find_ref(buf, p)
find_thread_group(buf, p)
}
fn find(buf: &mut FnvHashMap<ThreadHash, ThreadNode>, h: ThreadHash) -> ThreadHash {
if buf[&h].thread_group == h {
@ -617,7 +632,7 @@ fn union(buf: &mut FnvHashMap<ThreadHash, ThreadNode>, x: ThreadHash, y: ThreadH
impl Threads {
pub fn is_snoozed(&self, h: ThreadHash) -> bool {
let root = find_ref(&self.thread_nodes, h);
let root = find_thread_group(&self.thread_nodes, h);
self.thread_nodes[&root].snoozed()
}
pub fn find(&mut self, i: ThreadHash) -> ThreadHash {

View File

@ -21,6 +21,7 @@
use super::*;
use crate::components::utilities::PageMovement;
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
const MAX_COLS: usize = 500;
@ -892,6 +893,71 @@ impl ConversationsListing {
self.filtered_selection[cursor]
}
}
fn perform_action(
&mut self,
context: &mut Context,
thread_hash: ThreadHash,
a: &ListingAction,
) {
let account = &mut context.accounts[self.cursor_pos.0];
let mut envs_to_set: StackVec<EnvelopeHash> = StackVec::new();
{
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let mut stack = StackVec::new();
stack.push(thread_hash);
while let Some(thread_iter) = stack.pop() {
{
let threads = account.collection.threads.get_mut(&folder_hash).unwrap();
threads
.thread_nodes
.entry(thread_iter)
.and_modify(|t| t.set_has_unseen(false));
}
let threads = &account.collection.threads[&folder_hash];
if let Some(env_hash) = threads[&thread_iter].message() {
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
continue;
}
envs_to_set.push(env_hash);
}
for c in 0..threads[&thread_iter].children().len() {
let c = threads[&thread_iter].children()[c];
stack.push(c);
}
}
}
for env_hash in envs_to_set {
match a {
ListingAction::SetSeen => {
let hash = account.get_env(&env_hash).hash();
let op = account.operation(hash);
let envelope: &mut Envelope = &mut account.get_env_mut(&env_hash);
if let Err(e) = envelope.set_seen(op) {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(e.to_string()),
));
}
self.row_updates.push(thread_hash);
}
ListingAction::SetUnseen => {
let hash = account.get_env(&env_hash).hash();
let op = account.operation(hash);
let envelope: &mut Envelope = &mut account.get_env_mut(&env_hash);
if let Err(e) = envelope.set_unseen(op) {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(e.to_string()),
));
}
self.row_updates.push(thread_hash);
}
ListingAction::Delete => { /* do nothing */ }
_ => unreachable!(),
}
}
}
}
impl Component for ConversationsListing {
@ -1074,6 +1140,12 @@ impl Component for ConversationsListing {
self.movement = Some(PageMovement::End);
self.set_dirty();
}
UIEvent::Input(ref key) if *key == shortcuts["set_seen"] => {
let thread_hash = self.get_thread_under_cursor(self.cursor_pos.2, context);
self.perform_action(context, thread_hash, &ListingAction::SetSeen);
self.row_updates.push(thread_hash);
self.set_dirty();
}
UIEvent::Input(ref k) if self.unfocused && *k == shortcuts["exit_thread"] => {
self.unfocused = false;
self.dirty = true;
@ -1116,14 +1188,35 @@ impl Component for ConversationsListing {
if !threads.thread_nodes.contains_key(&new_env_thread_hash) {
return false;
}
let thread_group = threads.thread_nodes[&new_env_thread_hash].thread_group();
let (&thread_hash, _): (&ThreadHash, &usize) = self
let thread_group = melib::find_thread_group(
&threads.thread_nodes,
threads.thread_nodes[&new_env_thread_hash].thread_group(),
);
let (&thread_hash, &row): (&ThreadHash, &usize) = self
.order
.iter()
.find(|(n, _)| threads.thread_nodes[&n].thread_group() == thread_group)
.find(|(n, _)| {
melib::find_thread_group(
&threads.thread_nodes,
threads.thread_nodes[&n].thread_group(),
) == thread_group
})
.unwrap();
self.row_updates.push(thread_hash);
let new_thread_hash = threads.root_set(row);
self.row_updates.push(new_thread_hash);
if let Some(row) = self.order.remove(&thread_hash) {
self.order.insert(new_thread_hash, row);
let selection_status = self.selection.remove(&thread_hash).unwrap();
self.selection.insert(new_thread_hash, selection_status);
for h in self.filtered_selection.iter_mut() {
if *h == thread_hash {
*h = new_thread_hash;
break;
}
}
}
self.dirty = true;
self.view
@ -1182,10 +1275,6 @@ impl Component for ConversationsListing {
| Action::Listing(a @ ListingAction::Delete)
if !self.unfocused =>
{
/* Iterate over selection if exists, else only over the envelope under the
* cursor. Using two iterators allows chaining them which results into a Chain
* type. We can't conditonally select either a slice iterator or a Map iterator
* because of the type system */
let is_selection_empty =
self.selection.values().cloned().any(std::convert::identity);
let i = [self.get_thread_under_cursor(self.cursor_pos.2, context)];
@ -1200,60 +1289,11 @@ impl Component for ConversationsListing {
let iter = sel_iter
.into_iter()
.flatten()
.chain(cursor_iter.into_iter().flatten());
for &i in iter {
let account = &mut context.accounts[self.cursor_pos.0];
let mut envs_to_set: StackVec<EnvelopeHash> = StackVec::new();
{
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
let mut stack = StackVec::new();
stack.push(i);
while let Some(thread_iter) = stack.pop() {
if let Some(env_hash) = threads[&thread_iter].message() {
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
continue;
}
envs_to_set.push(env_hash);
}
for c in 0..threads[&thread_iter].children().len() {
let c = threads[&thread_iter].children()[c];
stack.push(c);
}
}
}
for env_hash in envs_to_set {
match a {
ListingAction::SetSeen => {
let hash = account.get_env(&env_hash).hash();
let op = account.operation(hash);
let envelope: &mut Envelope =
&mut account.get_env_mut(&env_hash);
if let Err(e) = envelope.set_seen(op) {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(e.to_string()),
));
}
self.row_updates.push(i);
}
ListingAction::SetUnseen => {
let hash = account.get_env(&env_hash).hash();
let op = account.operation(hash);
let envelope: &mut Envelope =
&mut account.get_env_mut(&env_hash);
if let Err(e) = envelope.set_unseen(op) {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(e.to_string()),
));
}
self.row_updates.push(i);
}
ListingAction::Delete => { /* do nothing */ }
_ => unreachable!(),
}
}
.chain(cursor_iter.into_iter().flatten())
.cloned();
let stack = StackVec::from_iter(iter.into_iter());
for i in stack {
self.perform_action(context, i, a);
}
self.dirty = true;
for v in self.selection.values_mut() {
@ -1344,6 +1384,14 @@ impl Component for ConversationsListing {
Key::Char('v')
},
),
(
"set_seen",
if let Some(key) = config_map.get("set_seen") {
(*key).clone()
} else {
Key::Char('n')
},
),
]
.iter()
.cloned()

View File

@ -51,7 +51,8 @@ shortcut_key_values! { "compact_listing",
/// Shortcut listing for a mail listing in compact mode.
pub struct CompactListingShortcuts {
open_thread: Key |> "Open thread.",
exit_thread: Key |> "Exit thread view."
exit_thread: Key |> "Exit thread view.",
set_seen: Key |> "Set thread as seen."
}
}
@ -60,6 +61,7 @@ impl Default for CompactListingShortcuts {
CompactListingShortcuts {
open_thread: Key::Char('\n'),
exit_thread: Key::Char('i'),
set_seen: Key::Char('n'),
}
}
}