use winapi::shared::minwindef::{WPARAM, LPARAM};
use winapi::um::winuser::{ES_AUTOVSCROLL, ES_AUTOHSCROLL, WS_VISIBLE, WS_DISABLED, WS_TABSTOP, WS_VSCROLL, WS_HSCROLL};
use crate::win32::window_helper as wh;
use crate::win32::base_helper::check_hwnd;
use crate::win32::richedit as rich;
use crate::{Font, NwgError};
use super::{ControlBase, ControlHandle};
use std::ops::Range;
use newline_converter::{unix2dos, dos2unix};
const NOT_BOUND: &'static str = "RichTextBox is not yet bound to a winapi object";
const BAD_HANDLE: &'static str = "INTERNAL ERROR: RichTextBox handle is not HWND!";
const ES_SAVESEL: u32 = 32768;
bitflags! {
pub struct RichTextBoxFlags: u32 {
const VSCROLL = WS_VSCROLL;
const HSCROLL = WS_HSCROLL;
const AUTOVSCROLL = ES_AUTOVSCROLL;
const AUTOHSCROLL = ES_AUTOHSCROLL;
const VISIBLE = WS_VISIBLE;
const DISABLED = WS_DISABLED;
const TAB_STOP = WS_TABSTOP;
const SAVE_SELECTION = ES_SAVESEL;
}
}
bitflags! {
pub struct CharEffects: u32 {
const BOLD = 0x0001;
const ITALIC = 0x0002;
const UNDERLINE = 0x0004;
const STRIKEOUT = 0x0008;
const AUTOCOLOR = 0x40000000;
}
}
#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum UnderlineType {
None,
Solid,
Dash,
DashDot,
DashDotDot,
Dotted,
DoubleSolid,
Wave,
}
#[derive(Clone, Debug, Default)]
pub struct CharFormat {
pub effects: Option<CharEffects>,
pub height: Option<i32>,
pub y_offset: Option<i32>,
pub text_color: Option<[u8; 3]>,
pub font_face_name: Option<String>,
pub underline_type: Option<UnderlineType>,
}
#[derive(Copy, Clone, Debug)]
pub enum ParaNumbering {
None,
Bullet,
Arabic,
LcLetter,
LcRoman,
UcLetter,
UcRoman,
Seq(char)
}
#[derive(Copy, Clone, Debug)]
pub enum ParaNumberingStyle {
Paren,
Parens,
Period,
Plain,
NoNumber,
NewNumber
}
#[derive(Copy, Clone, Debug)]
pub enum ParaAlignment {
Left,
Right,
Center,
Justify,
FullInterword
}
#[derive(Copy, Clone, Debug)]
pub enum ParaLineSpacing {
Single,
OneAndHalf,
Double,
SingleOr(i32),
Exact(i32),
Exact20(i32)
}
#[derive(Clone, Debug, Default)]
pub struct ParaFormat {
pub numbering: Option<ParaNumbering>,
pub numbering_style: Option<ParaNumberingStyle>,
pub numbering_tab: Option<u16>,
pub alignment: Option<ParaAlignment>,
pub space_before: Option<i32>,
pub space_after: Option<i32>,
pub start_indent: Option<i32>,
pub right_indent: Option<i32>,
pub offset: Option<i32>,
pub line_spacing: Option<ParaLineSpacing>,
pub rtl: Option<bool>,
}
#[derive(Default, PartialEq, Eq)]
pub struct RichTextBox {
pub handle: ControlHandle
}
impl RichTextBox {
pub fn builder<'a>() -> RichTextBoxBuilder<'a> {
RichTextBoxBuilder {
text: "",
size: (100, 25),
position: (0, 0),
flags: None,
ex_flags: 0,
limit: 0,
readonly: false,
focus: false,
font: None,
parent: None
}
}
pub fn set_background_color(&self, color: [u8; 3]) {
use winapi::um::wingdi::RGB;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let color = RGB(color[0], color[1], color[2]);
wh::send_message(handle, rich::EM_SETBKGNDCOLOR, 0, color as _);
}
pub fn set_char_format(&self, fmt: &CharFormat) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
rich::set_char_format(handle, fmt);
}
pub fn char_format(&self) -> CharFormat {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
rich::char_format(handle)
}
pub fn set_para_format(&self, fmt: &ParaFormat) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
rich::set_para_format(handle, fmt)
}
pub fn para_format(&self) -> ParaFormat {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
rich::para_format(handle)
}
pub fn set_font(&self, font: Option<&Font>) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_font(handle, font.map(|f| f.handle), true); }
}
pub fn limit(&self) -> u32 {
use winapi::um::winuser::EM_GETLIMITTEXT;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_GETLIMITTEXT as u32, 0, 0) as u32
}
pub fn set_limit(&self, limit: usize) {
use winapi::um::winuser::EM_SETLIMITTEXT;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_SETLIMITTEXT as u32, limit, 0);
}
pub fn modified(&self) -> bool {
use winapi::um::winuser::EM_GETMODIFY;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_GETMODIFY as u32, 0, 0) != 0
}
pub fn set_modified(&self, e: bool) {
use winapi::um::winuser::EM_SETMODIFY;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_SETMODIFY as u32, e as usize, 0);
}
pub fn undo(&self) {
use winapi::um::winuser::EM_UNDO;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_UNDO as u32, 0, 0);
}
pub fn selection(&self) -> Range<u32> {
use winapi::um::winuser::EM_GETSEL;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
let (mut out1, mut out2) = (0u32, 0u32);
let (ptr1, ptr2) = (&mut out1 as *mut u32, &mut out2 as *mut u32);
wh::send_message(handle, EM_GETSEL as u32, ptr1 as WPARAM, ptr2 as LPARAM);
Range { start: out1 as u32, end: out2 as u32 }
}
pub fn set_selection(&self, r: Range<u32>) {
use winapi::um::winuser::EM_SETSEL;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
}
pub fn len(&self) -> u32 {
use std::convert::TryInto;
dos2unix(&self.text()).chars().count().try_into().unwrap_or_default()
}
pub fn linecount(&self) -> i32 {
use winapi::um::winuser::EM_GETLINECOUNT;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_GETLINECOUNT as u32, 0, 0) as i32
}
pub fn scroll(&self, v: i32) {
use winapi::um::winuser::EM_LINESCROLL;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_LINESCROLL as u32, 0, v as LPARAM);
}
pub fn scroll_lastline(&self) {
let lines = self.linecount();
self.scroll(lines * -1);
self.scroll(lines - 2);
}
pub fn readonly(&self) -> bool {
use winapi::um::winuser::ES_READONLY;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::get_style(handle) & ES_READONLY == ES_READONLY
}
pub fn set_readonly(&self, r: bool) {
use winapi::um::winuser::EM_SETREADONLY;
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
wh::send_message(handle, EM_SETREADONLY as u32, r as WPARAM, 0);
}
pub fn clear(&self) {
self.set_text("");
}
pub fn focus(&self) -> bool {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_focus(handle) }
}
pub fn set_focus(&self) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_focus(handle); }
}
pub fn enabled(&self) -> bool {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_enabled(handle) }
}
pub fn set_enabled(&self, v: bool) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_enabled(handle, v) }
}
pub fn visible(&self) -> bool {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_visibility(handle) }
}
pub fn set_visible(&self, v: bool) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_visibility(handle, v) }
}
pub fn size(&self) -> (u32, u32) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_size(handle) }
}
pub fn set_size(&self, x: u32, y: u32) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_size(handle, x, y, false) }
}
pub fn position(&self) -> (i32, i32) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_position(handle) }
}
pub fn set_position(&self, x: i32, y: i32) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_position(handle, x, y) }
}
pub fn text(&self) -> String {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::get_window_text(handle) }
}
pub fn set_text<'a>(&self, v: &'a str) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_text(handle, v) }
}
pub fn set_text_unix2dos<'a>(&self, v: &'a str) {
let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
unsafe { wh::set_window_text(handle, &unix2dos(&v).to_string()) }
}
pub fn append<'a>(&self, v: &'a str) {
let text = self.text() + &unix2dos(&v).to_string();
self.set_text(&text);
self.scroll_lastline();
}
pub fn appendln<'a>(&self, v: &'a str) {
let text = self.text() + &unix2dos(&v).to_string() + "\r\n";
self.set_text(&text);
self.scroll_lastline();
}
pub fn class_name(&self) -> &'static str {
"RICHEDIT50W"
}
pub fn flags(&self) -> u32 {
WS_VISIBLE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | WS_TABSTOP | WS_VSCROLL | WS_HSCROLL | ES_SAVESEL
}
pub fn forced_flags(&self) -> u32 {
use winapi::um::winuser::{WS_BORDER, WS_CHILD, ES_MULTILINE, ES_WANTRETURN};
WS_BORDER | WS_CHILD | ES_MULTILINE | ES_WANTRETURN
}
}
impl Drop for RichTextBox {
fn drop(&mut self) {
self.handle.destroy();
}
}
pub struct RichTextBoxBuilder<'a> {
text: &'a str,
size: (i32, i32),
position: (i32, i32),
flags: Option<RichTextBoxFlags>,
ex_flags: u32,
limit: usize,
readonly: bool,
focus: bool,
font: Option<&'a Font>,
parent: Option<ControlHandle>
}
impl<'a> RichTextBoxBuilder<'a> {
pub fn flags(mut self, flags: RichTextBoxFlags) -> RichTextBoxBuilder<'a> {
self.flags = Some(flags);
self
}
pub fn ex_flags(mut self, flags: u32) -> RichTextBoxBuilder<'a> {
self.ex_flags = flags;
self
}
pub fn text(mut self, text: &'a str) -> RichTextBoxBuilder<'a> {
self.text = text;
self
}
pub fn size(mut self, size: (i32, i32)) -> RichTextBoxBuilder<'a> {
self.size = size;
self
}
pub fn position(mut self, pos: (i32, i32)) -> RichTextBoxBuilder<'a> {
self.position = pos;
self
}
pub fn limit(mut self, limit: usize) -> RichTextBoxBuilder<'a> {
self.limit = limit;
self
}
pub fn readonly(mut self, read: bool) -> RichTextBoxBuilder<'a> {
self.readonly = read;
self
}
pub fn font(mut self, font: Option<&'a Font>) -> RichTextBoxBuilder<'a> {
self.font = font;
self
}
pub fn focus(mut self, focus: bool) -> RichTextBoxBuilder<'a> {
self.focus = focus;
self
}
pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> RichTextBoxBuilder<'a> {
self.parent = Some(p.into());
self
}
pub fn build(self, out: &mut RichTextBox) -> Result<(), NwgError> {
let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
let parent = match self.parent {
Some(p) => Ok(p),
None => Err(NwgError::no_parent("RichTextBox"))
}?;
*out = Default::default();
out.handle = ControlBase::build_hwnd()
.class_name(out.class_name())
.forced_flags(out.forced_flags())
.flags(flags)
.ex_flags(self.ex_flags)
.size(self.size)
.position(self.position)
.text(self.text)
.parent(Some(parent))
.build()?;
if self.limit > 0 {
out.set_limit(self.limit);
}
if self.readonly {
out.set_readonly(self.readonly);
}
if self.font.is_some() {
out.set_font(self.font);
} else {
out.set_font(Font::global_default().as_ref());
}
if self.focus {
out.set_focus();
}
Ok(())
}
}