use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM};
use windows_sys::Win32::UI::WindowsAndMessaging::{
SetWindowPos, SendMessageW, CreateWindowExW, WS_VISIBLE, WS_CHILD, WS_BORDER, WM_NOTIFY,
SWP_NOZORDER, HMENU,
};
use windows_sys::Win32::Graphics::Gdi::{InvalidateRect, SetBkMode, SetTextColor, TRANSPARENT};
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
use windows_sys::Win32::UI::Controls::{
LVM_DELETEITEM, LVM_GETHEADER, LVM_GETNEXTITEM, LVM_INSERTCOLUMNW,
LVM_INSERTITEMW, LVM_SETBKCOLOR, LVM_SETEXTENDEDLISTVIEWSTYLE, LVM_SETITEMW,
LVM_SETTEXTBKCOLOR, LVM_SETTEXTCOLOR, LVCFMT_LEFT, LVCF_FMT, LVCF_TEXT, LVCF_WIDTH,
LVCOLUMNW, LVIF_PARAM, LVIF_TEXT, LVITEMW, LVNI_SELECTED, LVS_EX_DOUBLEBUFFER,
LVS_EX_FULLROWSELECT, LVS_REPORT, LVS_SHOWSELALWAYS, NM_CUSTOMDRAW, NMCUSTOMDRAW,
CDRF_NEWFONT, CDRF_NOTIFYITEMDRAW, CDDS_PREPAINT, CDDS_ITEMPREPAINT, NMHDR,
LVM_GETITEMCOUNT, LVM_SETITEMSTATE, LVIS_SELECTED, LVM_SORTITEMS,
};
use windows_sys::Win32::UI::Shell::{DefSubclassProc, SetWindowSubclass};
use super::base::Component;
use crate::engine::wof::{WofAlgorithm, CompressionState};
use crate::ui::state::BatchItem;
use crate::utils::to_wstring;
pub mod columns {
pub const PATH: i32 = 0;
pub const CURRENT: i32 = 1;
pub const ALGORITHM: i32 = 2;
pub const ACTION: i32 = 3;
pub const SIZE: i32 = 4;
pub const ON_DISK: i32 = 5;
pub const PROGRESS: i32 = 6;
pub const STATUS: i32 = 7;
pub const START: i32 = 8;
}
pub struct FileListView {
hwnd: HWND,
}
impl FileListView {
pub unsafe fn new(parent: HWND, x: i32, y: i32, w: i32, h: i32, id: u16) -> Self { unsafe {
let instance = GetModuleHandleW(std::ptr::null());
let class_name = to_wstring("SysListView32");
let empty_str = to_wstring("");
let hwnd = CreateWindowExW(
0,
class_name.as_ptr(),
empty_str.as_ptr(),
WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT | LVS_SHOWSELALWAYS,
x,
y,
w,
h,
parent,
id as usize as HMENU,
instance,
std::ptr::null(),
);
if hwnd == std::ptr::null_mut() {
}
SendMessageW(
hwnd,
LVM_SETEXTENDEDLISTVIEWSTYLE,
0,
(LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER) as isize,
);
let file_list = Self { hwnd };
file_list.setup_columns();
file_list
}}
#[inline]
pub fn hwnd(&self) -> HWND {
self.hwnd
}
fn setup_columns(&self) {
let columns = [
("Path", 250),
("Current", 70),
("Algorithm", 70),
("Action", 70),
("Size", 75),
("On Disk", 75),
("Progress", 70),
("Status", 80),
("â–¶ Start", 45),
];
for (i, (name, width)) in columns.iter().enumerate() {
let name_wide = to_wstring(name);
let col = LVCOLUMNW {
mask: LVCF_WIDTH | LVCF_TEXT | LVCF_FMT,
fmt: LVCFMT_LEFT,
cx: *width,
pszText: name_wide.as_ptr() as *mut _,
..Default::default()
};
unsafe {
SendMessageW(
self.hwnd,
LVM_INSERTCOLUMNW,
i as usize,
&col as *const _ as isize,
);
}
}
}
pub fn add_item(
&self,
id: u32,
item: &BatchItem,
size_logical: Vec<u16>,
size_disk: Vec<u16>,
state: CompressionState,
) -> i32 {
let path_wide = to_wstring(&item.path);
let algo_str = Self::algo_to_str(item.algorithm);
let algo_wide = to_wstring(algo_str);
let action_str = if item.action == crate::ui::state::BatchAction::Compress {
"Compress"
} else {
"Decompress"
};
let action_wide = to_wstring(action_str);
let size_wide = size_logical;
let disk_wide = size_disk;
let current_text = match state {
CompressionState::None => "-".to_string(),
CompressionState::Specific(algo) => Self::algo_to_str(algo).to_string(),
CompressionState::Mixed => "Mixed".to_string(),
};
let current_wide = to_wstring(¤t_text);
let status_wide = to_wstring("Pending");
let start_wide = to_wstring("â–¶");
unsafe {
let mut lvi = LVITEMW {
mask: LVIF_TEXT | LVIF_PARAM,
iItem: std::i32::MAX, iSubItem: 0,
pszText: path_wide.as_ptr() as *mut _,
lParam: id as isize,
..Default::default()
};
let idx = SendMessageW(
self.hwnd,
LVM_INSERTITEMW,
0,
&lvi as *const _ as isize,
);
let row = idx as i32;
lvi.mask = LVIF_TEXT;
lvi.iItem = row;
lvi.iSubItem = columns::CURRENT;
lvi.pszText = current_wide.as_ptr() as *mut _;
SendMessageW(self.hwnd, LVM_SETITEMW, 0, &lvi as *const _ as isize);
lvi.iSubItem = columns::ALGORITHM;
lvi.pszText = algo_wide.as_ptr() as *mut _;
SendMessageW(self.hwnd, LVM_SETITEMW, 0, &lvi as *const _ as isize);
lvi.iSubItem = columns::ACTION;
lvi.pszText = action_wide.as_ptr() as *mut _;
SendMessageW(self.hwnd, LVM_SETITEMW, 0, &lvi as *const _ as isize);
lvi.iSubItem = columns::SIZE;
lvi.pszText = size_wide.as_ptr() as *mut _;
SendMessageW(self.hwnd, LVM_SETITEMW, 0, &lvi as *const _ as isize);
lvi.iSubItem = columns::ON_DISK;
lvi.pszText = disk_wide.as_ptr() as *mut _;
SendMessageW(self.hwnd, LVM_SETITEMW, 0, &lvi as *const _ as isize);
lvi.iSubItem = columns::STATUS;
lvi.pszText = status_wide.as_ptr() as *mut _;
SendMessageW(self.hwnd, LVM_SETITEMW, 0, &lvi as *const _ as isize);
lvi.iSubItem = columns::START;
lvi.pszText = start_wide.as_ptr() as *mut _;
SendMessageW(self.hwnd, LVM_SETITEMW, 0, &lvi as *const _ as isize);
row
}
}
pub fn update_item_text(&self, row: i32, col: i32, text: Vec<u16>) {
let text_wide = if text.last() == Some(&0) {
text
} else {
let mut t = text;
t.push(0);
t
};
let item = LVITEMW {
mask: LVIF_TEXT,
iItem: row,
iSubItem: col,
pszText: text_wide.as_ptr() as *mut _,
..Default::default()
};
unsafe {
SendMessageW(
self.hwnd,
LVM_SETITEMW,
0,
&item as *const _ as isize,
);
}
}
pub fn get_selected_indices(&self) -> Vec<usize> {
let mut selected = Vec::new();
let mut item_idx: i32 = -1;
unsafe {
loop {
let start_param = item_idx as usize;
let next = SendMessageW(
self.hwnd,
LVM_GETNEXTITEM,
if item_idx < 0 { usize::MAX } else { start_param },
LVNI_SELECTED as isize,
);
if (next as i32) < 0 {
break;
}
item_idx = next as i32;
selected.push(item_idx as usize);
}
}
selected
}
pub fn remove_item(&self, index: i32) {
unsafe {
SendMessageW(self.hwnd, LVM_DELETEITEM, index as usize, 0);
}
}
pub fn set_theme(&self, is_dark: bool) {
unsafe {
crate::ui::theme::allow_dark_mode_for_window(self.hwnd, is_dark);
if is_dark {
crate::ui::theme::apply_theme(self.hwnd, crate::ui::theme::ControlType::ItemsView, true);
} else {
crate::ui::theme::apply_theme(self.hwnd, crate::ui::theme::ControlType::List, false);
}
let (bg_color, text_color) = if is_dark {
(crate::ui::theme::COLOR_LIST_BG_DARK, crate::ui::theme::COLOR_LIST_TEXT_DARK)
} else {
(crate::ui::theme::COLOR_LIST_BG_LIGHT, crate::ui::theme::COLOR_LIST_TEXT_LIGHT)
};
SendMessageW(self.hwnd, LVM_SETBKCOLOR, 0, bg_color as isize);
SendMessageW(self.hwnd, LVM_SETTEXTBKCOLOR, 0, bg_color as isize);
SendMessageW(self.hwnd, LVM_SETTEXTCOLOR, 0, text_color as isize);
let header = SendMessageW(self.hwnd, LVM_GETHEADER, 0, 0) as HWND;
if header != std::ptr::null_mut() {
crate::ui::theme::allow_dark_mode_for_window(header, is_dark);
crate::ui::theme::apply_theme(header, crate::ui::theme::ControlType::Header, is_dark);
}
let _ = InvalidateRect(self.hwnd, std::ptr::null(), 1);
}
}
pub fn apply_subclass(&self, main_hwnd: HWND) {
unsafe {
let _ = SetWindowSubclass(
self.hwnd,
Some(listview_subclass_proc),
1, main_hwnd as usize,
);
}
}
pub fn get_selection_count(&self) -> usize {
self.get_selected_indices().len()
}
pub fn get_item_count(&self) -> i32 {
unsafe { SendMessageW(self.hwnd, LVM_GETITEMCOUNT, 0, 0) as i32 }
}
pub fn set_selected(&self, index: i32, selected: bool) {
let state = if selected { LVIS_SELECTED } else { 0 };
let mask = LVIS_SELECTED;
let mut item = LVITEMW {
state,
stateMask: mask,
..Default::default()
};
unsafe {
SendMessageW(self.hwnd, LVM_SETITEMSTATE, index as usize, &mut item as *mut _ as isize);
}
}
pub fn sort_items(&self, callback: unsafe extern "system" fn(isize, isize, isize) -> i32, context: isize) {
unsafe {
SendMessageW(self.hwnd, LVM_SORTITEMS, context as usize, callback as isize);
}
}
fn algo_to_str(algo: WofAlgorithm) -> &'static str {
match algo {
WofAlgorithm::Xpress4K => "XPRESS4K",
WofAlgorithm::Xpress8K => "XPRESS8K",
WofAlgorithm::Xpress16K => "XPRESS16K",
WofAlgorithm::Lzx => "LZX",
}
}
}
impl Component for FileListView {
unsafe fn create(&mut self, _parent: HWND) -> Result<(), String> {
Ok(())
}
fn hwnd(&self) -> Option<HWND> {
Some(self.hwnd)
}
unsafe fn on_resize(&mut self, parent_rect: &RECT) {
unsafe {
let width = parent_rect.right - parent_rect.left;
let height = parent_rect.bottom - parent_rect.top;
let padding = 10;
let header_height = 25;
let progress_height = 25;
let btn_height = 30;
let lbl_height = 18;
let list_height = height - header_height - progress_height - btn_height - lbl_height - (padding * 5);
let list_y = padding + header_height + padding;
SetWindowPos(
self.hwnd,
std::ptr::null_mut(),
padding,
list_y,
width - padding * 2,
list_height,
SWP_NOZORDER,
);
}
}
unsafe fn on_theme_change(&mut self, is_dark: bool) {
self.set_theme(is_dark);
}
}
unsafe extern "system" fn listview_subclass_proc(
hwnd: HWND,
umsg: u32,
wparam: WPARAM,
lparam: LPARAM,
_uidsubclass: usize,
dwrefdata: usize,
) -> LRESULT { unsafe {
let main_hwnd = dwrefdata as HWND;
if umsg == WM_NOTIFY && crate::ui::theme::is_app_dark_mode(main_hwnd) {
let nmhdr = &*(lparam as *const NMHDR);
if nmhdr.code == NM_CUSTOMDRAW {
let nmcd = &mut *(lparam as *mut NMCUSTOMDRAW);
if nmcd.dwDrawStage == CDDS_PREPAINT {
return CDRF_NOTIFYITEMDRAW as LRESULT;
}
if nmcd.dwDrawStage == CDDS_ITEMPREPAINT {
SetTextColor(nmcd.hdc, crate::ui::theme::COLOR_HEADER_TEXT_DARK);
SetBkMode(nmcd.hdc, TRANSPARENT as i32);
return CDRF_NEWFONT as LRESULT;
}
}
}
DefSubclassProc(hwnd, umsg, wparam, lparam)
}}