use std::cmp;
use num_enum::TryFromPrimitive;
use widestring::U16CString;
use windows::{
Win32::{
System::Com::CoTaskMemFree,
UI::Shell::{
SHCreateItemFromIDList, SHCreateItemFromParsingName, SIGDN,
SIGDN_DESKTOPABSOLUTEEDITING, SIGDN_DESKTOPABSOLUTEPARSING, SIGDN_FILESYSPATH,
SIGDN_NORMALDISPLAY, SIGDN_PARENTRELATIVE, SIGDN_PARENTRELATIVEEDITING,
SIGDN_PARENTRELATIVEFORADDRESSBAR, SIGDN_PARENTRELATIVEFORUI,
SIGDN_PARENTRELATIVEPARSING, SIGDN_URL,
},
},
core::{PCWSTR, Result},
};
#[cfg(feature = "prop")]
pub mod item2;
pub use windows::Win32::UI::Shell::IShellItem;
use crate::{id_list::AbsoluteIDList, prop::attribute::ItemAttributes};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)]
#[repr(i32)]
pub enum ShellItemDisplayName {
NormalDisplay = SIGDN_NORMALDISPLAY.0,
ParentRelativeParsing = SIGDN_PARENTRELATIVEPARSING.0,
DesktopAbsoluteParsing = SIGDN_DESKTOPABSOLUTEPARSING.0,
ParentRelativeEditing = SIGDN_PARENTRELATIVEEDITING.0,
DesktopAbsoluteEditing = SIGDN_DESKTOPABSOLUTEEDITING.0,
FileSystemPath = SIGDN_FILESYSPATH.0,
Url = SIGDN_URL.0,
ParentRelativeForAddressBar = SIGDN_PARENTRELATIVEFORADDRESSBAR.0,
ParentRelative = SIGDN_PARENTRELATIVE.0,
ParentRelativeForUI = SIGDN_PARENTRELATIVEFORUI.0,
}
impl ShellItemDisplayName {
pub fn is_for_parse(&self) -> bool {
use ShellItemDisplayName::*;
matches!(
self,
ParentRelativeParsing | DesktopAbsoluteParsing | FileSystemPath | Url | ParentRelative
)
}
pub fn is_for_display(&self) -> bool {
use ShellItemDisplayName::*;
matches!(
self,
NormalDisplay | ParentRelativeForAddressBar | ParentRelativeForUI
)
}
pub fn is_for_edit(&self) -> bool {
use ShellItemDisplayName::*;
matches!(self, ParentRelativeEditing | DesktopAbsoluteEditing)
}
}
pub trait ShellItem {
fn from_path_w(path: PCWSTR) -> Result<IShellItem> {
unsafe { SHCreateItemFromParsingName::<_, _, IShellItem>(path, None) }
}
#[doc(alias = "from_pidl")]
fn from_id_list(id_list: &AbsoluteIDList) -> Result<IShellItem> {
unsafe { SHCreateItemFromIDList::<IShellItem>(id_list.0) }
}
fn get_display_name(&self, name: ShellItemDisplayName) -> Result<U16CString>;
fn compare(&self, psi: &IShellItem, flags: u32) -> Result<cmp::Ordering>;
fn get_attributes(&self, mask: ItemAttributes) -> Result<ItemAttributes>;
fn is_folder(&self) -> bool {
self.get_attributes(ItemAttributes::Folder)
.is_ok_and(|attrs| attrs.contains(ItemAttributes::Folder))
}
}
impl ShellItem for IShellItem {
fn get_display_name(&self, name: ShellItemDisplayName) -> Result<U16CString> {
let name = unsafe { self.GetDisplayName(SIGDN(name as i32)) }?;
let name_u16 = unsafe { U16CString::from_ptr_str(name.0) };
unsafe { CoTaskMemFree(Some(name.0 as _)) };
Ok(name_u16)
}
fn get_attributes(&self, mask: ItemAttributes) -> Result<ItemAttributes> {
unsafe { self.GetAttributes(mask.into()).map(Into::into) }
}
fn compare(&self, psi: &IShellItem, hint: u32) -> Result<cmp::Ordering> {
let order = unsafe { self.Compare(psi, hint)? };
Ok(order.cmp(&0))
}
}
#[cfg(test)]
mod tests {
use super::*;
use windows::core::w;
use crate::{init, prop::column::FSColumn};
#[test]
fn compare() {
_ = init();
let windows = IShellItem::from_path_w(w!(r"C:\Windows")).unwrap();
let users = IShellItem::from_path_w(w!(r"C:\Users")).unwrap();
let result = windows.compare(&users, 0).unwrap();
assert_eq!(result, cmp::Ordering::Greater);
assert_eq!(users.compare(&windows, 0).unwrap(), cmp::Ordering::Less);
}
#[test]
fn compare_size() {
_ = init();
let explorer = IShellItem::from_path_w(w!(r"C:\Windows\explorer.exe")).unwrap();
let notepad = IShellItem::from_path_w(w!(r"C:\Windows\notepad.exe")).unwrap();
let result = explorer.compare(¬epad, 0).unwrap();
assert_eq!(result, cmp::Ordering::Less);
let result = explorer.compare(¬epad, FSColumn::Size as u32).unwrap();
assert_eq!(result, cmp::Ordering::Less);
}
#[test]
fn compare_same() {
_ = init();
let windows = IShellItem::from_path_w(w!(r"C:\Windows")).unwrap();
let windows2 = IShellItem::from_path_w(w!(r"C:\Windows")).unwrap();
let result = windows.compare(&windows2, 0).unwrap();
assert_eq!(result, cmp::Ordering::Equal);
}
}