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.

thread.rs 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  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. const MAX_COLS: usize = 500;
  24. /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
  25. /// `MailView`.
  26. #[derive(Debug)]
  27. pub struct ThreadListing {
  28. /// (x, y, z): x is accounts, y is folders, z is index inside a folder.
  29. cursor_pos: (usize, usize, usize),
  30. new_cursor_pos: (usize, usize, usize),
  31. length: usize,
  32. sort: (SortField, SortOrder),
  33. subsort: (SortField, SortOrder),
  34. /// Cache current view.
  35. content: CellBuffer,
  36. locations: Vec<EnvelopeHash>,
  37. /// If we must redraw on next redraw event
  38. dirty: bool,
  39. /// If `self.view` is focused or not.
  40. unfocused: bool,
  41. initialised: bool,
  42. view: Option<MailView>,
  43. movement: Option<PageMovement>,
  44. id: ComponentId,
  45. }
  46. impl ListingTrait for ThreadListing {
  47. fn coordinates(&self) -> (usize, usize, Option<EnvelopeHash>) {
  48. (
  49. self.cursor_pos.0,
  50. self.cursor_pos.1,
  51. Some(self.locations[self.cursor_pos.2]),
  52. )
  53. }
  54. fn set_coordinates(&mut self, coordinates: (usize, usize, Option<EnvelopeHash>)) {
  55. self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
  56. }
  57. fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  58. if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0
  59. {
  60. self.refresh_mailbox(context);
  61. }
  62. let upper_left = upper_left!(area);
  63. let bottom_right = bottom_right!(area);
  64. if self.length == 0 {
  65. clear_area(grid, area);
  66. copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0)));
  67. context.dirty_areas.push_back(area);
  68. return;
  69. }
  70. let rows = get_y(bottom_right) - get_y(upper_left) + 1;
  71. if let Some(mvm) = self.movement.take() {
  72. match mvm {
  73. PageMovement::PageUp => {
  74. self.new_cursor_pos.2 = self.new_cursor_pos.2.saturating_sub(rows);
  75. }
  76. PageMovement::PageDown => {
  77. if self.new_cursor_pos.2 + rows + 1 < self.length {
  78. self.new_cursor_pos.2 += rows;
  79. } else if self.new_cursor_pos.2 + rows > self.length {
  80. self.new_cursor_pos.2 = self.length - 1;
  81. } else {
  82. self.new_cursor_pos.2 = (self.length / rows) * rows;
  83. }
  84. }
  85. PageMovement::Home => {
  86. self.new_cursor_pos.2 = 0;
  87. }
  88. PageMovement::End => {
  89. if self.new_cursor_pos.2 + rows > self.length {
  90. self.new_cursor_pos.2 = self.length - 1;
  91. } else {
  92. self.new_cursor_pos.2 = (self.length / rows) * rows;
  93. }
  94. }
  95. }
  96. }
  97. let prev_page_no = (self.cursor_pos.2).wrapping_div(rows);
  98. let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
  99. let top_idx = page_no * rows;
  100. if !self.initialised {
  101. self.initialised = false;
  102. copy_area(
  103. grid,
  104. &self.content,
  105. area,
  106. ((0, top_idx), (MAX_COLS - 1, self.length)),
  107. );
  108. self.highlight_line(
  109. grid,
  110. (
  111. set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
  112. set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
  113. ),
  114. self.cursor_pos.2,
  115. context,
  116. );
  117. context.dirty_areas.push_back(area);
  118. }
  119. /* If cursor position has changed, remove the highlight from the previous position and
  120. * apply it in the new one. */
  121. if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
  122. let old_cursor_pos = self.cursor_pos;
  123. self.cursor_pos = self.new_cursor_pos;
  124. for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] {
  125. if *idx >= self.length {
  126. continue; //bounds check
  127. }
  128. let new_area = (
  129. set_y(upper_left, get_y(upper_left) + (*idx % rows)),
  130. set_y(bottom_right, get_y(upper_left) + (*idx % rows)),
  131. );
  132. self.highlight_line(grid, new_area, *idx, context);
  133. context.dirty_areas.push_back(new_area);
  134. }
  135. return;
  136. } else if self.cursor_pos != self.new_cursor_pos {
  137. self.cursor_pos = self.new_cursor_pos;
  138. }
  139. /* Page_no has changed, so draw new page */
  140. copy_area(
  141. grid,
  142. &self.content,
  143. area,
  144. ((0, top_idx), (MAX_COLS - 1, self.length)),
  145. );
  146. self.highlight_line(
  147. grid,
  148. (
  149. set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
  150. set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
  151. ),
  152. self.cursor_pos.2,
  153. context,
  154. );
  155. context.dirty_areas.push_back(area);
  156. }
  157. fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
  158. let mailbox = if context.accounts[self.cursor_pos.0][self.cursor_pos.1].is_available() {
  159. context.accounts[self.cursor_pos.0][self.cursor_pos.1].unwrap()
  160. } else {
  161. return;
  162. };
  163. if mailbox.is_empty() || mailbox.len() <= idx {
  164. return;
  165. }
  166. if self.locations[idx] != 0 {
  167. let envelope: &Envelope =
  168. &context.accounts[self.cursor_pos.0].get_env(&self.locations[idx]);
  169. let fg_color = if !envelope.is_seen() {
  170. Color::Byte(0)
  171. } else {
  172. Color::Default
  173. };
  174. let bg_color = if self.cursor_pos.2 == idx {
  175. Color::Byte(246)
  176. } else if !envelope.is_seen() {
  177. Color::Byte(251)
  178. } else if idx % 2 == 0 {
  179. Color::Byte(236)
  180. } else {
  181. Color::Default
  182. };
  183. change_colors(grid, area, fg_color, bg_color);
  184. }
  185. }
  186. }
  187. impl Default for ThreadListing {
  188. fn default() -> Self {
  189. Self::new()
  190. }
  191. }
  192. impl fmt::Display for ThreadListing {
  193. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  194. write!(f, "mail")
  195. }
  196. }
  197. impl ThreadListing {
  198. pub fn new() -> Self {
  199. let content = CellBuffer::new(0, 0, Cell::with_char(' '));
  200. ThreadListing {
  201. cursor_pos: (0, 1, 0),
  202. new_cursor_pos: (0, 0, 0),
  203. length: 0,
  204. sort: (Default::default(), Default::default()),
  205. subsort: (Default::default(), Default::default()),
  206. content,
  207. locations: Vec::new(),
  208. dirty: true,
  209. unfocused: false,
  210. view: None,
  211. initialised: false,
  212. movement: None,
  213. id: ComponentId::new_v4(),
  214. }
  215. }
  216. /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
  217. /// chosen.
  218. fn refresh_mailbox(&mut self, context: &mut Context) {
  219. self.dirty = true;
  220. if !(self.cursor_pos.0 == self.new_cursor_pos.0
  221. && self.cursor_pos.1 == self.new_cursor_pos.1)
  222. {
  223. //TODO: store cursor_pos in each folder
  224. self.cursor_pos.2 = 0;
  225. self.new_cursor_pos.2 = 0;
  226. }
  227. self.cursor_pos.1 = self.new_cursor_pos.1;
  228. self.cursor_pos.0 = self.new_cursor_pos.0;
  229. let folder_hash = context.accounts[self.cursor_pos.0].folders_order[self.cursor_pos.1];
  230. // Inform State that we changed the current folder view.
  231. context
  232. .replies
  233. .push_back(UIEvent::RefreshMailbox((self.cursor_pos.0, folder_hash)));
  234. // Get mailbox as a reference.
  235. //
  236. match context.accounts[self.cursor_pos.0].status(folder_hash) {
  237. Ok(_) => {}
  238. Err(_) => {
  239. let message: String = context.accounts[self.cursor_pos.0][folder_hash].to_string();
  240. self.content = CellBuffer::new(message.len(), 1, Cell::with_char(' '));
  241. self.length = 0;
  242. write_string_to_grid(
  243. message.as_str(),
  244. &mut self.content,
  245. Color::Default,
  246. Color::Default,
  247. ((0, 0), (MAX_COLS - 1, 0)),
  248. false,
  249. );
  250. return;
  251. }
  252. }
  253. let account = &context.accounts[self.cursor_pos.0];
  254. let mailbox = account[self.cursor_pos.1].unwrap();
  255. let threads = &account.collection.threads[&mailbox.folder.hash()];
  256. self.length = threads.len();
  257. self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
  258. self.locations.clear();
  259. if self.length == 0 {
  260. write_string_to_grid(
  261. &format!("Folder `{}` is empty.", mailbox.folder.name()),
  262. &mut self.content,
  263. Color::Default,
  264. Color::Default,
  265. ((0, 0), (MAX_COLS - 1, 0)),
  266. true,
  267. );
  268. return;
  269. }
  270. let mut indentations: Vec<bool> = Vec::with_capacity(6);
  271. let mut thread_idx = 0; // needed for alternate thread colors
  272. /* Draw threaded view. */
  273. threads.sort_by(self.sort, self.subsort, &account.collection);
  274. let thread_nodes: &FnvHashMap<ThreadHash, ThreadNode> = &threads.thread_nodes();
  275. let mut iter = threads.threads_iter().peekable();
  276. /* This is just a desugared for loop so that we can use .peek() */
  277. let mut idx = 0;
  278. while let Some((indentation, thread_hash, has_sibling)) = iter.next() {
  279. let thread_node = &thread_nodes[&thread_hash];
  280. if indentation == 0 {
  281. thread_idx += 1;
  282. }
  283. if thread_node.has_message() {
  284. let envelope: &Envelope = &account.get_env(&thread_node.message().unwrap());
  285. self.locations.push(envelope.hash());
  286. let fg_color = if !envelope.is_seen() {
  287. Color::Byte(0)
  288. } else {
  289. Color::Default
  290. };
  291. let bg_color = if !envelope.is_seen() {
  292. Color::Byte(251)
  293. } else if thread_idx % 2 == 0 {
  294. Color::Byte(236)
  295. } else {
  296. Color::Default
  297. };
  298. let (x, _) = write_string_to_grid(
  299. &ThreadListing::make_thread_entry(
  300. envelope,
  301. idx,
  302. indentation,
  303. thread_hash,
  304. threads,
  305. &indentations,
  306. has_sibling,
  307. ),
  308. &mut self.content,
  309. fg_color,
  310. bg_color,
  311. ((0, idx), (MAX_COLS - 1, idx)),
  312. false,
  313. );
  314. for x in x..MAX_COLS {
  315. self.content[(x, idx)].set_ch(' ');
  316. self.content[(x, idx)].set_bg(bg_color);
  317. }
  318. idx += 1;
  319. } else {
  320. continue;
  321. }
  322. match iter.peek() {
  323. Some((x, _, _)) if *x > indentation => {
  324. if has_sibling {
  325. indentations.push(true);
  326. } else {
  327. indentations.push(false);
  328. }
  329. }
  330. Some((x, _, _)) if *x < indentation => {
  331. for _ in 0..(indentation - *x) {
  332. indentations.pop();
  333. }
  334. }
  335. _ => {}
  336. }
  337. }
  338. }
  339. fn highlight_line_self(&mut self, idx: usize, context: &Context) {
  340. let mailbox = if context.accounts[self.cursor_pos.0][self.cursor_pos.1].is_available() {
  341. context.accounts[self.cursor_pos.0][self.cursor_pos.1].unwrap()
  342. } else {
  343. return;
  344. };
  345. if mailbox.is_empty() {
  346. return;
  347. }
  348. if self.locations[idx] != 0 {
  349. let envelope: &Envelope =
  350. &context.accounts[self.cursor_pos.0].get_env(&self.locations[idx]);
  351. let fg_color = if !envelope.is_seen() {
  352. Color::Byte(0)
  353. } else {
  354. Color::Default
  355. };
  356. let bg_color = if !envelope.is_seen() {
  357. Color::Byte(251)
  358. } else if idx % 2 == 0 {
  359. Color::Byte(236)
  360. } else {
  361. Color::Default
  362. };
  363. change_colors(
  364. &mut self.content,
  365. ((0, idx), (MAX_COLS - 1, idx)),
  366. fg_color,
  367. bg_color,
  368. );
  369. }
  370. }
  371. fn make_thread_entry(
  372. envelope: &Envelope,
  373. idx: usize,
  374. indent: usize,
  375. node_idx: ThreadHash,
  376. threads: &Threads,
  377. indentations: &[bool],
  378. has_sibling: bool,
  379. //op: Box<BackendOp>,
  380. ) -> String {
  381. let thread_node = &threads[&node_idx];
  382. let has_parent = thread_node.has_parent();
  383. let show_subject = thread_node.show_subject();
  384. let mut s = format!("{}{}{} ", idx, " ", ThreadListing::format_date(&envelope));
  385. for i in 0..indent {
  386. if indentations.len() > i && indentations[i] {
  387. s.push('β”‚');
  388. } else if indentations.len() > i {
  389. s.push(' ');
  390. }
  391. if i > 0 {
  392. s.push(' ');
  393. }
  394. }
  395. if indent > 0 && (has_sibling || has_parent) {
  396. if has_sibling && has_parent {
  397. s.push('β”œ');
  398. } else if has_sibling {
  399. s.push('┬');
  400. } else {
  401. s.push('β””');
  402. }
  403. s.push('─');
  404. s.push('>');
  405. }
  406. if show_subject {
  407. s.push_str(&format!("{:.85}", envelope.subject()));
  408. }
  409. /* TODO Very slow since we have to build all attachments
  410. let attach_count = envelope.body(op).count_attachments();
  411. if attach_count > 1 {
  412. s.push_str(&format!(" {}Γ’Λ†ΕΎ ", attach_count - 1));
  413. }
  414. */
  415. s
  416. }
  417. fn format_date(envelope: &Envelope) -> String {
  418. let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
  419. let now: std::time::Duration = std::time::SystemTime::now()
  420. .duration_since(d)
  421. .unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
  422. match now.as_secs() {
  423. n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)),
  424. n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)),
  425. n if n < 4 * 24 * 60 * 60 => {
  426. format!("{} days ago{}", n / (24 * 60 * 60), " ".repeat(9))
  427. }
  428. _ => envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
  429. }
  430. }
  431. }
  432. impl Component for ThreadListing {
  433. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  434. if !self.unfocused {
  435. if !self.is_dirty() {
  436. return;
  437. }
  438. self.dirty = false;
  439. /* Draw the entire list */
  440. self.draw_list(grid, area, context);
  441. } else {
  442. self.cursor_pos = self.new_cursor_pos;
  443. let upper_left = upper_left!(area);
  444. let bottom_right = bottom_right!(area);
  445. if self.length == 0 && self.dirty {
  446. clear_area(grid, area);
  447. context.dirty_areas.push_back(area);
  448. }
  449. /* Render the mail body in a pager, basically copy what HSplit does */
  450. let total_rows = get_y(bottom_right) - get_y(upper_left);
  451. let pager_ratio = context.runtime_settings.pager.pager_ratio;
  452. let bottom_entity_rows = (pager_ratio * total_rows) / 100;
  453. if bottom_entity_rows > total_rows {
  454. clear_area(grid, area);
  455. context.dirty_areas.push_back(area);
  456. return;
  457. }
  458. let idx = self.cursor_pos.2;
  459. let has_message: bool = self.locations[self.new_cursor_pos.2] > 0;
  460. if !has_message {
  461. self.dirty = false;
  462. /* Draw the entire list */
  463. return self.draw_list(grid, area, context);
  464. }
  465. /* Mark message as read */
  466. let must_highlight = {
  467. if self.length == 0 {
  468. false
  469. } else {
  470. let account = &mut context.accounts[self.cursor_pos.0];
  471. let (hash, is_seen) = {
  472. let envelope: &Envelope =
  473. &account.get_env(&self.locations[self.cursor_pos.2]);
  474. (envelope.hash(), envelope.is_seen())
  475. };
  476. is_seen
  477. }
  478. };
  479. if must_highlight {
  480. self.highlight_line_self(idx, context);
  481. }
  482. let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
  483. self.draw_list(
  484. grid,
  485. (
  486. upper_left,
  487. (get_x(bottom_right), get_y(upper_left) + mid - 1),
  488. ),
  489. context,
  490. );
  491. if self.length == 0 {
  492. self.dirty = false;
  493. return;
  494. }
  495. {
  496. /* TODO: Move the box drawing business in separate functions */
  497. if get_x(upper_left) > 0 && grid[(get_x(upper_left) - 1, mid)].ch() == VERT_BOUNDARY
  498. {
  499. grid[(get_x(upper_left) - 1, mid)].set_ch(LIGHT_VERTICAL_AND_RIGHT);
  500. }
  501. for i in get_x(upper_left)..=get_x(bottom_right) {
  502. grid[(i, mid)].set_ch(HORZ_BOUNDARY);
  503. }
  504. context
  505. .dirty_areas
  506. .push_back((set_y(upper_left, mid), set_y(bottom_right, mid)));
  507. }
  508. // TODO: Make headers view configurable
  509. if !self.dirty {
  510. if let Some(v) = self.view.as_mut() {
  511. v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
  512. }
  513. return;
  514. }
  515. let coordinates = (
  516. self.cursor_pos.0,
  517. self.cursor_pos.1,
  518. self.locations[self.cursor_pos.2],
  519. );
  520. if let Some(ref mut v) = self.view {
  521. v.update(coordinates);
  522. } else {
  523. self.view = Some(MailView::new(coordinates, None, None));
  524. }
  525. self.view.as_mut().unwrap().draw(
  526. grid,
  527. (set_y(upper_left, mid + 1), bottom_right),
  528. context,
  529. );
  530. self.dirty = false;
  531. }
  532. }
  533. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  534. if let Some(ref mut v) = self.view {
  535. if v.process_event(event, context) {
  536. return true;
  537. }
  538. }
  539. match *event {
  540. UIEvent::Input(Key::Up) => {
  541. if self.cursor_pos.2 > 0 {
  542. self.new_cursor_pos.2 -= 1;
  543. self.dirty = true;
  544. }
  545. return true;
  546. }
  547. UIEvent::Input(Key::Down) => {
  548. if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 {
  549. self.new_cursor_pos.2 += 1;
  550. self.dirty = true;
  551. }
  552. return true;
  553. }
  554. UIEvent::Input(ref key) if *key == Key::PageUp => {
  555. self.movement = Some(PageMovement::PageUp);
  556. self.set_dirty();
  557. }
  558. UIEvent::Input(ref key) if *key == Key::PageDown => {
  559. self.movement = Some(PageMovement::PageDown);
  560. self.set_dirty();
  561. }
  562. UIEvent::Input(ref key) if *key == Key::Home => {
  563. self.movement = Some(PageMovement::Home);
  564. self.set_dirty();
  565. }
  566. UIEvent::Input(ref key) if *key == Key::End => {
  567. self.movement = Some(PageMovement::End);
  568. self.set_dirty();
  569. }
  570. UIEvent::Input(Key::Char('\n')) if !self.unfocused => {
  571. self.unfocused = true;
  572. self.dirty = true;
  573. return true;
  574. }
  575. UIEvent::Input(Key::Char('i')) if self.unfocused => {
  576. self.unfocused = false;
  577. self.dirty = true;
  578. self.view = None;
  579. return true;
  580. }
  581. UIEvent::RefreshMailbox(_) => {
  582. self.dirty = true;
  583. self.view = None;
  584. }
  585. UIEvent::MailboxUpdate((ref idxa, ref idxf))
  586. if (*idxa, *idxf)
  587. == (
  588. self.new_cursor_pos.0,
  589. context.accounts[self.new_cursor_pos.0].folders_order
  590. [self.new_cursor_pos.1],
  591. ) =>
  592. {
  593. self.refresh_mailbox(context);
  594. self.set_dirty();
  595. }
  596. UIEvent::StartupCheck(ref f)
  597. if *f
  598. == context.accounts[self.new_cursor_pos.0].folders_order
  599. [self.new_cursor_pos.1] =>
  600. {
  601. self.refresh_mailbox(context);
  602. self.set_dirty();
  603. }
  604. UIEvent::ChangeMode(UIMode::Normal) => {
  605. self.dirty = true;
  606. }
  607. UIEvent::Resize => {
  608. self.dirty = true;
  609. }
  610. UIEvent::Action(ref action) => match action {
  611. Action::ViewMailbox(idx_m) => {
  612. self.new_cursor_pos.1 = *idx_m;
  613. self.dirty = true;
  614. self.refresh_mailbox(context);
  615. return true;
  616. }
  617. Action::SubSort(field, order) => {
  618. debug!("SubSort {:?} , {:?}", field, order);
  619. self.subsort = (*field, *order);
  620. self.dirty = true;
  621. self.refresh_mailbox(context);
  622. return true;
  623. }
  624. Action::Sort(field, order) => {
  625. debug!("Sort {:?} , {:?}", field, order);
  626. self.sort = (*field, *order);
  627. self.dirty = true;
  628. self.refresh_mailbox(context);
  629. return true;
  630. }
  631. _ => {}
  632. },
  633. _ => {}
  634. }
  635. false
  636. }
  637. fn is_dirty(&self) -> bool {
  638. self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
  639. }
  640. fn set_dirty(&mut self) {
  641. if let Some(p) = self.view.as_mut() {
  642. p.set_dirty();
  643. };
  644. self.dirty = true;
  645. }
  646. fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
  647. self.view
  648. .as_ref()
  649. .map(|p| p.get_shortcuts(context))
  650. .unwrap_or_default()
  651. }
  652. fn id(&self) -> ComponentId {
  653. self.id
  654. }
  655. fn set_id(&mut self, id: ComponentId) {
  656. self.id = id;
  657. }
  658. }