#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use crate::ported::crt::{
ColorElements, KEY_DC, KEY_DEL_MAC, KEY_DOWN, KEY_ENTER, KEY_F, KEY_MOUSE, KEY_RECLICK, KEY_UP,
};
use crate::ported::dynamiccolumn::DynamicColumn;
use crate::ported::functionbar::FunctionBar_new;
use crate::ported::hashtable::{Hashtable, Hashtable_get};
use crate::ported::linux::linuxprocess::{Process_fields, LAST_PROCESSFIELD};
use crate::ported::listitem::{ListItem, ListItem_new};
use crate::ported::object::Object;
use crate::ported::panel::{
HandlerResult, Panel, PanelClass, Panel_add, Panel_done, Panel_get, Panel_getSelectedIndex,
Panel_moveSelectedDown, Panel_moveSelectedUp, Panel_new, Panel_prune, Panel_remove,
Panel_selectByTyping, Panel_setHeader, Panel_setSelectionColor, Panel_size,
EVENT_PANEL_LOST_FOCUS,
};
use crate::ported::settings::{RowField, ScreenSettings};
const KEY_F7: i32 = KEY_F(7);
const KEY_F8: i32 = KEY_F(8);
const KEY_F9: i32 = KEY_F(9);
const LEFT_BRACKET: i32 = b'[' as i32;
const MINUS: i32 = b'-' as i32;
const RIGHT_BRACKET: i32 = b']' as i32;
const PLUS: i32 = b'+' as i32;
static ColumnsFunctions: [&str; 10] = [
" ", " ", " ", " ", " ", " ", "MoveUp", "MoveDn", "Remove",
"Done ",
];
#[repr(C)]
pub struct ColumnsPanel {
pub super_: Panel,
pub ss: *mut ScreenSettings,
pub changed: *mut bool,
pub moving: bool,
}
impl PanelClass for ColumnsPanel {
fn as_panel(&self) -> &Panel {
&self.super_
}
fn as_panel_mut(&mut self) -> &mut Panel {
&mut self.super_
}
fn event_handler(&mut self, ev: i32) -> HandlerResult {
ColumnsPanel_eventHandler(self, ev)
}
}
pub fn ColumnsPanel_delete(this: ColumnsPanel) {
let ColumnsPanel {
super_,
moving,
ss,
changed,
} = this;
Panel_done(super_);
let _ = moving;
let _ = ss;
let _ = changed;
}
pub fn ColumnsPanel_cancelMoving(this: &mut ColumnsPanel) {
let super_ = &mut this.super_;
let size = Panel_size(super_);
for i in 0..size {
let obj: &mut dyn Object = super_.items[i as usize].object_mut();
let any: &mut dyn core::any::Any = obj;
if let Some(item) = any.downcast_mut::<ListItem>() {
item.moving = false;
}
}
this.moving = false;
Panel_setSelectionColor(&mut this.super_, ColorElements::PANEL_SELECTION_FOCUS);
}
pub fn ColumnsPanel_eventHandler(this: &mut ColumnsPanel, ch: i32) -> HandlerResult {
let selected = Panel_getSelectedIndex(&this.super_);
let mut result = HandlerResult::IGNORED;
let size = Panel_size(&this.super_);
match ch {
0x0a | 0x0d | KEY_ENTER | KEY_RECLICK => {
if selected < size {
if this.moving {
ColumnsPanel_cancelMoving(this);
} else {
this.moving = true;
Panel_setSelectionColor(
&mut this.super_,
ColorElements::PANEL_SELECTION_FOLLOW,
);
let sel = this.super_.selected as usize;
let any: &mut dyn core::any::Any = this.super_.items[sel].object_mut();
if let Some(item) = any.downcast_mut::<ListItem>() {
item.moving = true;
}
}
result = HandlerResult::HANDLED;
}
}
KEY_MOUSE => {
if this.moving {
ColumnsPanel_cancelMoving(this);
result = HandlerResult::HANDLED;
}
}
KEY_UP if !this.moving => {}
KEY_UP | KEY_F7 | LEFT_BRACKET | MINUS => {
if selected < size {
Panel_moveSelectedUp(&mut this.super_);
}
result = HandlerResult::HANDLED;
}
KEY_DOWN if !this.moving => {}
KEY_DOWN | KEY_F8 | RIGHT_BRACKET | PLUS => {
if selected < size - 1 {
Panel_moveSelectedDown(&mut this.super_);
}
result = HandlerResult::HANDLED;
}
KEY_F9 | KEY_DC | KEY_DEL_MAC => {
if size > 1 && selected < size {
Panel_remove(&mut this.super_, selected);
}
result = HandlerResult::HANDLED;
}
EVENT_PANEL_LOST_FOCUS => {
if this.moving {
ColumnsPanel_cancelMoving(this);
}
result = HandlerResult::HANDLED;
}
_ => {
if 0 < ch && ch < 255 && (ch as u8).is_ascii_graphic() {
result = Panel_selectByTyping(&mut this.super_, ch);
}
if result == HandlerResult::BREAK_LOOP {
result = HandlerResult::IGNORED;
}
}
}
if result == HandlerResult::HANDLED {
ColumnsPanel_update(&mut this.super_);
}
result
}
pub fn ColumnsPanel_add(super_: &mut Panel, key: u32, columns: &Hashtable) {
let name: &str = if (key as usize) < LAST_PROCESSFIELD {
Process_fields[key as usize].name
} else {
match Hashtable_get(columns, key) {
Some(obj) => {
let any: &dyn core::any::Any = obj;
let column = any
.downcast_ref::<DynamicColumn>()
.expect("ColumnsPanel_add: dynamic column entry is not a DynamicColumn");
column.heading.as_deref().unwrap_or(column.name.as_str())
}
None => "", }
};
let name = if name.is_empty() { "- " } else { name };
Panel_add(super_, Box::new(ListItem_new(name, key as i32)));
}
pub fn ColumnsPanel_fill(this: &mut ColumnsPanel, ss: *mut ScreenSettings, columns: &Hashtable) {
Panel_prune(&mut this.super_);
let ss_ref = unsafe { &*ss };
for i in 0..ss_ref.fields.len() {
let f = ss_ref.fields[i];
if f == 0 {
break; }
ColumnsPanel_add(&mut this.super_, f as u32, columns);
}
this.ss = ss;
}
pub fn ColumnsPanel_new(
ss: *mut ScreenSettings,
columns: &Hashtable,
changed: *mut bool,
) -> ColumnsPanel {
let fuBar = FunctionBar_new(Some(&ColumnsFunctions[..]), None, None);
let super_ = Panel_new(1, 1, 1, 1, Some(fuBar));
let mut this = ColumnsPanel {
super_,
ss,
changed,
moving: false,
};
Panel_setHeader(&mut this.super_, "Active Columns");
ColumnsPanel_fill(&mut this, ss, columns);
this
}
pub fn ColumnsPanel_update(super_: &mut Panel) {
let this = unsafe { &mut *(super_ as *mut Panel as *mut ColumnsPanel) };
let size = Panel_size(&this.super_);
unsafe {
*this.changed = true;
}
let ss = unsafe { &mut *this.ss };
ss.fields = vec![0; (size + 1) as usize];
ss.flags = 0;
for i in 0..size {
let key = {
let obj = Panel_get(&this.super_, i);
let any: &dyn core::any::Any = obj;
any.downcast_ref::<ListItem>()
.expect("ColumnsPanel_update: panel item is not a ListItem")
.key
};
ss.fields[i as usize] = key as RowField;
if (key as usize) < LAST_PROCESSFIELD {
ss.flags |= Process_fields[key as usize].flags;
}
}
ss.fields[size as usize] = 0;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::panel::{Panel_add, Panel_new};
fn panel_with_moving_rows(n: usize, moving: bool) -> ColumnsPanel {
let mut super_ = Panel_new(0, 0, 10, 10, None);
for i in 0..n {
Panel_add(
&mut super_,
Box::new(ListItem {
value: format!("field{i}"),
key: i as i32,
moving: true,
}),
);
}
ColumnsPanel {
super_,
ss: core::ptr::null_mut(),
changed: core::ptr::null_mut(),
moving,
}
}
fn row_moving(cp: &ColumnsPanel, i: usize) -> bool {
let obj: &dyn Object = cp.super_.items[i].object();
let any: &dyn core::any::Any = obj;
any.downcast_ref::<ListItem>().unwrap().moving
}
#[test]
fn cancel_moving_clears_all_rows_and_panel_flag() {
let mut cp = panel_with_moving_rows(3, true);
Panel_setSelectionColor(&mut cp.super_, ColorElements::PANEL_SELECTION_FOLLOW);
ColumnsPanel_cancelMoving(&mut cp);
assert!(!cp.moving);
for i in 0..3 {
assert!(!row_moving(&cp, i), "row {i} moving not cleared");
}
assert_eq!(
cp.super_.selectionColorId,
ColorElements::PANEL_SELECTION_FOCUS
);
}
#[test]
fn cancel_moving_on_empty_panel_only_touches_flags() {
let mut cp = panel_with_moving_rows(0, true);
Panel_setSelectionColor(&mut cp.super_, ColorElements::PANEL_SELECTION_FOLLOW);
ColumnsPanel_cancelMoving(&mut cp);
assert!(!cp.moving);
assert_eq!(Panel_size(&cp.super_), 0);
assert_eq!(
cp.super_.selectionColorId,
ColorElements::PANEL_SELECTION_FOCUS
);
}
#[test]
fn cancel_moving_is_idempotent() {
let mut cp = panel_with_moving_rows(2, false);
for i in 0..2 {
let obj: &mut dyn Object = cp.super_.items[i].object_mut();
let any: &mut dyn core::any::Any = obj;
any.downcast_mut::<ListItem>().unwrap().moving = false;
}
ColumnsPanel_cancelMoving(&mut cp);
ColumnsPanel_cancelMoving(&mut cp);
assert!(!cp.moving);
for i in 0..2 {
assert!(!row_moving(&cp, i));
}
}
fn row_value(cp: &ColumnsPanel, i: usize) -> String {
let obj: &dyn Object = cp.super_.items[i].object();
let any: &dyn core::any::Any = obj;
any.downcast_ref::<ListItem>().unwrap().value.clone()
}
fn drive(cp: &mut ColumnsPanel, ch: i32) -> (HandlerResult, bool, Vec<RowField>) {
let mut ss = ScreenSettings::default();
let mut changed = false;
cp.ss = &mut ss;
cp.changed = &mut changed;
let result = ColumnsPanel_eventHandler(cp, ch);
cp.ss = core::ptr::null_mut();
cp.changed = core::ptr::null_mut();
(result, changed, ss.fields)
}
#[test]
fn enter_on_empty_panel_is_ignored() {
let mut cp = panel_with_moving_rows(0, false);
let r = ColumnsPanel_eventHandler(&mut cp, KEY_ENTER);
assert_eq!(r, HandlerResult::IGNORED);
assert!(!cp.moving);
}
#[test]
fn mouse_without_move_mode_is_ignored() {
let mut cp = panel_with_moving_rows(3, false);
for i in 0..3 {
let obj: &mut dyn Object = cp.super_.items[i].object_mut();
(obj as &mut dyn core::any::Any)
.downcast_mut::<ListItem>()
.unwrap()
.moving = false;
}
let r = ColumnsPanel_eventHandler(&mut cp, KEY_MOUSE);
assert_eq!(r, HandlerResult::IGNORED);
assert!(!cp.moving);
}
#[test]
fn arrow_keys_without_move_mode_are_ignored() {
let mut up = panel_with_moving_rows(3, false);
assert_eq!(
ColumnsPanel_eventHandler(&mut up, KEY_UP),
HandlerResult::IGNORED
);
assert_eq!(up.super_.selected, 0);
let mut down = panel_with_moving_rows(3, false);
assert_eq!(
ColumnsPanel_eventHandler(&mut down, KEY_DOWN),
HandlerResult::IGNORED
);
assert_eq!(down.super_.selected, 0);
}
#[test]
fn default_hash_delegates_to_select_by_typing_and_is_ignored() {
let mut cp = panel_with_moving_rows(3, false);
let r = ColumnsPanel_eventHandler(&mut cp, b'#' as i32);
assert_eq!(r, HandlerResult::IGNORED);
}
#[test]
fn default_q_on_empty_buffer_breaks_then_downgrades_to_ignored() {
let mut cp = panel_with_moving_rows(2, false);
let r = ColumnsPanel_eventHandler(&mut cp, b'q' as i32);
assert_eq!(r, HandlerResult::IGNORED);
}
#[test]
fn default_nongraphic_is_ignored() {
let mut cp = panel_with_moving_rows(2, false);
let r = ColumnsPanel_eventHandler(&mut cp, 0x08);
assert_eq!(r, HandlerResult::IGNORED);
}
#[test]
fn enter_enters_move_mode_then_hits_update_stub() {
let mut cp = panel_with_moving_rows(3, false);
for i in 0..3 {
let obj: &mut dyn Object = cp.super_.items[i].object_mut();
(obj as &mut dyn core::any::Any)
.downcast_mut::<ListItem>()
.unwrap()
.moving = false;
}
cp.super_.selected = 1;
let (r, changed, fields) = drive(&mut cp, KEY_ENTER);
assert_eq!(r, HandlerResult::HANDLED);
assert!(cp.moving);
assert_eq!(
cp.super_.selectionColorId,
ColorElements::PANEL_SELECTION_FOLLOW
);
assert!(row_moving(&cp, 1), "selected row should be marked moving");
assert!(!row_moving(&cp, 0));
assert!(changed);
assert_eq!(fields, vec![0, 1, 2, 0]);
}
#[test]
fn enter_while_moving_cancels_move_then_runs_update() {
let mut cp = panel_with_moving_rows(3, true);
Panel_setSelectionColor(&mut cp.super_, ColorElements::PANEL_SELECTION_FOLLOW);
let (r, changed, fields) = drive(&mut cp, KEY_ENTER);
assert_eq!(r, HandlerResult::HANDLED);
assert!(!cp.moving);
assert_eq!(
cp.super_.selectionColorId,
ColorElements::PANEL_SELECTION_FOCUS
);
assert!(changed);
assert_eq!(fields, vec![0, 1, 2, 0]);
}
#[test]
fn f7_moves_selection_up_then_runs_update() {
let mut cp = panel_with_moving_rows(3, false); cp.super_.selected = 2;
let (r, changed, fields) = drive(&mut cp, KEY_F7);
assert_eq!(r, HandlerResult::HANDLED);
assert_eq!(row_value(&cp, 1), "field2");
assert_eq!(row_value(&cp, 2), "field1");
assert_eq!(cp.super_.selected, 1);
assert!(changed);
assert_eq!(fields, vec![0, 2, 1, 0]);
}
#[test]
fn f8_moves_selection_down_then_runs_update() {
let mut cp = panel_with_moving_rows(3, false);
cp.super_.selected = 0;
let (r, changed, fields) = drive(&mut cp, KEY_F8);
assert_eq!(r, HandlerResult::HANDLED);
assert_eq!(row_value(&cp, 0), "field1");
assert_eq!(row_value(&cp, 1), "field0");
assert_eq!(cp.super_.selected, 1);
assert!(changed);
assert_eq!(fields, vec![1, 0, 2, 0]);
}
#[test]
fn up_while_moving_falls_through_to_move_up() {
let mut cp = panel_with_moving_rows(3, true);
cp.super_.selected = 2;
let (r, _changed, fields) = drive(&mut cp, KEY_UP);
assert_eq!(r, HandlerResult::HANDLED);
assert_eq!(row_value(&cp, 1), "field2");
assert_eq!(cp.super_.selected, 1);
assert_eq!(fields, vec![0, 2, 1, 0]);
}
#[test]
fn down_while_moving_falls_through_to_move_down() {
let mut cp = panel_with_moving_rows(3, true);
cp.super_.selected = 0;
let (r, _changed, fields) = drive(&mut cp, KEY_DOWN);
assert_eq!(r, HandlerResult::HANDLED);
assert_eq!(row_value(&cp, 0), "field1");
assert_eq!(cp.super_.selected, 1);
assert_eq!(fields, vec![1, 0, 2, 0]);
}
#[test]
fn f9_removes_row_then_runs_update() {
let mut cp = panel_with_moving_rows(3, false);
cp.super_.selected = 1;
let (r, changed, fields) = drive(&mut cp, KEY_F9);
assert_eq!(r, HandlerResult::HANDLED);
assert_eq!(Panel_size(&cp.super_), 2);
assert_eq!(row_value(&cp, 0), "field0");
assert_eq!(row_value(&cp, 1), "field2");
assert!(changed);
assert_eq!(fields, vec![0, 2, 0]);
}
#[test]
fn f9_on_single_row_keeps_it_but_still_runs_update() {
let mut cp = panel_with_moving_rows(1, false);
let (r, changed, fields) = drive(&mut cp, KEY_F9);
assert_eq!(r, HandlerResult::HANDLED);
assert_eq!(Panel_size(&cp.super_), 1);
assert!(changed);
assert_eq!(fields, vec![0, 0]);
}
#[test]
fn lost_focus_while_moving_cancels_then_runs_update() {
let mut cp = panel_with_moving_rows(3, true);
Panel_setSelectionColor(&mut cp.super_, ColorElements::PANEL_SELECTION_FOLLOW);
let (r, changed, fields) = drive(&mut cp, EVENT_PANEL_LOST_FOCUS);
assert_eq!(r, HandlerResult::HANDLED);
assert!(!cp.moving);
assert_eq!(
cp.super_.selectionColorId,
ColorElements::PANEL_SELECTION_FOCUS
);
assert!(changed);
assert_eq!(fields, vec![0, 1, 2, 0]);
}
use crate::ported::hashtable::{Hashtable_new, Hashtable_put};
use crate::ported::panel::Panel_get;
use crate::ported::process::ProcessField;
fn added_row(panel: &Panel, i: i32) -> &ListItem {
let obj: &dyn Object = Panel_get(panel, i);
(obj as &dyn core::any::Any)
.downcast_ref::<ListItem>()
.expect("ColumnsPanel_add row is not a ListItem")
}
#[test]
fn add_reserved_field_uses_process_fields_name() {
let mut panel = Panel_new(0, 0, 10, 10, None);
let columns = Hashtable_new(10, false);
let key = ProcessField::PID as u32;
assert!((key as usize) < LAST_PROCESSFIELD);
ColumnsPanel_add(&mut panel, key, &columns);
assert_eq!(Panel_size(&panel), 1);
let item = added_row(&panel, 0);
assert_eq!(item.value, Process_fields[key as usize].name);
assert!(!item.value.is_empty());
assert_eq!(item.key, key as i32);
}
#[test]
fn add_empty_field_gap_falls_back_to_dash() {
let mut panel = Panel_new(0, 0, 10, 10, None);
let columns = Hashtable_new(10, false);
assert!(Process_fields[0].name.is_empty());
ColumnsPanel_add(&mut panel, 0, &columns);
let item = added_row(&panel, 0);
assert_eq!(item.value, "- ");
assert_eq!(item.key, 0);
}
#[test]
fn add_dynamic_column_prefers_heading_then_name() {
let mut panel = Panel_new(0, 0, 10, 10, None);
let mut columns = Hashtable_new(10, true);
let k_head = (LAST_PROCESSFIELD as u32) + 5;
let k_name = (LAST_PROCESSFIELD as u32) + 6;
columns_put(&mut columns, k_head, "internal_a", Some("Heading A"));
columns_put(&mut columns, k_name, "internal_b", None);
ColumnsPanel_add(&mut panel, k_head, &columns);
ColumnsPanel_add(&mut panel, k_name, &columns);
ColumnsPanel_add(&mut panel, (LAST_PROCESSFIELD as u32) + 99, &columns);
assert_eq!(added_row(&panel, 0).value, "Heading A"); assert_eq!(added_row(&panel, 0).key, k_head as i32);
assert_eq!(added_row(&panel, 1).value, "internal_b"); assert_eq!(added_row(&panel, 2).value, "- "); }
fn columns_put(
columns: &mut crate::ported::hashtable::Hashtable,
key: u32,
name: &str,
heading: Option<&str>,
) {
Hashtable_put(
columns,
key,
Box::new(crate::ported::dynamiccolumn::DynamicColumn {
name: name.to_string(),
heading: heading.map(|s| s.to_string()),
caption: None,
description: None,
width: 0,
enabled: true,
table: core::ptr::null(),
}),
);
}
#[test]
fn new_fills_list_from_ss_fields_and_stores_backpointers() {
let mut ss = ScreenSettings::default();
ss.fields = vec![1, 2, 0]; let columns = Hashtable_new(10, false);
let mut changed = false;
let cp = ColumnsPanel_new(&mut ss, &columns, &mut changed);
assert_eq!(Panel_size(&cp.super_), 2);
assert_eq!(added_row(&cp.super_, 0).key, 1);
assert_eq!(added_row(&cp.super_, 1).key, 2);
assert!(!changed); assert_eq!(cp.ss, &mut ss as *mut ScreenSettings);
assert_eq!(cp.changed, &mut changed as *mut bool);
}
}