ui: add contacts and account panel
parent
07a51de0b6
commit
92bb3bf8d3
|
@ -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"] }
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use chrono::{DateTime, Local};
|
||||
use uuid::Uuid;
|
||||
use fnv::FnvHashMap;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct AddressBook {
|
||||
display_name: String,
|
||||
created: DateTime<Local>,
|
||||
last_edited: DateTime<Local>,
|
||||
cards: FnvHashMap<Uuid, Card>
|
||||
}
|
||||
|
||||
#[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<DateTime<Local>>,
|
||||
email: String,
|
||||
url: String,
|
||||
key: String,
|
||||
|
||||
last_edited: DateTime<Local>,
|
||||
extra_properties: FnvHashMap<String, String>
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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 <foo@bar.de>")));
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -836,3 +836,114 @@ impl Component for Tabbed {
|
|||
self.children[self.cursor_pos].set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type EntryIdentifier = Vec<u8>;
|
||||
/// 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<EntryIdentifier> {
|
||||
self.entries.into_iter().filter(|v| v.1).map(|(id, _)| id).collect()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Option<Result<Mailbox>>>,
|
||||
|
||||
pub workers: Vec<Worker>,
|
||||
|
||||
sent_folder: Option<usize>,
|
||||
|
||||
pub settings: AccountConf,
|
||||
pub runtime_settings: AccountConf,
|
||||
pub backend: Box<MailBackend>,
|
||||
pub(crate) address_book: AddressBook,
|
||||
|
||||
pub(crate) workers: Vec<Worker>,
|
||||
|
||||
|
||||
pub(crate) settings: AccountConf,
|
||||
pub(crate) runtime_settings: AccountConf,
|
||||
pub(crate) backend: Box<MailBackend>,
|
||||
notify_fn: Arc<NotifyFn>,
|
||||
}
|
||||
|
||||
impl Drop for Account {
|
||||
fn drop(&mut self) {
|
||||
let data_dir =
|
||||
xdg::BaseDirectories::with_profile("meli", &self.name)
|
||||
.unwrap();
|
||||
if let Ok(data) = data_dir.place_data_file("addressbook") {
|
||||
/* place result in cache directory */
|
||||
let f = match fs::File::create(data) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
panic!("{}", e);
|
||||
}
|
||||
};
|
||||
let writer = io::BufWriter::new(f);
|
||||
bincode::serialize_into(writer, &self.address_book).unwrap();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(name: String, settings: AccountConf, map: &Backends, notify_fn: NotifyFn) -> Self {
|
||||
let mut backend = map.get(settings.account().format())(settings.account());
|
||||
|
@ -73,12 +98,31 @@ impl Account {
|
|||
folders.push(None);
|
||||
workers.push(Account::new_worker(f, &mut backend, notify_fn.clone()));
|
||||
}
|
||||
eprintln!("sent_folder for {} is {:?}", name, sent_folder);
|
||||
let data_dir =
|
||||
xdg::BaseDirectories::with_profile("meli", &name)
|
||||
.unwrap();
|
||||
let address_book = if let Ok(data) = data_dir.place_data_file("addressbook") {
|
||||
if data.exists() {
|
||||
let reader = io::BufReader::new(fs::File::open(data).unwrap());
|
||||
let result: result::Result<AddressBook, _> = bincode::deserialize_from(reader);
|
||||
if let Ok(mut data_t) = result {
|
||||
data_t
|
||||
} else {
|
||||
AddressBook::new(name.clone())
|
||||
}
|
||||
} else {
|
||||
AddressBook::new(name.clone())
|
||||
}
|
||||
} else {
|
||||
AddressBook::new(name.clone())
|
||||
};
|
||||
|
||||
Account {
|
||||
name,
|
||||
folders,
|
||||
workers,
|
||||
address_book,
|
||||
sent_folder,
|
||||
workers,
|
||||
settings: settings.clone(),
|
||||
runtime_settings: settings,
|
||||
backend,
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
extern crate config;
|
||||
extern crate serde;
|
||||
extern crate xdg;
|
||||
extern crate bincode;
|
||||
|
||||
pub mod pager;
|
||||
|
||||
pub mod accounts;
|
||||
|
|
Loading…
Reference in New Issue