diff options
-rw-r--r-- | melib/src/mailbox/email/mod.rs | 6 | ||||
-rw-r--r-- | melib/src/mailbox/mod.rs | 9 | ||||
-rw-r--r-- | melib/src/mailbox/thread.rs | 14 | ||||
-rw-r--r-- | src/bin.rs | 2 | ||||
-rw-r--r-- | ui/src/components/mail/listing/mod.rs | 655 | ||||
-rw-r--r-- | ui/src/components/mail/listing/plain.rs | 559 | ||||
-rw-r--r-- | ui/src/components/mail/listing/thread.rs | 630 | ||||
-rw-r--r-- | ui/src/execute/actions.rs | 4 | ||||
-rw-r--r-- | ui/src/execute/mod.rs | 15 | ||||
-rw-r--r-- | ui/src/state.rs | 12 |
10 files changed, 1286 insertions, 620 deletions
diff --git a/melib/src/mailbox/email/mod.rs b/melib/src/mailbox/email/mod.rs index 66f54bea..e45fa08d 100644 --- a/melib/src/mailbox/email/mod.rs +++ b/melib/src/mailbox/email/mod.rs @@ -630,8 +630,7 @@ impl Envelope { s.refs.push(new_ref); } None => { - let mut v = Vec::new(); - v.push(new_ref); + let v = vec![new_ref]; self.references = Some(References { raw: "".into(), refs: v, @@ -645,10 +644,9 @@ impl Envelope { s.raw = new_val.into(); } None => { - let v = Vec::new(); self.references = Some(References { raw: new_val.into(), - refs: v, + refs: Vec::new(), }); } } diff --git a/melib/src/mailbox/mod.rs b/melib/src/mailbox/mod.rs index 010e4aa9..fcc42192 100644 --- a/melib/src/mailbox/mod.rs +++ b/melib/src/mailbox/mod.rs @@ -83,6 +83,15 @@ impl Mailbox { pub fn len(&self) -> usize { self.collection.len() } + pub fn thread_to_mail_mut(&mut self, i: usize) -> &mut Envelope { + self.collection + .envelopes + .entry(self.collection.threads.thread_to_mail(i)) + .or_default() + } + pub fn thread_to_mail(&self, i: usize) -> &Envelope { + &self.collection.envelopes[&self.collection.threads.thread_to_mail(i)] + } pub fn threaded_mail(&self, i: usize) -> EnvelopeHash { self.collection.threads.thread_to_mail(i) } diff --git a/melib/src/mailbox/thread.rs b/melib/src/mailbox/thread.rs index b1e02680..3972b660 100644 --- a/melib/src/mailbox/thread.rs +++ b/melib/src/mailbox/thread.rs @@ -151,6 +151,10 @@ impl ThreadNode { pub fn children(&self) -> &[usize] { &self.children } + + pub fn indentation(&self) -> usize { + self.indentation + } } #[derive(Clone, Debug, Default)] @@ -194,7 +198,7 @@ impl Threads { if !thread_nodes[*v].has_message() && thread_nodes[*v].children.len() == 1 { /* Do not promote the children if doing so would promote them to the root set * -- unless there is only one child, in which case, do. */ - root_set.push(*v); + root_set.push(thread_nodes[*v].children[0]); continue 'root_set; } @@ -334,7 +338,13 @@ fn link_threads( /* The index of the reference we are currently examining, start from current message */ let mut ref_ptr = t_idx; + let mut iasf = 0; for &refn in v.references().iter().rev() { + if iasf == 1 { + /*FIXME: Skips anything other than direct parents */ + continue; + } + iasf += 1; let r_id = String::from_utf8_lossy(refn.raw()).into(); let parent_id = if message_ids.contains_key(&r_id) { let parent_id = message_ids[&r_id]; @@ -368,7 +378,6 @@ fn link_threads( if thread_nodes[ref_ptr].parent.is_none() { thread_nodes[ref_ptr].parent = Some(thread_nodes.len() - 1); } - /* Can't avoid copy here since we have different lifetimes */ message_ids.insert(r_id, thread_nodes.len() - 1); thread_nodes.len() - 1 }; @@ -401,6 +410,7 @@ impl Index<usize> for Threads { .expect("thread index out of bounds") } } + fn node_build( idx: usize, thread_nodes: &mut Vec<ThreadNode>, @@ -62,7 +62,7 @@ fn main() { /* Register some reasonably useful interfaces */ let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts))); - let listing = PlainListing::new(); + let listing = listing::Listing::default(); let b = Entity::from(Box::new(listing)); let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true))])); let window = Entity::from(tabs); diff --git a/ui/src/components/mail/listing/mod.rs b/ui/src/components/mail/listing/mod.rs index be1a2ef8..d600d33a 100644 --- a/ui/src/components/mail/listing/mod.rs +++ b/ui/src/components/mail/listing/mod.rs @@ -24,621 +24,75 @@ use super::*; mod compact; pub use self::compact::*; -const MAX_COLS: usize = 500; +mod thread; +pub use self::thread::*; -/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a -/// `MailView`. -#[derive(Debug)] -pub struct PlainListing { - /// (x, y, z): x is accounts, y is folders, z is index inside a folder. - cursor_pos: (usize, usize, usize), - new_cursor_pos: (usize, usize, usize), - length: usize, - local_collection: Vec<EnvelopeHash>, - sort: (SortField, SortOrder), - subsort: (SortField, SortOrder), - /// Cache current view. - content: CellBuffer, - /// If we must redraw on next redraw event - dirty: bool, - /// If `self.view` exists or not. - unfocused: bool, - view: Option<MailView>, -} +mod plain; +pub use self::plain::*; -impl Default for PlainListing { - fn default() -> Self { - Self::new() - } +#[derive(Debug)] +pub enum Listing { + Plain(PlainListing), + Threaded(ThreadListing), + Compact(CompactListing), } -impl fmt::Display for PlainListing { +impl fmt::Display for Listing { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "mail") - } -} - -impl PlainListing { - /// Helper function to format entry strings for PlainListing */ - /* TODO: Make this configurable */ - fn make_entry_string(e: &Envelope, idx: usize) -> String { - format!( - "{} {} {}", - idx, - &e.datetime().format("%Y-%m-%d %H:%M:%S").to_string(), - e.subject() - ) - } - - pub fn new() -> Self { - let content = CellBuffer::new(0, 0, Cell::with_char(' ')); - PlainListing { - cursor_pos: (0, 1, 0), - new_cursor_pos: (0, 0, 0), - length: 0, - local_collection: Vec::new(), - sort: (Default::default(), Default::default()), - subsort: (Default::default(), Default::default()), - content, - dirty: true, - unfocused: false, - view: None, - } - } - /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has - /// chosen. - fn refresh_mailbox(&mut self, context: &mut Context) { - self.dirty = true; - self.cursor_pos.2 = 0; - self.new_cursor_pos.2 = 0; - self.cursor_pos.1 = self.new_cursor_pos.1; - self.cursor_pos.0 = self.new_cursor_pos.0; - - let threaded = context.accounts[self.cursor_pos.0] - .runtime_settings - .conf() - .threaded(); - // Inform State that we changed the current folder view. - context.replies.push_back(UIEvent { - id: 0, - event_type: UIEventType::RefreshMailbox((self.cursor_pos.0, self.cursor_pos.1)), - }); - // Get mailbox as a reference. - // - loop { - // TODO: Show progress visually - if context.accounts[self.cursor_pos.0] - .status(self.cursor_pos.1) - .is_ok() - { - break; - } - } - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); - - self.length = mailbox.len(); - self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' ')); - if self.length == 0 { - write_string_to_grid( - &format!("Folder `{}` is empty.", mailbox.folder.name()), - &mut self.content, - Color::Default, - Color::Default, - ((0, 0), (MAX_COLS - 1, 0)), - true, - ); - return; - } - - // Populate `CellBuffer` with every entry. - let mut idx = 0; - for y in 0..=self.length { - if idx >= self.length { - /* No more entries left, so fill the rest of the area with empty space */ - clear_area(&mut self.content, ((0, y), (MAX_COLS - 1, self.length))); - break; - } - /* Write an entire line for each envelope entry. */ - self.local_collection = mailbox.collection.keys().map(|v| *v).collect(); - let sort = self.sort; - self.local_collection.sort_by(|a, b| match sort { - (SortField::Date, SortOrder::Desc) => { - let ma = &mailbox.collection[a]; - let mb = &mailbox.collection[b]; - mb.date().cmp(&ma.date()) - } - (SortField::Date, SortOrder::Asc) => { - let ma = &mailbox.collection[a]; - let mb = &mailbox.collection[b]; - ma.date().cmp(&mb.date()) - } - (SortField::Subject, SortOrder::Desc) => { - let ma = &mailbox.collection[a]; - let mb = &mailbox.collection[b]; - ma.subject().cmp(&mb.subject()) - } - (SortField::Subject, SortOrder::Asc) => { - let ma = &mailbox.collection[a]; - let mb = &mailbox.collection[b]; - mb.subject().cmp(&ma.subject()) - } - }); - let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]]; - - let fg_color = if !envelope.is_seen() { - Color::Byte(0) - } else { - Color::Default - }; - let bg_color = if !envelope.is_seen() { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - let (x, y) = write_string_to_grid( - &PlainListing::make_entry_string(envelope, idx), - &mut self.content, - fg_color, - bg_color, - ((0, y), (MAX_COLS - 1, y)), - false, - ); - - for x in x..MAX_COLS { - self.content[(x, y)].set_ch(' '); - self.content[(x, y)].set_bg(bg_color); - } - - idx += 1; + match self { + Listing::Compact(l) => write!(f, "{}", l), + Listing::Plain(l) => write!(f, "{}", l), + Listing::Threaded(l) => write!(f, "{}", l), } } +} - fn highlight_line_self(&mut self, idx: usize, context: &Context) { - let threaded = context.accounts[self.cursor_pos.0] - .runtime_settings - .conf() - .threaded(); - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); - let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]]; - - let fg_color = if !envelope.is_seen() { - Color::Byte(0) - } else { - Color::Default - }; - let bg_color = if !envelope.is_seen() { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - change_colors( - &mut self.content, - ((0, idx), (MAX_COLS - 1, idx)), - fg_color, - bg_color, - ); - } - - fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { - let threaded = context.accounts[self.cursor_pos.0] - .runtime_settings - .conf() - .threaded(); - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); - let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]]; - - let fg_color = if !envelope.is_seen() { - Color::Byte(0) - } else { - Color::Default - }; - let bg_color = if self.cursor_pos.2 == idx { - Color::Byte(246) - } else if !envelope.is_seen() { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - change_colors(grid, area, fg_color, bg_color); - } - - /// Draw the list of `Envelope`s. - fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if self.cursor_pos.1 != self.new_cursor_pos.1 { - self.refresh_mailbox(context); - } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - if self.length == 0 { - clear_area(grid, area); - copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0))); - context.dirty_areas.push_back(area); - return; - } - let rows = get_y(bottom_right) - get_y(upper_left) + 1; - let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); - let page_no = (self.new_cursor_pos.2).wrapping_div(rows); - - let top_idx = page_no * rows; - - /* If cursor position has changed, remove the highlight from the previous position and - * apply it in the new one. */ - if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { - let old_cursor_pos = self.cursor_pos; - self.cursor_pos = self.new_cursor_pos; - for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] { - if *idx >= self.length { - continue; //bounds check - } - let new_area = ( - set_y(upper_left, get_y(upper_left) + (*idx % rows)), - set_y(bottom_right, get_y(upper_left) + (*idx % rows)), - ); - self.highlight_line(grid, new_area, *idx, context); - context.dirty_areas.push_back(new_area); - } - return; - } else if self.cursor_pos != self.new_cursor_pos { - self.cursor_pos = self.new_cursor_pos; - } - - /* Page_no has changed, so draw new page */ - copy_area( - grid, - &self.content, - area, - ((0, top_idx), (MAX_COLS - 1, self.length)), - ); - self.highlight_line( - grid, - ( - set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), - set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), - ), - self.cursor_pos.2, - context, - ); - context.dirty_areas.push_back(area); - } - - fn make_thread_entry( - envelope: &Envelope, - idx: usize, - indent: usize, - node_idx: usize, - nodes: &Threads, - indentations: &[bool], - idx_width: usize, - //op: Box<BackendOp>, - ) -> String { - let has_sibling = nodes.has_sibling(node_idx); - let has_parent = nodes[node_idx].has_parent(); - let show_subject = nodes[node_idx].show_subject(); - - let mut s = format!( - "{}{}{} ", - idx, - " ".repeat(idx_width + 2 - (idx.to_string().chars().count())), - PlainListing::format_date(&envelope) - ); - for i in 0..indent { - if indentations.len() > i && indentations[i] { - s.push('│'); - } else { - s.push(' '); - } - if i > 0 { - s.push(' '); - } - } - if indent > 0 { - if has_sibling && has_parent { - s.push('├'); - } else if has_sibling { - s.push('┬'); - } else { - s.push('└'); - } - s.push('─'); - s.push('>'); - } - - if show_subject { - s.push_str(&format!("{:.85}", envelope.subject())); - } - /* TODO Very slow since we have to build all attachments - let attach_count = envelope.body(op).count_attachments(); - if attach_count > 1 { - s.push_str(&format!(" {}∞ ", attach_count - 1)); - } - */ - s - } - fn format_date(envelope: &Envelope) -> String { - let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date()); - let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap(); - match now.as_secs() { - n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)), - n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)), - n if n < 4 * 24 * 60 * 60 => { - format!("{} days ago{}", n / (24 * 60 * 60), " ".repeat(9)) - } - _ => envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string(), - } +impl Default for Listing { + fn default() -> Self { + Listing::Compact(Default::default()) } } -impl Component for PlainListing { +impl Component for Listing { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if !self.unfocused { - if !self.is_dirty() { - return; - } - self.dirty = false; - /* Draw the entire list */ - self.draw_list(grid, area, context); - } else { - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - if self.length == 0 && self.dirty { - clear_area(grid, area); - context.dirty_areas.push_back(area); - } - - /* Render the mail body in a pager, basically copy what HSplit does */ - let total_rows = get_y(bottom_right) - get_y(upper_left); - let pager_ratio = context.runtime_settings.pager.pager_ratio; - let bottom_entity_rows = (pager_ratio * total_rows) / 100; - - if bottom_entity_rows > total_rows { - clear_area(grid, area); - context.dirty_areas.push_back(area); - return; - } - /* Mark message as read */ - let idx = self.cursor_pos.2; - let must_highlight = { - if self.length == 0 { - false - } else { - let threaded = context.accounts[self.cursor_pos.0] - .runtime_settings - .conf() - .threaded(); - let account = &mut context.accounts[self.cursor_pos.0]; - let (hash, is_seen) = { - let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap(); - let envelope: &mut Envelope = &mut mailbox - .collection - .entry(self.local_collection[idx]) - .or_default(); - (envelope.hash(), envelope.is_seen()) - }; - if !is_seen { - let op = { - let backend = &account.backend; - backend.operation(hash) - }; - let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap(); - let envelope: &mut Envelope = &mut mailbox - .collection - .entry(self.local_collection[idx]) - .or_default(); - envelope.set_seen(op).unwrap(); - true - } else { - false - } - } - }; - if must_highlight { - self.highlight_line_self(idx, context); - } - let mid = get_y(upper_left) + total_rows - bottom_entity_rows; - self.draw_list( - grid, - ( - upper_left, - (get_x(bottom_right), get_y(upper_left) + mid - 1), - ), - context, - ); - if self.length == 0 { - self.dirty = false; - return; - } - { - /* TODO: Move the box drawing business in separate functions */ - if get_x(upper_left) > 0 && grid[(get_x(upper_left) - 1, mid)].ch() == VERT_BOUNDARY - { - grid[(get_x(upper_left) - 1, mid)].set_ch(LIGHT_VERTICAL_AND_RIGHT); - } - - for i in get_x(upper_left)..=get_x(bottom_right) { - grid[(i, mid)].set_ch('─'); - } - context - .dirty_areas - .push_back((set_y(upper_left, mid), set_y(bottom_right, mid))); - } - // TODO: Make headers view configurable - - if !self.dirty { - if let Some(v) = self.view.as_mut() { - v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context); - } - return; - } - { - let threaded = context.accounts[self.cursor_pos.0] - .runtime_settings - .conf() - .threaded(); - let account = &context.accounts[self.cursor_pos.0]; - let mailbox = &account[self.cursor_pos.1].as_ref().unwrap(); - let mut coordinates = self.cursor_pos; - let coordinates = ( - coordinates.0, - coordinates.1, - self.local_collection[self.cursor_pos.2], - ); - self.view = Some(MailView::new(coordinates, None, None)); - } - self.view.as_mut().unwrap().draw( - grid, - (set_y(upper_left, mid + 1), bottom_right), - context, - ); - self.dirty = false; + match self { + Listing::Compact(l) => l.draw(grid, area, context), + Listing::Plain(l) => l.draw(grid, area, context), + Listing::Threaded(l) => l.draw(grid, area, context), } } fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { - if let Some(ref mut v) = self.view { - if v.process_event(event, context) { - return true; - } + if match self { + Listing::Plain(l) => l.process_event(event, context), + Listing::Compact(l) => l.process_event(event, context), + Listing::Threaded(l) => l.process_event(event, context), + } { + return true; } + match event.event_type { - UIEventType::Input(Key::Up) => { - if self.cursor_pos.2 > 0 { - self.new_cursor_pos.2 -= 1; - self.dirty = true; - } - return true; - } - UIEventType::Input(Key::Down) => { - if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 { - self.new_cursor_pos.2 += 1; - self.dirty = true; - } - return true; - } - UIEventType::Input(Key::Char('\n')) if !self.unfocused => { - self.unfocused = true; - self.dirty = true; - return true; - } - UIEventType::Input(Key::Char('m')) if !self.unfocused => { - context.replies.push_back(UIEvent { - id: 0, - event_type: UIEventType::Action(Tab(NewDraft)), - }); - return true; - } - UIEventType::Input(Key::Char('i')) if self.unfocused => { - self.unfocused = false; - self.dirty = true; - self.view = None; - return true; - } - UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => { - let folder_length = context.accounts[self.cursor_pos.0].len(); - let accounts_length = context.accounts.len(); - match k { - 'J' if folder_length > 0 => { - if self.new_cursor_pos.1 < folder_length - 1 { - self.new_cursor_pos.1 = self.cursor_pos.1 + 1; - self.dirty = true; - self.refresh_mailbox(context); - } else if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 - { - self.new_cursor_pos.0 = self.cursor_pos.0 + 1; - self.new_cursor_pos.1 = 0; - self.dirty = true; - self.refresh_mailbox(context); - } - } - 'K' => { - if self.cursor_pos.1 > 0 { - self.new_cursor_pos.1 = self.cursor_pos.1 - 1; - self.dirty = true; - self.refresh_mailbox(context); - } else if self.cursor_pos.0 > 0 { - self.new_cursor_pos.0 = self.cursor_pos.0 - 1; - self.new_cursor_pos.1 = 0; - self.dirty = true; - self.refresh_mailbox(context); - } - } - _ => {} - } - return true; - } - UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => { - let accounts_length = context.accounts.len(); - match k { - 'h' if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 => { - self.new_cursor_pos.0 = self.cursor_pos.0 + 1; - self.new_cursor_pos.1 = 0; - self.dirty = true; - self.refresh_mailbox(context); - } - 'l' if self.cursor_pos.0 > 0 => { - self.new_cursor_pos.0 = self.cursor_pos.0 - 1; - self.new_cursor_pos.1 = 0; - self.dirty = true; - self.refresh_mailbox(context); - } - _ => {} - } - return true; - } - UIEventType::RefreshMailbox(_) => { - self.dirty = true; - self.view = None; - } - UIEventType::MailboxUpdate((ref idxa, ref idxf)) => { - if *idxa == self.new_cursor_pos.0 && *idxf == self.new_cursor_pos.1 { - self.dirty = true; - self.refresh_mailbox(context); - } - } - UIEventType::ChangeMode(UIMode::Normal) => { - self.dirty = true; - } - UIEventType::Resize => { - self.dirty = true; - } + UIEventType::Resize => self.set_dirty(), UIEventType::Action(ref action) => match action { - Action::Listing(ListingAction::ToggleThreaded) => { - context.accounts[self.cursor_pos.0] - .runtime_settings - .conf_mut() - .toggle_threaded(); - self.refresh_mailbox(context); - self.dirty = true; - return true; - } - Action::ViewMailbox(idx) => { - self.new_cursor_pos.1 = *idx; - self.dirty = true; - self.refresh_mailbox(context); + Action::Listing(ListingAction::SetPlain) => { + if let Listing::Plain(_) = self { + return true; + } + *self = Listing::Plain(PlainListing::default()); return true; } - Action::SubSort(field, order) => { - eprintln!("SubSort {:?} , {:?}", field, order); - self.subsort = (*field, *order); - self.dirty = true; - self.refresh_mailbox(context); + Action::Listing(ListingAction::SetThreaded) => { + if let Listing::Threaded(_) = self { + return true; + } + self.set_dirty(); + *self = Listing::Threaded(ThreadListing::default()); return true; } - Action::Sort(field, order) => { - eprintln!("Sort {:?} , {:?}", field, order); - self.sort = (*field, *order); - self.dirty = true; - self.refresh_mailbox(context); + Action::Listing(ListingAction::SetCompact) => { + if let Listing::Compact(_) = self { + return true; + } + *self = Listing::Compact(CompactListing::default()); return true; } _ => {} @@ -648,12 +102,17 @@ impl Component for PlainListing { false } fn is_dirty(&self) -> bool { - self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false) + match self { + Listing::Compact(l) => l.is_dirty(), + Listing::Plain(l) => l.is_dirty(), + Listing::Threaded(l) => l.is_dirty(), + } } fn set_dirty(&mut self) { - if let Some(p) = self.view.as_mut() { - p.set_dirty(); - }; - self.dirty = true; + match self { + Listing::Compact(l) => l.set_dirty(), + Listing::Plain(l) => l.set_dirty(), + Listing::Threaded(l) => l.set_dirty(), + } } } diff --git a/ui/src/components/mail/listing/plain.rs b/ui/src/components/mail/listing/plain.rs new file mode 100644 index 00000000..42369fc6 --- /dev/null +++ b/ui/src/components/mail/listing/plain.rs @@ -0,0 +1,559 @@ +/* + * meli - ui crate. + * + * 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 super::*; + +const MAX_COLS: usize = 500; + +/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a +/// `MailView`. +#[derive(Debug)] +pub struct PlainListing { + /// (x, y, z): x is accounts, y is folders, z is index inside a folder. + cursor_pos: (usize, usize, usize), + new_cursor_pos: (usize, usize, usize), + length: usize, + local_collection: Vec<EnvelopeHash>, + sort: (SortField, SortOrder), + subsort: (SortField, SortOrder), + /// Cache current view. + content: CellBuffer, + /// If we must redraw on next redraw event + dirty: bool, + /// If `self.view` exists or not. + unfocused: bool, + view: Option<MailView>, +} + +impl Default for PlainListing { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Display for PlainListing { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "mail") + } +} + +impl PlainListing { + /// Helper function to format entry strings for PlainListing */ + /* TODO: Make this configurable */ + fn make_entry_string(e: &Envelope, idx: usize) -> String { + format!( + "{} {} {}", + idx, + &e.datetime().format("%Y-%m-%d %H:%M:%S").to_string(), + e.subject() + ) + } + + pub fn new() -> Self { + let content = CellBuffer::new(0, 0, Cell::with_char(' ')); + PlainListing { + cursor_pos: (0, 1, 0), + new_cursor_pos: (0, 0, 0), + length: 0, + local_collection: Vec::new(), + sort: (Default::default(), Default::default()), + subsort: (Default::default(), Default::default()), + content, + dirty: true, + unfocused: false, + view: None, + } + } + /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has + /// chosen. + fn refresh_mailbox(&mut self, context: &mut Context) { + self.dirty = true; + self.cursor_pos.2 = 0; + self.new_cursor_pos.2 = 0; + self.cursor_pos.1 = self.new_cursor_pos.1; + self.cursor_pos.0 = self.new_cursor_pos.0; + + // Inform State that we changed the current folder view. + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::RefreshMailbox((self.cursor_pos.0, self.cursor_pos.1)), + }); + // Get mailbox as a reference. + // + loop { + // TODO: Show progress visually + if context.accounts[self.cursor_pos.0] + .status(self.cursor_pos.1) + .is_ok() + { + break; + } + } + let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] + .as_ref() + .unwrap(); + + self.length = mailbox.len(); + self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' ')); + if self.length == 0 { + write_string_to_grid( + &format!("Folder `{}` is empty.", mailbox.folder.name()), |