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}