You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1096 lines
42KB

  1. /*
  2. * meli - ui crate.
  3. *
  4. * Copyright 2017-2018 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. use super::*;
  22. use crate::components::utilities::PageMovement;
  23. use std::cmp;
  24. const INDENTATION_COLORS: &'static [u8] = &[
  25. 69, // CornflowerBlue
  26. 196, // Red1
  27. 175, // Pink3
  28. 220, // Gold1
  29. 172, // Orange3
  30. 072, // CadetBlue
  31. ];
  32. #[derive(Debug, Clone)]
  33. struct ThreadEntry {
  34. index: (usize, ThreadHash, usize),
  35. /// (indentation, thread_node index, line number in listing)
  36. indentation: usize,
  37. msg_hash: EnvelopeHash,
  38. seen: bool,
  39. dirty: bool,
  40. hidden: bool,
  41. heading: String,
  42. }
  43. #[derive(Debug, Default)]
  44. pub struct ThreadView {
  45. new_cursor_pos: usize,
  46. cursor_pos: usize,
  47. expanded_pos: usize,
  48. new_expanded_pos: usize,
  49. reversed: bool,
  50. coordinates: (usize, usize, usize),
  51. mailview: MailView,
  52. show_mailview: bool,
  53. entries: Vec<ThreadEntry>,
  54. visible_entries: Vec<Vec<usize>>,
  55. movement: Option<PageMovement>,
  56. dirty: bool,
  57. content: CellBuffer,
  58. initiated: bool,
  59. id: ComponentId,
  60. }
  61. impl ThreadView {
  62. const DESCRIPTION: &'static str = "thread view";
  63. /*
  64. * coordinates: (account index, mailbox index, root set thread_node index)
  65. * expanded_hash: optional position of expanded entry when we render the threadview. Default
  66. * expanded message is the last one.
  67. * context: current context
  68. */
  69. pub fn new(
  70. coordinates: (usize, usize, usize),
  71. expanded_hash: Option<ThreadHash>,
  72. context: &Context,
  73. ) -> Self {
  74. let mut view = ThreadView {
  75. reversed: false,
  76. initiated: false,
  77. coordinates,
  78. mailview: MailView::default(),
  79. show_mailview: true,
  80. entries: Vec::new(),
  81. cursor_pos: 1,
  82. new_cursor_pos: 0,
  83. dirty: true,
  84. id: ComponentId::new_v4(),
  85. ..Default::default()
  86. };
  87. view.initiate(expanded_hash, context);
  88. view.new_cursor_pos = view.new_expanded_pos;
  89. view
  90. }
  91. pub fn update(&mut self, context: &Context) {
  92. if self.entries.is_empty() {
  93. return;
  94. }
  95. let old_entries = self.entries.clone();
  96. let old_focused_entry = if self.entries.len() > self.cursor_pos {
  97. Some(self.entries.remove(self.cursor_pos))
  98. } else {
  99. None
  100. };
  101. let old_expanded_entry = if self.entries.len() > self.expanded_pos {
  102. Some(self.entries.remove(self.expanded_pos))
  103. } else {
  104. None
  105. };
  106. let expanded_hash = old_expanded_entry.as_ref().map(|e| e.index.1);
  107. self.initiate(expanded_hash, context);
  108. let mut old_cursor = 0;
  109. let mut new_cursor = 0;
  110. loop {
  111. if old_cursor >= old_entries.len() || new_cursor >= self.entries.len() {
  112. break;
  113. }
  114. if old_entries[old_cursor].msg_hash == self.entries[new_cursor].msg_hash
  115. || old_entries[old_cursor].index == self.entries[new_cursor].index
  116. || old_entries[old_cursor].heading == self.entries[new_cursor].heading
  117. {
  118. self.entries[new_cursor].hidden = old_entries[old_cursor].hidden;
  119. old_cursor += 1;
  120. new_cursor += 1;
  121. } else {
  122. new_cursor += 1;
  123. }
  124. self.recalc_visible_entries();
  125. }
  126. if let Some(old_focused_entry) = old_focused_entry {
  127. if let Some(new_entry_idx) = self.entries.iter().position(|e| {
  128. e.msg_hash == old_focused_entry.msg_hash
  129. || (e.index.1 == old_focused_entry.index.1
  130. && e.index.2 == old_focused_entry.index.2)
  131. }) {
  132. self.cursor_pos = new_entry_idx;
  133. }
  134. }
  135. if let Some(old_expanded_entry) = old_expanded_entry {
  136. if let Some(new_entry_idx) = self.entries.iter().position(|e| {
  137. e.msg_hash == old_expanded_entry.msg_hash
  138. || (e.index.1 == old_expanded_entry.index.1
  139. && e.index.2 == old_expanded_entry.index.2)
  140. }) {
  141. self.expanded_pos = new_entry_idx;
  142. }
  143. }
  144. self.set_dirty();
  145. }
  146. fn initiate(&mut self, expanded_hash: Option<ThreadHash>, context: &Context) {
  147. /* stack to push thread messages in order in order to pop and print them later */
  148. let account = &context.accounts[self.coordinates.0];
  149. let mailbox = &account[self.coordinates.1].as_ref().unwrap();
  150. let threads = &account.collection.threads[&mailbox.folder.hash()];
  151. let thread_iter = threads.thread_iter(self.coordinates.2);
  152. self.entries.clear();
  153. for (line, (ind, thread_hash)) in thread_iter.enumerate() {
  154. let entry = if let Some(msg_hash) = threads.thread_nodes()[&thread_hash].message() {
  155. let seen: bool = account.get_env(&msg_hash).is_seen();
  156. self.make_entry((ind, thread_hash, line), msg_hash, seen)
  157. } else {
  158. continue;
  159. };
  160. self.entries.push(entry);
  161. match expanded_hash {
  162. Some(expanded_hash) if expanded_hash == thread_hash => {
  163. self.new_expanded_pos = self.entries.len().saturating_sub(1);
  164. self.expanded_pos = self.new_expanded_pos + 1;
  165. }
  166. _ => {}
  167. }
  168. }
  169. if expanded_hash.is_none() {
  170. self.new_expanded_pos = self.entries.len().saturating_sub(1);
  171. self.expanded_pos = self.new_expanded_pos + 1;
  172. }
  173. let height = 2 * self.entries.len() + 1;
  174. let mut width = 0;
  175. let mut highlight_reply_subjects: Vec<Option<usize>> =
  176. Vec::with_capacity(self.entries.len());
  177. for e in &mut self.entries {
  178. let envelope: &Envelope = &context.accounts[self.coordinates.0].get_env(&e.msg_hash);
  179. let thread_node = &threads.thread_nodes()[&e.index.1];
  180. let string = if thread_node.show_subject() {
  181. let subject = envelope.subject();
  182. highlight_reply_subjects.push(Some(subject.grapheme_width()));
  183. format!(
  184. " {} - {} {}{}",
  185. envelope.date_as_str(),
  186. envelope.field_from_to_string(),
  187. envelope.subject(),
  188. if envelope.has_attachments() {
  189. " 📎"
  190. } else {
  191. ""
  192. },
  193. )
  194. } else {
  195. highlight_reply_subjects.push(None);
  196. format!(
  197. " {} - {}{}",
  198. envelope.date_as_str(),
  199. envelope.field_from_to_string(),
  200. if envelope.has_attachments() {
  201. " 📎"
  202. } else {
  203. ""
  204. },
  205. )
  206. };
  207. e.heading = string;
  208. width = cmp::max(width, e.index.0 * 4 + e.heading.grapheme_width() + 2);
  209. }
  210. let mut content = CellBuffer::new(width, height, Cell::default());
  211. if self.reversed {
  212. for (y, e) in self.entries.iter().rev().enumerate() {
  213. /* Box character drawing stuff */
  214. if y > 0 && content.get_mut(e.index.0 * 4, 2 * y - 1).is_some() {
  215. let index = (e.index.0 * 4, 2 * y - 1);
  216. if content[index].ch() == ' ' {
  217. let mut ctr = 1;
  218. while content.get(e.index.0 * 4 + ctr, 2 * y - 1).is_some() {
  219. if content[(e.index.0 * 4 + ctr, 2 * y - 1)].ch() != ' ' {
  220. break;
  221. }
  222. set_and_join_box(
  223. &mut content,
  224. (e.index.0 * 4 + ctr, 2 * y - 1),
  225. HORZ_BOUNDARY,
  226. );
  227. ctr += 1;
  228. }
  229. set_and_join_box(&mut content, index, HORZ_BOUNDARY);
  230. }
  231. }
  232. write_string_to_grid(
  233. &e.heading,
  234. &mut content,
  235. if e.seen {
  236. Color::Default
  237. } else {
  238. Color::Byte(0)
  239. },
  240. if e.seen {
  241. Color::Default
  242. } else {
  243. Color::Byte(251)
  244. },
  245. (
  246. (e.index.0 * 4 + 1, 2 * y),
  247. (e.index.0 * 4 + e.heading.grapheme_width() + 1, height - 1),
  248. ),
  249. true,
  250. );
  251. {
  252. let envelope: &Envelope =
  253. &context.accounts[self.coordinates.0].get_env(&e.msg_hash);
  254. if envelope.has_attachments() {
  255. content[(e.index.0 * 4 + e.heading.grapheme_width(), 2 * y)]
  256. .set_fg(Color::Byte(103));
  257. }
  258. }
  259. if let Some(len) = highlight_reply_subjects[y] {
  260. let index = e.index.0 * 4 + 1 + e.heading.grapheme_width() - len;
  261. let area = ((index, 2 * y), (width - 2, 2 * y));
  262. let fg_color = Color::Byte(33);
  263. let bg_color = Color::Default;
  264. change_colors(&mut content, area, fg_color, bg_color);
  265. }
  266. set_and_join_box(&mut content, (e.index.0 * 4, 2 * y), VERT_BOUNDARY);
  267. set_and_join_box(&mut content, (e.index.0 * 4, 2 * y + 1), VERT_BOUNDARY);
  268. for i in ((e.index.0 * 4) + 1)..width - 1 {
  269. set_and_join_box(&mut content, (i, 2 * y + 1), HORZ_BOUNDARY);
  270. }
  271. set_and_join_box(&mut content, (width - 1, 2 * y), VERT_BOUNDARY);
  272. set_and_join_box(&mut content, (width - 1, 2 * y + 1), VERT_BOUNDARY);
  273. }
  274. } else {
  275. for (y, e) in self.entries.iter().enumerate() {
  276. /* Box character drawing stuff */
  277. let mut x = 0;
  278. for i in 0..e.index.0 {
  279. let color = INDENTATION_COLORS[(i).wrapping_rem(INDENTATION_COLORS.len())];
  280. change_colors(
  281. &mut content,
  282. ((x, 2 * y), (x + 3, 2 * y + 1)),
  283. Color::Default,
  284. Color::Byte(color),
  285. );
  286. x += 4;
  287. }
  288. if y > 0 && content.get_mut(e.index.0 * 4, 2 * y - 1).is_some() {
  289. let index = (e.index.0 * 4, 2 * y - 1);
  290. if content[index].ch() == ' ' {
  291. let mut ctr = 1;
  292. content[(e.index.0 * 4, 2 * y - 1)].set_bg(Color::Default);
  293. while content.get(e.index.0 * 4 + ctr, 2 * y - 1).is_some() {
  294. content[(e.index.0 * 4 + ctr, 2 * y - 1)].set_bg(Color::Default);
  295. if content[(e.index.0 * 4 + ctr, 2 * y - 1)].ch() != ' ' {
  296. break;
  297. }
  298. set_and_join_box(
  299. &mut content,
  300. (e.index.0 * 4 + ctr, 2 * y - 1),
  301. HORZ_BOUNDARY,
  302. );
  303. ctr += 1;
  304. }
  305. set_and_join_box(&mut content, index, HORZ_BOUNDARY);
  306. }
  307. }
  308. write_string_to_grid(
  309. &e.heading,
  310. &mut content,
  311. if e.seen {
  312. Color::Default
  313. } else {
  314. Color::Byte(0)
  315. },
  316. if e.seen {
  317. Color::Default
  318. } else {
  319. Color::Byte(251)
  320. },
  321. (
  322. (e.index.0 * 4 + 1, 2 * y),
  323. (e.index.0 * 4 + e.heading.grapheme_width() + 1, height - 1),
  324. ),
  325. false,
  326. );
  327. {
  328. let envelope: &Envelope =
  329. &context.accounts[self.coordinates.0].get_env(&e.msg_hash);
  330. if envelope.has_attachments() {
  331. content[(e.index.0 * 4 + e.heading.grapheme_width(), 2 * y)]
  332. .set_fg(Color::Byte(103));
  333. }
  334. }
  335. if let Some(_len) = highlight_reply_subjects[y] {
  336. let index = e.index.0 * 4 + 1;
  337. let area = ((index, 2 * y), (width - 2, 2 * y));
  338. let fg_color = Color::Byte(33);
  339. let bg_color = Color::Default;
  340. change_colors(&mut content, area, fg_color, bg_color);
  341. }
  342. set_and_join_box(&mut content, (e.index.0 * 4, 2 * y), VERT_BOUNDARY);
  343. set_and_join_box(&mut content, (e.index.0 * 4, 2 * y + 1), VERT_BOUNDARY);
  344. for i in ((e.index.0 * 4) + 1)..width - 1 {
  345. set_and_join_box(&mut content, (i, 2 * y + 1), HORZ_BOUNDARY);
  346. }
  347. set_and_join_box(&mut content, (width - 1, 2 * y), VERT_BOUNDARY);
  348. set_and_join_box(&mut content, (width - 1, 2 * y + 1), VERT_BOUNDARY);
  349. }
  350. for y in 0..height - 1 {
  351. set_and_join_box(&mut content, (width - 1, y), VERT_BOUNDARY);
  352. }
  353. }
  354. self.content = content;
  355. self.visible_entries = vec![(0..self.entries.len()).collect()];
  356. }
  357. fn make_entry(
  358. &mut self,
  359. i: (usize, ThreadHash, usize),
  360. msg_hash: EnvelopeHash,
  361. seen: bool,
  362. ) -> ThreadEntry {
  363. let (ind, _, _) = i;
  364. ThreadEntry {
  365. index: i,
  366. indentation: ind,
  367. msg_hash,
  368. seen,
  369. dirty: true,
  370. hidden: false,
  371. heading: String::new(),
  372. }
  373. }
  374. fn highlight_line(&self, grid: &mut CellBuffer, dest_area: Area, src_area: Area, idx: usize) {
  375. let visibles: Vec<&usize> = self
  376. .visible_entries
  377. .iter()
  378. .flat_map(|ref v| v.iter())
  379. .collect();
  380. if idx == *visibles[self.cursor_pos] {
  381. let fg_color = Color::Default;
  382. let bg_color = Color::Byte(246);
  383. change_colors(grid, dest_area, fg_color, bg_color);
  384. return;
  385. }
  386. copy_area(grid, &self.content, dest_area, src_area);
  387. }
  388. /// draw the list
  389. fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  390. /* Make space on the left for the scrollbar */
  391. let mut upper_left = pos_inc(upper_left!(area), (1, 0));
  392. let bottom_right = bottom_right!(area);
  393. let (width, height) = self.content.size();
  394. if height == 0 {
  395. clear_area(grid, area);
  396. context.dirty_areas.push_back(area);
  397. return;
  398. }
  399. let rows = (get_y(bottom_right) - get_y(upper_left)).wrapping_div(2);
  400. if let Some(mvm) = self.movement.take() {
  401. match mvm {
  402. PageMovement::PageUp => {
  403. self.new_cursor_pos = self.new_cursor_pos.saturating_sub(rows);
  404. }
  405. PageMovement::PageDown => {
  406. if self.new_cursor_pos + rows + 1 < height {
  407. self.new_cursor_pos += rows;
  408. } else {
  409. self.new_cursor_pos = (height / rows) * rows;
  410. }
  411. }
  412. PageMovement::Home => {
  413. self.new_cursor_pos = 0;
  414. }
  415. PageMovement::End => {
  416. self.new_cursor_pos = (height / rows) * rows;
  417. }
  418. }
  419. }
  420. if self.new_cursor_pos >= self.entries.len() {
  421. self.new_cursor_pos = self.entries.len().saturating_sub(1);
  422. }
  423. let prev_page_no = (self.cursor_pos).wrapping_div(rows);
  424. let page_no = (self.new_cursor_pos).wrapping_div(rows);
  425. let top_idx = page_no * rows;
  426. /* This closure (written for code clarity, should be inlined by the compiler) returns the
  427. * **line** of an entry in the ThreadView grid. */
  428. let get_entry_area = |idx: usize, entries: &[ThreadEntry]| {
  429. let entries = &entries;
  430. let visual_indentation = entries[idx].index.0 * 4;
  431. (
  432. (visual_indentation, 2 * idx),
  433. (
  434. visual_indentation + entries[idx].heading.grapheme_width() + 1,
  435. 2 * idx,
  436. ),
  437. )
  438. };
  439. if self.dirty || (page_no != prev_page_no) {
  440. if page_no != prev_page_no {
  441. clear_area(grid, area);
  442. }
  443. let visibles: Vec<&usize> = self
  444. .visible_entries
  445. .iter()
  446. .flat_map(|ref v| v.iter())
  447. .collect();
  448. if rows >= visibles.len() {
  449. upper_left = pos_dec(upper_left!(area), (1, 0));
  450. }
  451. for (visible_entry_counter, v) in visibles.iter().skip(top_idx).take(rows).enumerate() {
  452. if visible_entry_counter >= rows {
  453. break;
  454. }
  455. let idx = *v;
  456. copy_area(
  457. grid,
  458. &self.content,
  459. (
  460. pos_inc(upper_left, (0, 2 * visible_entry_counter)), // dest_area
  461. bottom_right,
  462. ),
  463. (
  464. (0, 2 * idx), //src_area
  465. (width - 1, 2 * idx + 1),
  466. ),
  467. );
  468. }
  469. /* If cursor position has changed, remove the highlight from the previous position and
  470. * apply it in the new one. */
  471. self.cursor_pos = self.new_cursor_pos;
  472. if self.cursor_pos + 1 > visibles.len() {
  473. self.cursor_pos = visibles.len().saturating_sub(1);
  474. }
  475. let idx = *visibles[self.cursor_pos];
  476. let src_area = { get_entry_area(idx, &self.entries) };
  477. let visual_indentation = self.entries[idx].indentation * 4;
  478. let dest_area = (
  479. pos_inc(
  480. upper_left,
  481. (visual_indentation, 2 * (self.cursor_pos - top_idx)),
  482. ),
  483. (
  484. cmp::min(
  485. get_x(bottom_right),
  486. get_x(upper_left)
  487. + visual_indentation
  488. + self.entries[idx].heading.grapheme_width()
  489. + 1,
  490. ),
  491. cmp::min(
  492. get_y(bottom_right),
  493. get_y(upper_left) + 2 * (self.cursor_pos - top_idx),
  494. ),
  495. ),
  496. );
  497. self.highlight_line(grid, dest_area, src_area, idx);
  498. if rows < visibles.len() {
  499. ScrollBar::draw(
  500. ScrollBar::default(),
  501. grid,
  502. (
  503. upper_left!(area),
  504. set_x(bottom_right, get_x(upper_left!(area)) + 1),
  505. ),
  506. self.cursor_pos,
  507. rows,
  508. visibles.len(),
  509. );
  510. }
  511. self.dirty = false;
  512. context.dirty_areas.push_back(area);
  513. } else {
  514. let old_cursor_pos = self.cursor_pos;
  515. self.cursor_pos = self.new_cursor_pos;
  516. /* If cursor position has changed, remove the highlight from the previous position and
  517. * apply it in the new one. */
  518. let visibles: Vec<&usize> = self
  519. .visible_entries
  520. .iter()
  521. .flat_map(|ref v| v.iter())
  522. .collect();
  523. if rows >= visibles.len() {
  524. upper_left = pos_dec(upper_left!(area), (1, 0));
  525. }
  526. for &idx in &[old_cursor_pos, self.cursor_pos] {
  527. let entry_idx = *visibles[idx];
  528. let src_area = { get_entry_area(entry_idx, &self.entries) };
  529. let visual_indentation = self.entries[entry_idx].indentation * 4;
  530. let dest_area = (
  531. pos_inc(
  532. upper_left,
  533. (visual_indentation, 2 * (visibles[..idx].len() - top_idx)),
  534. ),
  535. (
  536. cmp::min(
  537. get_x(bottom_right),
  538. get_x(upper_left)
  539. + visual_indentation
  540. + self.entries[entry_idx].heading.grapheme_width()
  541. + 1,
  542. ),
  543. cmp::min(
  544. get_y(bottom_right),
  545. get_y(upper_left) + 2 * (visibles[..idx].len() - top_idx),
  546. ),
  547. ),
  548. );
  549. self.highlight_line(grid, dest_area, src_area, entry_idx);
  550. if rows < visibles.len() {
  551. ScrollBar::draw(
  552. ScrollBar::default(),
  553. grid,
  554. (
  555. upper_left!(area),
  556. set_x(bottom_right, get_x(upper_left!(area)) + 1),
  557. ),
  558. self.cursor_pos,
  559. rows,
  560. visibles.len(),
  561. );
  562. context.dirty_areas.push_back((
  563. upper_left!(area),
  564. set_x(bottom_right, get_x(upper_left!(area)) + 1),
  565. ));
  566. }
  567. let (upper_left, bottom_right) = dest_area;
  568. context
  569. .dirty_areas
  570. .push_back((upper_left, (get_x(bottom_right), get_y(upper_left) + 1)));
  571. }
  572. }
  573. }
  574. fn draw_vert(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  575. let upper_left = upper_left!(area);
  576. let bottom_right = bottom_right!(area);
  577. let mid = get_x(upper_left) + self.content.size().0;
  578. /* First draw the thread subject on the first row */
  579. let y = if self.dirty {
  580. let account = &context.accounts[self.coordinates.0];
  581. let mailbox = &account[self.coordinates.1].as_ref().unwrap();
  582. let threads = &account.collection.threads[&mailbox.folder.hash()];
  583. let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
  584. let i = if let Some(i) = thread_node.message() {
  585. i
  586. } else {
  587. let mut iter_ptr = thread_node.children()[0];
  588. while threads.thread_nodes()[&iter_ptr].message().is_none() {
  589. iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
  590. }
  591. threads.thread_nodes()[&iter_ptr].message().unwrap()
  592. };
  593. let envelope: &Envelope = account.get_env(&i);
  594. let (x, y) = write_string_to_grid(
  595. &envelope.subject(),
  596. grid,
  597. Color::Byte(33),
  598. Color::Default,
  599. area,
  600. true,
  601. );
  602. for x in x..=get_x(bottom_right) {
  603. grid[(x, y)].set_ch(' ');
  604. grid[(x, y)].set_bg(Color::Default);
  605. grid[(x, y)].set_fg(Color::Default);
  606. }
  607. context
  608. .dirty_areas
  609. .push_back((upper_left, set_y(bottom_right, y + 1)));
  610. context
  611. .dirty_areas
  612. .push_back(((mid, y + 1), set_x(bottom_right, mid)));
  613. clear_area(grid, ((mid, y + 1), set_x(bottom_right, mid)));
  614. y + 2
  615. } else {
  616. get_y(upper_left) + 2
  617. };
  618. let (width, height) = self.content.size();
  619. if height == 0 || width == 0 {
  620. return;
  621. }
  622. for x in get_x(upper_left)..=get_x(bottom_right) {
  623. set_and_join_box(grid, (x, y - 1), HORZ_BOUNDARY);
  624. grid[(x, y - 1)].set_fg(Color::Byte(33));
  625. grid[(x, y - 1)].set_bg(Color::Default);
  626. }
  627. if self.show_mailview {
  628. self.draw_list(
  629. grid,
  630. (set_y(upper_left, y), set_x(bottom_right, mid - 1)),
  631. context,
  632. );
  633. let upper_left = (mid + 1, get_y(upper_left) + y - 1);
  634. self.mailview
  635. .draw(grid, (upper_left, bottom_right), context);
  636. } else {
  637. clear_area(grid, ((mid + 1, get_y(upper_left) + y - 1), bottom_right));
  638. self.draw_list(grid, (set_y(upper_left, y), bottom_right), context);
  639. }
  640. }
  641. fn draw_horz(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  642. let upper_left = upper_left!(area);
  643. let bottom_right = bottom_right!(area);
  644. let total_rows = height!(area);
  645. let pager_ratio = context.runtime_settings.pager.pager_ratio;
  646. let bottom_entity_rows = (pager_ratio * total_rows) / 100;
  647. if bottom_entity_rows > total_rows {
  648. clear_area(grid, area);
  649. context.dirty_areas.push_back(area);
  650. return;
  651. }
  652. let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
  653. /* First draw the thread subject on the first row */
  654. let y = {
  655. let account = &context.accounts[self.coordinates.0];
  656. let mailbox = &account[self.coordinates.1].as_ref().unwrap();
  657. let threads = &account.collection.threads[&mailbox.folder.hash()];
  658. let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
  659. let i = if let Some(i) = thread_node.message() {
  660. i
  661. } else {
  662. let mut iter_ptr = thread_node.children()[0];
  663. while threads.thread_nodes()[&iter_ptr].message().is_none() {
  664. iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
  665. }
  666. threads.thread_nodes()[&iter_ptr].message().unwrap()
  667. };
  668. let envelope: &Envelope = account.get_env(&i);
  669. let (x, y) = write_string_to_grid(
  670. &envelope.subject(),
  671. grid,
  672. Color::Byte(33),
  673. Color::Default,
  674. area,
  675. true,
  676. );
  677. for x in x..=get_x(bottom_right) {
  678. grid[(x, y)].set_ch(' ');
  679. grid[(x, y)].set_bg(Color::Default);
  680. grid[(x, y)].set_fg(Color::Default);
  681. }
  682. //context.dirty_areas.push_back(((0,0), set_y(bottom_right, y)));
  683. y + 2
  684. };
  685. let (width, height) = self.content.size();
  686. if height == 0 || height == self.cursor_pos || width == 0 {
  687. return;
  688. }
  689. /* if this is the first ever draw, there is nothing on the grid to update so populate it
  690. * first */
  691. if !self.initiated {
  692. clear_area(grid, (set_y(upper_left, y - 1), bottom_right));
  693. let (width, height) = self.content.size();
  694. if self.show_mailview {
  695. let area = (set_y(upper_left, y), set_y(bottom_right, mid - 1));
  696. let upper_left = upper_left!(area);
  697. let bottom_right = bottom_right!(area);
  698. let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 2;
  699. let page_no = (self.new_cursor_pos).wrapping_div(rows);
  700. let top_idx = page_no * rows;
  701. copy_area(
  702. grid,
  703. &self.content,
  704. area,
  705. ((0, 2 * top_idx), (width - 1, height - 1)),
  706. );
  707. for x in get_x(upper_left)..=get_x(bottom_right) {
  708. set_and_join_box(grid, (x, mid), HORZ_BOUNDARY);
  709. }
  710. } else {
  711. let area = (set_y(upper_left, y), bottom_right);
  712. let upper_left = upper_left!(area);
  713. let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 2;
  714. let page_no = (self.new_cursor_pos).wrapping_div(rows);
  715. let top_idx = page_no * rows;
  716. copy_area(
  717. grid,
  718. &self.content,
  719. area,
  720. ((0, 2 * top_idx), (width - 1, height - 1)),
  721. );
  722. }
  723. context.dirty_areas.push_back(area);
  724. self.initiated = true;
  725. }
  726. if self.show_mailview {
  727. self.draw_list(
  728. grid,
  729. (set_y(upper_left, y), set_y(bottom_right, mid - 1)),
  730. context,
  731. );
  732. self.mailview
  733. .draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
  734. } else {
  735. self.dirty = true;
  736. self.draw_list(grid, (set_y(upper_left, y), bottom_right), context);
  737. }
  738. for x in get_x(upper_left)..=get_x(bottom_right) {
  739. set_and_join_box(grid, (x, y - 1), HORZ_BOUNDARY);
  740. }
  741. }
  742. fn recalc_visible_entries(&mut self) {
  743. if self
  744. .entries
  745. .iter_mut()
  746. .fold(false, |flag, e| e.dirty || flag)
  747. {
  748. self.visible_entries = self
  749. .entries
  750. .iter()
  751. .enumerate()
  752. .fold(
  753. (vec![Vec::new()], StackVec::new(), false),
  754. |(mut visies, mut stack, is_prev_hidden), (idx, e)| {
  755. match (e.hidden, is_prev_hidden) {
  756. (true, false) => {
  757. visies.last_mut().unwrap().push(idx);
  758. stack.push(e.indentation);
  759. (visies, stack, e.hidden)
  760. }
  761. (true, true)
  762. if !stack.is_empty() && stack[stack.len() - 1] == e.indentation =>
  763. {
  764. visies.push(vec![idx]);
  765. (visies, stack, e.hidden)
  766. }
  767. (true, true) => (visies, stack, e.hidden),
  768. (false, true)
  769. if stack[stack.len() - 1] >= e.indentation
  770. && stack.len() > 1
  771. && stack[stack.len() - 2] >= e.indentation =>
  772. {
  773. //FIXME pop all until e.indentation
  774. visies.push(vec![idx]);
  775. stack.pop();
  776. (visies, stack, e.hidden)
  777. }
  778. (false, true) if stack[stack.len() - 1] >= e.indentation => {
  779. visies.push(vec![idx]);
  780. stack.pop();
  781. (visies, stack, e.hidden)
  782. }
  783. (false, true) => (visies, stack, is_prev_hidden),
  784. (false, false) => {
  785. visies.last_mut().unwrap().push(idx);
  786. (visies, stack, e.hidden)
  787. }
  788. }
  789. },
  790. )
  791. .0;
  792. }
  793. if self.reversed {
  794. self.visible_entries.reverse()
  795. }
  796. }
  797. /// Current position in self.entries (not in drawn entries which might exclude nonvisible ones)
  798. fn current_pos(&self) -> usize {
  799. let visibles: Vec<&usize> = self
  800. .visible_entries
  801. .iter()
  802. .flat_map(|ref v| v.iter())
  803. .collect();
  804. *visibles[self.new_cursor_pos]
  805. }
  806. }
  807. impl fmt::Display for ThreadView {
  808. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  809. // TODO display subject/info
  810. write!(f, "view thread")
  811. }
  812. }
  813. impl Component for ThreadView {
  814. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  815. let total_rows = height!(area);
  816. let total_cols = width!(area);
  817. if total_rows < 24 || total_cols < 80 {
  818. return;
  819. }
  820. /* If user has selected another mail to view, change to it */
  821. if self.new_expanded_pos != self.expanded_pos {
  822. self.expanded_pos = self.new_expanded_pos;
  823. let coordinates = (
  824. self.coordinates.0,
  825. self.coordinates.1,
  826. self.entries[self.current_pos()].msg_hash,
  827. );
  828. self.mailview.update(coordinates);
  829. }
  830. if self.entries.len() == 1 {
  831. self.mailview.draw(grid, area, context);
  832. return;
  833. }
  834. if total_cols >= self.content.size().0 + 74 {
  835. self.draw_vert(grid, area, context);
  836. } else {
  837. self.draw_horz(grid, area, context);
  838. }
  839. }
  840. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  841. if self.show_mailview && self.mailview.process_event(event, context) {
  842. return true;
  843. }
  844. let shortcuts = &self.get_shortcuts(context)[ThreadView::DESCRIPTION];
  845. match *event {
  846. UIEvent::Input(Key::Char('R')) => {
  847. context.replies.push_back(UIEvent::Action(Tab(Reply(
  848. self.coordinates,
  849. self.entries[self.expanded_pos].index.1,
  850. ))));
  851. return true;
  852. }
  853. UIEvent::Input(Key::Char('e')) => {
  854. {
  855. let account = &context.accounts[self.coordinates.0];
  856. let mailbox = &account[self.coordinates.1].as_ref().unwrap();
  857. let threads = &account.collection.threads[&mailbox.folder.hash()];
  858. let thread_node =
  859. &threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
  860. let i = if let Some(i) = thread_node.message() {
  861. i
  862. } else {
  863. threads.thread_nodes()[&thread_node.children()[0]]
  864. .message()
  865. .unwrap()
  866. };
  867. let envelope: &Envelope = &account.get_env(&i);
  868. let op = account.operation(envelope.hash());
  869. debug!(
  870. "sending action edit for {}, {}",
  871. envelope.message_id(),
  872. op.description()
  873. );
  874. context.replies.push_back(UIEvent::Action(Tab(Edit(
  875. self.coordinates.0,
  876. envelope.hash(),
  877. ))));
  878. }
  879. return true;
  880. }
  881. UIEvent::Input(Key::Up) => {
  882. if self.cursor_pos > 0 {
  883. self.new_cursor_pos = self.new_cursor_pos.saturating_sub(1);
  884. }
  885. return true;
  886. }
  887. UIEvent::Input(Key::Down) => {
  888. let height = self.visible_entries.iter().flat_map(|v| v.iter()).count();
  889. if height > 0 && self.new_cursor_pos + 1 < height {
  890. self.new_cursor_pos += 1;
  891. }
  892. return true;
  893. }
  894. UIEvent::Input(ref key) if *key == shortcuts["prev_page"] => {
  895. self.movement = Some(PageMovement::PageUp);
  896. self.set_dirty();
  897. }
  898. UIEvent::Input(ref key) if *key == shortcuts["next_page"] => {
  899. self.movement = Some(PageMovement::PageDown);
  900. self.set_dirty();
  901. }
  902. UIEvent::Input(ref key) if *key == Key::Home => {
  903. self.movement = Some(PageMovement::Home);
  904. self.set_dirty();
  905. }
  906. UIEvent::Input(ref key) if *key == Key::End => {
  907. self.movement = Some(PageMovement::End);
  908. self.set_dirty();
  909. }
  910. UIEvent::Input(Key::Char('\n')) => {
  911. if self.entries.len() < 2 {
  912. return true;
  913. }
  914. self.new_expanded_pos = self.current_pos();
  915. self.show_mailview = true;
  916. //self.initiated = false;
  917. self.set_dirty();
  918. return true;
  919. }
  920. UIEvent::Input(Key::Char('p')) => {
  921. self.show_mailview = !self.show_mailview;
  922. self.initiated = false;
  923. self.set_dirty();
  924. return true;
  925. }
  926. UIEvent::Input(Key::Ctrl('r')) => {
  927. self.reversed = !self.reversed;
  928. let expanded_hash = self.entries[self.expanded_pos].index.1;
  929. self.initiate(Some(expanded_hash), context);
  930. self.initiated = false;
  931. self.dirty = true;
  932. return true;
  933. }
  934. UIEvent::Input(Key::Char('h')) => {
  935. let current_pos = self.current_pos();
  936. self.entries[current_pos].hidden = !self.entries[current_pos].hidden;
  937. self.entries[current_pos].dirty = true;
  938. {
  939. let visible_entries: Vec<&usize> =
  940. self.visible_entries.iter().flat_map(|v| v.iter()).collect();
  941. let search_old_cursor_pos = |entries: Vec<&usize>, x: usize| {
  942. let mut low = 0;
  943. let mut high = entries.len() - 1;
  944. while low <= high {
  945. let mid = low + (high - low) / 2;
  946. if *entries[mid] == x {
  947. return mid;
  948. }
  949. if x > *entries[mid] {
  950. low = mid + 1;
  951. } else {
  952. high = mid - 1;
  953. }
  954. }
  955. return high + 1; //mid
  956. };
  957. self.new_cursor_pos = search_old_cursor_pos(visible_entries, self.cursor_pos);
  958. }
  959. self.cursor_pos = self.new_cursor_pos;
  960. self.recalc_visible_entries();
  961. self.dirty = true;
  962. return true;
  963. }
  964. UIEvent::Resize => {
  965. self.set_dirty();
  966. }
  967. UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
  968. for e in self.entries.iter_mut() {
  969. if e.msg_hash == *old_hash {
  970. e.msg_hash = *new_hash;
  971. }
  972. }
  973. self.mailview
  974. .process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
  975. }
  976. _ => {
  977. if self.mailview.process_event(event, context) {
  978. return true;
  979. }
  980. }
  981. }
  982. false
  983. }
  984. fn is_dirty(&self) -> bool {
  985. (self.cursor_pos != self.new_cursor_pos)
  986. || self.dirty
  987. || (self.show_mailview && self.mailview.is_dirty())
  988. }
  989. fn set_dirty(&mut self) {
  990. self.dirty = true;
  991. self.mailview.set_dirty();
  992. }
  993. fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
  994. let mut map = self.mailview.get_shortcuts(context);
  995. //FIXME
  996. let config_map = context.settings.shortcuts.compact_listing.key_values();
  997. map.insert(
  998. ThreadView::DESCRIPTION.to_string(),
  999. [
  1000. ("reply", Key::Char('R')),
  1001. ("reverse thread order", Key::Ctrl('r')),
  1002. ("toggle_mailview", Key::Char('p')),
  1003. ("toggle_subthread visibility", Key::Char('h')),
  1004. (
  1005. "prev_page",
  1006. if let Some(key) = config_map.get("prev_page") {
  1007. (*key).clone()
  1008. } else {
  1009. Key::PageUp
  1010. },
  1011. ),
  1012. (
  1013. "next_page",
  1014. if let Some(key) = config_map.get("next_page") {
  1015. (*key).clone()
  1016. } else {
  1017. Key::PageDown
  1018. },
  1019. ),
  1020. (
  1021. "exit_thread",
  1022. if let Some(key) = config_map.get("exit_thread") {
  1023. (*key).clone()
  1024. } else {
  1025. Key::Char('i')
  1026. },
  1027. ),
  1028. ]
  1029. .iter()
  1030. .cloned()
  1031. .collect(),
  1032. );
  1033. map
  1034. }
  1035. fn id(&self) -> ComponentId {
  1036. self.id
  1037. }
  1038. fn set_id(&mut self, id: ComponentId) {
  1039. self.id = id;
  1040. }
  1041. }