use crate::cli_parser::dotconfig_file;
use crate::windowing::ll::mvwaddwch;
use super::input::CharPositions;
use super::input::CursorPos;
use super::ll::mvwaddwstr;
use super::result::HandleResult;
use super::traits::WindowLike;
use super::utils::create_window;
use super::utils::ColorPair;
use super::utils::StringExt;
use kconfig_represent::ConfigRegistry;
use kconfig_represent::DescriptorInfo;
use kconfig_represent::Error;
use kconfig_represent::InputMode;
use kconfig_represent::MenuItemDescriptor;
use ncurses::*;
pub(super) struct MainWindow {
w: Option<WINDOW>,
tree: Vec<String>,
sw: Option<WINDOW>,
registry: ConfigRegistry,
menu_index: usize,
first_visible_item: usize,
config_under_edit: Option<String>,
config_value_codepoints: Vec<char>,
raw_cursor_offset: usize,
input_mode: InputMode,
}
impl WindowLike for MainWindow {
fn new() -> Self {
Self {
w: None,
sw: None,
tree: Vec::new(),
registry: ConfigRegistry::default(),
menu_index: 0,
first_visible_item: 0,
config_under_edit: None,
config_value_codepoints: Vec::new(),
raw_cursor_offset: 0,
input_mode: InputMode::String,
}
}
fn del(&self) {
if let Some(w) = self.w {
delwin(w);
}
if let Some(w) = self.sw {
delwin(w);
}
}
fn create(&mut self) {
self.w = Some(create_window(
LINES() - 2,
COLS(),
1,
0,
ColorPair::Default.raw(),
));
self.sw = Some(create_window(
LINES() - 6,
COLS() - 4,
3,
2,
ColorPair::Internal.raw(),
));
}
fn raw(&self) -> Option<WINDOW> {
self.w
}
fn draw(&mut self) {
if let Some(w) = self.w {
wclear(w);
wbkgd(w, COLOR_PAIR(ColorPair::Default.raw()));
wrefresh(w);
self.draw_subwindow();
}
}
}
impl MainWindow {
pub(crate) fn set_registry(&mut self, registry: ConfigRegistry) {
self.tree.push(registry.main_menu_name());
self.registry = registry;
self.reset_index();
}
pub(crate) fn handle_ch(&mut self, opt_wch: Option<WchResult>) -> HandleResult {
match opt_wch {
Some(wch) => match wch {
WchResult::KeyCode(key_code) => match key_code {
KEY_F9 => HandleResult::Exit,
KEY_BACKSPACE => HandleResult::Backspace,
KEY_DC => HandleResult::Delete,
KEY_DOWN => HandleResult::NavDown,
KEY_UP => HandleResult::NavUp,
KEY_LEFT => HandleResult::MoveLeft,
KEY_RIGHT => HandleResult::MoveRight,
KEY_ENTER | KEY_SELECT => match self.config_under_edit {
None => HandleResult::Select,
Some(_) => HandleResult::Char('\n'),
},
_ => HandleResult::None,
},
WchResult::Char(utf32) => match char::from_u32(utf32) {
Some(c) => match self.config_under_edit {
Some(_) => HandleResult::Char(c),
None => match c {
'\n' | '\r' => HandleResult::Select,
'i' | 'I' => HandleResult::Info,
_ => HandleResult::None,
},
},
None => HandleResult::None,
},
},
_ => HandleResult::None,
}
}
pub fn increment_menu_index(&mut self) {
if self.menu_index + 1 != self.menu_len_enabled() {
self.menu_index += 1;
self.draw_subwindow();
}
}
pub fn decrement_menu_index(&mut self) {
if self.menu_index != 0 {
self.menu_index -= 1;
self.draw_subwindow();
}
}
pub(crate) fn activate_selection(&mut self) -> Result<(), Error> {
Ok(match self.active_descriptor() {
Ok(descriptor) => match descriptor {
MenuItemDescriptor::Menu(name) => self.push_menu(&name),
MenuItemDescriptor::Config(name) => self.edit_config(&name)?,
},
Err(_) => (), })
}
fn save_config_value(&mut self) -> Result<(), Error> {
if let Some(config_name) = &self.config_under_edit {
self.registry.mutate_through_string(
&config_name,
&self.config_value_codepoints.iter().collect::<String>(),
)?
}
Ok(())
}
pub(crate) fn active_descriptor(&self) -> Result<MenuItemDescriptor, Error> {
let mut current_item = 0;
let menu = self.active_menu_descriptor();
if let Some(menu) = menu {
for descriptor in self
.registry
.iter_enabled_sub_descriptors(&menu.to_string())
{
if current_item == self.menu_index {
return Ok(descriptor);
}
current_item += 1;
}
}
Err(Error::from(
"Current selected item descriptor not found".to_owned(),
))
}
pub(crate) fn active_descriptor_info(&self) -> Result<DescriptorInfo, Error> {
let descriptor = self.active_descriptor()?;
Ok(self.registry.descriptor_info(&descriptor))
}
pub(crate) fn pop_selection(&mut self) -> Result<bool, Error> {
Ok(match self.config_under_edit {
None => {
if self.tree.len() > 1 {
self.tree.pop();
self.draw_subwindow();
true
} else {
false
}
}
Some(_) => {
self.save_config_value()?;
self.raw_cursor_offset = 0;
self.config_under_edit = None;
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);
true
}
})
}
fn push_menu(&mut self, name: &str) {
self.tree.push(name.to_string());
self.reset_index();
self.draw_subwindow();
}
fn edit_config(&mut self, name: &str) -> Result<(), Error> {
match self.registry.config_mutation_mode(name) {
kconfig_represent::ConfigMutationMode::ThroughString(mode) => {
self.config_value_codepoints = self.registry.config_value(name).chars().collect();
self.raw_cursor_offset = self.config_value_codepoints.len();
self.config_under_edit = Some(name.to_string());
self.input_mode = mode;
}
kconfig_represent::ConfigMutationMode::ThroughStep => {
self.registry.mutate_through_step(name)?
}
kconfig_represent::ConfigMutationMode::None => (),
}
if let Some(_) = self.config_under_edit {
curs_set(CURSOR_VISIBILITY::CURSOR_VISIBLE);
}
Ok(())
}
fn determine_visible_item_range(&mut self) -> (usize, usize) {
if let Some(w) = self.sw {
let height = (getmaxy(w) - 4) as usize;
let mut last_visible_item = self.first_visible_item + height;
if self.menu_index < self.first_visible_item {
self.first_visible_item = self.menu_index;
} else if self.menu_index > last_visible_item {
self.first_visible_item = self.menu_index - height;
last_visible_item = self.first_visible_item + height;
}
(self.first_visible_item, last_visible_item)
} else {
(0, 0)
}
}
fn is_item_visible(&mut self, index: usize) -> bool {
let (first_visible_item, last_visible_item) = self.determine_visible_item_range();
index >= first_visible_item && index <= last_visible_item
}
pub fn refresh(&mut self) {
super::refresh();
if let Some(w) = self.sw {
wrefresh(w);
}
}
fn draw_subwindow(&mut self) {
self.draw_backdrop();
self.draw_menutext();
self.draw_posstr();
self.draw_inner();
self.refresh();
}
fn draw_inner(&mut self) {
match &self.config_under_edit {
None => self.draw_inner_menu_mode(),
Some(_) => self.draw_text_input(),
}
}
fn draw_inner_menu_mode(&mut self) {
let descriptor = match self.active_menu_descriptor() {
Some(descriptor) => descriptor,
None => return,
};
let mut draw_index = 0;
let mut current_item = 0;
let sub_descriptors = self
.registry
.iter_enabled_sub_descriptors(&descriptor.to_string());
for sub_descriptor in sub_descriptors {
if {
let ref this = self;
let menu_item = &sub_descriptor;
this.registry.menu_item_enabled(menu_item)
} {
let formatted = self.render_submenu(&sub_descriptor);
if self.is_item_visible(current_item) {
match current_item == self.menu_index {
true => self.draw_highlight(draw_index, &formatted),
false => self.draw_no_highlight(draw_index, &formatted),
};
draw_index += 1;
}
current_item += 1;
}
}
}
fn render_submenu(&self, sub_descriptor: &MenuItemDescriptor) -> String {
let text = self
.registry
.menu_item_string(sub_descriptor)
.chars()
.map(|c| match c {
'\n' => "\\n".to_owned(),
'\r' => "\\r".to_owned(),
'\t' => "\\t".to_owned(),
'\x00'..='\x1F' => format!("\\{}", c as u8),
' '.. => c.to_string(),
})
.collect::<String>();
if let Some(b) = self.registry.has_default_value_set(sub_descriptor) {
if b {
format!("[ ] {}", text)
} else {
format!("[*] {}", text)
}
} else {
format!("{} ---->", text)
}
}
fn draw_highlight(&self, index: usize, s: &str) {
if let Some(w) = self.sw {
wattron(w, COLOR_PAIR(ColorPair::Highlight.raw()));
let s = self.chop_string(s);
mvwaddstr(w, (index + 2) as i32, 2, s.as_str());
wattron(w, COLOR_PAIR(ColorPair::Internal.raw()));
}
}
fn draw_no_highlight(&self, index: usize, s: &str) {
if let Some(w) = self.sw {
let s = self.chop_string(s);
mvwaddstr(w, (index + 2) as i32, 2, s.as_str());
}
}
fn erase_text_input(&mut self) {
if let Some(w) = self.sw {
for y in 2..self.inner_height() - 4 {
mvwaddstr(
w,
y,
2,
&format!("{:width$}", " ", width = (self.inner_width() - 4) as usize),
);
}
}
}
fn draw_text_input(&mut self) {
self.erase_text_input();
let cursor_pos = CursorPos::new(
2,
2,
(self.inner_width() - 2) as usize,
(self.inner_height() - 2) as usize,
&self.config_value_codepoints,
self.raw_cursor_offset,
);
let char_positions = CharPositions::new(&cursor_pos);
if let Some(w) = self.sw {
for cp in char_positions.into_iter() {
mvwaddwch(w, cp.y() as i32, cp.x() as i32, cp.c());
}
wmove(w, cursor_pos.y() as i32, cursor_pos.x() as i32);
}
}
pub fn addchar(&mut self, c: char) {
if self.input_mode == InputMode::Int && (c < '0' || c > '9') {
return; }
if self.input_mode == InputMode::Hex
&& (c < '0' || c > '9')
&& (c < 'A' || c > 'F')
&& (c < 'a' || c > 'f')
{
return; }
if let Some(_) = self.config_under_edit {
let v = if self.input_mode == InputMode::Hex {
c.to_ascii_uppercase().to_string()
} else {
c.to_string()
};
let old_codepoints = self.config_value_codepoints.clone();
self.config_value_codepoints.clear();
let lhs = &old_codepoints[..self.raw_cursor_offset];
let rhs = &old_codepoints[self.raw_cursor_offset..];
self.config_value_codepoints.extend(lhs);
self.config_value_codepoints.extend(v.chars());
self.config_value_codepoints.extend(rhs);
self.raw_cursor_offset += 1;
self.draw_text_input();
self.refresh();
}
}
pub fn rmchar_left(&mut self) {
if let Some(_) = self.config_under_edit {
if !self.config_value_codepoints.is_empty()
&& ((self.input_mode == InputMode::Hex && self.raw_cursor_offset > 2)
|| (self.input_mode != InputMode::Hex && self.raw_cursor_offset > 0))
{
let end = self.raw_cursor_offset - 1;
let splitted = self.split_config_value();
let lhs = splitted.0[..end].iter().cloned().collect::<Vec<char>>();
let rhs = splitted.1.iter().cloned().collect::<Vec<char>>();
self.replace_config_value_with_lhs_and_rhs(lhs, rhs);
self.raw_cursor_offset = end;
self.draw_text_input();
self.refresh();
}
}
}
pub fn rmchar_right(&mut self) {
if let Some(_) = self.config_under_edit {
if self.raw_cursor_offset < self.config_value_codepoints.len() {
let splitted = self.split_config_value();
let lhs = splitted.0.iter().cloned().collect::<Vec<char>>();
let rhs = splitted.1[1..].iter().cloned().collect::<Vec<char>>();
self.replace_config_value_with_lhs_and_rhs(lhs, rhs);
self.draw_text_input();
self.refresh();
}
}
}
fn split_config_value(&mut self) -> (&[char], &[char]) {
self.config_value_codepoints
.split_at(self.raw_cursor_offset)
}
fn replace_config_value_with_lhs_and_rhs(&mut self, lhs: Vec<char>, rhs: Vec<char>) {
self.config_value_codepoints.clear();
self.config_value_codepoints.extend(&lhs);
self.config_value_codepoints.extend(&rhs);
}
pub fn char_left(&mut self) {
if self.raw_cursor_offset > 0 {
self.raw_cursor_offset -= 1;
self.draw_text_input();
self.refresh();
}
}
pub fn char_right(&mut self) {
if self.raw_cursor_offset < self.config_value_codepoints.len() {
self.raw_cursor_offset += 1;
self.draw_text_input();
self.refresh();
}
}
pub fn changed(&self) -> bool {
self.registry.changed()
}
pub fn save(&mut self) -> Result<(), Error> {
self.registry.write_dotconfig_file(&dotconfig_file())
}
fn chop_string(&self, s: &str) -> String {
if let Some(w) = self.sw {
let width = (getmaxx(w) - 4) as usize;
let mut max_string = if s.len() > width {
s[..width].to_string()
} else {
s.to_string()
};
while max_string.len() < width {
max_string.push(' ');
}
max_string
} else {
"".to_string()
}
}
fn draw_menutext(&self) {
if let Some(w) = self.sw {
let width = getmaxx(w) - 4;
let mut final_string = String::new();
for item in self.tree.iter() {
if final_string.len() == 0 {
final_string += &item;
} else {
final_string += " > ";
final_string += &item;
}
}
if let Some(config_name) = &self.config_under_edit {
final_string += " ( ";
final_string += config_name;
final_string += " ) ";
}
wattron(w, A_BOLD());
mvwaddwstr(w, 0, 2, &final_string.unicode_truncate(width));
wattroff(w, A_BOLD());
}
}
fn draw_posstr(&self) {
if let None = self.config_under_edit {
let index = self.menu_index + 1;
let size = self.menu_len_enabled();
if let Some(w) = self.sw {
let pos_str = format!(
" Selected: {} of {} ({:.0}%) ",
index,
size,
(index as f32) * 100.0 / (size as f32)
);
wattron(w, A_BOLD());
mvwaddstr(
w,
self.inner_height() - 1,
self.inner_width() - 2 - pos_str.len() as i32,
pos_str.as_str(),
);
wattroff(w, A_BOLD());
}
}
}
fn draw_backdrop(&self) {
if let Some(w) = self.sw {
wclear(w);
wbkgd(w, COLOR_PAIR(ColorPair::Internal.raw()));
wborder(
w,
ACS_VLINE(),
ACS_VLINE(),
ACS_HLINE(),
ACS_HLINE(),
ACS_ULCORNER(),
ACS_URCORNER(),
ACS_LLCORNER(),
ACS_LRCORNER(),
);
}
}
fn menu_len_enabled(&self) -> usize {
let descriptor = self.active_menu_descriptor();
descriptor
.map(|m| self.registry.menu_enabled_children_len(&m.to_string()))
.unwrap_or_default()
}
fn active_menu_descriptor(&self) -> Option<MenuItemDescriptor> {
let name = self.tree.last()?;
Some(MenuItemDescriptor::Menu(name.clone()))
}
fn reset_index(&mut self) {
self.menu_index = 0;
self.first_visible_item = 0;
}
fn inner_width(&self) -> i32 {
match self.sw {
Some(w) => getmaxx(w),
None => 0,
}
}
fn inner_height(&self) -> i32 {
match self.sw {
Some(w) => getmaxy(w),
None => 0,
}
}
}