use winapi::shared::minwindef::{WPARAM, LPARAM};
use winapi::um::winuser::{WS_VISIBLE, WS_DISABLED, WS_TABSTOP};
use winapi::um::commctrl::{HIMAGELIST, HTREEITEM, TVIS_EXPANDED, TVIS_SELECTED, TVS_SHOWSELALWAYS, TVITEMW};
use crate::win32::window_helper as wh;
use crate::win32::base_helper::{check_hwnd, to_utf16, from_utf16};
use crate::{Font, NwgError};
use super::{ControlBase, ControlHandle};
use std::{mem, ptr};
#[cfg(feature="image-list")]
use crate::ImageList;
const NOT_BOUND: &'static str = "TreeView is not yet bound to a winapi object";
const BAD_HANDLE: &'static str = "INTERNAL ERROR: TreeView handle is not HWND!";
bitflags! {
pub struct TreeViewFlags: u32 {
const VISIBLE = WS_VISIBLE;
const DISABLED = WS_DISABLED;
const TAB_STOP = WS_TABSTOP;
const ALWAYS_SHOW_SELECTION = TVS_SHOWSELALWAYS;
}
}
bitflags! {
pub struct TreeItemState: u32 {
const SELECTED = TVIS_SELECTED;
const EXPANDED = TVIS_EXPANDED;
}
}
#[derive(Copy, Clone, Debug)]
pub enum TreeInsert {
First,
Last,
Root,
Sort,
After(HTREEITEM)
}
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum ExpandState {
Collapse,
CollapseReset,
Expand,
ExpandPartial,
Toggle
}
#[derive(Copy, Clone, Debug)]
pub enum TreeItemAction {
Unknown,
Expand(ExpandState),
State { old: TreeItemState, new: TreeItemState }
}
#[derive(Debug)]
pub struct TreeItem {
pub handle: HTREEITEM
}
impl TreeItem {
pub fn is_null(&self) -> bool {
self.handle.is_null()
}
}
#[derive(Default, PartialEq, Eq)]
pub struct TreeView {
pub handle: ControlHandle
}
impl TreeView {
pub fn builder<'a>() -> TreeViewBuilder<'a> {
TreeViewBuilder {
size: (100, 200),
position: (0, 0),
enabled: true,
focus: false,
flags: None,
font: None,
parent: None,
image_list: None,
}
}
#[cfg(feature="image-list")]
pub fn set_image_list(&self, list: Option<&ImageList>) {
use winapi::um::commctrl::{TVM_SETIMAGELIST, TVSIL_NORMAL};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let list_handle = list.map(|l| l.handle).unwrap_or(ptr::null_mut());
wh::send_message(handle, TVM_SETIMAGELIST, TVSIL_NORMAL, list_handle as _);
}
#[cfg(feature="image-list")]
pub fn image_list(&self) -> Option<ImageList> {
use winapi::um::commctrl::{TVM_GETIMAGELIST, TVSIL_NORMAL};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let handle = wh::send_message(handle, TVM_GETIMAGELIST, TVSIL_NORMAL, 0) as HIMAGELIST;
if handle.is_null() {
None
} else {
Some(ImageList { handle, owned: false })
}
}
#[cfg(feature="image-list")]
pub fn set_item_image(&self, item: &TreeItem, index: i32, on_select: bool) {
use winapi::um::commctrl::{TVM_SETITEMW, TVIF_IMAGE, TVIF_SELECTEDIMAGE};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let mut tree_item = blank_item();
tree_item.hItem = item.handle;
tree_item.mask = match on_select {
true => TVIF_SELECTEDIMAGE,
false => TVIF_IMAGE
};
match on_select {
true => { tree_item.iSelectedImage = index; },
false => { tree_item.iImage = index; }
}
wh::send_message(handle, TVM_SETITEMW, 0, &mut tree_item as *mut TVITEMW as LPARAM);
}
#[cfg(feature="image-list")]
pub fn item_image(&self, item: &TreeItem, on_select: bool) -> i32 {
use winapi::um::commctrl::{TVM_GETITEMW, TVIF_IMAGE, TVIF_SELECTEDIMAGE};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let mut tree_item = blank_item();
tree_item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE;
tree_item.hItem = item.handle;
match wh::send_message(handle, TVM_GETITEMW, 0, &mut tree_item as *mut TVITEMW as LPARAM) {
0 => 0,
_ => match on_select {
true => tree_item.iSelectedImage,
false => tree_item.iImage
}
}
}
pub fn set_text_color(&self, r: u8, g: u8, b: u8) {
use winapi::um::commctrl::TVM_SETTEXTCOLOR;
use winapi::um::wingdi::RGB;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let color = RGB(r, g, b);
wh::send_message(handle, TVM_SETTEXTCOLOR, 0, color as _);
self.invalidate();
}
pub fn text_color(&self) -> [u8; 3] {
use winapi::um::commctrl::TVM_GETTEXTCOLOR;
use winapi::um::wingdi::{GetRValue, GetGValue, GetBValue};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let col = wh::send_message(handle, TVM_GETTEXTCOLOR, 0, 0) as u32;
[
GetRValue(col),
GetGValue(col),
GetBValue(col),
]
}
pub fn indent(&self) -> u32 {
use winapi::um::commctrl::TVM_GETINDENT;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, TVM_GETINDENT, 0, 0) as u32
}
pub fn set_indent(&self, indent: u32) {
use winapi::um::commctrl::TVM_SETINDENT;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, TVM_SETINDENT, indent as _, 0);
}
pub fn root(&self) -> Option<TreeItem> {
use winapi::um::commctrl::TVGN_ROOT;
next_treeview_item(&self.handle, TVGN_ROOT, ptr::null_mut())
}
pub fn first_child(&self, item: &TreeItem) -> Option<TreeItem> {
use winapi::um::commctrl::TVGN_CHILD;
next_treeview_item(&self.handle, TVGN_CHILD, item.handle)
}
pub fn next_sibling(&self, item: &TreeItem) -> Option<TreeItem> {
use winapi::um::commctrl::TVGN_NEXT;
next_treeview_item(&self.handle, TVGN_NEXT, item.handle)
}
pub fn previous_sibling(&self, item: &TreeItem) -> Option<TreeItem> {
use winapi::um::commctrl::TVGN_PREVIOUS;
next_treeview_item(&self.handle, TVGN_PREVIOUS, item.handle)
}
pub fn parent(&self, item: &TreeItem) -> Option<TreeItem> {
use winapi::um::commctrl::TVGN_PARENT;
next_treeview_item(&self.handle, TVGN_PARENT, item.handle)
}
pub fn selected_item(&self) -> Option<TreeItem> {
use winapi::um::commctrl::{TVM_GETNEXTITEM, TVGN_CARET};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let tree_handle = wh::send_message(handle, TVM_GETNEXTITEM, TVGN_CARET, 0) as HTREEITEM;
if tree_handle.is_null() {
None
} else {
Some(TreeItem { handle: tree_handle })
}
}
pub fn insert_item<'a>(&self, new: &'a str, parent: Option<&TreeItem>, position: TreeInsert) -> TreeItem {
use winapi::um::commctrl::{TVM_INSERTITEMW, TVINSERTSTRUCTW, TVI_FIRST, TVI_LAST, TVI_ROOT, TVI_SORT, TVIF_TEXT};
use winapi::um::commctrl::TVINSERTSTRUCTW_u;
use winapi::um::winnt::LPWSTR;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let insert = match position {
TreeInsert::First => TVI_FIRST,
TreeInsert::Last => TVI_LAST,
TreeInsert::Root => TVI_ROOT,
TreeInsert::Sort => TVI_SORT,
TreeInsert::After(i) => i
};
let text = to_utf16(new);
let item = {
let mut item: TVINSERTSTRUCTW_u = unsafe { mem::zeroed() };
let i = unsafe { item.item_mut() };
i.mask = TVIF_TEXT;
i.pszText = text.as_ptr() as LPWSTR;
item
};
let new_item = TVINSERTSTRUCTW {
hParent: parent.map(|p| p.handle ).unwrap_or(ptr::null_mut()),
hInsertAfter: insert,
u: item
};
let ptr = &new_item as *const TVINSERTSTRUCTW;
let handle = wh::send_message(handle, TVM_INSERTITEMW, 0, ptr as LPARAM) as HTREEITEM;
self.invalidate();
TreeItem { handle }
}
pub fn remove_item(&self, item: &TreeItem) {
use winapi::um::commctrl::{TVM_DELETEITEM};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, TVM_DELETEITEM, 0, item.handle as LPARAM);
}
pub fn select_item(&self, item: &TreeItem) {
use winapi::um::commctrl::{TVM_SETITEMW, TVIF_STATE};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let mut tree_item = blank_item();
tree_item.mask = TVIF_STATE;
tree_item.hItem = item.handle;
tree_item.state = TVIS_SELECTED;
tree_item.stateMask = TVIS_SELECTED;
wh::send_message(handle, TVM_SETITEMW, 0, &mut tree_item as *mut TVITEMW as LPARAM);
}
pub fn unselect_item(&self, item: &TreeItem) {
use winapi::um::commctrl::{TVM_SETITEMW, TVIF_STATE};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let mut tree_item = blank_item();
tree_item.mask = TVIF_STATE;
tree_item.hItem = item.handle;
tree_item.state = 0;
tree_item.stateMask = TVIS_SELECTED;
wh::send_message(handle, TVM_SETITEMW, 0, &mut tree_item as *mut TVITEMW as LPARAM);
}
#[cfg(feature="tree-view-iterator")]
pub fn iter<'a>(&'a self) -> crate::TreeViewIterator<'a> {
check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
crate::TreeViewIterator::new(self, ptr::null_mut())
}
#[cfg(feature="tree-view-iterator")]
pub fn iter_item<'a>(&'a self, item: &TreeItem) -> crate::TreeViewIterator<'a> {
check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
crate::TreeViewIterator::new(self, item.handle)
}
pub fn item_text(&self, tree_item: &TreeItem) -> Option<String> {
use winapi::um::commctrl::{TVM_GETITEMW, TVIF_TEXT, TVIF_HANDLE};
const BUFFER_MAX: usize = 260;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let mut text_buffer = Vec::with_capacity(BUFFER_MAX);
unsafe { text_buffer.set_len(BUFFER_MAX); }
let mut item: TVITEMW = blank_item();
item.mask = TVIF_TEXT | TVIF_HANDLE;
item.hItem = tree_item.handle;
item.pszText = text_buffer.as_mut_ptr();
item.cchTextMax = BUFFER_MAX as _;
let result = wh::send_message(handle, TVM_GETITEMW, 0, &mut item as *mut TVITEMW as LPARAM);
if result == 0 {
return None;
}
Some(from_utf16(&text_buffer))
}
pub fn item_has_children(&self, tree_item: &TreeItem) -> Option<bool> {
use winapi::um::commctrl::{TVM_GETITEMW, TVIF_CHILDREN, TVIF_HANDLE};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let mut item: TVITEMW = blank_item();
item.hItem = tree_item.handle;
item.mask = TVIF_CHILDREN | TVIF_HANDLE;
let result = wh::send_message(handle, TVM_GETITEMW, 0, &mut item as *mut TVITEMW as LPARAM);
if result == 0 {
return None;
}
Some(item.cChildren != 0)
}
pub fn item_state(&self, tree_item: &TreeItem) -> Option<TreeItemState> {
use winapi::um::commctrl::{TVM_GETITEMW, TVIF_STATE, TVIF_HANDLE};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let mut item: TVITEMW = unsafe { mem::zeroed() };
item.hItem = tree_item.handle;
item.mask = TVIF_STATE | TVIF_HANDLE;
item.stateMask = 0xFF;
let result = wh::send_message(handle, TVM_GETITEMW, 0, &mut item as *mut TVITEMW as LPARAM);
if result == 0 {
return None;
}
Some(TreeItemState::from_bits_truncate(item.state))
}
pub fn set_expand_state(&self, item: &TreeItem, state: ExpandState) {
use winapi::um::commctrl::{TVM_EXPAND, TVE_COLLAPSE, TVE_COLLAPSERESET, TVE_EXPAND, TVE_EXPANDPARTIAL, TVE_TOGGLE};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let state = match state {
ExpandState::Collapse => TVE_COLLAPSE,
ExpandState::CollapseReset => TVE_COLLAPSE | TVE_COLLAPSERESET,
ExpandState::Expand => TVE_EXPAND,
ExpandState::ExpandPartial => TVE_EXPANDPARTIAL,
ExpandState::Toggle => TVE_TOGGLE,
};
wh::send_message(handle, TVM_EXPAND, state as WPARAM, item.handle as LPARAM);
}
pub fn ensure_visible(&self, item: &TreeItem) {
use winapi::um::commctrl::{TVM_ENSUREVISIBLE};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, TVM_ENSUREVISIBLE, 0, item.handle as LPARAM);
}
pub fn clear(&self) {
use winapi::um::commctrl::{TVM_DELETEITEM, TVI_ROOT};
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, TVM_DELETEITEM, 0, TVI_ROOT as LPARAM);
}
pub fn len(&self) -> usize {
use winapi::um::commctrl::TVM_GETCOUNT;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, TVM_GETCOUNT, 0, 0) as usize
}
pub fn visible_len(&self) -> usize {
use winapi::um::commctrl::TVM_GETVISIBLECOUNT;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, TVM_GETVISIBLECOUNT, 0, 0) as usize
}
pub fn invalidate(&self) {
use winapi::um::winuser::InvalidateRect;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { InvalidateRect(handle, ptr::null(), 1); }
}
pub fn font(&self) -> Option<Font> {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let font_handle = wh::get_window_font(handle);
if font_handle.is_null() {
None
} else {
Some(Font { handle: font_handle })
}
}
pub fn set_font(&self, font: Option<&Font>) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_font(handle, font.map(|f| f.handle), true); }
}
pub fn focus(&self) -> bool {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_focus(handle) }
}
pub fn set_focus(&self) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_focus(handle); }
}
pub fn enabled(&self) -> bool {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_enabled(handle) }
}
pub fn set_enabled(&self, v: bool) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_enabled(handle, v) }
}
pub fn visible(&self) -> bool {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_visibility(handle) }
}
pub fn set_visible(&self, v: bool) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_visibility(handle, v) }
}
pub fn size(&self) -> (u32, u32) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_size(handle) }
}
pub fn set_size(&self, x: u32, y: u32) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_size(handle, x, y, false) }
}
pub fn position(&self) -> (i32, i32) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_position(handle) }
}
pub fn set_position(&self, x: i32, y: i32) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_position(handle, x, y) }
}
pub fn class_name(&self) -> &'static str {
winapi::um::commctrl::WC_TREEVIEW
}
pub fn flags(&self) -> u32 {
use winapi::um::commctrl::{TVS_HASBUTTONS, TVS_LINESATROOT, TVS_HASLINES};
WS_VISIBLE | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_HASLINES | WS_TABSTOP | TVS_SHOWSELALWAYS
}
pub fn forced_flags(&self) -> u32 {
use winapi::um::winuser::{WS_CHILD, WS_BORDER};
use winapi::um::commctrl::TVS_NOTOOLTIPS;
WS_CHILD | WS_BORDER | TVS_NOTOOLTIPS
}
}
impl Drop for TreeView {
fn drop(&mut self) {
self.handle.destroy();
}
}
pub struct TreeViewBuilder<'a> {
size: (i32, i32),
position: (i32, i32),
enabled: bool,
focus: bool,
flags: Option<TreeViewFlags>,
font: Option<&'a Font>,
parent: Option<ControlHandle>,
#[cfg(feature="image-list")]
image_list: Option<&'a ImageList>,
}
impl<'a> TreeViewBuilder<'a> {
pub fn flags(mut self, flags: TreeViewFlags) -> TreeViewBuilder<'a> {
self.flags = Some(flags);
self
}
pub fn size(mut self, size: (i32, i32)) -> TreeViewBuilder<'a> {
self.size = size;
self
}
pub fn position(mut self, pos: (i32, i32)) -> TreeViewBuilder<'a> {
self.position = pos;
self
}
pub fn enabled(mut self, e: bool) -> TreeViewBuilder<'a> {
self.enabled = e;
self
}
pub fn focus(mut self, focus: bool) -> TreeViewBuilder<'a> {
self.focus = focus;
self
}
pub fn font(mut self, font: Option<&'a Font>) -> TreeViewBuilder<'a> {
self.font = font;
self
}
pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TreeViewBuilder<'a> {
self.parent = Some(p.into());
self
}
#[cfg(feature="image-list")]
pub fn image_list(mut self, list: Option<&'a ImageList>) -> TreeViewBuilder<'a> {
self.image_list = list;
self
}
pub fn build(self, out: &mut TreeView) -> Result<(), NwgError> {
let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
let parent = match self.parent {
Some(p) => Ok(p),
None => Err(NwgError::no_parent("TreeView"))
}?;
out.handle = ControlBase::build_hwnd()
.class_name(out.class_name())
.forced_flags(out.forced_flags())
.flags(flags)
.size(self.size)
.position(self.position)
.parent(Some(parent))
.build()?;
if self.font.is_some() {
out.set_font(self.font);
} else {
out.set_font(Font::global_default().as_ref());
}
builder_set_image_list(&self, out);
if self.focus {
out.set_focus();
}
out.set_enabled(self.enabled);
Ok(())
}
}
impl PartialEq for TreeItem {
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle
}
}
impl Eq for TreeItem {}
fn next_treeview_item(handle: &ControlHandle, action: usize, item: HTREEITEM) -> Option<TreeItem> {
use winapi::um::commctrl::TVM_GETNEXTITEM;
if handle.blank() { panic!(NOT_BOUND); }
let handle = handle.hwnd().expect(BAD_HANDLE);
let handle = wh::send_message(handle, TVM_GETNEXTITEM, action as _, item as _) as HTREEITEM;
if handle.is_null() {
None
} else {
Some(TreeItem { handle })
}
}
#[cfg(feature="image-list")]
fn builder_set_image_list(builder: &TreeViewBuilder, out: &TreeView) {
if builder.image_list.is_some() {
out.set_image_list(builder.image_list);
}
}
#[cfg(not(feature="image-list"))]
fn builder_set_image_list(_builder: &TreeViewBuilder, _out: &TreeView) {
}
fn blank_item() -> TVITEMW {
TVITEMW {
mask: 0,
hItem: ptr::null_mut(),
state: 0,
stateMask: 0,
pszText: ptr::null_mut(),
cchTextMax: 0,
iImage: 0,
iSelectedImage: 0,
cChildren: 0,
lParam: 0
}
}