diff --git a/melib/Cargo.toml b/melib/Cargo.toml
index 63afcd1d..ba2d27b9 100644
--- a/melib/Cargo.toml
+++ b/melib/Cargo.toml
@@ -7,7 +7,7 @@ workspace = ".."
[dependencies]
bitflags = "1.0"
chan = "0.1.21"
-chrono = "0.4"
+chrono = { version = "0.4", features = ["serde"] }
crossbeam = "^0.3.0"
data-encoding = "2.1.1"
encoding = "0.2.33"
@@ -21,3 +21,4 @@ xdg = "2.1.0"
serde = "1.0.71"
serde_derive = "1.0.71"
bincode = "1.0.1"
+uuid = { version = "0.6", features = ["serde", "v4"] }
diff --git a/melib/src/addressbook/mod.rs b/melib/src/addressbook/mod.rs
new file mode 100644
index 00000000..71695c43
--- /dev/null
+++ b/melib/src/addressbook/mod.rs
@@ -0,0 +1,160 @@
+/*
+ * meli - addressbook module
+ *
+ * Copyright 2019 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 .
+ */
+use chrono::{DateTime, Local};
+use uuid::Uuid;
+use fnv::FnvHashMap;
+
+
+#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
+pub struct AddressBook {
+ display_name: String,
+ created: DateTime,
+ last_edited: DateTime,
+ cards: FnvHashMap
+}
+
+#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
+pub struct Card {
+ uuid: Uuid,
+ title: String,
+ firstname: String,
+ lastname: String,
+ additionalname: String,
+ name_prefix: String,
+ name_suffix: String,
+ //address
+
+ birthday: Option>,
+ email: String,
+ url: String,
+ key: String,
+
+ last_edited: DateTime,
+ extra_properties: FnvHashMap
+}
+
+impl AddressBook {
+ pub fn new(display_name: String) -> AddressBook {
+ AddressBook {
+ display_name,
+ created: Local::now(),
+ last_edited: Local::now(),
+ cards: FnvHashMap::default(),
+ }
+ }
+ pub fn add_card(&mut self, card: Card) {
+ self.cards.insert(card.uuid, card);
+ }
+ pub fn remove_card(&mut self, card_uuid: Uuid) {
+ self.cards.remove(&card_uuid);
+ }
+ pub fn card_exists(&self, card_uuid: Uuid) -> bool {
+ self.cards.contains_key(&card_uuid)
+ }
+}
+
+
+impl Card {
+ pub fn new() -> Card {
+ Card {
+ uuid: Uuid::new_v4(),
+ title: String::new(),
+ firstname: String::new(),
+ lastname: String::new(),
+ additionalname: String::new(),
+ name_prefix: String::new(),
+ name_suffix: String::new(),
+ //address
+
+ birthday: None,
+ email: String::new(),
+ url: String::new(),
+ key: String::new(),
+
+ last_edited: Local::now(),
+ extra_properties: FnvHashMap::default(),
+ }
+ }
+
+ pub fn title(&self) -> &str {
+ self.title.as_str()
+ }
+ pub fn firstname(&self) -> &str {
+ self.firstname.as_str()
+ }
+ pub fn lastname(&self) -> &str {
+ self.lastname.as_str()
+ }
+ pub fn additionalname(&self) -> &str {
+ self.additionalname.as_str()
+ }
+ pub fn name_prefix(&self) -> &str {
+ self.name_prefix.as_str()
+ }
+ pub fn name_suffix(&self) -> &str {
+ self.name_suffix.as_str()
+ }
+ pub fn email(&self) -> &str {
+ self.email.as_str()
+ }
+ pub fn url(&self) -> &str {
+ self.url.as_str()
+ }
+ pub fn key(&self) -> &str {
+ self.key.as_str()
+ }
+
+ pub fn set_title(&mut self, new: &str) {
+ self.title = new.to_string();()
+ }
+ pub fn set_firstname(&mut self, new: &str) {
+ self.firstname = new.to_string();
+ }
+ pub fn set_lastname(&mut self, new: &str) {
+ self.lastname = new.to_string();
+ }
+ pub fn set_additionalname(&mut self, new: &str) {
+ self.additionalname = new.to_string();
+ }
+ pub fn set_name_prefix(&mut self, new: &str) {
+ self.name_prefix = new.to_string();
+ }
+ pub fn set_name_suffix(&mut self, new: &str) {
+ self.name_suffix = new.to_string();
+ }
+ pub fn set_email(&mut self, new: &str) {
+ self.email = new.to_string();
+ }
+ pub fn set_url(&mut self, new: &str) {
+ self.url = new.to_string();
+ }
+ pub fn set_key(&mut self, new: &str) {
+ self.key = new.to_string();
+ }
+
+ pub fn set_extra_property(&mut self, key: &str, value: String) {
+ self.extra_properties.insert(key.to_string(), value);
+ }
+ pub fn extra_property(&self, key: &str) -> Option<&str> {
+ self.extra_properties.get(key).map(|v| v.as_str())
+ }
+
+}
diff --git a/melib/src/lib.rs b/melib/src/lib.rs
index 1953a8a1..7ba22d0c 100644
--- a/melib/src/lib.rs
+++ b/melib/src/lib.rs
@@ -22,6 +22,7 @@ pub mod async;
pub mod conf;
pub mod error;
pub mod mailbox;
+pub mod addressbook;
#[macro_use]
extern crate serde_derive;
@@ -37,6 +38,8 @@ extern crate chan;
#[macro_use]
extern crate bitflags;
+extern crate uuid;
+extern crate fnv;
pub use conf::*;
pub use mailbox::*;
@@ -44,3 +47,5 @@ pub use mailbox::*;
pub use error::{MeliError, Result};
pub use mailbox::backends::{Backends, RefreshEvent, RefreshEventConsumer};
pub use mailbox::email::{Envelope, Flag};
+
+pub use addressbook::*;
diff --git a/melib/src/mailbox/email/mod.rs b/melib/src/mailbox/email/mod.rs
index b0de6811..290b23e6 100644
--- a/melib/src/mailbox/email/mod.rs
+++ b/melib/src/mailbox/email/mod.rs
@@ -67,6 +67,22 @@ pub enum Address {
Group(GroupAddress),
}
+impl Address {
+ pub fn get_display_name(&self) -> String {
+ match self {
+ Address::Mailbox(m) => m.display_name.display(&m.raw),
+ Address::Group(g) => g.display_name.display(&g.raw),
+ }
+ }
+
+ pub fn get_email(&self) -> String {
+ match self {
+ Address::Mailbox(m) => m.address_spec.display(&m.raw),
+ Address::Group(_) => String::new(),
+ }
+ }
+}
+
impl Eq for Address {}
impl PartialEq for Address {
fn eq(&self, other: &Address) -> bool {
diff --git a/src/bin.rs b/src/bin.rs
index 5b314f94..49585720 100644
--- a/src/bin.rs
+++ b/src/bin.rs
@@ -66,7 +66,7 @@ fn main() {
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
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 tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context))]));
let window = Entity::from(tabs);
let status_bar = Entity::from(Box::new(StatusBar::new(window)));
diff --git a/ui/Cargo.toml b/ui/Cargo.toml
index 38f4d016..ff66bd2d 100644
--- a/ui/Cargo.toml
+++ b/ui/Cargo.toml
@@ -19,4 +19,5 @@ nom = "3.2.0"
notify = "4.0.1"
notify-rust = "^3"
termion = "1.5.1"
+bincode = "1.0.1"
uuid = { version = "0.6", features = ["serde", "v4"] }
diff --git a/ui/src/components/contacts.rs b/ui/src/components/contacts.rs
new file mode 100644
index 00000000..6168fb34
--- /dev/null
+++ b/ui/src/components/contacts.rs
@@ -0,0 +1,76 @@
+/*
+ * meli - contacts module
+ *
+ * Copyright 2019 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 .
+ */
+
+use super::*;
+
+use melib::{AddressBook, Card};
+
+#[derive(Debug)]
+pub struct ContactManager {
+ content: CellBuffer,
+ dirty: bool,
+ initialized: bool,
+}
+
+impl Default for ContactManager {
+ fn default() -> Self {
+ ContactManager {
+ content: CellBuffer::default(),
+ dirty: true,
+ initialized: false,
+ }
+ }
+}
+
+impl fmt::Display for ContactManager {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "contacts")
+ }
+}
+
+impl ContactManager {
+}
+
+impl Component for ContactManager {
+ fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
+ if !self.initialized {
+ clear_area(grid, area);
+ self.initialized = true;
+ }
+ context.dirty_areas.push_back(area);
+ }
+
+ fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
+ false
+ }
+
+ fn is_dirty(&self) -> bool {
+ self.dirty
+ }
+
+ fn set_dirty(&mut self) {
+ self.dirty = true;
+ self.initialized = false;
+ }
+
+ fn kill(&mut self, uuid: Uuid) {
+ }
+}
diff --git a/ui/src/components/mail/accounts/contacts.rs b/ui/src/components/mail/accounts/contacts.rs
new file mode 100644
index 00000000..e914a095
--- /dev/null
+++ b/ui/src/components/mail/accounts/contacts.rs
@@ -0,0 +1,105 @@
+/*
+ * meli - ui crate.
+ *
+ * Copyright 2019 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 .
+ */
+
+use super::*;
+
+#[derive(Debug)]
+pub struct ContactsPanel {
+ content: CellBuffer,
+ dirty: bool,
+}
+
+impl fmt::Display for ContactsPanel {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "contacts")
+ }
+}
+
+
+impl Component for ContactsPanel {
+ fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
+ if self.dirty {
+ self.dirty = false;
+ }
+ clear_area(grid, area);
+
+ let (width, height) = self.content.size();
+ copy_area(grid, &self.content, area, ((0, 0), (width - 1, height - 1)));
+ context.dirty_areas.push_back(area);
+ }
+ fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
+ false
+ }
+ fn is_dirty(&self) -> bool {
+ self.dirty
+ }
+ fn set_dirty(&mut self) {
+ self.dirty = true;
+ }
+}
+
+impl ContactsPanel {
+ pub fn new(context: &Context) -> ContactsPanel {
+ let mut content = CellBuffer::new(120, 25 + context.accounts.len() * 20, Cell::default());
+ write_string_to_grid(
+ "Contacts",
+ &mut content,
+ Color::Default,
+ Color::Default,
+ ((2, 3), (120 - 1, 3)),
+ true,
+ );
+
+ for (i, a) in context.accounts.iter().enumerate() {
+ create_box(&mut content, ((2,5+i*10 ), (120-1, 15+i*10)));
+ let (x, y) = write_string_to_grid(
+ a.name(),
+ &mut content,
+ Color::Default,
+ Color::Default,
+ ((3, 5 + i*10), (120 - 2, 5 + i*10)),
+ true,
+ );
+ write_string_to_grid(
+ " ▒██▒ ",
+ &mut content,
+ Color::Byte(32),
+ Color::Default,
+ ((x, y), (120 - 2, 5 + i*10)),
+ true,
+ );
+ write_string_to_grid(
+ &a.runtime_settings.account().identity,
+ &mut content,
+ Color::Default,
+ Color::Default,
+ ((4, y + 2), (120 - 2, y + 2)),
+ true,
+ );
+
+ }
+
+ ContactsPanel {
+ content,
+ dirty: true,
+ }
+ }
+}
diff --git a/ui/src/components/mail/accounts/mod.rs b/ui/src/components/mail/accounts/mod.rs
new file mode 100644
index 00000000..3e3e3da3
--- /dev/null
+++ b/ui/src/components/mail/accounts/mod.rs
@@ -0,0 +1,159 @@
+/*
+ * meli - accounts module.
+ *
+ * Copyright 2019 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 .
+ */
+
+mod contacts;
+
+pub use contacts::*;
+
+use super::*;
+use std::fmt;
+
+#[derive(Debug)]
+pub struct AccountsPanel {
+ cursor: usize,
+ content: CellBuffer,
+ dirty: bool,
+}
+
+impl fmt::Display for AccountsPanel {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "accounts")
+ }
+}
+
+
+impl Component for AccountsPanel {
+ fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
+ if self.dirty {
+ write_string_to_grid(
+ "Accounts",
+ &mut self.content,
+ Color::Default,
+ Color::Default,
+ ((2, 3), (120 - 1, 3)),
+ true,
+ );
+
+ for (i, a) in context.accounts.iter().enumerate() {
+ create_box(&mut self.content, ((2,5+i*10 ), (120-1, 15+i*10)));
+ let (x, y) = write_string_to_grid(
+ a.name(),
+ &mut self.content,
+ Color::Default,
+ Color::Default,
+ ((3, 5 + i*10), (120 - 2, 5 + i*10)),
+ true,
+ );
+ write_string_to_grid(
+ " ▒██▒ ",
+ &mut self.content,
+ Color::Byte(32),
+ Color::Default,
+ ((x, y), (120 - 2, 5 + i*10)),
+ true,
+ );
+ write_string_to_grid(
+ &a.runtime_settings.account().identity,
+ &mut self.content,
+ Color::Default,
+ Color::Default,
+ ((4, y + 2), (120 - 2, y + 2)),
+ true,
+ );
+ if i == self.cursor {
+ for h in 1..8 {
+ self.content[(2, h+y+1)].set_ch('*');
+ }
+ }
+ write_string_to_grid(
+ "- Settings",
+ &mut self.content,
+ Color::Default,
+ Color::Default,
+ ((5, y + 3), (120 - 2, y + 3)),
+ true,
+ );
+ write_string_to_grid(
+ "- Contacts",
+ &mut self.content,
+ Color::Default,
+ Color::Default,
+ ((5, y + 4), (120 - 2, y + 4)),
+ true,
+ );
+ write_string_to_grid(
+ "- Mailing Lists",
+ &mut self.content,
+ Color::Default,
+ Color::Default,
+ ((5, y + 5), (120 - 2, y + 5)),
+ true,
+ );
+
+
+
+ }
+ self.dirty = false;
+ }
+ clear_area(grid, area);
+
+ let (width, height) = self.content.size();
+ copy_area(grid, &self.content, area, ((0, 0), (width - 1, height - 1)));
+ context.dirty_areas.push_back(area);
+ }
+ fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
+ match event.event_type {
+ UIEventType::Input(Key::Up) => {
+ self.cursor = self.cursor.saturating_sub(1);
+ self.dirty = true;
+ return true;
+ },
+ UIEventType::Input(Key::Down) => {
+ if self.cursor + 1 < context.accounts.len() {
+ self.cursor += 1;
+ self.dirty = true;
+ }
+ return true;
+ },
+ _ => {},
+ }
+
+ false
+ }
+ fn is_dirty(&self) -> bool {
+ self.dirty
+ }
+ fn set_dirty(&mut self) {
+ self.dirty = true;
+ }
+}
+
+impl AccountsPanel {
+ pub fn new(context: &Context) -> AccountsPanel {
+ let mut content = CellBuffer::new(120, 25 + context.accounts.len() * 20, Cell::default());
+
+ AccountsPanel {
+ cursor: 0,
+ content,
+ dirty: true,
+ }
+ }
+}
diff --git a/ui/src/components/mail/mod.rs b/ui/src/components/mail/mod.rs
index f3dbc19a..e2d32bef 100644
--- a/ui/src/components/mail/mod.rs
+++ b/ui/src/components/mail/mod.rs
@@ -31,6 +31,9 @@ pub use view::*;
mod compose;
pub use self::compose::*;
+mod accounts;
+pub use self::accounts::*;
+
#[derive(Debug)]
struct AccountMenuEntry {
name: String,
diff --git a/ui/src/components/mail/view/mod.rs b/ui/src/components/mail/view/mod.rs
index b2de65d6..96e5da36 100644
--- a/ui/src/components/mail/view/mod.rs
+++ b/ui/src/components/mail/view/mod.rs
@@ -22,6 +22,7 @@
use super::*;
use linkify::{Link, LinkFinder};
use std::process::{Command, Stdio};
+use std::any::Any;
mod html;
pub use self::html::*;
@@ -40,6 +41,7 @@ enum ViewMode {
Attachment(usize),
Raw,
Subview,
+ ContactSelector(Selector),
}
impl Default for ViewMode {
@@ -170,7 +172,8 @@ impl MailView {
let mut ret = "Viewing attachment. Press `r` to return \n".to_string();
ret.push_str(&attachments[aidx].text());
ret
- }
+ },
+ ViewMode::ContactSelector(_) => { unimplemented!()},
}
}
pub fn plain_text_to_buf(s: &str, highlight_urls: bool) -> CellBuffer {
@@ -339,11 +342,15 @@ impl Component for MailView {
};
self.dirty = false;
}
+
match self.mode {
ViewMode::Subview => {
if let Some(s) = self.subview.as_mut() {
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
}
+ },
+ ViewMode::ContactSelector(ref mut s) => {
+ s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
}
_ => {
if let Some(p) = self.pager.as_mut() {
@@ -361,7 +368,12 @@ impl Component for MailView {
return true;
}
}
- }
+ },
+ ViewMode::ContactSelector(ref mut s) => {
+ if s.process_event(event, context) {
+ return true;
+ }
+ },
_ => {
if let Some(p) = self.pager.as_mut() {
if p.process_event(event, context) {
@@ -372,6 +384,36 @@ impl Component for MailView {
}
match event.event_type {
+ UIEventType::Input(Key::Char('c')) => {
+ /*
+ let mut new_card: Card = Card::new();
+ new_card.set_email(&envelope.from()[0].get_email());
+ new_card.set_firstname(&envelope.from()[0].get_display_name());
+
+ eprintln!("{:?}", new_card);
+
+ */
+ match self.mode {
+ ViewMode::ContactSelector(_) => {
+ if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) {
+ //eprintln!("{:?}", s.collect());
+ }
+ return true;
+ },
+ _ => {},
+ }
+
+ let accounts = &context.accounts;
+ let mailbox = &accounts[self.coordinates.0][self.coordinates.1]
+ .as_ref()
+ .unwrap();
+ let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
+ let mut entries = Vec::new();
+ entries.push((envelope.from()[0].get_email().into_bytes(), format!("{}", envelope.from()[0])));
+ entries.push((String::from("foo@bar.de").into_bytes(), String::from("Johann de Vir ")));
+ self.mode = ViewMode::ContactSelector(Selector::new(entries, true));
+ //context.accounts.context(self.coordinates.0).address_book.add_card(new_card);
+ },
UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Alt('')) => {
self.cmd_buf.clear();
context.replies.push_back(UIEvent {
@@ -556,7 +598,7 @@ impl Component for MailView {
true
}
fn is_dirty(&self) -> bool {
- self.dirty
+ self.dirty || true
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
}
diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs
index a3ffa21c..1c9cd952 100644
--- a/ui/src/components/mod.rs
+++ b/ui/src/components/mod.rs
@@ -38,6 +38,9 @@ pub use self::indexer::*;
pub mod utilities;
pub use self::utilities::*;
+pub mod contacts;
+pub use contacts::*;
+
use std::fmt;
use std::fmt::{Debug, Display};
use std::ops::{Deref, DerefMut};
@@ -67,6 +70,11 @@ const LIGHT_DOWN_AND_HORIZONTAL: char = '┬';
const LIGHT_UP_AND_HORIZONTAL: char = '┴';
+const DOUBLE_DOWN_AND_RIGHT: char = '╔';
+const DOUBLE_DOWN_AND_LEFT: char = '╗';
+const DOUBLE_UP_AND_LEFT: char = '╝';
+const DOUBLE_UP_AND_RIGHT: char = '╚';
+
/// `Entity` is a container for Components.
#[derive(Debug)]
pub struct Entity {
@@ -405,3 +413,22 @@ pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: char) {
grid[idx].set_ch(bin_to_ch(bin_set));
}
+
+pub fn create_box(grid: &mut CellBuffer, area: Area) {
+ let upper_left = upper_left!(area);
+ let bottom_right = bottom_right!(area);
+
+ for x in get_x(upper_left)..get_x(bottom_right) {
+ grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY);
+ grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY);
+ }
+
+ for y in get_y(upper_left)..get_y(bottom_right) {
+ grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
+ grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY);
+ }
+ set_and_join_box(grid, upper_left, HORZ_BOUNDARY);
+ set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY);
+ set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY);
+ set_and_join_box(grid, bottom_right, VERT_BOUNDARY);
+}
diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs
index 93fa4e93..4139fae0 100644
--- a/ui/src/components/utilities.rs
+++ b/ui/src/components/utilities.rs
@@ -836,3 +836,114 @@ impl Component for Tabbed {
self.children[self.cursor_pos].set_dirty();
}
}
+
+
+type EntryIdentifier = Vec;
+/// Shows selection to user
+#[derive(Debug, PartialEq)]
+pub struct Selector {
+ single_only: bool, /// allow only one selection
+ entries: Vec<(EntryIdentifier, bool)>,
+ selected_entry_count: u32,
+ content: CellBuffer,
+
+ cursor: usize,
+
+ dirty: bool,
+}
+
+impl fmt::Display for Selector {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Display::fmt("Selector", f)
+ }
+}
+
+impl Component for Selector {
+ fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
+ let (width, height) = self.content.size();
+ copy_area_with_break(
+ grid,
+ &self.content,
+ area,
+ ((0, 0), (width, height)),
+ );
+ context.dirty_areas.push_back(area);
+ }
+ fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
+ let (width, height) = self.content.size();
+ match event.event_type {
+ UIEventType::Input(Key::Char(' ')) => {
+ self.entries[self.cursor].1 = ! self.entries[self.cursor].1;
+ if self.entries[self.cursor].1 {
+ write_string_to_grid(
+ "x",
+ &mut self.content,
+ Color::Default,
+ Color::Default,
+ ((1, self.cursor), (width, self.cursor)),
+ false,
+ );
+ } else {
+ write_string_to_grid(
+ " ",
+ &mut self.content,
+ Color::Default,
+ Color::Default,
+ ((1, self.cursor), (width, self.cursor)),
+ false,
+ );
+ }
+ return true;
+ },
+ UIEventType::Input(Key::Up) if self.cursor > 0 => {
+ self.cursor -= 1;
+ return true;
+ },
+ UIEventType::Input(Key::Down) if self.cursor < height - 1=> {
+ self.cursor += 1;
+ return true;
+ },
+ _ => {}
+ }
+
+ false
+ }
+ fn is_dirty(&self) -> bool {
+ self.dirty
+ }
+ fn set_dirty(&mut self) {
+ self.dirty = true;
+ }
+}
+
+impl Selector {
+ pub fn new(mut entries: Vec<(EntryIdentifier, String)>, single_only: bool) -> Selector {
+ let width = entries.iter().max_by_key(|e| e.1.len()).map(|v| v.1.len()).unwrap_or(0) + 4;
+ let height = entries.len();
+ let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
+ let identifiers = entries.iter_mut().map(|(id, _)| (std::mem::replace(&mut *id, Vec::new()), false)).collect();
+ for (i, e) in entries.into_iter().enumerate() {
+ write_string_to_grid(
+ &format!("[ ] {}", e.1),
+ &mut content,
+ Color::Default,
+ Color::Default,
+ ((0, i), (width - 1, i)),
+ false,
+ );
+ }
+
+ Selector {
+ single_only,
+ entries: identifiers,
+ selected_entry_count: 0,
+ content,
+ cursor: 0,
+ dirty: true,
+ }
+ }
+
+ pub fn collect(self) -> Vec {
+ self.entries.into_iter().filter(|v| v.1).map(|(id, _)| id).collect()
+ }
+}
diff --git a/ui/src/conf/accounts.rs b/ui/src/conf/accounts.rs
index a0598f92..271f966f 100644
--- a/ui/src/conf/accounts.rs
+++ b/ui/src/conf/accounts.rs
@@ -30,6 +30,10 @@ use mailbox::backends::{
};
use mailbox::*;
use melib::error::Result;
+use melib::AddressBook;
+
+use std::fs;
+use std::io;
use std::mem;
use std::ops::{Index, IndexMut};
use std::result;
@@ -48,17 +52,38 @@ macro_rules! mailbox {
pub struct Account {
name: String,
folders: Vec