1use super::{ControlBase, ControlHandle};
2use crate::win32::base_helper::check_hwnd;
3use crate::win32::richedit as rich;
4use crate::win32::window_helper as wh;
5use crate::{Font, NwgError};
6use newline_converter::{dos2unix, unix2dos};
7use std::ops::Range;
8use winapi::shared::minwindef::{LPARAM, WPARAM};
9use winapi::um::winuser::{
10 ES_AUTOHSCROLL, ES_AUTOVSCROLL, WS_DISABLED, WS_HSCROLL, WS_TABSTOP, WS_VISIBLE, WS_VSCROLL,
11};
12
13const NOT_BOUND: &'static str = "RichTextBox is not yet bound to a winapi object";
14const BAD_HANDLE: &'static str = "INTERNAL ERROR: RichTextBox handle is not HWND!";
15
16const ES_SAVESEL: u32 = 32768;
17
18bitflags! {
19 pub struct RichTextBoxFlags: u32 {
32 const VSCROLL = WS_VSCROLL;
33 const HSCROLL = WS_HSCROLL;
34 const AUTOVSCROLL = ES_AUTOVSCROLL;
35 const AUTOHSCROLL = ES_AUTOHSCROLL;
36 const VISIBLE = WS_VISIBLE;
37 const DISABLED = WS_DISABLED;
38 const TAB_STOP = WS_TABSTOP;
39 const SAVE_SELECTION = ES_SAVESEL;
40 }
41}
42
43bitflags! {
44 pub struct CharEffects: u32 {
54 const BOLD = 0x0001;
55 const ITALIC = 0x0002;
56 const UNDERLINE = 0x0004;
57 const STRIKEOUT = 0x0008;
58 const AUTOCOLOR = 0x40000000;
59 }
60}
61
62#[repr(u8)]
63#[derive(Copy, Clone, Debug)]
64pub enum UnderlineType {
65 None,
66 Solid,
67 Dash,
68 DashDot,
69 DashDotDot,
70 Dotted,
71 DoubleSolid,
72 Wave,
73}
74
75#[derive(Clone, Debug, Default)]
77pub struct CharFormat {
78 pub effects: Option<CharEffects>,
83
84 pub height: Option<i32>,
86
87 pub y_offset: Option<i32>,
89
90 pub text_color: Option<[u8; 3]>,
92
93 pub font_face_name: Option<String>,
95
96 pub underline_type: Option<UnderlineType>,
98}
99
100#[derive(Copy, Clone, Debug)]
101pub enum ParaNumbering {
103 None,
105
106 Bullet,
108
109 Arabic,
111
112 LcLetter,
114
115 LcRoman,
117
118 UcLetter,
120
121 UcRoman,
123
124 Seq(char),
126}
127
128#[derive(Copy, Clone, Debug)]
129pub enum ParaNumberingStyle {
131 Paren,
133 Parens,
135 Period,
137 Plain,
139 NoNumber,
141 NewNumber,
143}
144
145#[derive(Copy, Clone, Debug)]
146pub enum ParaAlignment {
148 Left,
150 Right,
152 Center,
154 Justify,
156 FullInterword,
158}
159
160#[derive(Copy, Clone, Debug)]
162pub enum ParaLineSpacing {
163 Single,
165
166 OneAndHalf,
168
169 Double,
171
172 SingleOr(i32),
174
175 Exact(i32),
177
178 Exact20(i32),
180}
181
182#[derive(Clone, Debug, Default)]
184pub struct ParaFormat {
185 pub numbering: Option<ParaNumbering>,
187
188 pub numbering_style: Option<ParaNumberingStyle>,
190
191 pub numbering_tab: Option<u16>,
193
194 pub alignment: Option<ParaAlignment>,
196
197 pub space_before: Option<i32>,
199
200 pub space_after: Option<i32>,
202
203 pub start_indent: Option<i32>,
205
206 pub right_indent: Option<i32>,
208
209 pub offset: Option<i32>,
211
212 pub line_spacing: Option<ParaLineSpacing>,
214
215 pub rtl: Option<bool>,
217}
218
219#[derive(Default, PartialEq, Eq)]
260pub struct RichTextBox {
261 pub handle: ControlHandle,
262}
263
264impl RichTextBox {
265 pub fn builder<'a>() -> RichTextBoxBuilder<'a> {
266 RichTextBoxBuilder {
267 text: "",
268 size: (100, 25),
269 position: (0, 0),
270 flags: None,
271 ex_flags: 0,
272 limit: 0,
273 readonly: false,
274 focus: false,
275 font: None,
276 parent: None,
277 }
278 }
279
280 pub fn set_background_color(&self, color: [u8; 3]) {
283 use winapi::um::wingdi::RGB;
284
285 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
286 let color = RGB(color[0], color[1], color[2]);
287 wh::send_message(handle, rich::EM_SETBKGNDCOLOR, 0, color as _);
288 }
289
290 pub fn set_char_format(&self, fmt: &CharFormat) {
292 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
293 rich::set_char_format(handle, fmt);
294 }
295
296 pub fn char_format(&self) -> CharFormat {
298 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
299 rich::char_format(handle)
300 }
301
302 pub fn set_para_format(&self, fmt: &ParaFormat) {
304 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
305 rich::set_para_format(handle, fmt)
306 }
307
308 pub fn para_format(&self) -> ParaFormat {
311 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
312 rich::para_format(handle)
313 }
314
315 pub fn set_font(&self, font: Option<&Font>) {
318 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
319
320 wh::set_window_font(handle, font.map(|f| f.handle), true);
321 }
322
323 pub fn limit(&self) -> u32 {
325 use winapi::um::winuser::EM_GETLIMITTEXT;
326 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
327 wh::send_message(handle, EM_GETLIMITTEXT as u32, 0, 0) as u32
328 }
329
330 pub fn set_limit(&self, limit: usize) {
332 use winapi::um::winuser::EM_SETLIMITTEXT;
333
334 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
335 wh::send_message(handle, EM_SETLIMITTEXT as u32, limit, 0);
336 }
337
338 pub fn modified(&self) -> bool {
340 use winapi::um::winuser::EM_GETMODIFY;
341
342 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
343 wh::send_message(handle, EM_GETMODIFY as u32, 0, 0) != 0
344 }
345
346 pub fn set_modified(&self, e: bool) {
348 use winapi::um::winuser::EM_SETMODIFY;
349 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
350 wh::send_message(handle, EM_SETMODIFY as u32, e as usize, 0);
351 }
352
353 pub fn undo(&self) {
355 use winapi::um::winuser::EM_UNDO;
356
357 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
358 wh::send_message(handle, EM_UNDO as u32, 0, 0);
359 }
360
361 pub fn selection(&self) -> Range<u32> {
363 use winapi::um::winuser::EM_GETSEL;
364
365 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
366
367 let (mut out1, mut out2) = (0u32, 0u32);
368 let (ptr1, ptr2) = (&mut out1 as *mut u32, &mut out2 as *mut u32);
369 wh::send_message(handle, EM_GETSEL as u32, ptr1 as WPARAM, ptr2 as LPARAM);
370
371 Range {
372 start: out1 as u32,
373 end: out2 as u32,
374 }
375 }
376
377 pub fn set_selection(&self, r: Range<u32>) {
379 use winapi::um::winuser::EM_SETSEL;
380
381 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
382 wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
383 }
384
385 pub fn len(&self) -> u32 {
388 use std::convert::TryInto;
389
390 dos2unix(&self.text())
391 .chars()
392 .count()
393 .try_into()
394 .unwrap_or_default()
395 }
396
397 pub fn linecount(&self) -> i32 {
400 use winapi::um::winuser::EM_GETLINECOUNT;
401
402 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
403 wh::send_message(handle, EM_GETLINECOUNT as u32, 0, 0) as i32
404 }
405
406 pub fn scroll(&self, v: i32) {
408 use winapi::um::winuser::EM_LINESCROLL;
409
410 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
411 wh::send_message(handle, EM_LINESCROLL as u32, 0, v as LPARAM);
412 }
413
414 pub fn scroll_lastline(&self) {
416 let lines = self.linecount();
417 self.scroll(lines * -1);
418 self.scroll(lines - 2);
419 }
420
421 pub fn readonly(&self) -> bool {
424 use winapi::um::winuser::ES_READONLY;
425
426 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
427 wh::get_style(handle) & ES_READONLY == ES_READONLY
428 }
429
430 pub fn set_readonly(&self, r: bool) {
433 use winapi::um::winuser::EM_SETREADONLY;
434
435 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
436 wh::send_message(handle, EM_SETREADONLY as u32, r as WPARAM, 0);
437 }
438
439 pub fn clear(&self) {
441 self.set_text("");
442 }
443
444 pub fn focus(&self) -> bool {
446 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
447 wh::get_focus(handle)
448 }
449
450 pub fn set_focus(&self) {
452 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
453
454 wh::set_focus(handle);
455 }
456
457 pub fn enabled(&self) -> bool {
459 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
460 wh::get_window_enabled(handle)
461 }
462
463 pub fn set_enabled(&self, v: bool) {
465 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
466 wh::set_window_enabled(handle, v)
467 }
468
469 pub fn visible(&self) -> bool {
472 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
473 wh::get_window_visibility(handle)
474 }
475
476 pub fn set_visible(&self, v: bool) {
478 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
479 wh::set_window_visibility(handle, v)
480 }
481
482 pub fn size(&self) -> (u32, u32) {
484 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
485 wh::get_window_size(handle)
486 }
487
488 pub fn set_size(&self, x: u32, y: u32) {
490 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
491 wh::set_window_size(handle, x, y, false)
492 }
493
494 pub fn position(&self) -> (i32, i32) {
496 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
497 wh::get_window_position(handle)
498 }
499
500 pub fn set_position(&self, x: i32, y: i32) {
502 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
503 wh::set_window_position(handle, x, y)
504 }
505
506 pub fn text(&self) -> String {
508 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
509 wh::get_window_text(handle)
510 }
511
512 pub fn set_text<'a>(&self, v: &'a str) {
514 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
515 wh::set_window_text(handle, v)
516 }
517
518 pub fn set_text_unix2dos<'a>(&self, v: &'a str) {
520 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
521 wh::set_window_text(handle, &unix2dos(&v).to_string())
522 }
523
524 pub fn append<'a>(&self, v: &'a str) {
526 let text = self.text() + &unix2dos(&v).to_string();
527 self.set_text(&text);
528 self.scroll_lastline();
529 }
530
531 pub fn appendln<'a>(&self, v: &'a str) {
533 let text = self.text() + &unix2dos(&v).to_string() + "\r\n";
534 self.set_text(&text);
535 self.scroll_lastline();
536 }
537
538 pub fn class_name(&self) -> &'static str {
540 "RICHEDIT50W"
541 }
542
543 pub fn flags(&self) -> u32 {
545 WS_VISIBLE
546 | ES_AUTOVSCROLL
547 | ES_AUTOHSCROLL
548 | WS_TABSTOP
549 | WS_VSCROLL
550 | WS_HSCROLL
551 | ES_SAVESEL
552 }
553
554 pub fn forced_flags(&self) -> u32 {
556 use winapi::um::winuser::{ES_MULTILINE, ES_WANTRETURN, WS_BORDER, WS_CHILD};
557
558 WS_BORDER | WS_CHILD | ES_MULTILINE | ES_WANTRETURN
559 }
560}
561
562impl Drop for RichTextBox {
563 fn drop(&mut self) {
564 self.handle.destroy();
565 }
566}
567pub struct RichTextBoxBuilder<'a> {
568 text: &'a str,
569 size: (i32, i32),
570 position: (i32, i32),
571 flags: Option<RichTextBoxFlags>,
572 ex_flags: u32,
573 limit: usize,
574 readonly: bool,
575 focus: bool,
576 font: Option<&'a Font>,
577 parent: Option<ControlHandle>,
578}
579
580impl<'a> RichTextBoxBuilder<'a> {
581 pub fn flags(mut self, flags: RichTextBoxFlags) -> RichTextBoxBuilder<'a> {
582 self.flags = Some(flags);
583 self
584 }
585
586 pub fn ex_flags(mut self, flags: u32) -> RichTextBoxBuilder<'a> {
587 self.ex_flags = flags;
588 self
589 }
590
591 pub fn text(mut self, text: &'a str) -> RichTextBoxBuilder<'a> {
592 self.text = text;
593 self
594 }
595
596 pub fn size(mut self, size: (i32, i32)) -> RichTextBoxBuilder<'a> {
597 self.size = size;
598 self
599 }
600
601 pub fn position(mut self, pos: (i32, i32)) -> RichTextBoxBuilder<'a> {
602 self.position = pos;
603 self
604 }
605
606 pub fn limit(mut self, limit: usize) -> RichTextBoxBuilder<'a> {
607 self.limit = limit;
608 self
609 }
610
611 pub fn readonly(mut self, read: bool) -> RichTextBoxBuilder<'a> {
612 self.readonly = read;
613 self
614 }
615
616 pub fn font(mut self, font: Option<&'a Font>) -> RichTextBoxBuilder<'a> {
617 self.font = font;
618 self
619 }
620
621 pub fn focus(mut self, focus: bool) -> RichTextBoxBuilder<'a> {
622 self.focus = focus;
623 self
624 }
625
626 pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> RichTextBoxBuilder<'a> {
627 self.parent = Some(p.into());
628 self
629 }
630
631 pub fn build(self, out: &mut RichTextBox) -> Result<(), NwgError> {
632 let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
633
634 let parent = match self.parent {
635 Some(p) => Ok(p),
636 None => Err(NwgError::no_parent("RichTextBox")),
637 }?;
638
639 *out = Default::default();
640
641 out.handle = ControlBase::build_hwnd()
642 .class_name(out.class_name())
643 .forced_flags(out.forced_flags())
644 .flags(flags)
645 .ex_flags(self.ex_flags)
646 .size(self.size)
647 .position(self.position)
648 .text(self.text)
649 .parent(Some(parent))
650 .build()?;
651
652 if self.limit > 0 {
653 out.set_limit(self.limit);
654 }
655
656 if self.readonly {
657 out.set_readonly(self.readonly);
658 }
659
660 if self.font.is_some() {
661 out.set_font(self.font);
662 } else {
663 out.set_font(Font::global_default().as_ref());
664 }
665
666 if self.focus {
667 out.set_focus();
668 }
669
670 Ok(())
671 }
672}