1 use {
2     super::*,
3     crate::{
4         command::*,
5         display::{
6             status_line,
7             Areas,
8             Screen,
9             W,
10             WIDE_STATUS,
11             flags_display,
12         },
13         errors::ProgramError,
14         keys,
15         skin::PanelSkin,
16         stage::*,
17         task_sync::Dam,
18         verb::*,
19     },
20     termimad::{
21         minimad::{Alignment, Composite},
22         TimedEvent,
23     },
24 };
25 
26 /// A colon on screen containing a stack of states, the top
27 /// one being visible
28 pub struct Panel {
29     pub id: PanelId,
30     states: Vec<Box<dyn PanelState>>, // stack: the last one is current
31     pub areas: Areas,
32     status: Status,
33     pub purpose: PanelPurpose,
34     input: PanelInput,
35 }
36 
37 impl Panel {
38 
new( id: PanelId, state: Box<dyn PanelState>, areas: Areas, con: &AppContext, ) -> Self39     pub fn new(
40         id: PanelId,
41         state: Box<dyn PanelState>,
42         areas: Areas,
43         con: &AppContext,
44     ) -> Self {
45         let mut input = PanelInput::new(areas.input.clone());
46         input.set_content(&state.get_starting_input());
47         let status = state.no_verb_status(false, con);
48         Self {
49             id,
50             states: vec![state],
51             areas,
52             status,
53             purpose: PanelPurpose::None,
54             input,
55         }
56     }
57 
set_error(&mut self, text: String)58     pub fn set_error(&mut self, text: String) {
59         self.status = Status::from_error(text);
60     }
61 
62     /// apply a command on the current state, with no
63     /// effect on screen
64     #[allow(clippy::too_many_arguments)] // a refactory could still be useful
apply_command<'c>( &mut self, w: &'c mut W, cmd: &'c Command, app_state: &mut AppState, app_cmd_context: &'c AppCmdContext<'c>, ) -> Result<CmdResult, ProgramError>65     pub fn apply_command<'c>(
66         &mut self,
67         w: &'c mut W,
68         cmd: &'c Command,
69         app_state: &mut AppState,
70         app_cmd_context: &'c AppCmdContext<'c>,
71     ) -> Result<CmdResult, ProgramError> {
72         let state_idx = self.states.len() - 1;
73         let cc = CmdContext {
74             cmd,
75             app: app_cmd_context,
76             panel: PanelCmdContext {
77                 areas: &self.areas,
78                 purpose: self.purpose,
79             },
80         };
81         let result = self.states[state_idx].on_command(w, app_state, &cc);
82         let has_previous_state = self.states.len() > 1;
83         self.status = self.state().get_status(app_state, &cc, has_previous_state);
84         result
85     }
86 
87     /// called on focusing the panel and before the display,
88     /// this updates the status from the command read in the input
refresh_input_status<'c>( &mut self, app_state: &AppState, app_cmd_context: &'c AppCmdContext<'c>, )89     pub fn refresh_input_status<'c>(
90         &mut self,
91         app_state: &AppState,
92         app_cmd_context: &'c AppCmdContext<'c>,
93     ) {
94         let cmd = Command::from_raw(self.input.get_content(), false);
95         let cc = CmdContext {
96             cmd: &cmd,
97             app: app_cmd_context,
98             panel: PanelCmdContext {
99                 areas: &self.areas,
100                 purpose: self.purpose,
101             },
102         };
103         let has_previous_state = self.states.len() > 1;
104         self.status = self.state().get_status(app_state, &cc, has_previous_state);
105     }
106 
107 
108     /// do the next pending task stopping as soon as there's an event
109     /// in the dam
do_pending_task( &mut self, stage: &Stage, screen: Screen, con: &AppContext, dam: &mut Dam, )110     pub fn do_pending_task(
111         &mut self,
112         stage: &Stage,
113         screen: Screen,
114         con: &AppContext,
115         dam: &mut Dam,
116     ) {
117         self.mut_state().do_pending_task(stage, screen, con, dam)
118     }
119 
has_pending_task(&self) -> bool120     pub fn has_pending_task(&self) -> bool {
121         self.state().get_pending_task().is_some()
122     }
123 
124     /// return a new command
125     /// Update the input field
add_event( &mut self, w: &mut W, event: TimedEvent, app_state: &AppState, con: &AppContext, ) -> Result<Command, ProgramError>126     pub fn add_event(
127         &mut self,
128         w: &mut W,
129         event: TimedEvent,
130         app_state: &AppState,
131         con: &AppContext,
132     ) -> Result<Command, ProgramError> {
133         let sel_info = self.states[self.states.len() - 1].sel_info(app_state);
134         self.input.on_event(w, event, con, sel_info, app_state, self.state().get_mode())
135     }
136 
push_state(&mut self, new_state: Box<dyn PanelState>)137     pub fn push_state(&mut self, new_state: Box<dyn PanelState>) {
138         self.input.set_content(&new_state.get_starting_input());
139         self.states.push(new_state);
140     }
mut_state(&mut self) -> &mut dyn PanelState141     pub fn mut_state(&mut self) -> &mut dyn PanelState {
142         self.states.last_mut().unwrap().as_mut()
143     }
state(&self) -> &dyn PanelState144     pub fn state(&self) -> &dyn PanelState {
145         self.states.last().unwrap().as_ref()
146     }
147 
clear_input(&mut self)148     pub fn clear_input(&mut self) {
149         self.input.set_content("");
150     }
151     /// remove the verb invocation from the input but keep
152     /// the filter if there's one
clear_input_invocation(&mut self, con: &AppContext)153     pub fn clear_input_invocation(&mut self, con: &AppContext) {
154         let mut command_parts = CommandParts::from(self.input.get_content());
155         if command_parts.verb_invocation.is_some() {
156             command_parts.verb_invocation = None;
157             let new_input = format!("{}", command_parts);
158             self.input.set_content(&new_input);
159         }
160         self.mut_state().set_mode(initial_mode(con));
161     }
162 
set_input_content(&mut self, content: &str)163     pub fn set_input_content(&mut self, content: &str) {
164         self.input.set_content(content);
165     }
166 
get_input_content(&self) -> String167     pub fn get_input_content(&self) -> String {
168         self.input.get_content()
169     }
170 
171     /// change the argument of the verb in the input, if there's one
set_input_arg(&mut self, arg: String)172     pub fn set_input_arg(&mut self, arg: String) {
173         let mut command_parts = CommandParts::from(self.input.get_content());
174         if let Some(invocation) = &mut command_parts.verb_invocation {
175             invocation.args = Some(arg);
176             let new_input = format!("{}", command_parts);
177             self.input.set_content(&new_input);
178         }
179     }
180 
181     /// return true when the element has been removed
remove_state(&mut self) -> bool182     pub fn remove_state(&mut self) -> bool {
183         if self.states.len() > 1 {
184             self.states.pop();
185             self.input.set_content(&self.state().get_starting_input());
186             true
187         } else {
188             false
189         }
190     }
191 
192     /// render the whole panel (state, status, purpose, input, flags)
display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError>193     pub fn display(
194         &mut self,
195         w: &mut W,
196         disc: &DisplayContext,
197     ) -> Result<(), ProgramError> {
198         self.mut_state().display(w, disc)?;
199         if disc.active || !WIDE_STATUS {
200             self.write_status(w, disc.panel_skin, disc.screen)?;
201         }
202         let mut input_area = self.areas.input.clone();
203         if disc.active {
204             self.write_purpose(w, disc.panel_skin, disc.screen, disc.con)?;
205             let flags = self.state().get_flags();
206             let input_content_len = self.input.get_content().len() as u16;
207             let flags_len = flags_display::visible_width(&flags);
208             if input_area.width > input_content_len + 1 + flags_len {
209                 input_area.width -= flags_len + 1;
210                 disc.screen.goto(w, input_area.left + input_area.width, input_area.top)?;
211                 flags_display::write(w, &flags, disc.panel_skin)?;
212             }
213         }
214         self.input.display(w, disc.active, self.state().get_mode(), input_area, disc.panel_skin)?;
215         Ok(())
216     }
217 
write_status( &self, w: &mut W, panel_skin: &PanelSkin, screen: Screen, ) -> Result<(), ProgramError>218     fn write_status(
219         &self,
220         w: &mut W,
221         panel_skin: &PanelSkin,
222         screen: Screen,
223     ) -> Result<(), ProgramError> {
224         let task = self.state().get_pending_task();
225         status_line::write(
226             w,
227             task,
228             &self.status,
229             &self.areas.status,
230             panel_skin,
231             screen,
232         )
233     }
234 
235     /// if a panel has a specific purpose (i.e. is here for
236     /// editing of the verb argument on another panel), render
237     /// a hint of that purpose on screen
write_purpose( &self, w: &mut W, panel_skin: &PanelSkin, screen: Screen, con: &AppContext, ) -> Result<(), ProgramError>238     fn write_purpose(
239         &self,
240         w: &mut W,
241         panel_skin: &PanelSkin,
242         screen: Screen,
243         con: &AppContext,
244     ) -> Result<(), ProgramError> {
245         if !self.purpose.is_arg_edition() {
246             return Ok(());
247         }
248         if let Some(area) = &self.areas.purpose {
249             let shortcut = con
250                 .verb_store
251                 .verbs
252                 .iter()
253                 .filter(|v| match &v.execution {
254                     VerbExecution::Internal(exec) => exec.internal == Internal::start_end_panel,
255                     _ => false,
256                 })
257                 .filter_map(|v| v.keys.first())
258                 .map(|&k| keys::key_event_desc(k))
259                 .next()
260                 .unwrap_or_else(|| ":start_end_panel".to_string());
261 
262             let md = format!("hit *{}* to fill arg ", shortcut);
263             // Add verbindex in purpose ?
264             screen.goto(w, area.left, area.top)?;
265             panel_skin.purpose_skin.write_composite_fill(
266                 w,
267                 Composite::from_inline(&md),
268                 area.width as usize,
269                 Alignment::Right,
270             )?;
271         }
272         Ok(())
273     }
274 
275 }
276