winsafe\user\handles/
hmenu.rs

1#![allow(non_camel_case_types, non_snake_case)]
2
3use crate::co;
4use crate::decl::*;
5use crate::kernel::privs::*;
6use crate::msg::*;
7use crate::prelude::*;
8use crate::user::{ffi, iterators::*};
9
10handle! { HMENU;
11	/// Handle to a
12	/// [menu](https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types#hmenu).
13}
14
15impl HMENU {
16	/// A more convenient [`HMENU::AppendMenu`](crate::HMENU::AppendMenu).
17	///
18	/// # Examples
19	///
20	/// Adding multiple entries at once, with their command IDs:
21	///
22	/// ```no_run
23	/// use winsafe::{self as w, prelude::*, seq_ids};
24	///
25	/// seq_ids! {
26	///     ID_FILE_OPEN = 2001;
27	///     ID_FILE_SAVE
28	///     ID_FILE_EXIT
29	/// }
30	///
31	/// let hmenu: w::HMENU; // initialized somewhere
32	/// # let hmenu = w::HMENU::NULL;
33	///
34	/// hmenu.append_item(&[
35	///     w::MenuItem::Entry { cmd_id: ID_FILE_OPEN, text: "&Open" },
36	///     w::MenuItem::Entry { cmd_id: ID_FILE_OPEN, text: "&Save" },
37	///     w::MenuItem::Separator,
38	///     w::MenuItem::Entry { cmd_id: ID_FILE_EXIT, text: "E&xit" },
39	/// ])?;
40	/// # w::SysResult::Ok(())
41	/// ```
42	pub fn append_item(&self, items: &[MenuItem]) -> SysResult<()> {
43		items
44			.iter()
45			.map(|item| match item {
46				MenuItem::Entry { cmd_id, text: entry_text } => self.AppendMenu(
47					co::MF::STRING,
48					IdMenu::Id(*cmd_id),
49					BmpPtrStr::from_str(*entry_text),
50				),
51				MenuItem::Separator => {
52					self.AppendMenu(co::MF::SEPARATOR, IdMenu::None, BmpPtrStr::None)
53				},
54				MenuItem::Submenu { submenu, text: entry_text } => self.AppendMenu(
55					co::MF::POPUP,
56					IdMenu::Menu(submenu),
57					BmpPtrStr::from_str(*entry_text),
58				),
59			})
60			.collect::<Result<Vec<_>, _>>()?;
61
62		Ok(())
63	}
64
65	/// Simpler version of
66	/// [`HMENU::GetMenuItemInfo`](crate::HMENU::GetMenuItemInfo), which returns
67	/// a [`MenuItemInfo`](crate::MenuItemInfo) instead of the tricky
68	/// [`MENUITEMINFO`](crate::MENUITEMINFO).
69	///
70	/// # Examples
71	///
72	/// ```rust,no_run
73	/// use winsafe::{self as w, prelude::*};
74	///
75	/// let hmenu: w::HMENU; // initialized somewhere
76	/// # let hmenu = w::HMENU::NULL;
77	///
78	/// let item_info = hmenu.item_info(w::IdPos::Id(0))?;
79	/// match item_info {
80	///     w::MenuItemInfo::Entry { cmd_id, text } =>
81	///         println!("item {} {}", cmd_id, text),
82	///     w::MenuItemInfo::Separator =>
83	///         println!("separator"),
84	///     w::MenuItemInfo::Submenu { submenu, text } =>
85	///         println!("submenu {} {}", submenu, text),
86	/// }
87	/// # w::SysResult::Ok(())
88	/// ```
89	#[must_use]
90	pub fn item_info(&self, id_or_pos: IdPos) -> SysResult<MenuItemInfo> {
91		let mut mii = MENUITEMINFO::default();
92		mii.fMask = co::MIIM::FTYPE | co::MIIM::ID | co::MIIM::STATE | co::MIIM::SUBMENU;
93		self.GetMenuItemInfo(id_or_pos, &mut mii)?;
94
95		let nfo = if mii.fType == co::MFT::SEPARATOR {
96			MenuItemInfo::Separator
97		} else {
98			let text = self.GetMenuString(id_or_pos)?;
99			if mii.hSubMenu != HMENU::NULL {
100				MenuItemInfo::Submenu { submenu: mii.hSubMenu, text }
101			} else {
102				MenuItemInfo::Entry { cmd_id: mii.wID as _, text }
103			}
104		};
105
106		Ok(nfo)
107	}
108
109	/// Returns an iterator over all menu items, including submenus and
110	/// separators.
111	///
112	/// # Examples
113	///
114	/// ```rust,no_run
115	/// use winsafe::{self as w, prelude::*};
116	///
117	/// let hmenu: w::HMENU; // initialized somewhere
118	/// # let hmenu = w::HMENU::NULL;
119	///
120	/// for item_info in hmenu.iter_items()? {
121	///     let item_info = item_info?;
122	///     match item_info {
123	///         w::MenuItemInfo::Entry { cmd_id, text } =>
124	///             println!("item {} {}", cmd_id, text),
125	///         w::MenuItemInfo::Separator =>
126	///             println!("separator"),
127	///         w::MenuItemInfo::Submenu { submenu, text } =>
128	///             println!("submenu {} {}", submenu, text),
129	///     }
130	/// }
131	/// # w::SysResult::Ok(())
132	/// ```
133	#[must_use]
134	pub fn iter_items(
135		&self,
136	) -> SysResult<impl DoubleEndedIterator<Item = SysResult<MenuItemInfo>> + '_> {
137		HmenuIteritems::new(self)
138	}
139
140	/// Shows the popup menu anchored at the given coordinates using
141	/// [`TrackPopupMenu`](crate::HMENU::TrackPopupMenu), and performs other
142	/// needed operations.
143	///
144	/// This method will block until the menu disappears.
145	pub fn track_popup_menu_at_point(
146		&self,
147		pos: POINT,
148		hwnd_parent: &HWND,
149		hwnd_coords_relative_to: &HWND,
150	) -> SysResult<()> {
151		let pos = hwnd_coords_relative_to.ClientToScreen(pos)?; // now relative to screen
152		hwnd_parent.SetForegroundWindow();
153		self.TrackPopupMenu(co::TPM::LEFTBUTTON, pos, hwnd_parent)?;
154		unsafe {
155			hwnd_parent.PostMessage(wm::Null {})?; // necessary according to TrackPopupMenu docs
156		}
157		Ok(())
158	}
159
160	/// [`AppendMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-appendmenuw)
161	/// function.
162	///
163	/// This method is rather tricky, consider using
164	/// [`HMENU::append_item`](crate::HMENU::append_item).
165	pub fn AppendMenu(&self, flags: co::MF, new_item: IdMenu, content: BmpPtrStr) -> SysResult<()> {
166		BoolRet(unsafe {
167			ffi::AppendMenuW(self.ptr(), flags.raw(), new_item.as_usize(), content.as_ptr())
168		})
169		.to_sysresult()
170	}
171
172	/// [`CheckMenuItem`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-checkmenuitem)
173	/// function.
174	pub fn CheckMenuItem(&self, id_or_pos: IdPos, check: bool) -> SysResult<co::MF> {
175		match unsafe {
176			ffi::CheckMenuItem(
177				self.ptr(),
178				id_or_pos.id_or_pos_u32(),
179				(id_or_pos.mf_flag() | if check { co::MF::CHECKED } else { co::MF::UNCHECKED })
180					.raw(),
181			)
182		} {
183			-1 => Err(co::ERROR::BAD_ARGUMENTS),
184			ret => Ok(unsafe { co::MF::from_raw(ret as _) }),
185		}
186	}
187
188	/// [`CheckMenuRadioItem`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-checkmenuradioitem)
189	/// function.
190	///
191	/// # Panics
192	///
193	/// Panics if `first`, `last` and `check` don't use the same enum field.
194	pub fn CheckMenuRadioItem(&self, first: IdPos, last: IdPos, check: IdPos) -> SysResult<()> {
195		if !(first.is_by_pos() == last.is_by_pos() && last.is_by_pos() == check.is_by_pos()) {
196			panic!("Different enum fields.");
197		}
198
199		BoolRet(unsafe {
200			ffi::CheckMenuRadioItem(
201				self.ptr(),
202				first.id_or_pos_u32(),
203				last.id_or_pos_u32(),
204				check.id_or_pos_u32(),
205				check.mf_flag().raw(),
206			)
207		})
208		.to_sysresult()
209	}
210
211	/// [`CreateMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createmenu)
212	/// function.
213	///
214	/// **Note:** If not attached to a window, must be paired with an
215	/// [`HMENU::DestroyMenu`](crate::HMENU::DestroyMenu) call.
216	#[must_use]
217	pub fn CreateMenu() -> SysResult<HMENU> {
218		PtrRet(unsafe { ffi::CreateMenu() }).to_sysresult_handle()
219	}
220
221	/// [`CreatePopupMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createpopupmenu)
222	/// function.
223	///
224	/// **Note:** When a menu is attached to a window, it's automatically
225	/// destroyed along with the window. However, if the menu is not attached to
226	/// any window, you must call
227	/// [`HMENU::DestroyMenu`](crate::HMENU::DestroyMenu).
228	#[must_use]
229	pub fn CreatePopupMenu() -> SysResult<HMENU> {
230		PtrRet(unsafe { ffi::CreatePopupMenu() }).to_sysresult_handle()
231	}
232
233	/// [`DeleteMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-deletemenu)
234	/// function.
235	pub fn DeleteMenu(&self, id_or_pos: IdPos) -> SysResult<()> {
236		BoolRet(unsafe {
237			ffi::DeleteMenu(self.ptr(), id_or_pos.id_or_pos_u32(), id_or_pos.mf_flag().raw())
238		})
239		.to_sysresult()
240	}
241
242	/// [`DestroyMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-destroymenu)
243	/// function.
244	///
245	/// After calling this method, the handle will be invalidated and further
246	/// operations will fail with
247	/// [`ERROR::INVALID_HANDLE`](crate::co::ERROR::INVALID_HANDLE) error code.
248	pub fn DestroyMenu(&mut self) -> SysResult<()> {
249		let ret = BoolRet(unsafe { ffi::DestroyMenu(self.ptr()) }).to_sysresult();
250		*self = Self::INVALID;
251		ret
252	}
253
254	/// [`EnableMenuItem`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablemenuitem)
255	/// function.
256	///
257	/// # Examples
258	///
259	/// Disabling a menu item:
260	///
261	/// ```no_run
262	/// use winsafe::{self as w, prelude::*, seq_ids};
263	///
264	/// seq_ids! {
265	///     ID_FILE_OPEN = 101;
266	/// }
267	///
268	/// let hmenu: w::HMENU; // initialized somewhere
269	/// # let hmenu = w::HMENU::NULL;
270	///
271	/// hmenu.EnableMenuItem(
272	///     w::IdPos::Id(ID_FILE_OPEN),
273	///     false,
274	/// )?;
275	/// # w::SysResult::Ok(())
276	/// ```
277	///
278	/// Disabling multiple menu items at once:
279	///
280	/// ```no_run
281	/// use winsafe::{self as w, prelude::*, seq_ids};
282	///
283	/// seq_ids! {
284	///     ID_FILE_OPEN = 201;
285	///     ID_FILE_SAVE
286	/// }
287	///
288	/// let hmenu: w::HMENU; // initialized somewhere
289	/// # let hmenu = w::HMENU::NULL;
290	///
291	/// [ID_FILE_OPEN, ID_FILE_SAVE]
292	///     .into_iter()
293	///     .try_for_each(|id|
294	///         hmenu.EnableMenuItem(
295	///             w::IdPos::Id(id),
296	///             false,
297	///         ).map(|_| ())
298	///     )?;
299	/// # w::SysResult::Ok(())
300	/// ```
301	pub fn EnableMenuItem(&self, id_or_pos: IdPos, enable: bool) -> SysResult<co::MF> {
302		match unsafe {
303			ffi::EnableMenuItem(
304				self.ptr(),
305				id_or_pos.id_or_pos_u32(),
306				(id_or_pos.mf_flag() | if enable { co::MF::ENABLED } else { co::MF::DISABLED })
307					.raw(),
308			)
309		} {
310			-1 => Err(co::ERROR::BAD_ARGUMENTS),
311			ret => Ok(unsafe { co::MF::from_raw(ret as _) }),
312		}
313	}
314
315	/// [`GetMenuDefaultItem`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmenudefaultitem)
316	/// function.
317	#[must_use]
318	pub fn GetMenuDefaultItem(&self, by_pos: bool, flags: co::GMDI) -> SysResult<IdPos> {
319		match unsafe { ffi::GetMenuDefaultItem(self.ptr(), by_pos as _, flags.raw()) as i32 } {
320			-1 => Err(GetLastError()),
321			n => Ok(if by_pos { IdPos::Pos(n as _) } else { IdPos::Id(n as _) }),
322		}
323	}
324
325	/// [`GetMenuInfo`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmenuinfo)
326	/// function.
327	pub fn GetMenuInfo(&self, mi: &mut MENUINFO) -> SysResult<()> {
328		BoolRet(unsafe { ffi::GetMenuInfo(self.ptr(), pvoid(mi)) }).to_sysresult()
329	}
330
331	/// [`GetMenuItemCount`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmenuitemcount)
332	/// function.
333	#[must_use]
334	pub fn GetMenuItemCount(&self) -> SysResult<u32> {
335		match unsafe { ffi::GetMenuItemCount(self.ptr()) } {
336			-1 => Err(GetLastError()),
337			count => Ok(count as _),
338		}
339	}
340
341	/// [`GetMenuItemID`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmenuitemid)
342	/// function.
343	///
344	/// If `item_index` corresponds to a submenu, returns `None`.
345	#[must_use]
346	pub fn GetMenuItemID(&self, item_index: i32) -> Option<u16> {
347		match unsafe { ffi::GetMenuItemID(self.ptr(), item_index) } {
348			-1 => None,
349			id => Some(id as _),
350		}
351	}
352
353	/// [`GetMenuItemInfo`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmenuiteminfow)
354	/// function.
355	///
356	/// This method is rather tricky, consider using
357	/// [`HMENU::item_info`](crate::HMENU::item_info).
358	pub fn GetMenuItemInfo(&self, id_or_pos: IdPos, mii: &mut MENUITEMINFO) -> SysResult<()> {
359		BoolRet(unsafe {
360			ffi::GetMenuItemInfoW(
361				self.ptr(),
362				id_or_pos.id_or_pos_u32(),
363				id_or_pos.is_by_pos() as _,
364				pvoid(mii),
365			)
366		})
367		.to_sysresult()
368	}
369
370	/// [`GetMenuState`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmenustate)
371	/// function.
372	#[must_use]
373	pub fn GetMenuState(&self, id_or_pos: IdPos) -> SysResult<co::MF> {
374		match unsafe {
375			ffi::GetMenuState(self.ptr(), id_or_pos.id_or_pos_u32(), id_or_pos.is_by_pos() as _)
376				as i32
377		} {
378			-1 => Err(GetLastError()),
379			mf => Ok(unsafe { co::MF::from_raw(mf as _) }),
380		}
381	}
382
383	/// [`GetMenuString`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmenustringw)
384	/// function.
385	#[must_use]
386	pub fn GetMenuString(&self, id_or_pos: IdPos) -> SysResult<String> {
387		let mut buf_sz = WString::SSO_LEN; // start with no string heap allocation
388		loop {
389			let mut buf = WString::new_alloc_buf(buf_sz);
390
391			let returned_chars = match unsafe {
392				// char count without terminating null
393				ffi::GetMenuStringW(
394					self.ptr(),
395					id_or_pos.id_or_pos_u32(),
396					buf.as_mut_ptr(),
397					buf.buf_len() as _,
398					id_or_pos.mf_flag().raw(),
399				)
400			} {
401				0 => return Err(GetLastError()),
402				n => n + 1, // plus terminating null count
403			};
404
405			if (returned_chars as usize) < buf_sz {
406				return Ok(buf.to_string()); // to break, must have at least 1 char gap
407			}
408
409			buf_sz *= 2; // double the buffer size to try again
410		}
411	}
412
413	/// [`GetSubMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsubmenu)
414	/// function.
415	#[must_use]
416	pub fn GetSubMenu(&self, pos: u32) -> Option<HMENU> {
417		PtrRet(unsafe { ffi::GetSubMenu(self.ptr(), pos as _) }).to_opt_handle()
418	}
419
420	/// [`InsertMenuItem`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-insertmenuitemw)
421	/// function.
422	pub fn InsertMenuItem(&self, id_or_pos: IdPos, mii: &MENUITEMINFO) -> SysResult<()> {
423		BoolRet(unsafe {
424			ffi::InsertMenuItemW(
425				self.ptr(),
426				id_or_pos.id_or_pos_u32(),
427				id_or_pos.is_by_pos() as _,
428				pcvoid(mii),
429			)
430		})
431		.to_sysresult()
432	}
433
434	/// [`IsMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-ismenu)
435	/// function.
436	#[must_use]
437	pub fn IsMenu(&self) -> bool {
438		unsafe { ffi::IsMenu(self.ptr()) != 0 }
439	}
440
441	/// [`RemoveMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-removemenu)
442	/// function.
443	pub fn RemoveMenu(&self, id_or_pos: IdPos) -> SysResult<()> {
444		BoolRet(unsafe {
445			ffi::RemoveMenu(self.ptr(), id_or_pos.id_or_pos_u32(), id_or_pos.mf_flag().raw())
446		})
447		.to_sysresult()
448	}
449
450	/// [`SetMenuDefaultItem`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setmenudefaultitem)
451	/// function.
452	pub fn SetMenuDefaultItem(&self, id_or_pos: IdPos) -> SysResult<()> {
453		BoolRet(unsafe {
454			ffi::SetMenuDefaultItem(
455				self.ptr(),
456				id_or_pos.id_or_pos_u32(),
457				id_or_pos.is_by_pos() as _,
458			)
459		})
460		.to_sysresult()
461	}
462
463	/// [`SetMenuInfo`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setmenuinfo)
464	/// function.
465	pub fn SetMenuInfo(&self, mi: &MENUINFO) -> SysResult<()> {
466		BoolRet(unsafe { ffi::SetMenuInfo(self.ptr(), pcvoid(mi)) }).to_sysresult()
467	}
468
469	/// [`SetMenuItemBitmaps`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setmenuitembitmaps)
470	/// function.
471	pub fn SetMenuItemBitmaps(
472		&self,
473		id_or_pos: IdPos,
474		hbmp_unchecked: Option<&HBITMAP>,
475		hbmp_checked: Option<&HBITMAP>,
476	) -> SysResult<()> {
477		BoolRet(unsafe {
478			ffi::SetMenuItemBitmaps(
479				self.ptr(),
480				id_or_pos.id_or_pos_u32(),
481				id_or_pos.mf_flag().raw(),
482				hbmp_unchecked.map_or(std::ptr::null_mut(), |h| h.ptr()),
483				hbmp_checked.map_or(std::ptr::null_mut(), |h| h.ptr()),
484			)
485		})
486		.to_sysresult()
487	}
488
489	/// [`SetMenuItemInfo`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setmenuiteminfow)
490	/// function.
491	pub fn SetMenuItemInfo(&self, id_or_pos: IdPos, mii: &MENUITEMINFO) -> SysResult<()> {
492		BoolRet(unsafe {
493			ffi::SetMenuItemInfoW(
494				self.ptr(),
495				id_or_pos.id_or_pos_u32(),
496				id_or_pos.is_by_pos() as _,
497				pcvoid(mii),
498			)
499		})
500		.to_sysresult()
501	}
502
503	/// [`TrackPopupMenu`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-trackpopupmenu)
504	/// function.
505	///
506	/// **Note:** If you just want to display a popup menu, consider the simpler
507	/// [`HMENU::track_popup_menu_at_point`](crate::HMENU::track_popup_menu_at_point).
508	pub fn TrackPopupMenu(
509		&self,
510		flags: co::TPM,
511		location: POINT,
512		hwnd: &HWND,
513	) -> SysResult<Option<i32>> {
514		let ret = unsafe {
515			ffi::TrackPopupMenu(
516				self.ptr(),
517				flags.raw(),
518				location.x,
519				location.y,
520				0,
521				hwnd.ptr(),
522				std::ptr::null(),
523			)
524		};
525
526		if flags.has(co::TPM::RETURNCMD) {
527			match ret {
528				0 => match GetLastError() {
529					co::ERROR::SUCCESS => Ok(None), // success, user cancelled the menu
530					error => Err(error),
531				},
532				id => Ok(Some(id)), // success, menu item identifier
533			}
534		} else {
535			match ret {
536				0 => Err(GetLastError()),
537				_ => Ok(None),
538			}
539		}
540	}
541}