1use super::{ControlBase, ControlHandle};
2use crate::win32::base_helper::{check_hwnd, from_utf16, to_utf16};
3use crate::win32::window_helper as wh;
4use crate::{Font, NwgError, RawEventHandler, VTextAlign, unbind_raw_event_handler};
5use std::cell::{Ref, RefCell, RefMut};
6use std::fmt::Display;
7use std::mem;
8use winapi::shared::minwindef::{LPARAM, WPARAM};
9use winapi::shared::windef::HWND;
10use winapi::um::winuser::{WS_DISABLED, WS_TABSTOP, WS_VISIBLE};
11
12const NOT_BOUND: &'static str = "Combobox is not yet bound to a winapi object";
13const BAD_HANDLE: &'static str = "INTERNAL ERROR: Combobox handle is not HWND!";
14
15bitflags! {
16 pub struct ComboBoxFlags: u32 {
25 const NONE = 0;
26 const VISIBLE = WS_VISIBLE;
27 const DISABLED = WS_DISABLED;
28 const TAB_STOP = WS_TABSTOP;
29 }
30}
31
32#[derive(Default)]
74pub struct ComboBox<D: Display + Default> {
75 pub handle: ControlHandle,
76 collection: RefCell<Vec<D>>,
77 handler0: RefCell<Option<RawEventHandler>>,
78}
79
80impl<D: Display + Default> ComboBox<D> {
81 pub fn builder<'a>() -> ComboBoxBuilder<'a, D> {
82 ComboBoxBuilder {
83 size: (100, 25),
84 position: (0, 0),
85 enabled: true,
86 focus: false,
87 flags: None,
88 ex_flags: 0,
89 font: None,
90 collection: None,
91 selected_index: None,
92 parent: None,
93 }
94 }
95
96 pub fn remove(&self, index: usize) -> D {
99 use winapi::um::winuser::CB_DELETESTRING;
100
101 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
102
103 wh::send_message(handle, CB_DELETESTRING, index as WPARAM, 0);
104
105 let mut col_ref = self.collection.borrow_mut();
106 col_ref.remove(index)
107 }
108
109 pub fn sort(&self) {
112 use winapi::um::winuser::CB_ADDSTRING;
113
114 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
115
116 self.clear_inner(handle);
117
118 let mut col = self.collection.borrow_mut();
119 col.sort_unstable_by(|a, b| {
120 let astr = format!("{}", a);
121 let bstr = format!("{}", b);
122 astr.cmp(&bstr)
123 });
124
125 for item in col.iter() {
126 let display = format!("{}", item);
127 let display_os = to_utf16(&display);
128 wh::send_message(handle, CB_ADDSTRING, 0, display_os.as_ptr() as LPARAM);
129 }
130 }
131
132 pub fn dropdown(&self, v: bool) {
134 use winapi::um::winuser::CB_SHOWDROPDOWN;
135
136 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
137 wh::send_message(handle, CB_SHOWDROPDOWN, v as usize, 0);
138 }
139
140 pub fn selection(&self) -> Option<usize> {
142 use winapi::um::winuser::{CB_ERR, CB_GETCURSEL};
143
144 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
145
146 let index = wh::send_message(handle, CB_GETCURSEL, 0, 0);
147
148 if index == CB_ERR {
149 None
150 } else {
151 Some(index as usize)
152 }
153 }
154
155 pub fn selection_string(&self) -> Option<String> {
158 use winapi::shared::ntdef::WCHAR;
159 use winapi::um::winuser::{CB_ERR, CB_GETCURSEL, CB_GETLBTEXT, CB_GETLBTEXTLEN};
160
161 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
162
163 let index = wh::send_message(handle, CB_GETCURSEL, 0, 0);
164
165 if index == CB_ERR {
166 None
167 } else {
168 let index = index as usize;
169 let length = (wh::send_message(handle, CB_GETLBTEXTLEN, index, 0) as usize) + 1; let mut buffer: Vec<WCHAR> = Vec::with_capacity(length);
171 unsafe {
172 buffer.set_len(length);
173 wh::send_message(handle, CB_GETLBTEXT, index, buffer.as_ptr() as LPARAM);
174 }
175
176 Some(from_utf16(&buffer))
177 }
178 }
179
180 pub fn set_selection(&self, index: Option<usize>) {
184 use winapi::um::winuser::CB_SETCURSEL;
185
186 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
187
188 let index = index.unwrap_or(-1isize as usize);
189 wh::send_message(handle, CB_SETCURSEL, index, 0);
190 }
191
192 pub fn set_selection_string(&self, value: &str) -> Option<usize> {
196 use winapi::um::winuser::{CB_ERR, CB_SELECTSTRING};
197
198 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
199
200 let os_string = to_utf16(value);
201
202 let index = wh::send_message(handle, CB_SELECTSTRING, 0, os_string.as_ptr() as LPARAM);
203 if index == CB_ERR {
204 None
205 } else {
206 Some(index as usize)
207 }
208 }
209
210 pub fn push(&self, item: D) {
212 use winapi::um::winuser::CB_ADDSTRING;
213
214 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
215
216 let display = format!("{}", item);
217 let display_os = to_utf16(&display);
218
219 wh::send_message(handle, CB_ADDSTRING, 0, display_os.as_ptr() as LPARAM);
220
221 self.collection.borrow_mut().push(item);
222 }
223
224 pub fn insert(&self, index: usize, item: D) {
229 use winapi::um::winuser::CB_INSERTSTRING;
230
231 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
232
233 let display = format!("{}", item);
234 let display_os = to_utf16(&display);
235
236 let mut col = self.collection.borrow_mut();
237 if index == std::usize::MAX {
238 col.push(item);
239 } else {
240 col.insert(index, item);
241 }
242
243 wh::send_message(
244 handle,
245 CB_INSERTSTRING,
246 index,
247 display_os.as_ptr() as LPARAM,
248 );
249 }
250
251 pub fn sync(&self) {
254 use winapi::um::winuser::CB_ADDSTRING;
255
256 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
257
258 self.clear_inner(handle);
259
260 for item in self.collection.borrow().iter() {
261 let display = format!("{}", item);
262 let display_os = to_utf16(&display);
263
264 wh::send_message(handle, CB_ADDSTRING, 0, display_os.as_ptr() as LPARAM);
265 }
266 }
267
268 pub fn set_collection(&self, mut col: Vec<D>) -> Vec<D> {
270 use winapi::um::winuser::CB_ADDSTRING;
271
272 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
273
274 self.clear_inner(handle);
275
276 for item in col.iter() {
277 let display = format!("{}", item);
278 let display_os = to_utf16(&display);
279 wh::send_message(handle, CB_ADDSTRING, 0, display_os.as_ptr() as LPARAM);
280 }
281
282 let mut col_ref = self.collection.borrow_mut();
283 mem::swap::<Vec<D>>(&mut col_ref, &mut col);
284
285 col
286 }
287
288 pub fn len(&self) -> usize {
290 use winapi::um::winuser::CB_GETCOUNT;
291 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
292 wh::send_message(handle, CB_GETCOUNT, 0, 0) as usize
293 }
294
295 pub fn font(&self) -> Option<Font> {
301 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
302
303 let font_handle = wh::get_window_font(handle);
304 if font_handle.is_null() {
305 None
306 } else {
307 Some(Font {
308 handle: font_handle,
309 })
310 }
311 }
312
313 pub fn set_font(&self, font: Option<&Font>) {
315 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
316
317 wh::set_window_font(handle, font.map(|f| f.handle), true);
318 }
319
320 pub fn focus(&self) -> bool {
322 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
323 wh::get_focus(handle)
324 }
325
326 pub fn set_focus(&self) {
328 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
329
330 wh::set_focus(handle);
331 }
332
333 pub fn enabled(&self) -> bool {
335 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
336 wh::get_window_enabled(handle)
337 }
338
339 pub fn set_enabled(&self, v: bool) {
341 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
342 wh::set_window_enabled(handle, v)
343 }
344
345 pub fn visible(&self) -> bool {
348 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
349 wh::get_window_visibility(handle)
350 }
351
352 pub fn set_visible(&self, v: bool) {
354 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
355 wh::set_window_visibility(handle, v)
356 }
357
358 pub fn size(&self) -> (u32, u32) {
360 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
361 wh::get_window_size(handle)
362 }
363
364 pub fn set_size(&self, x: u32, y: u32) {
366 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
367 wh::set_window_size(handle, x, y, false)
368 }
369
370 pub fn position(&self) -> (i32, i32) {
372 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
373 wh::get_window_position(handle)
374 }
375
376 pub fn set_position(&self, x: i32, y: i32) {
378 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
379 wh::set_window_position(handle, x, y)
380 }
381
382 pub fn collection(&self) -> Ref<'_, Vec<D>> {
386 self.collection.borrow()
387 }
388
389 pub fn collection_mut(&self) -> RefMut<'_, Vec<D>> {
393 self.collection.borrow_mut()
394 }
395
396 pub fn class_name(&self) -> &'static str {
398 "COMBOBOX"
399 }
400
401 pub fn flags(&self) -> u32 {
403 WS_VISIBLE | WS_TABSTOP
404 }
405
406 pub fn forced_flags(&self) -> u32 {
408 use winapi::um::winuser::{CBS_DROPDOWNLIST, WS_BORDER, WS_CHILD};
409 CBS_DROPDOWNLIST | WS_CHILD | WS_BORDER
410 }
411
412 fn clear_inner(&self, handle: HWND) {
414 use winapi::um::winuser::CB_RESETCONTENT;
415 wh::send_message(handle, CB_RESETCONTENT, 0, 0);
416 }
417
418 #[allow(unused)]
420 fn hook_non_client_size(&self, bg: Option<[u8; 3]>, v_align: VTextAlign) {
421 use crate::bind_raw_event_handler_inner;
422 use std::ptr;
423 use winapi::shared::windef::{HBRUSH, HGDIOBJ, POINT, RECT};
424 use winapi::um::wingdi::{CreateSolidBrush, RGB, SelectObject};
425 use winapi::um::winuser::{
426 COLOR_WINDOW, DT_CALCRECT, DT_LEFT, NCCALCSIZE_PARAMS, WM_NCCALCSIZE, WM_NCPAINT,
427 WM_SIZE,
428 };
429 use winapi::um::winuser::{
430 DrawTextW, FillRect, GetClientRect, GetDC, GetWindowRect, ReleaseDC, ScreenToClient,
431 SetWindowPos,
432 };
433 use winapi::um::winuser::{SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOOWNERZORDER, SWP_NOSIZE};
434
435 if self.handle.blank() {
436 panic!("{}", NOT_BOUND);
437 }
438 let brush = match bg {
439 Some(c) => unsafe { CreateSolidBrush(RGB(c[0], c[1], c[2])) },
440 None => COLOR_WINDOW as HBRUSH,
441 };
442
443 unsafe {
444 let handler0 = bind_raw_event_handler_inner(&self.handle, 0, move |hwnd, msg, w, l| {
445 match msg {
446 WM_NCCALCSIZE => {
447 if w == 0 {
448 return None;
449 }
450
451 let font_handle = wh::get_window_font(hwnd);
453 let mut r: RECT = mem::zeroed();
454 let dc = GetDC(hwnd);
455
456 let old = SelectObject(dc, font_handle as HGDIOBJ);
457
458 let calc: [u16; 2] = [75, 121];
459 DrawTextW(dc, calc.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
460
461 let client_height = r.bottom - 5; SelectObject(dc, old);
464 ReleaseDC(hwnd, dc);
465
466 let mut client: RECT = mem::zeroed();
468 let mut window: RECT = mem::zeroed();
469 GetClientRect(hwnd, &mut client);
470 GetWindowRect(hwnd, &mut window);
471
472 let window_height = window.bottom - window.top;
473 let info_ptr: *mut NCCALCSIZE_PARAMS = l as *mut NCCALCSIZE_PARAMS;
474 let info = &mut *info_ptr;
475 match v_align {
476 VTextAlign::Top => {
477 info.rgrc[0].bottom -= window_height - client_height;
478 }
479 VTextAlign::Center => {
480 let center = ((window_height - client_height) / 2) - 1;
481 info.rgrc[0].top += center;
482 info.rgrc[0].bottom -= center;
483 }
484 VTextAlign::Bottom => {
485 info.rgrc[0].top += window_height - client_height;
486 }
487 }
488 }
489 WM_NCPAINT => {
490 let mut window: RECT = mem::zeroed();
491 let mut client: RECT = mem::zeroed();
492 GetWindowRect(hwnd, &mut window);
493 GetClientRect(hwnd, &mut client);
494
495 let mut pt1 = POINT {
496 x: window.left,
497 y: window.top,
498 };
499 ScreenToClient(hwnd, &mut pt1);
500
501 let mut pt2 = POINT {
502 x: window.right,
503 y: window.bottom,
504 };
505 ScreenToClient(hwnd, &mut pt2);
506
507 let top = RECT {
508 left: 0,
509 top: pt1.y,
510 right: client.right,
511 bottom: client.top,
512 };
513
514 let bottom = RECT {
515 left: 0,
516 top: client.bottom,
517 right: client.right,
518 bottom: pt2.y,
519 };
520
521 let dc = GetDC(hwnd);
522 FillRect(dc, &top, brush);
523 FillRect(dc, &bottom, brush);
524 ReleaseDC(hwnd, dc);
525 }
526 WM_SIZE => {
527 SetWindowPos(
528 hwnd,
529 ptr::null_mut(),
530 0,
531 0,
532 0,
533 0,
534 SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED,
535 );
536 }
537 _ => {}
538 }
539
540 None
541 });
542
543 *self.handler0.borrow_mut() = Some(handler0.unwrap());
544 }
545 }
546}
547
548impl<D: Display + Default> Drop for ComboBox<D> {
549 fn drop(&mut self) {
550 let handler = self.handler0.borrow();
551 if let Some(h) = handler.as_ref() {
552 drop(unbind_raw_event_handler(h));
553 }
554
555 self.handle.destroy();
556 }
557}
558
559pub struct ComboBoxBuilder<'a, D: Display + Default> {
560 size: (i32, i32),
561 position: (i32, i32),
562 enabled: bool,
563 focus: bool,
564 flags: Option<ComboBoxFlags>,
565 ex_flags: u32,
566 font: Option<&'a Font>,
567 collection: Option<Vec<D>>,
568 selected_index: Option<usize>,
569 parent: Option<ControlHandle>,
570}
571
572impl<'a, D: Display + Default> ComboBoxBuilder<'a, D> {
573 pub fn flags(mut self, flags: ComboBoxFlags) -> ComboBoxBuilder<'a, D> {
574 self.flags = Some(flags);
575 self
576 }
577
578 pub fn ex_flags(mut self, flags: u32) -> ComboBoxBuilder<'a, D> {
579 self.ex_flags = flags;
580 self
581 }
582
583 pub fn size(mut self, size: (i32, i32)) -> ComboBoxBuilder<'a, D> {
584 self.size = size;
585 self
586 }
587
588 pub fn position(mut self, pos: (i32, i32)) -> ComboBoxBuilder<'a, D> {
589 self.position = pos;
590 self
591 }
592
593 pub fn font(mut self, font: Option<&'a Font>) -> ComboBoxBuilder<'a, D> {
594 self.font = font;
595 self
596 }
597
598 pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> ComboBoxBuilder<'a, D> {
599 self.parent = Some(p.into());
600 self
601 }
602
603 pub fn collection(mut self, collection: Vec<D>) -> ComboBoxBuilder<'a, D> {
604 self.collection = Some(collection);
605 self
606 }
607
608 pub fn selected_index(mut self, index: Option<usize>) -> ComboBoxBuilder<'a, D> {
609 self.selected_index = index;
610 self
611 }
612
613 pub fn enabled(mut self, e: bool) -> ComboBoxBuilder<'a, D> {
614 self.enabled = e;
615 self
616 }
617
618 pub fn focus(mut self, focus: bool) -> ComboBoxBuilder<'a, D> {
619 self.focus = focus;
620 self
621 }
622
623 pub fn v_align(self, _align: VTextAlign) -> ComboBoxBuilder<'a, D> {
624 self
626 }
627
628 pub fn build(self, out: &mut ComboBox<D>) -> Result<(), NwgError> {
629 let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
630
631 let parent = match self.parent {
632 Some(p) => Ok(p),
633 None => Err(NwgError::no_parent("ComboBox")),
634 }?;
635
636 *out = ComboBox::default();
638
639 out.handle = ControlBase::build_hwnd()
640 .class_name(out.class_name())
641 .forced_flags(out.forced_flags())
642 .flags(flags)
643 .ex_flags(self.ex_flags)
644 .size(self.size)
645 .position(self.position)
646 .parent(Some(parent))
647 .build()?;
648
649 if self.font.is_some() {
650 out.set_font(self.font);
651 } else {
652 out.set_font(Font::global_default().as_ref());
653 }
654
655 if self.collection.is_some() {
656 out.set_collection(self.collection.unwrap());
657 }
658
659 if self.selected_index.is_some() {
660 out.set_selection(self.selected_index);
661 }
662
663 out.set_enabled(self.enabled);
664
665 if self.focus {
666 out.set_focus();
667 }
668
669 Ok(())
670 }
671}
672
673impl<D: Display + Default> PartialEq for ComboBox<D> {
674 fn eq(&self, other: &Self) -> bool {
675 self.handle == other.handle
676 }
677}