composer: fix cursor/widget focus scrolling logic

Scrolling up/down with scroll_{up,down} shortcuts didn't work correctly,
because the form widget used its own shortcuts. This commit refactors
the cursor logic.
pull/237/head
Manos Pitsidianakis 2023-06-22 13:23:27 +03:00
parent 0c0a678cff
commit 65179d4816
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
16 changed files with 365 additions and 146 deletions

View File

@ -292,7 +292,7 @@ mode and
key to exit.
.It
At any time you may press
.Shortcut e composing edit_mail Ns
.Shortcut e composing edit Ns
to launch your editor (see
.Xr meli.conf 5 COMPOSING Ns
, setting
@ -320,7 +320,7 @@ To stop your editor and return to
press
.Aq Ctrl-z
and to resume editing press the
.Ic edit_mail
.Ic edit
command again.
.El
.Ss Attachments
@ -353,7 +353,7 @@ To save your draft without sending it, issue
and select 'save as draft'.
.sp
To open a draft for further editing, select your draft in the mail listing and press
.Ic edit_mail Ns
.Ic edit Ns
\&.
.Sh CONTACTS
.Nm

View File

@ -605,7 +605,7 @@ Reply to all.
.El
.sp
To launch your editor, press
.ShortcutPeriod e composing edit_mail
.ShortcutPeriod e composing edit
\&.
To send your draft, press
.ShortcutPeriod s composing send_mail
@ -619,7 +619,7 @@ and select
You can return to the draft by going to your
.Qq Drafts
mailbox and selecting
.ShortcutPeriod e envelope_view edit_mail
.ShortcutPeriod e envelope_view edit
\&.
.Bd -literal -offset center
┌────────────────────────────────────────────────────────────┐
@ -648,7 +648,7 @@ mailbox and selecting
.Ed
.sp
If you enable the embed terminal option, you can launch your terminal editor of choice when you press
.Ic edit_mail Ns
.Ic edit Ns
\&.
.Bd -literal -offset center
┌────────────────────────────────────────────────────────────┐

View File

@ -114,7 +114,7 @@ editor_command = 'vim +/^$'
[shortcuts]
[shortcuts.composing]
edit_mail = 'e'
edit = 'e'
[shortcuts.listing]
new_mail = 'm'
@ -964,7 +964,7 @@ Toggle visibility of side menu in mail list.
.sp
.Em composing
.Bl -tag -width 36n
.It Ic edit_mail
.It Ic edit
Edit mail.
.\" default value
.Pq Em e

View File

@ -101,7 +101,7 @@
#
###shortcuts
#[shortcuts.composing]
#edit_mail = 'e'
#edit = 'e'
#
#[shortcuts.contact-list]
#create_contact = 'c'

View File

@ -77,7 +77,7 @@ impl ContactManager {
}
}
fn initialize(&mut self) {
fn initialize(&mut self, context: &Context) {
let (width, _) = self.content.size();
let (x, _) = write_string_to_grid(
@ -113,7 +113,12 @@ impl ContactManager {
);
}
self.form = FormWidget::new(("Save".into(), true));
self.form = FormWidget::new(
("Save".into(), true),
/* cursor_up_shortcut */ context.settings.shortcuts.general.scroll_up.clone(),
/* cursor_down_shortcut */
context.settings.shortcuts.general.scroll_down.clone(),
);
self.form.add_button(("Cancel(Esc)".into(), false));
self.form
.push(("NAME".into(), self.card.name().to_string()));
@ -142,7 +147,7 @@ impl ContactManager {
impl Component for ContactManager {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !self.initialized {
self.initialize();
self.initialize(context);
self.initialized = true;
}

View File

@ -686,7 +686,7 @@ impl Component for ContactList {
*draft.headers_mut().get_mut("To").unwrap() =
format!("{} <{}>", &card.name(), &card.email());
let mut composer = Composer::with_account(account_hash, context);
composer.set_draft(draft);
composer.set_draft(draft, context);
context
.replies
.push_back(UIEvent::Action(Tab(New(Some(Box::new(composer))))));

View File

@ -535,13 +535,13 @@ To: {}
draft.attachments.push(preamble);
draft.attachments.push(env.body_bytes(bytes).into());
}
composer.set_draft(draft);
composer.set_draft(draft, context);
composer
}
pub fn set_draft(&mut self, draft: Draft) {
pub fn set_draft(&mut self, draft: Draft, context: &Context) {
self.draft = draft;
self.update_form();
self.update_form(context);
}
fn update_draft(&mut self) {
@ -554,9 +554,22 @@ To: {}
}
}
fn update_form(&mut self) {
fn update_form(&mut self, context: &Context) {
let old_cursor = self.form.cursor();
self.form = FormWidget::new(("Save".into(), true));
let shortcuts = self.shortcuts(context);
self.form = FormWidget::new(
("Save".into(), true),
/* cursor_up_shortcut */
shortcuts
.get(Shortcuts::COMPOSING)
.and_then(|c| c.get("scroll_up").cloned())
.unwrap_or_else(|| context.settings.shortcuts.composing.scroll_up.clone()),
/* cursor_down_shortcut */
shortcuts
.get(Shortcuts::COMPOSING)
.and_then(|c| c.get("scroll_down").cloned())
.unwrap_or_else(|| context.settings.shortcuts.composing.scroll_down.clone()),
);
self.form.hide_buttons();
self.form.set_cursor(old_cursor);
let headers = self.draft.headers();
@ -814,8 +827,6 @@ impl Component for Composer {
return;
}
let width = width!(area);
if !self.initialized {
#[cfg(feature = "gpgme")]
if self.gpg_state.sign_mail.is_unset() {
@ -835,12 +846,14 @@ impl Component for Composer {
);
}
self.pager.update_from_str(self.draft.body(), Some(77));
self.update_form();
self.update_form(context);
self.initialized = true;
}
let header_height = self.form.len();
let theme_default = crate::conf::value(context, "theme_default");
let mid = 0;
/*
let mid = if width > 80 {
let width = width - 80;
let mid = width / 2;
@ -861,6 +874,7 @@ impl Component for Composer {
} else {
0
};
*/
let header_area = (
set_x(upper_left, mid + 1),
@ -971,8 +985,8 @@ impl Component for Composer {
let stopped_message: String =
format!("Process with PID {} has stopped.", guard.child_pid);
let stopped_message_2: String = format!(
"-press '{}' (edit_mail shortcut) to re-activate.",
shortcuts[Shortcuts::COMPOSING]["edit_mail"]
"-press '{}' (edit shortcut) to re-activate.",
shortcuts[Shortcuts::COMPOSING]["edit"]
);
const STOPPED_MESSAGE_3: &str =
"-press Ctrl-C to forcefully kill it and return to editor.";
@ -1024,13 +1038,14 @@ impl Component for Composer {
self.embed_area = (upper_left!(header_area), bottom_right!(body_area));
}
if !self.mode.is_edit_attachments() {
self.pager.set_dirty(true);
if self.pager.size().0 > width!(body_area) {
self.pager.set_initialised(false);
}
self.pager.draw(grid, body_area, context);
if self.pager.size().0 > width!(body_area) {
self.pager.set_initialised(false);
}
// Force clean pager area, because if body height is less than body_area it will
// might leave draw artifacts in the remaining area.
clear_area(grid, body_area, theme_default);
self.set_dirty(true);
self.pager.draw(grid, body_area, context);
match self.cursor {
Cursor::Headers => {
@ -1123,6 +1138,58 @@ impl Component for Composer {
self.pager.process_event(event, context);
}
let shortcuts = self.shortcuts(context);
// Process scrolling first, since in my infinite wisdom I made this so
// unnecessarily complex
match &event {
UIEvent::Input(ref key)
if self.mode.is_edit()
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_up"]) =>
{
self.set_dirty(true);
self.cursor = match self.cursor {
// match order is evaluation order, so it matters here because of the if guard
// process_event side effects
Cursor::Attachments => Cursor::Encrypt,
Cursor::Encrypt => Cursor::Sign,
Cursor::Sign => Cursor::Body,
Cursor::Body if !self.pager.process_event(event, context) => {
self.form.process_event(event, context);
Cursor::Headers
}
Cursor::Body => Cursor::Body,
Cursor::Headers if self.form.process_event(event, context) => Cursor::Headers,
Cursor::Headers => Cursor::Headers,
};
return true;
}
UIEvent::Input(ref key)
if self.mode.is_edit()
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_down"]) =>
{
self.set_dirty(true);
self.cursor = match self.cursor {
Cursor::Headers if self.form.process_event(event, context) => Cursor::Headers,
Cursor::Headers => Cursor::Body,
Cursor::Body if self.pager.process_event(event, context) => Cursor::Body,
Cursor::Body => Cursor::Sign,
Cursor::Sign => Cursor::Encrypt,
Cursor::Encrypt => Cursor::Attachments,
Cursor::Attachments => Cursor::Attachments,
};
return true;
}
_ => {}
}
if self.cursor == Cursor::Headers
&& self.mode.is_edit()
&& self.form.process_event(event, context)
{
if let UIEvent::InsertInput(_) = event {
self.has_changes = true;
}
self.set_dirty(true);
return true;
}
match (&mut self.mode, &mut event) {
(ViewMode::Edit, _) => {
if self.pager.process_event(event, context) {
@ -1136,10 +1203,13 @@ impl Component for Composer {
})
.process_event(event, context)
{
if widget.buttons.result() == Some(FormButtonActions::Cancel) {
if matches!(
widget.buttons.result(),
Some(FormButtonActions::Cancel | FormButtonActions::Accept)
) {
self.mode = ViewMode::Edit;
self.set_dirty(true);
}
self.set_dirty(true);
return true;
}
}
@ -1237,6 +1307,7 @@ impl Component for Composer {
}
(ViewMode::Send(ref mut selector), _) => {
if selector.process_event(event, context) {
self.set_dirty(true);
return true;
}
}
@ -1247,13 +1318,15 @@ impl Component for Composer {
if let Some(to_val) = result.downcast_mut::<String>() {
self.draft
.set_header(HeaderName::TO, std::mem::take(to_val));
self.update_form();
self.update_form(context);
}
self.mode = ViewMode::Edit;
self.set_dirty(true);
return true;
}
(ViewMode::SelectRecipients(ref mut selector), _) => {
if selector.process_event(event, context) {
self.set_dirty(true);
return true;
}
}
@ -1281,12 +1354,13 @@ impl Component for Composer {
_ => {}
}
}
self.set_dirty(true);
self.mode = ViewMode::Edit;
self.set_dirty(true);
return true;
}
(ViewMode::Discard(_, ref mut selector), _) => {
if selector.process_event(event, context) {
self.set_dirty(true);
return true;
}
}
@ -1300,6 +1374,7 @@ impl Component for Composer {
context
.replies
.push_back(UIEvent::Action(Tab(Kill(self.id))));
self.set_dirty(true);
return true;
}
'n' => {
@ -1347,6 +1422,7 @@ impl Component for Composer {
}
(ViewMode::WaitingForSendResult(ref mut selector, _), _) => {
if selector.process_event(event, context) {
self.set_dirty(true);
return true;
}
}
@ -1373,20 +1449,12 @@ impl Component for Composer {
#[cfg(feature = "gpgme")]
(ViewMode::SelectEncryptKey(_, ref mut selector), _) => {
if selector.process_event(event, context) {
self.set_dirty(true);
return true;
}
}
_ => {}
}
if self.cursor == Cursor::Headers
&& self.mode.is_edit()
&& self.form.process_event(event, context)
{
if let UIEvent::InsertInput(_) = event {
self.has_changes = true;
}
return true;
}
match *event {
UIEvent::ConfigReload { old_settings: _ } => {
@ -1399,6 +1467,7 @@ impl Component for Composer {
if self.mode.is_edit()
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_up"]) =>
{
self.set_dirty(true);
self.cursor = match self.cursor {
Cursor::Headers => return true,
Cursor::Body => {
@ -1409,12 +1478,12 @@ impl Component for Composer {
Cursor::Encrypt => Cursor::Sign,
Cursor::Attachments => Cursor::Encrypt,
};
self.dirty = true;
}
UIEvent::Input(ref key)
if self.mode.is_edit()
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_down"]) =>
{
self.set_dirty(true);
self.cursor = match self.cursor {
Cursor::Headers => Cursor::Body,
Cursor::Body => Cursor::Sign,
@ -1422,7 +1491,6 @@ impl Component for Composer {
Cursor::Encrypt => Cursor::Attachments,
Cursor::Attachments => return true,
};
self.dirty = true;
}
UIEvent::Input(Key::Char('\n'))
if self.mode.is_edit()
@ -1440,7 +1508,7 @@ impl Component for Composer {
}
_ => {}
};
self.dirty = true;
self.set_dirty(true);
}
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::COMPOSING]["send_mail"])
@ -1565,6 +1633,8 @@ impl Component for Composer {
context
.replies
.push_back(UIEvent::EmbedInput((k.clone(), b.to_vec())));
drop(embed_guard);
self.set_dirty(true);
return true;
}
Ok(WaitStatus::Signaled(_, signal, _)) => {
@ -1608,7 +1678,7 @@ impl Component for Composer {
UIEvent::Input(ref key)
if self.mode.is_edit()
&& self.cursor == Cursor::Sign
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit_mail"]) =>
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) =>
{
#[cfg(feature = "gpgme")]
match melib::email::parser::address::rfc2822address_list(
@ -1648,7 +1718,7 @@ impl Component for Composer {
UIEvent::Input(ref key)
if self.mode.is_edit()
&& self.cursor == Cursor::Encrypt
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit_mail"]) =>
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) =>
{
#[cfg(feature = "gpgme")]
match melib::email::parser::address::rfc2822address_list(
@ -1688,10 +1758,10 @@ impl Component for Composer {
UIEvent::Input(ref key)
if self.mode.is_edit()
&& self.cursor == Cursor::Attachments
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit_mail"]) =>
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) =>
{
self.mode = ViewMode::EditAttachments {
widget: EditAttachments::new(),
widget: EditAttachments::new(Some(self.account_hash)),
};
self.set_dirty(true);
@ -1699,7 +1769,7 @@ impl Component for Composer {
}
UIEvent::Input(ref key)
if self.embed.is_some()
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit_mail"]) =>
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) =>
{
self.embed.as_ref().unwrap().lock().unwrap().wake_up();
match self.embed.take() {
@ -1743,7 +1813,7 @@ impl Component for Composer {
}
UIEvent::Input(ref key)
if self.mode.is_edit()
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit_mail"]) =>
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["edit"]) =>
{
/* Edit draft in $EDITOR */
let editor = if let Some(editor_command) =
@ -1760,6 +1830,7 @@ impl Component for Composer {
.to_string(),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
self.set_dirty(true);
return true;
}
Ok(v) => v,
@ -1805,6 +1876,7 @@ impl Component for Composer {
));
}
}
self.set_dirty(true);
return true;
}
/* Kill input thread so that spawned command can be sole receiver of stdin */
@ -1834,6 +1906,7 @@ impl Component for Composer {
));
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
context.restore_input();
self.set_dirty(true);
return true;
}
}
@ -1913,6 +1986,7 @@ impl Component for Composer {
format!("could not execute pipe command {}: {}", command, &err),
Some(NotificationType::Error(melib::error::ErrorKind::External)),
));
self.set_dirty(true);
return true;
}
}
@ -1945,9 +2019,12 @@ impl Component for Composer {
} else {
context.replies.push_back(UIEvent::Notification(
None,
"You haven't defined any command to launch.".into(),
"You haven't defined any command to launch in \
[terminal.file_picker_command]."
.into(),
Some(NotificationType::Error(melib::error::ErrorKind::None)),
));
self.set_dirty(true);
return true;
};
/* Kill input thread so that spawned command can be sole receiver of stdin */
@ -1997,6 +2074,7 @@ impl Component for Composer {
Some(NotificationType::Error(melib::error::ErrorKind::External)),
));
context.restore_input();
self.set_dirty(true);
return true;
}
}
@ -2031,6 +2109,7 @@ impl Component for Composer {
Flag::SEEN | Flag::DRAFT,
self.account_hash,
);
self.set_dirty(true);
return true;
}
#[cfg(feature = "gpgme")]
@ -2057,7 +2136,13 @@ impl Component for Composer {
fn is_dirty(&self) -> bool {
match self.mode {
ViewMode::Embed => true,
ViewMode::EditAttachments { ref widget } => widget.dirty || widget.buttons.is_dirty(),
ViewMode::EditAttachments { ref widget } => {
widget.dirty
|| widget.buttons.is_dirty()
|| self.dirty
|| self.pager.is_dirty()
|| self.form.is_dirty()
}
ViewMode::Edit => self.dirty || self.pager.is_dirty() || self.form.is_dirty(),
ViewMode::Discard(_, ref widget) => {
widget.is_dirty() || self.pager.is_dirty() || self.form.is_dirty()

View File

@ -39,6 +39,8 @@ pub enum EditAttachmentMode {
#[derive(Debug)]
pub struct EditAttachments {
/// For shortcut setting retrieval.
pub account_hash: Option<AccountHash>,
pub mode: EditAttachmentMode,
pub buttons: ButtonWidget<FormButtonActions>,
pub cursor: EditAttachmentCursor,
@ -47,12 +49,13 @@ pub struct EditAttachments {
}
impl EditAttachments {
pub fn new() -> Self {
let mut buttons = ButtonWidget::new(("Add".into(), FormButtonActions::Other("add")));
buttons.push(("Go Back".into(), FormButtonActions::Cancel));
pub fn new(account_hash: Option<AccountHash>) -> Self {
//ButtonWidget::new(("Add".into(), FormButtonActions::Other("add")));
let mut buttons = ButtonWidget::new(("Go Back".into(), FormButtonActions::Cancel));
buttons.set_focus(true);
buttons.set_cursor(1);
EditAttachments {
account_hash,
mode: EditAttachmentMode::Overview,
buttons,
cursor: EditAttachmentCursor::Buttons,
@ -63,13 +66,31 @@ impl EditAttachments {
}
impl EditAttachmentsRefMut<'_, '_> {
fn new_edit_widget(&self, no: usize) -> Option<Box<FormWidget<FormButtonActions>>> {
fn new_edit_widget(
&self,
no: usize,
context: &Context,
) -> Option<Box<FormWidget<FormButtonActions>>> {
if no >= self.draft.attachments().len() {
return None;
}
let filename = self.draft.attachments()[no].content_type().name();
let mime_type = self.draft.attachments()[no].content_type();
let mut ret = FormWidget::new(("Save".into(), FormButtonActions::Accept));
let shortcuts = self.shortcuts(context);
let mut ret = FormWidget::new(
("Save".into(), FormButtonActions::Accept),
/* cursor_up_shortcut */
shortcuts
.get(Shortcuts::COMPOSING)
.and_then(|c| c.get("scroll_up").cloned())
.unwrap_or_else(|| context.settings.shortcuts.composing.scroll_up.clone()),
/* cursor_down_shortcut */
shortcuts
.get(Shortcuts::COMPOSING)
.and_then(|c| c.get("scroll_down").cloned())
.unwrap_or_else(|| context.settings.shortcuts.composing.scroll_down.clone()),
);
ret.add_button(("Reset".into(), FormButtonActions::Reset));
ret.add_button(("Cancel".into(), FormButtonActions::Cancel));
@ -188,7 +209,7 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
}
Some(FormButtonActions::Reset) => {
let no = *no;
if let Some(inner) = self.new_edit_widget(no) {
if let Some(inner) = self.new_edit_widget(no, context) {
self.inner.mode = EditAttachmentMode::Edit { inner, no };
}
}
@ -197,8 +218,12 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
return true;
}
} else {
let shortcuts = self.shortcuts(context);
match event {
UIEvent::Input(Key::Up) => {
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_up"]) =>
{
self.set_dirty(true);
match self.inner.cursor {
EditAttachmentCursor::AttachmentNo(ref mut n) => {
@ -224,7 +249,9 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
}
return true;
}
UIEvent::Input(Key::Down) => {
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_down"]) =>
{
self.set_dirty(true);
match self.inner.cursor {
EditAttachmentCursor::AttachmentNo(ref mut n) => {
@ -246,7 +273,7 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
UIEvent::Input(Key::Char('\n')) => {
match self.inner.cursor {
EditAttachmentCursor::AttachmentNo(ref no) => {
if let Some(inner) = self.new_edit_widget(*no) {
if let Some(inner) = self.new_edit_widget(*no, context) {
self.inner.mode = EditAttachmentMode::Edit { inner, no: *no };
}
self.set_dirty(true);
@ -293,8 +320,17 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
fn kill(&mut self, _uuid: ComponentId, _context: &mut Context) {}
fn shortcuts(&self, _context: &Context) -> ShortcutMaps {
ShortcutMaps::default()
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = ShortcutMaps::default();
let our_map: ShortcutMap = self
.inner
.account_hash
.map(|acc| account_settings!(context[acc].shortcuts.composing).key_values())
.unwrap_or_else(|| context.settings.shortcuts.composing.key_values());
map.insert(Shortcuts::COMPOSING, our_map);
map
}
fn id(&self) -> ComponentId {

View File

@ -2030,7 +2030,7 @@ impl Component for Listing {
UIEvent::Action(Action::Compose(ComposeAction::Mailto(ref mailto))) => {
let account_hash = context.accounts[self.cursor_pos.0].hash();
let mut composer = Composer::with_account(account_hash, context);
composer.set_draft(mailto.into());
composer.set_draft(mailto.into(), context);
context
.replies
.push_back(UIEvent::Action(Tab(New(Some(Box::new(composer))))));

View File

@ -58,6 +58,7 @@ pub struct MailView {
coordinates: Option<(AccountHash, MailboxHash, EnvelopeHash)>,
dirty: bool,
contact_selector: Option<Box<UIDialog<Card>>>,
forward_dialog: Option<Box<UIDialog<Option<PendingReplyAction>>>>,
theme_default: ThemeAttribute,
active_jobs: HashSet<JobId>,
state: MailViewState,
@ -68,6 +69,7 @@ impl Clone for MailView {
fn clone(&self) -> Self {
MailView {
contact_selector: None,
forward_dialog: None,
state: MailViewState::default(),
active_jobs: self.active_jobs.clone(),
..*self
@ -90,6 +92,7 @@ impl MailView {
coordinates,
dirty: true,
contact_selector: None,
forward_dialog: None,
theme_default: crate::conf::value(context, "mail.view.body"),
active_jobs: Default::default(),
state: MailViewState::default(),
@ -328,6 +331,8 @@ impl Component for MailView {
};
if let Some(ref mut s) = self.contact_selector.as_mut() {
s.draw(grid, area, context);
} else if let Some(ref mut s) = self.forward_dialog.as_mut() {
s.draw(grid, area, context);
}
self.dirty = false;
@ -339,13 +344,29 @@ impl Component for MailView {
return false;
}
if let Some(ref mut s) = self.contact_selector {
if s.process_event(event, context) {
return true;
}
}
if let Some(ref mut s) = self.forward_dialog {
if s.process_event(event, context) {
return true;
}
}
/* If envelope data is loaded, pass it to envelope views */
if self.state.process_event(event, context) {
return true;
}
match (&mut self.contact_selector, &mut event) {
(Some(ref s), UIEvent::FinishedUIDialog(id, results)) if *id == s.id() => {
match (
&mut self.contact_selector,
&mut self.forward_dialog,
&mut event,
) {
(Some(ref s), _, UIEvent::FinishedUIDialog(id, results)) if *id == s.id() => {
if let Some(results) = results.downcast_ref::<Vec<Card>>() {
let account = &mut context.accounts[&coordinates.0];
{
@ -353,54 +374,61 @@ impl Component for MailView {
account.address_book.add_card(card.clone());
}
}
self.contact_selector = None;
}
self.set_dirty(true);
return true;
}
(Some(ref mut s), _) => {
if s.process_event(event, context) {
return true;
(_, Some(ref s), UIEvent::FinishedUIDialog(id, result)) if *id == s.id() => {
if let Some(result) = result.downcast_ref::<Option<PendingReplyAction>>() {
self.forward_dialog = None;
if let Some(result) = *result {
self.perform_action(result, context);
}
}
self.set_dirty(true);
return true;
}
_ => match event {
UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id))
if self.active_jobs.contains(job_id) =>
{
match self.state {
MailViewState::LoadingBody {
ref mut handle,
pending_action: _,
} if handle.job_id == *job_id => {
match handle.chan.try_recv() {
Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker
* thread panicked */
}
Ok(Some(Ok(bytes))) => {
MailViewState::load_bytes(self, bytes, context);
}
Ok(Some(Err(err))) => {
self.state = MailViewState::Error { err };
}
_ => {}
}
match &event {
UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id))
if self.active_jobs.contains(job_id) =>
{
match self.state {
MailViewState::LoadingBody {
ref mut handle,
pending_action: _,
} if handle.job_id == *job_id => {
match handle.chan.try_recv() {
Err(_) => { /* Job was canceled */ }
Ok(None) => { /* something happened, perhaps a worker
* thread panicked */
}
Ok(Some(Ok(bytes))) => {
MailViewState::load_bytes(self, bytes, context);
}
Ok(Some(Err(err))) => {
self.state = MailViewState::Error { err };
}
}
MailViewState::Init { .. } => {
self.init_futures(context);
}
MailViewState::Loaded { .. } => {
log::debug!(
"MailView.active_jobs contains job id {:?} but MailViewState is \
already loaded; what job was this and why was it in active_jobs?",
job_id
);
}
_ => {}
}
self.active_jobs.remove(job_id);
self.set_dirty(true);
MailViewState::Init { .. } => {
self.init_futures(context);
}
MailViewState::Loaded { .. } => {
log::debug!(
"MailView.active_jobs contains job id {:?} but MailViewState is \
already loaded; what job was this and why was it in active_jobs?",
job_id
);
}
_ => {}
}
_ => {}
},
self.active_jobs.remove(job_id);
self.set_dirty(true);
}
_ => {}
}
let shortcuts = &self.shortcuts(context);
@ -436,27 +464,28 @@ impl Component for MailView {
.forward_as_attachment
) {
f if f.is_ask() => {
let id = self.id;
context.replies.push_back(UIEvent::GlobalUIDialog(Box::new(
UIConfirmationDialog::new(
"How do you want the email to be forwarded?",
vec![
(true, "inline".to_string()),
(false, "as attachment".to_string()),
],
true,
Some(Box::new(move |_: ComponentId, result: bool| {
self.forward_dialog = Some(Box::new(UIDialog::new(
"How do you want the email to be forwarded?",
vec![
(
Some(PendingReplyAction::ForwardInline),
"inline".to_string(),
),
(
Some(PendingReplyAction::ForwardAttachment),
"as attachment".to_string(),
),
],
true,
Some(Box::new(
move |id: ComponentId, result: &[Option<PendingReplyAction>]| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(if result {
PendingReplyAction::ForwardInline
} else {
PendingReplyAction::ForwardAttachment
}),
Box::new(result.get(0).cloned()),
))
})),
context,
),
},
)),
context,
)));
}
f if f.is_true() => {
@ -562,9 +591,10 @@ impl Component for MailView {
return true;
}
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt(''))
if self.contact_selector.is_some() =>
if self.contact_selector.is_some() || self.forward_dialog.is_some() =>
{
self.contact_selector = None;
self.forward_dialog = None;
self.set_dirty(true);
return true;
}
@ -592,7 +622,7 @@ impl Component for MailView {
let draft: Draft = mailto.into();
let mut composer =
Composer::with_account(coordinates.0, context);
composer.set_draft(draft);
composer.set_draft(draft, context);
context.replies.push_back(UIEvent::Action(Tab(New(Some(
Box::new(composer),
)))));
@ -747,12 +777,19 @@ impl Component for MailView {
.as_ref()
.map(|s| s.is_dirty())
.unwrap_or(false)
|| self
.forward_dialog
.as_ref()
.map(|s| s.is_dirty())
.unwrap_or(false)
}
fn set_dirty(&mut self, value: bool) {
self.dirty = value;
if let Some(ref mut s) = self.contact_selector {
s.set_dirty(value);
} else if let Some(ref mut s) = self.forward_dialog {
s.set_dirty(value);
}
self.state.set_dirty(value);
}

View File

@ -24,7 +24,7 @@ use melib::{Envelope, Error, Mail, Result};
use super::{EnvelopeView, MailView, ViewSettings};
use crate::{jobs::JoinHandle, mailbox_settings, Component, Context, ShortcutMaps, UIEvent};
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum PendingReplyAction {
Reply,
ReplyToAuthor,

View File

@ -693,7 +693,7 @@ impl Component for Pager {
}
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"])
&& dbg!(self.cols_lt_width) =>
&& self.cols_lt_width =>
{
self.movement = Some(PageMovement::Right(1));
self.dirty = true;

View File

@ -203,7 +203,7 @@ pub enum FormButtonActions {
Other(&'static str),
}
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct FormWidget<T>
where
T: 'static + std::fmt::Debug + Copy + Default + Send + Sync,
@ -217,21 +217,47 @@ where
focus: FormFocus,
hide_buttons: bool,
dirty: bool,
cursor_up_shortcut: Key,
cursor_down_shortcut: Key,
id: ComponentId,
}
impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Default for FormWidget<T> {
fn default() -> Self {
Self {
fields: Default::default(),
layout: Default::default(),
buttons: Default::default(),
focus: FormFocus::Fields,
hide_buttons: false,
field_name_max_length: 10,
cursor: 0,
dirty: true,
cursor_up_shortcut: Key::Up,
cursor_down_shortcut: Key::Down,
id: ComponentId::default(),
}
}
}
impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> fmt::Display for FormWidget<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt("", f)
write!(f, "form")
}
}
impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> FormWidget<T> {
pub fn new(action: (Cow<'static, str>, T)) -> FormWidget<T> {
pub fn new(
action: (Cow<'static, str>, T),
cursor_up_shortcut: Key,
cursor_down_shortcut: Key,
) -> FormWidget<T> {
FormWidget {
buttons: ButtonWidget::new(action),
focus: FormFocus::Fields,
hide_buttons: false,
cursor_up_shortcut,
cursor_down_shortcut,
id: ComponentId::default(),
dirty: true,
..Default::default()
@ -269,6 +295,7 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> FormWidget<T>
self.fields
.insert(value.0, Field::Choice(value.1, 0, ComponentId::default()));
}
pub fn push_cl(&mut self, value: (Cow<'static, str>, String, AutoCompleteFn)) {
self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len());
self.layout.push(value.0.clone());
@ -280,6 +307,7 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> FormWidget<T>
)),
);
}
pub fn push(&mut self, value: (Cow<'static, str>, String)) {
self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len());
self.layout.push(value.0.clone());
@ -309,6 +337,7 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> FormWidget<T>
None
}
}
pub fn buttons_result(&self) -> Option<T> {
self.buttons.result
}
@ -423,13 +452,19 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
context.dirty_areas.push_back(area);
}
}
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
if self.focus == FormFocus::Buttons && self.buttons.process_event(event, context) {
if !self.hide_buttons
&& self.focus == FormFocus::Buttons
&& self.buttons.process_event(event, context)
{
return true;
}
match *event {
UIEvent::Input(Key::Up) if self.focus == FormFocus::Buttons => {
UIEvent::Input(ref k)
if *k == self.cursor_up_shortcut && self.focus == FormFocus::Buttons =>
{
self.focus = FormFocus::Fields;
self.buttons.set_focus(false);
self.set_dirty(true);
@ -441,7 +476,7 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
self.set_dirty(true);
return true;
}
UIEvent::Input(Key::Up) => {
UIEvent::Input(ref k) if *k == self.cursor_up_shortcut => {
self.cursor = self.cursor.saturating_sub(1);
self.set_dirty(true);
return true;
@ -452,12 +487,17 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
self.set_dirty(true);
return true;
}
UIEvent::Input(Key::Down) if self.cursor < self.layout.len().saturating_sub(1) => {
UIEvent::Input(ref k)
if *k == self.cursor_down_shortcut
&& self.cursor < self.layout.len().saturating_sub(1) =>
{
self.cursor += 1;
self.set_dirty(true);
return true;
}
UIEvent::Input(Key::Down) if self.focus == FormFocus::Fields => {
UIEvent::Input(ref k)
if *k == self.cursor_down_shortcut && self.focus == FormFocus::Fields =>
{
self.focus = FormFocus::Buttons;
self.buttons.set_focus(true);
self.set_dirty(true);
@ -517,9 +557,11 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
}
false
}
fn is_dirty(&self) -> bool {
self.dirty || self.buttons.is_dirty()
}
fn set_dirty(&mut self, value: bool) {
self.dirty = value;
self.buttons.set_dirty(value);
@ -630,6 +672,7 @@ where
self.dirty = false;
}
}
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
match *event {
UIEvent::Input(Key::Char('\n')) => {
@ -655,9 +698,11 @@ where
}
false
}
fn is_dirty(&self) -> bool {
self.dirty
}
fn set_dirty(&mut self, value: bool) {
self.dirty = value;
}

View File

@ -228,7 +228,7 @@ shortcut_key_values! { "general",
shortcut_key_values! { "composing",
pub struct ComposingShortcuts {
edit_mail |> "Edit mail." |> Key::Char('e'),
edit |> "Edit." |> Key::Char('e'),
send_mail |> "Deliver draft to mailer" |> Key::Char('s'),
scroll_up |> "Change field focus." |> Key::Char('k'),
scroll_down |> "Change field focus." |> Key::Char('j')

View File

@ -1217,12 +1217,17 @@ impl State {
(callback_fn.0)(&mut self.context);
return;
}
UIEvent::GlobalUIDialog(dialog) => {
self.overlay.insert(dialog.id(), dialog);
UIEvent::GlobalUIDialog { value, parent } => {
self.context.realized.insert(value.id(), parent);
self.overlay.insert(value.id(), value);
self.process_realizations();
return;
}
_ => {}
}
self.process_realizations();
let Self {
ref mut components,
ref mut context,
@ -1237,6 +1242,15 @@ impl State {
}
}
if !self.context.replies.is_empty() {
let replies: smallvec::SmallVec<[UIEvent; 8]> =
self.context.replies.drain(0..).collect();
// Pass replies to self and call count on the map iterator to force evaluation
replies.into_iter().map(|r| self.rcv_event(r)).count();
}
}
fn process_realizations(&mut self) {
while let Some((id, parent)) = self.context.realized.pop() {
match parent {
None => {
@ -1271,6 +1285,7 @@ impl State {
}
}
}
while let Some(id) = self.context.unrealized.pop() {
let mut to_delete = BTreeSet::new();
for (desc, _) in self.component_tree.iter().filter(|(_, path)| {
@ -1285,13 +1300,6 @@ impl State {
self.components.remove(&id);
self.overlay.remove(&id);
}
if !self.context.replies.is_empty() {
let replies: smallvec::SmallVec<[UIEvent; 8]> =
self.context.replies.drain(0..).collect();
// Pass replies to self and call count on the map iterator to force evaluation
replies.into_iter().map(|r| self.rcv_event(r)).count();
}
}
pub fn try_wait_on_child(&mut self) -> Option<bool> {

View File

@ -154,7 +154,10 @@ pub enum UIEvent {
content: UIMessage,
},
Callback(CallbackFn),
GlobalUIDialog(Box<dyn Component>),
GlobalUIDialog {
value: Box<dyn Component>,
parent: Option<ComponentId>,
},
Timer(TimerId),
ConfigReload {
old_settings: Box<crate::conf::Settings>,