1use super::{ControlBase, ControlHandle};
2use crate::win32::base_helper::{check_hwnd, from_utf16, to_utf16};
3use crate::win32::window_helper as wh;
4use crate::{Icon, NwgError};
5use std::{mem, ptr};
6use winapi::shared::minwindef::{LPARAM, UINT, WPARAM};
7use winapi::um::winnt::WCHAR;
8
9const NOT_BOUND: &'static str = "Tooltip is not yet bound to a winapi object";
10const BAD_HANDLE: &'static str = "INTERNAL ERROR: Tooltip handle is not HWND!";
11
12#[derive(Copy, Clone, Debug)]
14pub enum TooltipIcon {
15 None,
16 Info,
17 Warning,
18 Error,
19 InfoLarge,
20 WarningLarge,
21 ErrorLarge,
22}
23
24#[derive(Default, PartialEq, Eq)]
89pub struct Tooltip {
90 pub handle: ControlHandle,
91}
92
93impl Tooltip {
94 pub fn builder<'a>() -> TooltipBuilder<'a> {
95 TooltipBuilder {
96 title: None,
97 ico: None,
98 default_ico: None,
99 register: Vec::new(),
100 register_cb: Vec::new(),
101 }
102 }
103
104 pub fn text(&self, owner: &ControlHandle, buffer_size: Option<usize>) -> String {
142 use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
143 use winapi::um::commctrl::{TTF_IDISHWND, TTF_SUBCLASS, TTM_GETTEXTW, TTTOOLINFOW};
144
145 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
146
147 let owner_handle = {
148 if owner.blank() {
149 panic!("{}", NOT_BOUND);
150 }
151 owner.hwnd().expect(BAD_HANDLE)
152 };
153
154 let buffer_size = buffer_size.unwrap_or(200);
155 let mut text: Vec<WCHAR> = Vec::with_capacity(buffer_size);
156 unsafe {
157 text.set_len(buffer_size);
158 }
159
160 let mut tool = TTTOOLINFOW {
161 cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
162 uFlags: TTF_IDISHWND | TTF_SUBCLASS,
163 hwnd: owner_handle,
164 uId: owner_handle as UINT_PTR,
165 rect: RECT {
166 left: 0,
167 top: 0,
168 right: 0,
169 bottom: 0,
170 },
171 hinst: ptr::null_mut(),
172 lpszText: text.as_mut_ptr(),
173 lParam: 0,
174 lpReserved: ptr::null_mut(),
175 };
176
177 let tool_ptr = &mut tool as *mut TTTOOLINFOW;
178 wh::send_message(handle, TTM_GETTEXTW, 0, tool_ptr as LPARAM);
179
180 from_utf16(&text)
181 }
182
183 pub fn set_text<'a>(&self, owner: &ControlHandle, text: &'a str) {
186 use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
187 use winapi::um::commctrl::{TTF_IDISHWND, TTF_SUBCLASS, TTM_UPDATETIPTEXTW, TTTOOLINFOW};
188
189 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
190
191 let mut text = to_utf16(text);
192 let owner_handle = {
193 if owner.blank() {
194 panic!("{}", NOT_BOUND);
195 }
196 owner.hwnd().expect(BAD_HANDLE)
197 };
198
199 let tool = TTTOOLINFOW {
200 cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
201 uFlags: TTF_IDISHWND | TTF_SUBCLASS,
202 hwnd: owner_handle,
203 uId: owner_handle as UINT_PTR,
204 rect: RECT {
205 left: 0,
206 top: 0,
207 right: 0,
208 bottom: 0,
209 },
210 hinst: ptr::null_mut(),
211 lpszText: text.as_mut_ptr(),
212 lParam: 0,
213 lpReserved: ptr::null_mut(),
214 };
215
216 let tool_ptr = &tool as *const TTTOOLINFOW;
217 wh::send_message(handle, TTM_UPDATETIPTEXTW, 0, tool_ptr as LPARAM);
218 }
219
220 pub fn set_decoration<'a>(&self, title: &'a str, ico: &Icon) {
222 use winapi::um::commctrl::TTM_SETTITLEW;
223
224 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
225 let title = to_utf16(title);
226 wh::send_message(
227 handle,
228 TTM_SETTITLEW,
229 ico.handle as WPARAM,
230 title.as_ptr() as LPARAM,
231 );
232 }
233
234 pub fn set_default_decoration<'a>(&self, title: &'a str, icon: TooltipIcon) {
236 use winapi::um::commctrl::TTM_SETTITLEW;
237 use winapi::um::commctrl::{
238 TTI_ERROR, TTI_ERROR_LARGE, TTI_INFO, TTI_INFO_LARGE, TTI_NONE, TTI_WARNING,
239 TTI_WARNING_LARGE,
240 };
241
242 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
243
244 let bitmap_handle = match icon {
245 TooltipIcon::None => TTI_NONE,
246 TooltipIcon::Info => TTI_INFO,
247 TooltipIcon::Warning => TTI_WARNING,
248 TooltipIcon::Error => TTI_ERROR,
249 TooltipIcon::InfoLarge => TTI_INFO_LARGE,
250 TooltipIcon::WarningLarge => TTI_WARNING_LARGE,
251 TooltipIcon::ErrorLarge => TTI_ERROR_LARGE,
252 };
253
254 let title = to_utf16(title);
255
256 wh::send_message(
257 handle,
258 TTM_SETTITLEW,
259 bitmap_handle as WPARAM,
260 title.as_ptr() as LPARAM,
261 );
262 }
263
264 pub fn hide(&self) {
266 use winapi::um::commctrl::TTM_POP;
267
268 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
269 wh::send_message(handle, TTM_POP, 0, 0);
270 }
271
272 pub fn count(&self) -> usize {
274 use winapi::um::commctrl::TTM_GETTOOLCOUNT;
275
276 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
277 wh::send_message(handle, TTM_GETTOOLCOUNT, 0, 0) as usize
278 }
279
280 pub fn set_delay_time(&self, delay: Option<u16>) {
283 use winapi::um::commctrl::{TTDT_INITIAL, TTM_SETDELAYTIME};
284
285 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
286 let value = match delay {
287 Some(d) => d & 0xFFFF,
288 None => u16::max_value() & 0xFFFF,
289 };
290
291 wh::send_message(
292 handle,
293 TTM_SETDELAYTIME,
294 TTDT_INITIAL as WPARAM,
295 value as LPARAM,
296 );
297 }
298
299 pub fn delay_time(&self) -> u16 {
301 use winapi::um::commctrl::{TTDT_INITIAL, TTM_GETDELAYTIME};
302
303 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
304 wh::send_message(handle, TTM_GETDELAYTIME, TTDT_INITIAL as WPARAM, 0) as u16
305 }
306
307 pub fn set_enabled(&self, v: bool) {
310 use winapi::um::commctrl::TTM_ACTIVATE;
311
312 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
313 wh::send_message(handle, TTM_ACTIVATE, v as WPARAM, 0);
314 }
315
316 pub fn register<'a, W: Into<ControlHandle>>(&self, owner: W, text: &'a str) {
319 use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
320 use winapi::um::commctrl::{TTF_IDISHWND, TTF_SUBCLASS, TTM_ADDTOOLW, TTTOOLINFOW};
321
322 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
323
324 let owner = owner.into();
325
326 let mut text = to_utf16(text);
327 let owner_handle = {
328 if owner.blank() {
329 panic!("{}", NOT_BOUND);
330 }
331 owner.hwnd().expect(BAD_HANDLE)
332 };
333
334 let tool = TTTOOLINFOW {
335 cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
336 uFlags: TTF_IDISHWND | TTF_SUBCLASS,
337 hwnd: owner_handle,
338 uId: owner_handle as UINT_PTR,
339 rect: RECT {
340 left: 0,
341 top: 0,
342 right: 0,
343 bottom: 0,
344 },
345 hinst: ptr::null_mut(),
346 lpszText: text.as_mut_ptr(),
347 lParam: 0,
348 lpReserved: ptr::null_mut(),
349 };
350
351 let tool_ptr = &tool as *const TTTOOLINFOW;
352 wh::send_message(handle, TTM_ADDTOOLW, 0, tool_ptr as LPARAM);
353 }
354
355 pub fn register_callback<W: Into<ControlHandle>>(&self, owner: W) {
359 use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
360 use winapi::um::commctrl::{
361 LPSTR_TEXTCALLBACKW, TTF_IDISHWND, TTF_SUBCLASS, TTM_ADDTOOLW, TTTOOLINFOW,
362 };
363
364 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
365
366 let owner = owner.into();
367 let owner_handle = {
368 if owner.blank() {
369 panic!("{}", NOT_BOUND);
370 }
371 owner.hwnd().expect(BAD_HANDLE)
372 };
373
374 let tool = TTTOOLINFOW {
375 cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
376 uFlags: TTF_IDISHWND | TTF_SUBCLASS,
377 hwnd: owner_handle,
378 uId: owner_handle as UINT_PTR,
379 rect: RECT {
380 left: 0,
381 top: 0,
382 right: 0,
383 bottom: 0,
384 },
385 hinst: ptr::null_mut(),
386 lpszText: LPSTR_TEXTCALLBACKW,
387 lParam: 0,
388 lpReserved: ptr::null_mut(),
389 };
390
391 let tool_ptr = &tool as *const TTTOOLINFOW;
392 wh::send_message(handle, TTM_ADDTOOLW, 0, tool_ptr as LPARAM);
393 }
394
395 pub fn unregister<W: Into<ControlHandle>>(&self, owner: W) {
397 use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
398 use winapi::um::commctrl::{TTF_IDISHWND, TTF_SUBCLASS, TTM_DELTOOLW, TTTOOLINFOW};
399
400 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
401 let owner = owner.into();
402
403 let owner_handle = {
404 if owner.blank() {
405 panic!("{}", NOT_BOUND);
406 }
407 owner.hwnd().expect(BAD_HANDLE)
408 };
409
410 let tool = TTTOOLINFOW {
411 cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
412 uFlags: TTF_IDISHWND | TTF_SUBCLASS,
413 hwnd: owner_handle,
414 uId: owner_handle as UINT_PTR,
415 rect: RECT {
416 left: 0,
417 top: 0,
418 right: 0,
419 bottom: 0,
420 },
421 hinst: ptr::null_mut(),
422 lpszText: ptr::null_mut(),
423 lParam: 0,
424 lpReserved: ptr::null_mut(),
425 };
426
427 let tool_ptr = &tool as *const TTTOOLINFOW;
428 wh::send_message(handle, TTM_DELTOOLW, 0, tool_ptr as LPARAM);
429 }
430
431 pub fn class_name(&self) -> &'static str {
433 winapi::um::commctrl::TOOLTIPS_CLASS
434 }
435
436 pub fn flags(&self) -> u32 {
438 0
439 }
440
441 pub fn forced_flags(&self) -> u32 {
443 use winapi::um::commctrl::{TTS_ALWAYSTIP, TTS_NOPREFIX};
444 use winapi::um::winuser::WS_POPUP;
445
446 WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX
447 }
448}
449
450impl Drop for Tooltip {
451 fn drop(&mut self) {
452 self.handle.destroy();
453 }
454}
455pub struct TooltipBuilder<'a> {
456 title: Option<&'a str>,
457 ico: Option<&'a Icon>,
458 default_ico: Option<TooltipIcon>,
459 register: Vec<(ControlHandle, &'a str)>,
460 register_cb: Vec<ControlHandle>,
461}
462
463impl<'a> TooltipBuilder<'a> {
464 pub fn register<W: Into<ControlHandle>>(
465 mut self,
466 widget: W,
467 text: &'a str,
468 ) -> TooltipBuilder<'a> {
469 self.register.push((widget.into(), text));
470 self
471 }
472
473 pub fn register_callback<W: Into<ControlHandle>>(mut self, widget: W) -> TooltipBuilder<'a> {
474 self.register_cb.push(widget.into());
475 self
476 }
477
478 pub fn decoration(
479 mut self,
480 title: Option<&'a str>,
481 ico: Option<&'a Icon>,
482 ) -> TooltipBuilder<'a> {
483 self.title = title;
484 self.ico = ico;
485 self
486 }
487
488 pub fn default_decoration(
489 mut self,
490 title: Option<&'a str>,
491 ico: Option<TooltipIcon>,
492 ) -> TooltipBuilder<'a> {
493 self.title = title;
494 self.default_ico = ico;
495 self
496 }
497
498 pub fn build(self, tooltip: &mut Tooltip) -> Result<(), NwgError> {
499 *tooltip = Default::default();
500
501 tooltip.handle = ControlBase::build_hwnd()
502 .class_name(tooltip.class_name())
503 .forced_flags(tooltip.forced_flags())
504 .flags(tooltip.flags())
505 .build()?;
506
507 if self.title.is_some() || self.ico.is_some() || self.default_ico.is_some() {
508 let title = self.title.unwrap_or("");
509 match (self.ico, self.default_ico) {
510 (Some(ico), None) | (Some(ico), _) => tooltip.set_decoration(title, ico),
511 (None, Some(ico)) => tooltip.set_default_decoration(title, ico),
512 (None, None) => tooltip.set_default_decoration(title, TooltipIcon::None),
513 }
514 }
515
516 for (handle, text) in self.register {
517 tooltip.register(&handle, text);
518 }
519
520 for handle in self.register_cb {
521 tooltip.register_callback(&handle);
522 }
523
524 Ok(())
525 }
526}