diff --git a/dfl.code-workspace b/dfl.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/dfl.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/examples/textbox/.gitignore b/examples/textbox/.gitignore new file mode 100644 index 0000000..60a5f2a --- /dev/null +++ b/examples/textbox/.gitignore @@ -0,0 +1,16 @@ +.dub +docs.json +__dummy.html +docs/ +/hello_dfl +hello_dfl.so +hello_dfl.dylib +hello_dfl.dll +hello_dfl.a +hello_dfl.lib +hello_dfl-test-* +*.exe +*.pdb +*.o +*.obj +*.lst diff --git a/examples/textbox/dub.json b/examples/textbox/dub.json new file mode 100644 index 0000000..e629f60 --- /dev/null +++ b/examples/textbox/dub.json @@ -0,0 +1,16 @@ +{ + "authors": ["haru-s"], + "copyright": "Copyright (C) 2022 haru-s", + "description": "DFL sample code.", + "name": "textbox_sample", + "targetType": "executable", + "targetPath": "bin", + "dependencies": { + "dfl": { + "path": "../../../dfl" + } + }, + "lflags-windows-x86_omf-dmd": ["/exet:nt/su:windows:6.0"], + "lflags-windows-x86_mscoff-dmd": ["/SUBSYSTEM:WINDOWS", "/ENTRY:mainCRTStartup"], + "lflags-windows-x86_64-dmd": ["/SUBSYSTEM:WINDOWS", "/ENTRY:mainCRTStartup"] +} \ No newline at end of file diff --git a/examples/textbox/dub.selections.json b/examples/textbox/dub.selections.json new file mode 100644 index 0000000..203d1ff --- /dev/null +++ b/examples/textbox/dub.selections.json @@ -0,0 +1,7 @@ +{ + "fileVersion": 1, + "versions": { + "dfl": {"path":"../.."}, + "undead": "1.1.8" + } +} diff --git a/examples/textbox/shell.bat b/examples/textbox/shell.bat new file mode 100644 index 0000000..49ab56b --- /dev/null +++ b/examples/textbox/shell.bat @@ -0,0 +1,3 @@ +set dmd_path=c:\d\dmd2\windows +set dmc_path=c:\dmc\dm +cmd diff --git a/examples/textbox/source/textbox_sample.d b/examples/textbox/source/textbox_sample.d new file mode 100644 index 0000000..4f84a03 --- /dev/null +++ b/examples/textbox/source/textbox_sample.d @@ -0,0 +1,127 @@ +import dfl; + +version(Have_dfl) // For DUB. +{ +} +else +{ + pragma(lib, "dfl.lib"); +} + +class MainForm : Form +{ + private TextBox _textbox1; + private TextBox _textbox2; + private TextBox _textbox3; + private TextBox _textbox4; + private TextBox _textbox5; + private Button _button1; + private Button _button2; + + this() + { + // Form setting + text = "TextBox sample"; + size = Size(500,400); + resizeRedraw = true; + + // Button setting (Default button) + _button1 = new Button(); + _button1.location = Point(10,10); + _button1.text = "OK"; + _button1.click ~= (Control c, EventArgs e) + { + this.text = this.text ~ "+"; + }; + _button1.parent = this; + + // Button setting (Default button's enable/disable is switched). + _button2 = new Button(); + _button2.location = Point(100,10); + _button2.text = "On/Off"; + _button2.click ~= (Control c, EventArgs e) + { + if(acceptButton) + { + acceptButton = null; // Default button is now none. + _button1.notifyDefault(false); + _textbox1.text = "Now Off"; + } + else + { + acceptButton = _button1; // Set default button. + _button1.notifyDefault(true); + _textbox1.text = "Now On"; + } + }; + _button2.parent = this; + + // TextBox setting (Single line) + _textbox1 = new TextBox(); + _textbox1.location = Point(200,10); + _textbox1.size = Size(100,30); + _textbox1.multiline = false; // false: One line textbox. + _textbox1.scrollBars = ScrollBars.NONE; // NONE: Without scroll bar. + _textbox1.acceptsReturn = false; // false: Disables RETURN key. + _textbox1.acceptsTab = false; // false: Disables TAB key. + // true: Enables TAB key. But be changed focus only + // because one line textbox is not able to input TAB char. + _textbox1.wordWrap = false; // false: Do not send words that span the right edge to the next line. + _button1.notifyDefault(false); // false: Do not set as default button + _textbox1.text = "Default Off"; + _textbox1.parent = this; + + // TextBox setting (return:yes, tab:yes) + _textbox2 = new TextBox(); + _textbox2.location = Point(10,60); + _textbox2.size = Size(450,50); + _textbox2.multiline = true; // true: Multi line textbox. + _textbox2.scrollBars = ScrollBars.VERTICAL; // VERTICAL: within vertical scroll bar. + _textbox2.acceptsReturn = true; // true: Enables RETURN key. + _textbox2.acceptsTab = true; // true: Enables TAB key. + _textbox2.wordWrap = true; // true: Send words that span the right edge to the next line. + _textbox2.text = "return:yes, tab:yes"; + _textbox2.parent = this; + + // TextBox setting (return:yes, tab:no) + _textbox3 = new TextBox(); + _textbox3.location = Point(10,130); + _textbox3.size = Size(450,50); + _textbox3.multiline = true; // false: One line textbox. + _textbox3.scrollBars = ScrollBars.VERTICAL; // VERTICAL: within vertical scroll bar. + _textbox3.acceptsReturn = true; // true: Enables RETURN key. + _textbox3.acceptsTab = false; // false: Disables TAB key. + _textbox3.wordWrap = true; // true: Send words that span the right edge to the next line. + _textbox3.text = "return:yes, tab:no"; + _textbox3.parent = this; + + // TextBox setting (return:no, tab:yes) + _textbox4 = new TextBox(); + _textbox4.location = Point(10,190); + _textbox4.size = Size(450,50); + _textbox4.multiline = true; // true: Multi line textbox. + _textbox4.scrollBars = ScrollBars.VERTICAL; // VERTICAL: within vertical scroll bar. + _textbox4.acceptsReturn = false; // false: Disables RETURN key. + _textbox4.acceptsTab = true; // true: Enables TAB key. + _textbox4.wordWrap = true; // true: Send words that span the right edge to the next line. + _textbox4.text = "return:no, tab:yes"; + _textbox4.parent = this; + + // TextBox setting (return:no, tab:no) + _textbox5 = new TextBox(); + _textbox5.location = Point(10,250); + _textbox5.size = Size(450,50); + _textbox5.multiline = true; // true: Multi line textbox. + _textbox5.scrollBars = ScrollBars.VERTICAL; // VERTICAL: within vertical scroll bar. + _textbox5.acceptsReturn = false; // false: Disables RETURN key. + _textbox5.acceptsTab = false; // false: Disables TAB key. + _textbox5.wordWrap = true; // true: Send words that span the right edge to the next line. + _textbox5.text = "return:no, tab:no"; + _textbox5.parent = this; + } +} + +void main() +{ + Application.run(new MainForm()); +} diff --git a/source/dfl/application.d b/source/dfl/application.d index 4955c33..1937acf 100644 --- a/source/dfl/application.d +++ b/source/dfl/application.d @@ -2540,7 +2540,7 @@ extern(Windows) } -WNDPROC _superClass(HINSTANCE hinst, Dstring className, Dstring newClassName, out WNDCLASSA getInfo) // deprecated +deprecated WNDPROC _superClass(HINSTANCE hinst, Dstring className, Dstring newClassName, out WNDCLASSA getInfo) { WNDPROC wndProc; diff --git a/source/dfl/control.d b/source/dfl/control.d index 7088687..8b93feb 100644 --- a/source/dfl/control.d +++ b/source/dfl/control.d @@ -5,13 +5,16 @@ /// module dfl.control; -private import dfl.internal.dlib, dfl.internal.clib; - private import dfl.base, dfl.form, dfl.drawing; -private import dfl.internal.winapi, dfl.application, dfl.event, dfl.label; -private import dfl.internal.wincom, dfl.internal.utf, dfl.collections, dfl.internal.com; +private import dfl.application, dfl.event, dfl.label; +private import dfl.collections; private import core.memory; +private import dfl.internal.dlib, dfl.internal.clib; +private import dfl.internal.winapi, dfl.internal.wincom; +private import dfl.internal.utf; +private import dfl.internal.com; + version(NO_DRAG_DROP) version = DFL_NO_DRAG_DROP; @@ -178,9 +181,6 @@ enum ControlStyles: uint //DOUBLE_BUFFER = 0x10000, // TODO: implement. //OPTIMIZED_DOUBLE_BUFFER = 0x20000, // TODO: implement. USE_TEXT_FOR_ACCESSIBILITY = 0x40000, /// ditto - - WANT_TAB_KEY = 0x01000000, - WANT_ALL_KEYS = 0x02000000, } @@ -3704,11 +3704,92 @@ class Control: DObject, IWindow // docmain } - /// - // Return true if processed. + /// Process shortcut key (ctrl+A etc). + // Returns false when parent is none, otherwise call parent form's one. + protected bool processCmdKey(ref Message msg, Keys keyData) + { + if (parent) + return parent.processCmdKey(msg, keyData); + else + return false; + } + + /// Process dialog key (TAB, RETURN, ESC, UP, DOWN, LEFT, RIGHT and so on). + // Returns false when parent is none, otherwise call parent form's one. + protected bool processDialogKey(Keys keyData) + { + if (parent) + return parent.processDialogKey(keyData); + else + return false; + } + + /// Process mnemonic (access key) such as Alt+T. + // Returns false when parent is none, otherwise call parent form's one. + protected bool processDialogChar(char charCode) + { + if (parent) + return parent.processDialogChar(charCode); + else + return false; + } + + /// Pre-process keybord message + // This function called from Application.DflWndProc(). + // If return true, processed message, + // then wndProc() be not called after preProcessMessage(). + // If return false, wndProc() be called after preProcessMessage(). bool preProcessMessage(ref Message msg) { - return false; + bool result/+ = false+/; + + if (msg.msg == WM_KEYDOWN || msg.msg == WM_SYSKEYDOWN) + { + // TODO: Implement + // if (!getExtendedState(ExtendedStates.UiCues)) + // { + // processUICues(msg); + // } + + Keys keyData = cast(Keys)msg.wParam | modifierKeys; + + // processCmdKey returns true when keyData is a shortcut key (ctrl+C etc). + if (processCmdKey(msg, keyData)) + { + result = true; + } + // isInputKey returns true when keyData is a regular input key. + else if (isInputKey(keyData)) + { + // TODO: Implement + // SetExtendedState(ExtendedStates.InputKey, true); + result = false; // End preprocessing and call wndProc(). + } + else + { + // Process dialog keys such as TAB, RETURN, ESC, UP, DOWN, RIGHT, LEFT. + result = processDialogKey(keyData); + } + } + else if (msg.msg == WM_CHAR || msg.msg == WM_SYSCHAR) + { + if (msg.msg == WM_CHAR && isInputChar(cast(char)msg.wParam)) + { + // TODO: Implement + // setExtendedState(ExtendedStates.InputChar, true); + result = false; + } + else + { + result = processDialogChar(cast(char)msg.wParam); + } + } + else + { + result = false; + } + + return result; } @@ -3909,7 +3990,7 @@ class Control: DObject, IWindow // docmain } - package static void _dlgselnext(Form dlg, HWND hwcursel, bool forward, + package static bool _dlgselnext(Form dlg, HWND hwcursel, bool forward, bool tabStopOnly = true, bool selectableOnly = false, bool nested = true, bool wrap = true, HWND hwchildrenof = null) @@ -3960,6 +4041,7 @@ class Control: DObject, IWindow // docmain { //DefDlgProcA(dlg.handle, WM_NEXTDLGCTL, cast(WPARAM)hwfirst, MAKELPARAM(true, 0)); dlg._selectChild(hwfirst); + return true; } } } @@ -3990,22 +4072,26 @@ class Control: DObject, IWindow // docmain }, nested); // If it falls through without finding hwcursel, let it select the last one, even if not wrapping. if(HWND.init != hwprev) + { //DefDlgProcA(dlg.handle, WM_NEXTDLGCTL, cast(WPARAM)hwprev, MAKELPARAM(true, 0)); dlg._selectChild(hwprev); + return true; + } } + return false; } - package final void _selectNextControl(Form ctrltoplevel, + package final bool _selectNextControl(Form ctrltoplevel, Control ctrl, bool forward, bool tabStopOnly, bool nested, bool wrap) { if(!created) - return; + return false; assert(ctrltoplevel !is null); assert(ctrltoplevel.isHandleCreated); - _dlgselnext(ctrltoplevel, + return _dlgselnext(ctrltoplevel, (ctrl && ctrl.isHandleCreated) ? ctrl.handle : null, forward, tabStopOnly, !tabStopOnly, nested, wrap, this.handle); @@ -4019,14 +4105,16 @@ class Control: DObject, IWindow // docmain // Only considers child controls of this control. - final void selectNextControl(Control ctrl, bool forward, bool tabStopOnly, bool nested, bool wrap) + final bool selectNextControl(Control ctrl, bool forward, bool tabStopOnly, bool nested, bool wrap) { if(!created) - return; + return false; auto ctrltoplevel = findForm(); if(ctrltoplevel) return _selectNextControl(ctrltoplevel, ctrl, forward, tabStopOnly, nested, wrap); + + return false; } @@ -4594,7 +4682,26 @@ class Control: DObject, IWindow // docmain DWORD ldlgcode = 0; } - + + /// + protected bool processKeyPreview(ref Message m) + { + return parent && parent.processKeyPreview(m); + } + + + /// + // Returns true in order to break in wndProc(), because processed. + protected final bool processKeyMessage(ref Message m) + { + if ( parent && parent.processKeyPreview(m)) + { + return true; + } + return processKeyEventArgs(m); + } + + /// protected void wndProc(ref Message msg) { @@ -4845,7 +4952,7 @@ class Control: DObject, IWindow // docmain case WM_KEYUP: case WM_CHAR: case WM_SYSKEYDOWN: - case WM_SYSKEYUP: + case WM_SYSKEYUP: // TODO: Is it correct? case WM_SYSCHAR: //case WM_IMECHAR: /+ @@ -4858,7 +4965,14 @@ class Control: DObject, IWindow // docmain msg.result = 1; // The key was not processed. break; +/ - msg.result = !processKeyEventArgs(msg); + if (processKeyMessage(msg)) + { + //msg.result = 0; + return; + } + defWndProc(msg); + + //msg.result = !processKeyEventArgs(msg); return; case WM_MOUSEWHEEL: // Requires Windows 98 or NT4. @@ -5450,9 +5564,6 @@ class Control: DObject, IWindow // docmain defWndProc(msg); - if(ctrlStyle & ControlStyles.WANT_ALL_KEYS) - msg.result |= DLGC_WANTALLKEYS; - // Only want chars if ALT isn't down, because it would break mnemonics. if(!(GetKeyState(VK_MENU) & 0x8000)) msg.result |= DLGC_WANTCHARS; @@ -6802,7 +6913,6 @@ class Control: DObject, IWindow // docmain cbits &= ~CBits.IN_LAYOUT; } - /+ // Not sure what to do here. deprecated bool isInputChar(char charCode) @@ -6810,8 +6920,58 @@ class Control: DObject, IWindow // docmain return false; } +/ - - + protected bool isInputChar(char charCode) + { + int mask = 0; + if (charCode == Keys.TAB) + { + mask = (DLGC_WANTCHARS | DLGC_WANTALLKEYS | DLGC_WANTTAB); + } + else + { + mask = (DLGC_WANTCHARS | DLGC_WANTALLKEYS); + } + + return (SendMessage(hwnd, WM_GETDLGCODE, 0, 0) & mask) != 0; + } + + + /// isInputKey returns true when keyData is a regular input key. + // If keyData is input key, then window message is sended to wndProc() + // such as WM_KEYDOWN, WM_KEYUP, WM_CHAR and so on. + protected bool isInputKey(Keys keyData) + { + if ((keyData & Keys.ALT) == Keys.ALT) + { + return false; + } + + auto mask = DLGC_WANTALLKEYS; + switch (keyData & Keys.KEY_CODE) + { + case Keys.TAB: + mask = DLGC_WANTALLKEYS | DLGC_WANTTAB; + break; + case Keys.LEFT: + case Keys.RIGHT: + case Keys.UP: + case Keys.DOWN: + mask = DLGC_WANTALLKEYS | DLGC_WANTARROWS; + break; + default: + // Nothing + } + + // return isHandleCreated + // && (SendMessage(hwnd, WM_GETDLGCODE, 0, 0) & mask) != 0; + if (isHandleCreated) + { + LRESULT r = SendMessage(hwnd, WM_GETDLGCODE, 0, 0); + return (r & mask) != 0; + } + return false; + } + /// void setVisibleCore(bool byes) { @@ -6847,9 +7007,11 @@ class Control: DObject, IWindow // docmain package final bool _wantTabKey() { - if(ctrlStyle & ControlStyles.WANT_TAB_KEY) + // if(ctrlStyle & ControlStyles.WANT_TAB_KEY) + if((DLGC_WANTTAB & sendMessage(hwnd, WM_GETDLGCODE, 0, 0)) != 0) return true; - return false; + else + return false; } @@ -6857,12 +7019,22 @@ class Control: DObject, IWindow // docmain // Return true if processed. protected bool processKeyEventArgs(ref Message msg) { + // TODO: implement more (IME etc...) + switch(msg.msg) { + case WM_CHAR: + case WM_SYSCHAR: + { + scope KeyPressEventArgs kpea = new KeyPressEventArgs(cast(dchar)msg.wParam, modifierKeys); + onKeyPress(kpea); + return kpea.handled; + } + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { scope KeyEventArgs kea = new KeyEventArgs(cast(Keys)(msg.wParam | modifierKeys)); - ushort repeat = msg.lParam & 0xFFFF; // First 16 bits. for(; repeat; repeat--) { @@ -6870,35 +7042,21 @@ class Control: DObject, IWindow // docmain onKeyDown(kea); } - if(kea.handled) - return true; + return kea.handled; } - break; case WM_KEYUP: + case WM_SYSKEYUP: // TODO: Is it correct? { // Repeat count is always 1 for key up. scope KeyEventArgs kea = new KeyEventArgs(cast(Keys)(msg.wParam | modifierKeys)); onKeyUp(kea); - if(kea.handled) - return true; + return kea.handled; } - break; - - case WM_CHAR: - { - scope KeyPressEventArgs kpea = new KeyPressEventArgs(cast(dchar)msg.wParam, modifierKeys); - onKeyPress(kpea); - if(kpea.handled) - return true; - } - break; default: + assert(0); // Does not reached here, if WM is traped in Control.wndProc() correctly. } - - defWndProc(msg); - return !msg.result; } @@ -6907,25 +7065,7 @@ class Control: DObject, IWindow // docmain return processKeyEventArgs(msg); } - - /+ - bool processKeyPreview(ref Message m) - { - if(wparent) - return wparent.processKeyPreview(m); - return false; - } - - - protected bool processDialogChar(dchar charCode) - { - if(wparent) - return wparent.processDialogChar(charCode); - return false; - } - +/ - - + /// protected bool processMnemonic(dchar charCode) { @@ -8197,182 +8337,24 @@ class ContainerControl: ScrollableControl, IContainerControl // docmain } - protected: - /+ - override bool processDialogChar(char charCode) - { - // Not sure if this is correct. - return false; - } - +/ - - - /+ - deprecated protected override bool processMnemonic(dchar charCode) - { - return false; - } - - - bool processTabKey(bool forward) + protected bool processTabKey(bool forward) { if(isHandleCreated) { - //SendMessageA(hwnd, WM_NEXTDLGCTL, !forward, 0); - //return true; - select(true, forward); + return selectNextControl(activeControl, forward, tabStop, true, false); } return false; } - +/ } -import std.traits, std.typecons; private template hasLocalAliasing(T...) { + import std.traits, std.typecons; + static if( !T.length ) enum hasLocalAliasing = false; else enum hasLocalAliasing = std.traits.hasLocalAliasing!(T[0]) || dfl.control.hasLocalAliasing!(T[1 .. $]); } - -/// -shared class SharedControl -{ -private: - Control _ctrl; - - LPARAM makeParam(ARGS...)(void function(Control, ARGS) fn, Tuple!(ARGS)* args) - if (ARGS.length) - { - static assert((DflInvokeParam*).sizeof <= LPARAM.sizeof); - static struct InvokeParam - { - void function(Control, ARGS) fn; - ARGS args; - } - alias dfl.internal.clib.malloc malloc; - alias dfl.internal.clib.free free; - - auto param = cast(InvokeParam*)malloc(InvokeParam.sizeof); - param.fn = fn; - param.args = args.field; - - if (!param) - throw new OomException(); - - auto p = cast(DflInvokeParam*)malloc(DflInvokeParam.sizeof); - - if (!p) - throw new OomException(); - - - static void fnentry(Control c, size_t[] p) - { - auto param = cast(InvokeParam*)p[0]; - param.fn(c, param.args); - free(param); - } - - p.fp = &fnentry; - p.nparams = 1; - p.params[0] = cast(size_t)param; - - return cast(LPARAM)p; - } - - - LPARAM makeParamNoneArgs(void function(Control) fn) - { - static assert((DflInvokeParam*).sizeof <= LPARAM.sizeof); - alias dfl.internal.clib.malloc malloc; - alias dfl.internal.clib.free free; - - auto p = cast(DflInvokeParam*)malloc(DflInvokeParam.sizeof); - - if (!p) - throw new OomException(); - - static void fnentry(Control c, size_t[] p) - { - auto fn = cast(void function(Control))p[0]; - fn(c); - } - - p.fp = &fnentry; - p.nparams = 1; - p.params[0] = cast(size_t)fn; - - return cast(LPARAM)p; - } - - - -public: - /// - this(Control ctrl) - { - assert(ctrl); - _ctrl = cast(shared)ctrl; - } - - /// - void invoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) - if (ARGS.length && !hasLocalAliasing!(ARGS)) - { - auto ctrl = cast(Control)_ctrl; - auto hwnd = ctrl.handle; - - if(!hwnd) - Control.badInvokeHandle(); - - auto t = tuple(args); - auto p = makeParam(fn, &t); - SendMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); - } - - /// - void invoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) - if (!ARGS.length) - { - auto ctrl = cast(Control)_ctrl; - auto hwnd = ctrl.handle; - - if(!hwnd) - Control.badInvokeHandle(); - - auto p = makeParamNoneArgs(fn); - SendMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); - } - - /// - void delayInvoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) - if (ARGS.length && !hasLocalAliasing!(ARGS)) - { - auto ctrl = cast(Control)_ctrl; - auto hwnd = ctrl.handle; - - if(!hwnd) - Control.badInvokeHandle(); - - auto t = tuple(args); - auto p = makeParam(fn, &t); - PostMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); - } - - /// - void delayInvoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) - if (!ARGS.length) - { - auto ctrl = cast(Control)_ctrl; - auto hwnd = ctrl.handle; - - if(!hwnd) - Control.badInvokeHandle(); - - auto p = makeParamNoneArgs(fn); - PostMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); - } -} diff --git a/source/dfl/form.d b/source/dfl/form.d index ebacc7b..6f7435a 100644 --- a/source/dfl/form.d +++ b/source/dfl/form.d @@ -6,10 +6,13 @@ module dfl.form; private import dfl.internal.dlib; +private import dfl.internal.winapi; +private import dfl.internal.utf; -private import dfl.control, dfl.internal.winapi, dfl.event, dfl.drawing; -private import dfl.application, dfl.base, dfl.internal.utf; +private import dfl.control, dfl.event, dfl.drawing; +private import dfl.application, dfl.base; private import dfl.collections; + debug(APP_PRINT) { private import dfl.internal.clib; @@ -129,10 +132,10 @@ class Form: ContainerControl, IDialogResult // docmain /// final @property void acceptButton(IButtonControl btn) // setter { - if(acceptBtn) - acceptBtn.notifyDefault(false); + if(_acceptButton) + _acceptButton.notifyDefault(false); - acceptBtn = btn; + _acceptButton = btn; if(btn) btn.notifyDefault(true); @@ -141,14 +144,14 @@ class Form: ContainerControl, IDialogResult // docmain /// ditto final @property IButtonControl acceptButton() // getter { - return acceptBtn; + return _acceptButton; } /// final @property void cancelButton(IButtonControl btn) // setter { - cancelBtn = btn; + _cancelButton = btn; if(btn) { @@ -162,7 +165,7 @@ class Form: ContainerControl, IDialogResult // docmain /// ditto final @property IButtonControl cancelButton() // getter { - return cancelBtn; + return _cancelButton; } @@ -462,7 +465,8 @@ class Form: ContainerControl, IDialogResult // docmain debug { static import std.string; - er = std.string.format("CreateWindowEx failed {className=%s;exStyle=0x%X;style=0x%X;parent=0x%X;menu=0x%X;inst=0x%X;}", + er = std.string.format( + "CreateWindowEx failed {className=%s;exStyle=0x%X;style=0x%X;parent=0x%X;menu=0x%X;inst=0x%X;}", className, exStyle, style, cast(void*)parent, cast(void*)menu, cast(void*)inst); } goto create_err; @@ -918,10 +922,29 @@ class Form: ContainerControl, IDialogResult // docmain return wicon; } - - // TODO: implement. - // keyPreview - + /// keyPreview + final @property bool keyPreview() // getter + { + return _keyPreview; + } + + /// ditto + final @property void keyPreview(bool v) // setter + { + _keyPreview = v; + } + + /// + protected override bool processKeyPreview(ref Message m) + { + if(_keyPreview && processKeyEventArgs(m)) + { + // Returns true for call onKeyPress(), onKeyDown() or onKeyUp() on this form. + return true; + } + + return super.processKeyPreview(m); + } /// final @property bool isMdiChild() // getter @@ -2273,20 +2296,20 @@ class Form: ContainerControl, IDialogResult // docmain switch(LOWORD(msg.wParam)) { case IDOK: - if(acceptBtn) + if(_acceptButton) { if(HIWORD(msg.wParam) == BN_CLICKED) - acceptBtn.performClick(); + _acceptButton.performClick(); return; } break; //return; case IDCANCEL: - if(cancelBtn) + if(_cancelButton) { if(HIWORD(msg.wParam) == BN_CLICKED) - cancelBtn.performClick(); + _cancelButton.performClick(); return; } break; @@ -2742,8 +2765,139 @@ class Form: ContainerControl, IDialogResult // docmain } } + /// Process shortcut key (ctrl+A etc). + // Returns true when processed, false when not. + protected override bool processCmdKey(ref Message msg, Keys keyData) + { + if (super.processCmdKey(msg, keyData)) + { + return true; + } + + // Process MDI accelerator keys. + bool retValue = false; + MSG win32Message = msg._winMsg; + if ((_mdiClient !is null) && (_mdiClient.handle !is/+ != +/ null) && + TranslateMDISysAccel(_mdiClient.hwnd, &win32Message)) + { + retValue = true; + } + + msg.msg = win32Message.message; + msg.wParam = win32Message.wParam; + msg.lParam = win32Message.lParam; + msg.hWnd = win32Message.hwnd; + + return retValue; + } - package alias dfl.internal.utf.defDlgProc _defFormProc; + /// Process dialog key (TAB, RETURN, ESC, UP, DOWN, LEFT, RIGHT and so on). + // Returns true when processed, false when called super class. + protected override bool processDialogKey(Keys keyData) + { + if ((keyData & (Keys.ALT | Keys.CONTROL)) == Keys.NONE) + { + Keys keyCode = keyData & Keys.KEY_CODE; + IButtonControl button; + + switch (keyCode) + { + case Keys.RETURN: + // button = cast(IButtonControl)GetObject(propDefaultButton); + button = _acceptButton; + if (button) + { + //PerformClick now checks for validationcancelled... + //if (button is Control) + { + button.performClick(); + } + + return true; + } + + break; + case Keys.ESCAPE: + // button = cast(IButtonControl)GetObject(propCancelButton); + button = _cancelButton; + if (button) + { + // In order to keep the behavior in sync with native + // and MFC dialogs, we want to not give the cancel button + // the focus on Escape. If we do, we end up with giving it + // the focus when we reshow the dialog. + + //if (button is Control) { + // ((Control)button).Focus(); + //} + button.performClick(); + return true; + } + + break; + default: + // Fall through + } + } + + return super.processDialogKey(keyData); + } + + /// Process mnemonic (access key) such as Alt+T. + // Returns true when processed, otherwise call super class. + protected override bool processDialogChar(char charCode) + { + // If we're the top-level form or control, we need to do the mnemonic handling + // + if (isMdiChild && charCode != ' ') + { + if (processMnemonic(charCode)) + { + return true; + } + + // ContainerControl calls ProcessMnemonic starting from the active MdiChild form (this) + // so let's flag it as processed. + _mnemonicProcessed = true; + try + { + return super.processDialogChar(charCode); + } + finally + { + _mnemonicProcessed = false; + } + } + + // Non-MdiChild form, just pass the call to ContainerControl. + return super.processDialogChar(charCode); + } + + /// Changes forcus by TAB + // Returns true when changed forcus, otherwise returns false. + protected override bool processTabKey(bool forward) + { + if (selectNextControl(activeControl, forward, true, true, true)) + { + return true; + } + + // I've added a special case for UserControls because they shouldn't cycle back to the + // beginning if they don't have a parent form, such as when they're on an ActiveXBridge. + if (isMdiChild || !parentForm) + { + bool selected = selectNextControl(null, forward, true, true, false); + + if (selected) + { + return true; + } + } + + return false; + } + + package alias _defFormProc = dfl.internal.utf.defDlgProc; protected override void defWndProc(ref Message msg) { @@ -2990,7 +3144,7 @@ class Form: ContainerControl, IDialogResult // docmain private: - IButtonControl acceptBtn, cancelBtn; + IButtonControl _acceptButton, _cancelButton; bool autoscale = true; Size autoscaleBase; DialogResult fresult = DialogResult.NONE; @@ -3018,7 +3172,10 @@ class Form: ContainerControl, IDialogResult // docmain Form wowner = null, wmdiparent = null; //bool _closingvisible; bool nofilter = false; - + bool _keyPreview = false; // Gets or sets a value indicating whether the form will receive key events + // before the event is passed to the control that has focus. + bool _mnemonicProcessed = false; + version(NO_MDI) {} else { MdiClient _mdiClient = null; // null == not MDI container. @@ -3027,13 +3184,15 @@ class Form: ContainerControl, IDialogResult // docmain package static bool wantsAllKeys(HWND hwnd) { - return (SendMessageA(hwnd, WM_GETDLGCODE, 0, 0) & - DLGC_WANTALLKEYS) != 0; + return (SendMessageA(hwnd, WM_GETDLGCODE, 0, 0) & DLGC_WANTALLKEYS) != 0; } private static class FormMessageFilter: IMessageFilter { + /// Filters out a message before it is dispatched. + // Returns: true when filter the message and stop it from being dispatched; + // false when allow the message and continue to the next filter or control. protected bool preFilterMessage(ref Message m) { version(NO_MDI) @@ -3086,13 +3245,11 @@ class Form: ContainerControl, IDialogResult // docmain case WM_CHAR: switch(cast(Keys)m.wParam) { - case Keys.ENTER: - if(form.acceptButton) - { - dfl.internal.utf.isDialogMessage(form.handle, &m._winMsg); - return true; // Prevent. - } - return false; + // case Keys.ENTER: + // This case has been moved to wndProc(). + // If parent form does not have a default button, + // input RETURN to TextBox regardless of + // the value of TextBox.acceptsReturn. case Keys.ESCAPE: if(form.cancelButton) @@ -3145,22 +3302,28 @@ class Form: ContainerControl, IDialogResult // docmain case Keys.TAB: { - LRESULT dlgc; - Control cc; - dlgc = SendMessageA(m.hWnd, WM_GETDLGCODE, 0, 0); - cc = fromHandle(m.hWnd); + // When window message is WM_KEYDOWN, WM_KEYUP and WM_CHAR, + // wParam is not contain modifier keys such as CONTROL, SHIFT. + // So uses GetKeyState(). + + LRESULT dlgc = SendMessageA(m.hWnd, WM_GETDLGCODE, 0, 0); + Control cc = fromHandle(m.hWnd); + bool isPressingControl = (GetKeyState(VK_CONTROL) & 0x8000) != 0; if(cc) { - if(cc._wantTabKey()) + // When CONTROL is pressing, do not input TAB but change forcus. + if(cc._wantTabKey() && !isPressingControl) return false; // Continue. } else { - if(dlgc & DLGC_WANTALLKEYS) + // When CONTROL is pressing, do not input TAB but change forcus. + if((dlgc & DLGC_WANTALLKEYS) && !isPressingControl) return false; // Continue. } - //if(dlgc & (DLGC_WANTTAB | DLGC_WANTALLKEYS)) - if(dlgc & DLGC_WANTTAB) + + // When CONTROL is pressing, do not input TAB but change forcus. + if((dlgc & DLGC_WANTTAB) && !isPressingControl) return false; // Continue. if(WM_KEYDOWN == m.msg) { @@ -3302,9 +3465,9 @@ class Form: ContainerControl, IDialogResult // docmain /+ package final bool _dlgescape() { - if(cancelBtn) + if(_cancelButton) { - cancelBtn.performClick(); + _cancelButton.performClick(); return true; } return false; diff --git a/source/dfl/package.d b/source/dfl/package.d index d7d3ec6..3f46054 100644 --- a/source/dfl/package.d +++ b/source/dfl/package.d @@ -8,10 +8,10 @@ module dfl; public import dfl.base, dfl.menu, dfl.control, dfl.usercontrol, dfl.form, dfl.drawing, dfl.panel, dfl.event, dfl.application, dfl.button, dfl.socket, - dfl.timer, dfl.environment, dfl.label, dfl.textbox, + dfl.timer, dfl.environment, dfl.label, dfl.textboxbase, dfl.textbox, dfl.listbox, dfl.splitter, dfl.groupbox, dfl.messagebox, dfl.registry, dfl.notifyicon, dfl.collections, dfl.data, dfl.clipboard, dfl.commondialog, dfl.richtextbox, dfl.tooltip, dfl.combobox, dfl.treeview, dfl.picturebox, dfl.tabcontrol, dfl.listview, dfl.statusbar, dfl.progressbar, dfl.resources, - dfl.imagelist, dfl.toolbar, dfl.trackbar; + dfl.imagelist, dfl.toolbar, dfl.trackbar, dfl.sharedcontrol; diff --git a/source/dfl/richtextbox.d b/source/dfl/richtextbox.d index fb0d773..f3b1788 100644 --- a/source/dfl/richtextbox.d +++ b/source/dfl/richtextbox.d @@ -5,9 +5,14 @@ /// module dfl.richtextbox; -private import dfl.textbox, dfl.internal.winapi, dfl.event, dfl.application; -private import dfl.base, dfl.drawing, dfl.data; -private import dfl.control, dfl.internal.utf, dfl.internal.dlib; +private import dfl.textboxbase; +private import dfl.base, dfl.application; +private import dfl.event, dfl.drawing, dfl.data; +private import dfl.control; + +private import dfl.internal.winapi; +private import dfl.internal.utf; +private import dfl.internal.dlib; version(DFL_NO_MENUS) { @@ -118,7 +123,7 @@ class RichTextBox: TextBoxBase // docmain return wcurs; // Do return null and don't inherit. } - alias TextBoxBase.cursor cursor; // Overload. + alias cursor = TextBoxBase.cursor; // Overload. override @property Dstring selectedText() // getter @@ -138,7 +143,7 @@ class RichTextBox: TextBoxBase // docmain return null; } - alias TextBoxBase.selectedText selectedText; // Overload. + alias selectedText = TextBoxBase.selectedText; // Overload. override @property void selectionLength(uint len) // setter @@ -200,13 +205,13 @@ class RichTextBox: TextBoxBase // docmain override @property void maxLength(uint len) // setter { - lim = len; + _lim = len; if(created) SendMessageA(handle, EM_EXLIMITTEXT, 0, cast(LPARAM)len); } - alias TextBoxBase.maxLength maxLength; // Overload. + alias maxLength = TextBoxBase.maxLength; // Overload. override @property Size defaultSize() // getter @@ -233,7 +238,7 @@ class RichTextBox: TextBoxBase // docmain super.backColor(c); } - alias TextBoxBase.backColor backColor; // Overload. + alias backColor = TextBoxBase.backColor; // Overload. private void _setfc(Color c) @@ -260,7 +265,7 @@ class RichTextBox: TextBoxBase // docmain super.foreColor(c); } - alias TextBoxBase.foreColor foreColor; // Overload. + alias foreColor = TextBoxBase.foreColor; // Overload. /// @@ -303,7 +308,7 @@ class RichTextBox: TextBoxBase // docmain } } - alias TextBoxBase.paste paste; // Overload. + alias paste = TextBoxBase.paste; // Overload. /// diff --git a/source/dfl/sharedcontrol.d b/source/dfl/sharedcontrol.d new file mode 100644 index 0000000..d5a5819 --- /dev/null +++ b/source/dfl/sharedcontrol.d @@ -0,0 +1,152 @@ +// Written by Christopher E. Miller +// See the included license.txt for copyright and license details. + + +/// +module dfl.sharedcontrol; + +private import dfl.control; +private import dfl.application; + +private import dfl.internal.winapi; +private import dfl.internal.dlib; +private import dfl.internal.clib; + +/// +shared class SharedControl +{ +private: + Control _ctrl; + + LPARAM makeParam(ARGS...)(void function(Control, ARGS) fn, Tuple!(ARGS)* args) + if (ARGS.length) + { + static assert((DflInvokeParam*).sizeof <= LPARAM.sizeof); + static struct InvokeParam + { + void function(Control, ARGS) fn; + ARGS args; + } + alias dfl.internal.clib.malloc malloc; + alias dfl.internal.clib.free free; + + auto param = cast(InvokeParam*)malloc(InvokeParam.sizeof); + param.fn = fn; + param.args = args.field; + + if (!param) + throw new OomException(); + + auto p = cast(DflInvokeParam*)malloc(DflInvokeParam.sizeof); + + if (!p) + throw new OomException(); + + + static void fnentry(Control c, size_t[] p) + { + auto param = cast(InvokeParam*)p[0]; + param.fn(c, param.args); + free(param); + } + + p.fp = &fnentry; + p.nparams = 1; + p.params[0] = cast(size_t)param; + + return cast(LPARAM)p; + } + + + LPARAM makeParamNoneArgs(void function(Control) fn) + { + static assert((DflInvokeParam*).sizeof <= LPARAM.sizeof); + alias dfl.internal.clib.malloc malloc; + alias dfl.internal.clib.free free; + + auto p = cast(DflInvokeParam*)malloc(DflInvokeParam.sizeof); + + if (!p) + throw new OomException(); + + static void fnentry(Control c, size_t[] p) + { + auto fn = cast(void function(Control))p[0]; + fn(c); + } + + p.fp = &fnentry; + p.nparams = 1; + p.params[0] = cast(size_t)fn; + + return cast(LPARAM)p; + } + + + +public: + /// + this(Control ctrl) + { + assert(ctrl); + _ctrl = cast(shared)ctrl; + } + + /// + void invoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) + if (ARGS.length && !hasLocalAliasing!(ARGS)) + { + auto ctrl = cast(Control)_ctrl; + auto hwnd = ctrl.handle; + + if(!hwnd) + Control.badInvokeHandle(); + + auto t = tuple(args); + auto p = makeParam(fn, &t); + SendMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); + } + + /// + void invoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) + if (!ARGS.length) + { + auto ctrl = cast(Control)_ctrl; + auto hwnd = ctrl.handle; + + if(!hwnd) + Control.badInvokeHandle(); + + auto p = makeParamNoneArgs(fn); + SendMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); + } + + /// + void delayInvoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) + if (ARGS.length && !hasLocalAliasing!(ARGS)) + { + auto ctrl = cast(Control)_ctrl; + auto hwnd = ctrl.handle; + + if(!hwnd) + Control.badInvokeHandle(); + + auto t = tuple(args); + auto p = makeParam(fn, &t); + PostMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); + } + + /// + void delayInvoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) + if (!ARGS.length) + { + auto ctrl = cast(Control)_ctrl; + auto hwnd = ctrl.handle; + + if(!hwnd) + Control.badInvokeHandle(); + + auto p = makeParamNoneArgs(fn); + PostMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); + } +} diff --git a/source/dfl/textbox.d b/source/dfl/textbox.d index ee04f74..a3efab6 100644 --- a/source/dfl/textbox.d +++ b/source/dfl/textbox.d @@ -5,10 +5,14 @@ /// module dfl.textbox; -private import dfl.internal.dlib; +private import dfl.control, dfl.base, dfl.application; +private import dfl.drawing, dfl.event; +private import dfl.textboxbase; + +private import dfl.internal.dlib; +private import dfl.internal.winapi; +private import dfl.internal.utf; -private import dfl.control, dfl.base, dfl.internal.winapi, dfl.application; -private import dfl.drawing, dfl.event, dfl.internal.utf; debug(APP_PRINT) { private import dfl.internal.clib; @@ -29,1041 +33,40 @@ private extern(Windows) void _initTextBox(); // Note: ControlStyles.CACHE_TEXT might not work correctly with a text box. // It's not actually a bug, but a limitation of this control. -/// -abstract class TextBoxBase: ControlSuperClass // docmain -{ - /// - final @property void acceptsTab(bool byes) // setter - { - atab = byes; - setStyle(ControlStyles.WANT_TAB_KEY, atab); - } - - /// ditto - final @property bool acceptsTab() // getter - { - return atab; - } - - - /// - @property void borderStyle(BorderStyle bs) // setter - { - final switch(bs) - { - case BorderStyle.FIXED_3D: - _style(_style() & ~WS_BORDER); - _exStyle(_exStyle() | WS_EX_CLIENTEDGE); - break; - - case BorderStyle.FIXED_SINGLE: - _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE); - _style(_style() | WS_BORDER); - break; - - case BorderStyle.NONE: - _style(_style() & ~WS_BORDER); - _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE); - break; - } - - if(created) - { - redrawEntire(); - } - } - - /// ditto - @property BorderStyle borderStyle() // getter - { - if(_exStyle() & WS_EX_CLIENTEDGE) - return BorderStyle.FIXED_3D; - else if(_style() & WS_BORDER) - return BorderStyle.FIXED_SINGLE; - return BorderStyle.NONE; - } - - - /// - final @property bool canUndo() // getter - { - if(!created) - return false; - return SendMessageA(handle, EM_CANUNDO, 0, 0) != 0; - } - - - /// - final @property void hideSelection(bool byes) // setter - { - if(byes) - _style(_style() & ~ES_NOHIDESEL); - else - _style(_style() | ES_NOHIDESEL); - } - - /// ditto - final @property bool hideSelection() // getter - { - return (_style() & ES_NOHIDESEL) == 0; - } - - - /// - final @property void lines(Dstring[] lns) // setter - { - Dstring result; - foreach(Dstring s; lns) - { - result ~= s ~ "\r\n"; - } - if(result.length) // Remove last \r\n. - result = result[0 .. result.length - 2]; - text = result; - } - - /// ditto - final @property Dstring[] lines() // getter - { - return stringSplitLines(text); - } - - - /// - @property void maxLength(uint len) // setter - { - if(!len) - { - if(multiline) - lim = 0xFFFFFFFF; - else - lim = 0x7FFFFFFE; - } - else - { - lim = len; - } - - if(created) - { - Message m; - m = Message(handle, EM_SETLIMITTEXT, cast(WPARAM)lim, 0); - prevWndProc(m); - } - } - - /// ditto - @property uint maxLength() // getter - { - if(created) - lim = cast(uint)SendMessageA(handle, EM_GETLIMITTEXT, 0, 0); - return lim; - } - - - /// - final uint getLineCount() - { - if(!multiline) - return 1; - - if(created) - { - return cast(uint)SendMessageA(handle, EM_GETLINECOUNT, 0, 0); - } - - Dstring s; - size_t iw = 0; - uint count = 1; - s = text; - for(; iw != s.length; iw++) - { - if('\r' == s[iw]) - { - if(iw + 1 == s.length) - break; - if('\n' == s[iw + 1]) - { - iw++; - count++; - } - } - } - return count; - } - - - /// - final @property void modified(bool byes) // setter - { - if(created) - SendMessageA(handle, EM_SETMODIFY, byes, 0); - } - - /// ditto - final @property bool modified() // getter - { - if(!created) - return false; - return SendMessageA(handle, EM_GETMODIFY, 0, 0) != 0; - } - - - /// - @property void multiline(bool byes) // setter - { - /+ - if(byes) - _style(_style() & ~ES_AUTOHSCROLL | ES_MULTILINE); - else - _style(_style() & ~ES_MULTILINE | ES_AUTOHSCROLL); - +/ - - // TODO: check if correct implementation. - - LONG st; - - if(byes) - { - st = _style() | ES_MULTILINE | ES_AUTOVSCROLL; - - if(_wrap) - st &= ~ES_AUTOHSCROLL; - else - st |= ES_AUTOHSCROLL; - } - else - { - st = _style() & ~(ES_MULTILINE | ES_AUTOVSCROLL); - - // Always H-scroll when single line. - st |= ES_AUTOHSCROLL; - } - - _style(st); - - _crecreate(); - } - - /// ditto - @property bool multiline() // getter - { - return (_style() & ES_MULTILINE) != 0; - } - - - /// - final @property void readOnly(bool byes) // setter - { - if(created) - { - SendMessageA(handle, EM_SETREADONLY, byes, 0); // Should trigger WM_STYLECHANGED. - invalidate(); // ? - } - else - { - if(byes) - _style(_style() | ES_READONLY); - else - _style(_style() & ~ES_READONLY); - } - } - - /// ditto - final @property bool readOnly() // getter - { - return (_style() & ES_READONLY) != 0; - } - - - /// - @property void selectedText(Dstring sel) // setter - { - /+ - if(created) - SendMessageA(handle, EM_REPLACESEL, FALSE, cast(LPARAM)unsafeStringz(sel)); - +/ - - if(created) - { - //dfl.internal.utf.sendMessage(handle, EM_REPLACESEL, FALSE, sel); - dfl.internal.utf.sendMessageUnsafe(handle, EM_REPLACESEL, FALSE, sel); - } - } - - /// ditto - @property Dstring selectedText() // getter - { - /+ - if(created) - { - uint v1, v2; - SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); - if(v1 == v2) - return null; - assert(v2 > v1); - Dstring result = new char[v2 - v1 + 1]; - result[result.length - 1] = 0; - result = result[0 .. result.length - 1]; - result[] = text[v1 .. v2]; - return result; - } - return null; - +/ - - if(created) - return dfl.internal.utf.getSelectedText(handle); - return null; - } - - - /// - @property void selectionLength(uint len) // setter - { - if(created) - { - uint v1, v2; - SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); - v2 = v1 + len; - SendMessageA(handle, EM_SETSEL, v1, v2); - } - } - - /// ditto - // Current selection length, in characters. - // This does not necessarily correspond to the length of chars; some characters use multiple chars. - // An end of line (\r\n) takes up 2 characters. - @property uint selectionLength() // getter - { - if(created) - { - uint v1, v2; - SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); - assert(v2 >= v1); - return v2 - v1; - } - return 0; - } - - - /// - @property void selectionStart(uint pos) // setter - { - if(created) - { - uint v1, v2; - SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); - assert(v2 >= v1); - v2 = pos + (v2 - v1); - SendMessageA(handle, EM_SETSEL, pos, v2); - } - } - - /// ditto - // Current selection starting index, in characters. - // This does not necessarily correspond to the index of chars; some characters use multiple chars. - // An end of line (\r\n) takes up 2 characters. - @property uint selectionStart() // getter - { - if(created) - { - uint v1, v2; - SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); - return v1; - } - return 0; - } - - - /// - // Number of characters in the textbox. - // This does not necessarily correspond to the number of chars; some characters use multiple chars. - // An end of line (\r\n) takes up 2 characters. - // Return may be larger than the amount of characters. - // This is a lot faster than retrieving the text, but retrieving the text is completely accurate. - @property uint textLength() // getter - { - if(!(ctrlStyle & ControlStyles.CACHE_TEXT) && created()) - //return cast(uint)SendMessageA(handle, WM_GETTEXTLENGTH, 0, 0); - return cast(uint)dfl.internal.utf.sendMessage(handle, WM_GETTEXTLENGTH, 0, 0); - return wtext.length.toI32; - } - - - /// - @property final void wordWrap(bool byes) // setter - { - /+ - if(byes) - _style(_style() | ES_AUTOVSCROLL); - else - _style(_style() & ~ES_AUTOVSCROLL); - +/ - - // TODO: check if correct implementation. - - if(_wrap == byes) - return; - - _wrap = byes; - - // Always H-scroll when single line. - if(multiline) - { - if(byes) - { - _style(_style() & ~(ES_AUTOHSCROLL | WS_HSCROLL)); - } - else - { - LONG st; - st = _style(); - - st |= ES_AUTOHSCROLL; - - if(_hscroll) - st |= WS_HSCROLL; - - _style(st); - } - } - - _crecreate(); - } - - /// ditto - final @property bool wordWrap() // getter - { - //return (_style() & ES_AUTOVSCROLL) != 0; - - return _wrap; - } - - - /// - final void appendText(Dstring txt) - { - if(created) - { - selectionStart = textLength; - selectedText = txt; - } - else - { - text = text ~ txt; - } - } - - - /// - final void clear() - { - /+ - // WM_CLEAR only clears the selection ? - if(created) - SendMessageA(handle, WM_CLEAR, 0, 0); - else - wtext = null; - +/ - - text = null; - } - - - /// - final void clearUndo() - { - if(created) - SendMessageA(handle, EM_EMPTYUNDOBUFFER, 0, 0); - } - - - /// - final void copy() - { - if(created) - { - SendMessageA(handle, WM_COPY, 0, 0); - } - else - { - // There's never a selection if the window isn't created; so just empty the clipboard. - - if(!OpenClipboard(null)) - { - debug(APP_PRINT) - cprintf("Unable to OpenClipboard().\n"); - //throw new DflException("Unable to set clipboard data."); - return; - } - EmptyClipboard(); - CloseClipboard(); - } - } - - - /// - final void cut() - { - if(created) - { - SendMessageA(handle, WM_CUT, 0, 0); - } - else - { - // There's never a selection if the window isn't created; so just empty the clipboard. - - if(!OpenClipboard(null)) - { - debug(APP_PRINT) - cprintf("Unable to OpenClipboard().\n"); - //throw new DflException("Unable to set clipboard data."); - return; - } - EmptyClipboard(); - CloseClipboard(); - } - } - - - /// - final void paste() - { - if(created) - { - SendMessageA(handle, WM_PASTE, 0, 0); - } - else - { - // Can't do anything because there's no selection ? - } - } - - - /// - final void scrollToCaret() - { - if(created) - SendMessageA(handle, EM_SCROLLCARET, 0, 0); - } - - - /// - final void select(uint start, uint length) - { - if(created) - SendMessageA(handle, EM_SETSEL, start, start + length); - } - - alias Control.select select; // Overload. - - - /// - final void selectAll() - { - if(created) - SendMessageA(handle, EM_SETSEL, 0, -1); - } - - - override Dstring toString() - { - return text; // ? - } - - - /// - final void undo() - { - if(created) - SendMessageA(handle, EM_UNDO, 0, 0); - } - - - /+ - override void createHandle() - { - if(isHandleCreated) - return; - - createClassHandle(TEXTBOX_CLASSNAME); - - onHandleCreated(EventArgs.empty); - } - +/ - - - override void createHandle() - { - if(!isHandleCreated) - { - Dstring txt; - txt = wtext; - - super.createHandle(); - - //dfl.internal.utf.setWindowText(hwnd, txt); - text = txt; // So that it can be overridden. - } - } - - - protected override void createParams(ref CreateParams cp) - { - super.createParams(cp); - - cp.className = TEXTBOX_CLASSNAME; - cp.caption = null; // Set in createHandle() to allow larger buffers. - } - - - protected override void onHandleCreated(EventArgs ea) - { - super.onHandleCreated(ea); - - //SendMessageA(hwnd, EM_SETLIMITTEXT, cast(WPARAM)lim, 0); - maxLength = lim; // Call virtual function. - } - - - private - { - version(DFL_NO_MENUS) - { - } - else - { - void menuUndo(Object sender, EventArgs ea) - { - undo(); - } - - - void menuCut(Object sender, EventArgs ea) - { - cut(); - } - - - void menuCopy(Object sender, EventArgs ea) - { - copy(); - } - - - void menuPaste(Object sender, EventArgs ea) - { - paste(); - } - - - void menuDelete(Object sender, EventArgs ea) - { - // Only clear selection. - SendMessageA(handle, WM_CLEAR, 0, 0); - } - - - void menuSelectAll(Object sender, EventArgs ea) - { - selectAll(); - } - - - bool isClipboardText() - { - if(!OpenClipboard(handle)) - return false; - - bool result; - result = GetClipboardData(CF_TEXT) != null; - - CloseClipboard(); - - return result; - } - - - void menuPopup(Object sender, EventArgs ea) - { - int slen, tlen; - bool issel; - - slen = selectionLength; - tlen = textLength; - issel = slen != 0; - - miundo.enabled = canUndo; - micut.enabled = !readOnly() && issel; - micopy.enabled = issel; - mipaste.enabled = !readOnly() && isClipboardText(); - midel.enabled = !readOnly() && issel; - misel.enabled = tlen != 0 && tlen != slen; - } - - - MenuItem miundo, micut, micopy, mipaste, midel, misel; - } - } - - - this() - { - _initTextBox(); - - wstyle |= WS_TABSTOP | ES_AUTOHSCROLL; - wexstyle |= WS_EX_CLIENTEDGE; - ctrlStyle |= ControlStyles.SELECTABLE; - wclassStyle = textBoxClassStyle; - - version(DFL_NO_MENUS) - { - } - else - { - MenuItem mi; - - cmenu = new ContextMenu; - cmenu.popup ~= &menuPopup; - - miundo = new MenuItem; - miundo.text = "&Undo"; - miundo.click ~= &menuUndo; - miundo.index = 0; - cmenu.menuItems.add(miundo); - - mi = new MenuItem; - mi.text = "-"; - mi.index = 1; - cmenu.menuItems.add(mi); - - micut = new MenuItem; - micut.text = "Cu&t"; - micut.click ~= &menuCut; - micut.index = 2; - cmenu.menuItems.add(micut); - - micopy = new MenuItem; - micopy.text = "&Copy"; - micopy.click ~= &menuCopy; - micopy.index = 3; - cmenu.menuItems.add(micopy); - - mipaste = new MenuItem; - mipaste.text = "&Paste"; - mipaste.click ~= &menuPaste; - mipaste.index = 4; - cmenu.menuItems.add(mipaste); - - midel = new MenuItem; - midel.text = "&Delete"; - midel.click ~= &menuDelete; - midel.index = 5; - cmenu.menuItems.add(midel); - - mi = new MenuItem; - mi.text = "-"; - mi.index = 6; - cmenu.menuItems.add(mi); - - misel = new MenuItem; - misel.text = "Select &All"; - misel.click ~= &menuSelectAll; - misel.index = 7; - cmenu.menuItems.add(misel); - } - } - - - override @property Color backColor() // getter - { - if(Color.empty == backc) - return defaultBackColor; - return backc; - } - - alias Control.backColor backColor; // Overload. - - - static @property Color defaultBackColor() // getter - { - return Color.systemColor(COLOR_WINDOW); - } - - - override @property Color foreColor() // getter - { - if(Color.empty == forec) - return defaultForeColor; - return forec; - } - - alias Control.foreColor foreColor; // Overload. - - - static @property Color defaultForeColor() //getter - { - return Color.systemColor(COLOR_WINDOWTEXT); - } - - - override @property Cursor cursor() // getter - { - if(!wcurs) - return _defaultCursor; - return wcurs; - } - - alias Control.cursor cursor; // Overload. - - - /// - int getFirstCharIndexFromLine(int line) - { - if(!isHandleCreated) - return -1; // ... - if(line < 0) - return -1; - return SendMessageA(hwnd, EM_LINEINDEX, line, 0).toI32; - } - - /// ditto - int getFirstCharIndexOfCurrentLine() - { - if(!isHandleCreated) - return -1; // ... - return SendMessageA(hwnd, EM_LINEINDEX, -1, 0).toI32; - } - - - /// - int getLineFromCharIndex(int charIndex) - { - if(!isHandleCreated) - return -1; // ... - if(charIndex < 0) - return -1; - return SendMessageA(hwnd, EM_LINEFROMCHAR, charIndex, 0).toI32; - } - - - /// - Point getPositionFromCharIndex(int charIndex) - { - if(!isHandleCreated) - return Point(0, 0); // ... - if(charIndex < 0) - return Point(0, 0); - POINT point; - SendMessageA(hwnd, EM_POSFROMCHAR, cast(WPARAM)&point, charIndex); - return Point(point.x, point.y); - } - - /// ditto - int getCharIndexFromPosition(Point pt) - { - if(!isHandleCreated) - return -1; // ... - if(!multiline) - return 0; - auto lresult = SendMessageA(hwnd, EM_CHARFROMPOS, 0, MAKELPARAM(pt.x, pt.y)); - if(-1 == lresult) - return -1; - return cast(int)cast(short)(lresult & 0xFFFF); - } - - - package static @property Cursor _defaultCursor() // getter - { - static Cursor def = null; - - if(!def) - { - synchronized - { - if(!def) - def = new SafeCursor(LoadCursorA(null, IDC_IBEAM)); - } - } - - return def; - } - - - protected: - protected override void onReflectedMessage(ref Message m) - { - super.onReflectedMessage(m); - - switch(m.msg) - { - case WM_COMMAND: - switch(HIWORD(m.wParam)) - { - case EN_CHANGE: - onTextChanged(EventArgs.empty); - break; - - default: - } - break; - - /+ - case WM_CTLCOLORSTATIC: - case WM_CTLCOLOREDIT: - /+ - //SetBkColor(cast(HDC)m.wParam, backColor.toRgb()); // ? - SetBkMode(cast(HDC)m.wParam, OPAQUE); // ? - +/ - break; - +/ - - default: - } - } - - - override void prevWndProc(ref Message msg) - { - version(DFL_NO_MENUS) - { - // Don't prevent WM_CONTEXTMENU so at least it'll have a default menu. - } - else - { - if(msg.msg == WM_CONTEXTMENU) // Ignore the default context menu. - return; - } - - //msg.result = CallWindowProcA(textBoxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam); - msg.result = dfl.internal.utf.callWindowProc(textBoxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam); - } - - - protected override bool processKeyEventArgs(ref Message msg) // package - { - switch(msg.msg) - { - case WM_KEYDOWN: - case WM_KEYUP: - case WM_CHAR: - if('\t' == msg.wParam) - { - // TODO: fix this. This case shouldn't be needed. - if(atab) - { - if(super.processKeyEventArgs(msg)) - return true; // Handled. - if(WM_KEYDOWN == msg.msg) - { - if(multiline) // Only multiline textboxes can have real tabs.. - { - //selectedText = "\t"; - //SendMessageA(handle, EM_REPLACESEL, TRUE, cast(LPARAM)"\t".ptr); // Allow undo. // Crashes DMD 0.161. - auto str = "\t".ptr; - SendMessageA(handle, EM_REPLACESEL, TRUE, cast(LPARAM)str); // Allow undo. - } - } - return true; // Handled. - } - } - break; - - default: - } - return super.processKeyEventArgs(msg); - } - - - override void wndProc(ref Message msg) - { - switch(msg.msg) - { - case WM_GETDLGCODE: - super.wndProc(msg); - if(atab) - { - //if(GetKeyState(Keys.TAB) & 0x8000) - { - //msg.result |= DLGC_WANTALLKEYS; - msg.result |= DLGC_WANTTAB; - } - } - else - { - msg.result &= ~DLGC_WANTTAB; - } - return; - - default: - super.wndProc(msg); - } - } - - - override @property Size defaultSize() // getter - { - return Size(120, 23); // ? - } - - - private: - package uint lim = 30_000; // Documented as default. - bool _wrap = true; - bool _hscroll; - - bool atab = false; - - /+ - @property bool atab() // getter - { - if(_style() & X) - return true; - return false; - } - - @property void atab(bool byes) // setter - { - if(byes) - _style(_style() | X); - else - _style(_style() & ~X); - } - +/ - - - @property void hscroll(bool byes) // setter - { - _hscroll = byes; - - if(byes && (!_wrap || !multiline)) - _style(_style() | WS_HSCROLL | ES_AUTOHSCROLL); - } - - - @property bool hscroll() // getter - { - return _hscroll; - } -} - - /// class TextBox: TextBoxBase // docmain { /// final @property void acceptsReturn(bool byes) // setter { - if(byes) - _style(_style() | ES_WANTRETURN); - else - _style(_style() & ~ES_WANTRETURN); + // if(byes) + // { + // _style(_style() | ES_WANTRETURN); + // } + // else + // { + // _style(_style() & ~ES_WANTRETURN); + // } + if (_acceptsReturn == byes) return; + _acceptsReturn = byes; + if (multiline) + { + if (byes) + { + _style = _style | ES_WANTRETURN; + } + else + { + _style = _style & ~ES_WANTRETURN; + } + } } /// ditto final @property bool acceptsReturn() // getter { - return (_style() & ES_WANTRETURN) != 0; + // return (_style() & ES_WANTRETURN) != 0; + return _acceptsReturn; } @@ -1129,16 +132,16 @@ class TextBox: TextBoxBase // docmain _style(_style() & ~ES_PASSWORD); } - passchar = pwc; + _passchar = pwc; } /// ditto final @property dchar passwordChar() // getter { if(created) - //passchar = cast(dchar)SendMessageA(handle, EM_GETPASSWORDCHAR, 0, 0); - passchar = dfl.internal.utf.emGetPasswordChar(handle); - return passchar; + //_passchar = cast(dchar)SendMessageA(handle, EM_GETPASSWORDCHAR, 0, 0); + _passchar = dfl.internal.utf.emGetPasswordChar(handle); + return _passchar; } @@ -1253,18 +256,89 @@ class TextBox: TextBoxBase // docmain wstyle |= ES_LEFT; } - + /// protected override @property void onHandleCreated(EventArgs ea) { super.onHandleCreated(ea); - if(passchar) + if(_passchar) { - SendMessageA(hwnd, EM_SETPASSWORDCHAR, passchar, 0); + SendMessageA(hwnd, EM_SETPASSWORDCHAR, _passchar, 0); } } - + /// isInputKey returns true when keyData is a regular input key. + // If keyData is input key, then window message is sended to wndProc() + // such as WM_KEYDOWN, WM_KEYUP, WM_CHAR and so on. + protected override bool isInputKey(Keys keyData) + { + if (multiline && (keyData & Keys.ALT) == 0) + { + switch (keyData & Keys.KEY_CODE) + { + case Keys.RETURN: + { + IButtonControl b = findForm.acceptButton; + if(b is null) + return true; // When Form's default button is none, RETURN is always input key. + else if(b !is null && _acceptsReturn == false) + return super.isInputKey(keyData); + else if(b !is null && _acceptsReturn == true) + return true; + else + assert(0); + } + default: + // Fall through + } + } + + return super.isInputKey(keyData); + } + + // Process dialog key (TAB, RETURN, ESC, UP, DOWN, LEFT, RIGHT and so on). + // Returns true when processed, otherwise call super class. + protected override bool processDialogKey(Keys keyData) + { + Keys keyCode = cast(Keys)keyData & Keys.KEY_CODE; + + if (keyCode == Keys.RETURN) + { + if (_acceptsReturn && (keyData & Keys.CONTROL) != 0) + { + // When this control accepts Returns, Ctrl-Return is treated exactly like Return. + keyData &= ~Keys.CONTROL; + } + + IButtonControl b = findForm.acceptButton; + if(b is null) + return true; // When Form's default button is none, RETURN is always input key. + else if(b !is null && _acceptsReturn == false) + return super.processDialogKey(keyData); + else if(b !is null && _acceptsReturn == true) + return true; + else + assert(0); + } + return super.processDialogKey(keyData); + } + + /// Process shortcut key (ctrl+A etc). + // Returns true when processed, false when not. + protected override bool processCmdKey(ref Message m, Keys keyData) + { + bool isProcessed = super.processCmdKey(m, keyData); + + // TODO: Implement ShortcutsEnabled + if (!isProcessed && multiline /+&& ShortcutsEnabled+/ && (keyData == (Keys.CONTROL | Keys.A))) + { + selectAll(); + return true; + } + + return isProcessed; + } + /+ override @property void wndProc(ref Message msg) { @@ -1287,9 +361,73 @@ class TextBox: TextBoxBase // docmain super.wndProc(msg); } +/ + protected override void wndProc(ref Message msg) + { + switch(msg.msg) + { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_CHAR: + case WM_SYSCHAR: + case WM_KEYUP: + case WM_SYSKEYUP: + Keys keyCode = cast(Keys)msg.wParam & Keys.KEY_CODE; + + if (keyCode == Keys.RETURN) + { + IButtonControl b = findForm.acceptButton; + if(b is null) + super.wndProc(msg); + else if(b !is null && _acceptsReturn == false) + return; + else if(b !is null && _acceptsReturn == true) + super.wndProc(msg); + else + assert(0); + } + else + { + super.wndProc(msg); + } + return; + + case WM_GETDLGCODE: + Keys keyCode = cast(Keys)msg.wParam & Keys.KEY_CODE; + if(keyCode == Keys.RETURN) + { + // DLGC_WANTARROWS = 1 + // Want VK_LEFT, VK_RIGHT, VK_UP and VK_DOWN in WM_KEYDOWN. + // DLGC_WANTTAB = 2 + // Want VK_TAB in WM_KEYDOWN. + // DLGC_WANTALLKEYS = 4 + // Want VK_RETURN, VK_EXECUTE, VK_ESCAPE and VK_CANCEL in WM_KEYDOWN. + // DLGC_WANTCHARS = 0x80 + // Want WM_CHAR. + if(_acceptsReturn) + { + msg.result |= DLGC_WANTCHARS | DLGC_WANTALLKEYS | DLGC_WANTARROWS; + } + else + { + msg.result &= ~(DLGC_WANTCHARS | DLGC_WANTALLKEYS | DLGC_WANTARROWS); + } + return; // Do not call super.wndProc() because processing RETURN was done. + } + else + { + msg.result |= DLGC_WANTCHARS | DLGC_WANTARROWS; + super.wndProc(msg); + return; + } + + default: + super.wndProc(msg); + } + } private: - dchar passchar = 0; + dchar _passchar = 0; + bool _acceptsReturn = false; } diff --git a/source/dfl/textboxbase.d b/source/dfl/textboxbase.d new file mode 100644 index 0000000..412b592 --- /dev/null +++ b/source/dfl/textboxbase.d @@ -0,0 +1,1214 @@ +// Written by Christopher E. Miller +// See the included license.txt for copyright and license details. + + +/// +module dfl.textboxbase; + +private import dfl.control, dfl.base, dfl.application; +private import dfl.drawing, dfl.event; + +private import dfl.internal.dlib; +private import dfl.internal.winapi; +private import dfl.internal.utf; + +debug(APP_PRINT) +{ + private import dfl.internal.clib; +} + +version(DFL_NO_MENUS) +{ +} +else +{ + private import dfl.menu; +} + + +private extern(Windows) void _initTextBox(); + + +// Note: ControlStyles.CACHE_TEXT might not work correctly with a text box. +// It's not actually a bug, but a limitation of this control. + +/// +abstract class TextBoxBase: ControlSuperClass // docmain +{ + /// + final @property void acceptsTab(bool byes) // setter + { + if (_acceptsTab != byes) + { + _acceptsTab = byes; + // setStyle(ControlStyles.WANT_TAB_KEY, _acceptsTab); // Do not call here + + // TODO: implement + // onAcceptsTabChanged(EventArgs.empty); + } + } + + /// ditto + final @property bool acceptsTab() // getter + { + return _acceptsTab; + } + + + /// + @property void borderStyle(BorderStyle bs) // setter + { + final switch(bs) + { + case BorderStyle.FIXED_3D: + _style(_style() & ~WS_BORDER); + _exStyle(_exStyle() | WS_EX_CLIENTEDGE); + break; + + case BorderStyle.FIXED_SINGLE: + _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE); + _style(_style() | WS_BORDER); + break; + + case BorderStyle.NONE: + _style(_style() & ~WS_BORDER); + _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE); + break; + } + + if(created) + { + redrawEntire(); + } + } + + /// ditto + @property BorderStyle borderStyle() // getter + { + if(_exStyle() & WS_EX_CLIENTEDGE) + return BorderStyle.FIXED_3D; + else if(_style() & WS_BORDER) + return BorderStyle.FIXED_SINGLE; + return BorderStyle.NONE; + } + + + /// + final @property bool canUndo() // getter + { + if(!created) + return false; + return SendMessageA(handle, EM_CANUNDO, 0, 0) != 0; + } + + + /// + final @property void hideSelection(bool byes) // setter + { + if(byes) + _style(_style() & ~ES_NOHIDESEL); + else + _style(_style() | ES_NOHIDESEL); + } + + /// ditto + final @property bool hideSelection() // getter + { + return (_style() & ES_NOHIDESEL) == 0; + } + + + /// + final @property void lines(Dstring[] lns) // setter + { + Dstring result; + foreach(Dstring s; lns) + { + result ~= s ~ "\r\n"; + } + if(result.length) // Remove last \r\n. + result = result[0 .. $ - 2]; + text = result; + } + + /// ditto + final @property Dstring[] lines() // getter + { + return stringSplitLines(text); + } + + + /// + @property void maxLength(uint len) // setter + { + if(!len) + { + if(multiline) + _lim = 0xFFFFFFFF; + else + _lim = 0x7FFFFFFE; + } + else + { + _lim = len; + } + + if(created) + { + Message m; + m = Message(handle, EM_SETLIMITTEXT, cast(WPARAM)_lim, 0); + prevWndProc(m); + } + } + + /// ditto + @property uint maxLength() // getter + { + if(created) + _lim = cast(uint)SendMessageA(handle, EM_GETLIMITTEXT, 0, 0); + return _lim; + } + + + /// + final uint getLineCount() + { + if(!multiline) + return 1; + + if(created) + { + return cast(uint)SendMessageA(handle, EM_GETLINECOUNT, 0, 0); + } + + Dstring s; + size_t iw = 0; + uint count = 1; + s = text; + for(; iw != s.length; iw++) + { + if('\r' == s[iw]) + { + if(iw + 1 == s.length) + break; + if('\n' == s[iw + 1]) + { + iw++; + count++; + } + } + } + return count; + } + + + /// + final @property void modified(bool byes) // setter + { + if(created) + SendMessageA(handle, EM_SETMODIFY, byes, 0); + } + + /// ditto + final @property bool modified() // getter + { + if(!created) + return false; + return SendMessageA(handle, EM_GETMODIFY, 0, 0) != 0; + } + + + /// + @property void multiline(bool byes) // setter + { + /+ + if(byes) + _style(_style() & ~ES_AUTOHSCROLL | ES_MULTILINE); + else + _style(_style() & ~ES_MULTILINE | ES_AUTOHSCROLL); + +/ + + // TODO: check if correct implementation. + + LONG st; + + if(byes) + { + st = _style() | ES_MULTILINE | ES_AUTOVSCROLL; + + if(_wrap) + st &= ~ES_AUTOHSCROLL; + else + st |= ES_AUTOHSCROLL; + } + else + { + st = _style() & ~(ES_MULTILINE | ES_AUTOVSCROLL); + + // Always H-scroll when single line. + st |= ES_AUTOHSCROLL; + } + + _style(st); + + _crecreate(); + } + + /// ditto + @property bool multiline() // getter + { + return (_style() & ES_MULTILINE) != 0; + } + + + /// + final @property void readOnly(bool byes) // setter + { + if(created) + { + SendMessageA(handle, EM_SETREADONLY, byes, 0); // Should trigger WM_STYLECHANGED. + invalidate(); // ? + } + else + { + if(byes) + _style(_style() | ES_READONLY); + else + _style(_style() & ~ES_READONLY); + } + } + + /// ditto + final @property bool readOnly() // getter + { + return (_style() & ES_READONLY) != 0; + } + + + /// + @property void selectedText(Dstring sel) // setter + { + /+ + if(created) + SendMessageA(handle, EM_REPLACESEL, FALSE, cast(LPARAM)unsafeStringz(sel)); + +/ + + if(created) + { + //dfl.internal.utf.sendMessage(handle, EM_REPLACESEL, FALSE, sel); + dfl.internal.utf.sendMessageUnsafe(handle, EM_REPLACESEL, FALSE, sel); + } + } + + /// ditto + @property Dstring selectedText() // getter + { + /+ + if(created) + { + uint v1, v2; + SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); + if(v1 == v2) + return null; + assert(v2 > v1); + Dstring result = new char[v2 - v1 + 1]; + result[result.length - 1] = 0; + result = result[0 .. result.length - 1]; + result[] = text[v1 .. v2]; + return result; + } + return null; + +/ + + if(created) + return dfl.internal.utf.getSelectedText(handle); + return null; + } + + + /// + @property void selectionLength(uint len) // setter + { + if(created) + { + uint v1, v2; + SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); + v2 = v1 + len; + SendMessageA(handle, EM_SETSEL, v1, v2); + } + } + + /// ditto + // Current selection length, in characters. + // This does not necessarily correspond to the length of chars; some characters use multiple chars. + // An end of line (\r\n) takes up 2 characters. + @property uint selectionLength() // getter + { + if(created) + { + uint v1, v2; + SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); + assert(v2 >= v1); + return v2 - v1; + } + return 0; + } + + + /// + @property void selectionStart(uint pos) // setter + { + if(created) + { + uint v1, v2; + SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); + assert(v2 >= v1); + v2 = pos + (v2 - v1); + SendMessageA(handle, EM_SETSEL, pos, v2); + } + } + + /// ditto + // Current selection starting index, in characters. + // This does not necessarily correspond to the index of chars; some characters use multiple chars. + // An end of line (\r\n) takes up 2 characters. + @property uint selectionStart() // getter + { + if(created) + { + uint v1, v2; + SendMessageA(handle, EM_GETSEL, cast(WPARAM)&v1, cast(LPARAM)&v2); + return v1; + } + return 0; + } + + + /// + // Number of characters in the textbox. + // This does not necessarily correspond to the number of chars; some characters use multiple chars. + // An end of line (\r\n) takes up 2 characters. + // Return may be larger than the amount of characters. + // This is a lot faster than retrieving the text, but retrieving the text is completely accurate. + @property uint textLength() // getter + { + if(!(ctrlStyle & ControlStyles.CACHE_TEXT) && created()) + //return cast(uint)SendMessageA(handle, WM_GETTEXTLENGTH, 0, 0); + return cast(uint)dfl.internal.utf.sendMessage(handle, WM_GETTEXTLENGTH, 0, 0); + return wtext.length.toI32; + } + + + /// + @property final void wordWrap(bool byes) // setter + { + /+ + if(byes) + _style(_style() | ES_AUTOVSCROLL); + else + _style(_style() & ~ES_AUTOVSCROLL); + +/ + + // TODO: check if correct implementation. + + if(_wrap == byes) + return; + + _wrap = byes; + + // Always H-scroll when single line. + if(multiline) + { + if(byes) + { + _style(_style() & ~(ES_AUTOHSCROLL | WS_HSCROLL)); + } + else + { + LONG st; + st = _style(); + + st |= ES_AUTOHSCROLL; + + if(_hscroll) + st |= WS_HSCROLL; + + _style(st); + } + } + + _crecreate(); + } + + /// ditto + final @property bool wordWrap() // getter + { + //return (_style() & ES_AUTOVSCROLL) != 0; + + return _wrap; + } + + + /// + final void appendText(Dstring txt) + { + if(created) + { + selectionStart = textLength; + selectedText = txt; + } + else + { + text = text ~ txt; + } + } + + + /// + final void clear() + { + /+ + // WM_CLEAR only clears the selection ? + if(created) + SendMessageA(handle, WM_CLEAR, 0, 0); + else + wtext = null; + +/ + + text = null; + } + + + /// + final void clearUndo() + { + if(created) + SendMessageA(handle, EM_EMPTYUNDOBUFFER, 0, 0); + } + + + /// + final void copy() + { + if(created) + { + SendMessageA(handle, WM_COPY, 0, 0); + } + else + { + // There's never a selection if the window isn't created; so just empty the clipboard. + + if(!OpenClipboard(null)) + { + debug(APP_PRINT) + cprintf("Unable to OpenClipboard().\n"); + //throw new DflException("Unable to set clipboard data."); + return; + } + EmptyClipboard(); + CloseClipboard(); + } + } + + + /// + final void cut() + { + if(created) + { + SendMessageA(handle, WM_CUT, 0, 0); + } + else + { + // There's never a selection if the window isn't created; so just empty the clipboard. + + if(!OpenClipboard(null)) + { + debug(APP_PRINT) + cprintf("Unable to OpenClipboard().\n"); + //throw new DflException("Unable to set clipboard data."); + return; + } + EmptyClipboard(); + CloseClipboard(); + } + } + + + /// + final void paste() + { + if(created) + { + SendMessageA(handle, WM_PASTE, 0, 0); + } + else + { + // Can't do anything because there's no selection ? + } + } + + + /// + final void scrollToCaret() + { + if(created) + SendMessageA(handle, EM_SCROLLCARET, 0, 0); + } + + + /// + final void select(uint start, uint length) + { + if(created) + SendMessageA(handle, EM_SETSEL, start, start + length); + } + + alias select = Control.select; // Overload. + + + /// + final void selectAll() + { + if(created) + SendMessageA(handle, EM_SETSEL, 0, -1); + } + + + override Dstring toString() + { + return text; // ? + } + + + /// + final void undo() + { + if(created) + SendMessageA(handle, EM_UNDO, 0, 0); + } + + + /+ + override void createHandle() + { + if(isHandleCreated) + return; + + createClassHandle(TEXTBOX_CLASSNAME); + + onHandleCreated(EventArgs.empty); + } + +/ + + + override void createHandle() + { + if(!isHandleCreated) + { + Dstring txt; + txt = wtext; + + super.createHandle(); + + //dfl.internal.utf.setWindowText(hwnd, txt); + text = txt; // So that it can be overridden. + } + } + + + protected override void createParams(ref CreateParams cp) + { + super.createParams(cp); + + cp.className = TEXTBOX_CLASSNAME; + cp.caption = null; // Set in createHandle() to allow larger buffers. + } + + + protected override void onHandleCreated(EventArgs ea) + { + super.onHandleCreated(ea); + + //SendMessageA(hwnd, EM_SETLIMITTEXT, cast(WPARAM)lim, 0); + maxLength = _lim; // Call virtual function. + } + + + private + { + version(DFL_NO_MENUS) + { + } + else + { + void menuUndo(Object sender, EventArgs ea) + { + undo(); + } + + + void menuCut(Object sender, EventArgs ea) + { + cut(); + } + + + void menuCopy(Object sender, EventArgs ea) + { + copy(); + } + + + void menuPaste(Object sender, EventArgs ea) + { + paste(); + } + + + void menuDelete(Object sender, EventArgs ea) + { + // Only clear selection. + SendMessageA(handle, WM_CLEAR, 0, 0); + } + + + void menuSelectAll(Object sender, EventArgs ea) + { + selectAll(); + } + + + bool isClipboardText() + { + if(!OpenClipboard(handle)) + return false; + + bool result; + result = GetClipboardData(CF_TEXT) != null; + + CloseClipboard(); + + return result; + } + + + void menuPopup(Object sender, EventArgs ea) + { + int slen, tlen; + bool issel; + + slen = selectionLength; + tlen = textLength; + issel = slen != 0; + + _miundo.enabled = canUndo; + _micut.enabled = !readOnly() && issel; + _micopy.enabled = issel; + _mipaste.enabled = !readOnly() && isClipboardText(); + _midel.enabled = !readOnly() && issel; + _misel.enabled = tlen != 0 && tlen != slen; + } + + + MenuItem _miundo, _micut, _micopy, _mipaste, _midel, _misel; + } + } + + + this() + { + _initTextBox(); + + wstyle |= WS_TABSTOP | ES_AUTOHSCROLL; + wexstyle |= WS_EX_CLIENTEDGE; + ctrlStyle |= ControlStyles.SELECTABLE; + wclassStyle = textBoxClassStyle; + + version(DFL_NO_MENUS) + { + } + else + { + MenuItem mi; + + cmenu = new ContextMenu; + cmenu.popup ~= &menuPopup; + + _miundo = new MenuItem; + _miundo.text = "&Undo"; + _miundo.click ~= &menuUndo; + _miundo.index = 0; + cmenu.menuItems.add(_miundo); + + mi = new MenuItem; + mi.text = "-"; + mi.index = 1; + cmenu.menuItems.add(mi); + + _micut = new MenuItem; + _micut.text = "Cu&t"; + _micut.click ~= &menuCut; + _micut.index = 2; + cmenu.menuItems.add(_micut); + + _micopy = new MenuItem; + _micopy.text = "&Copy"; + _micopy.click ~= &menuCopy; + _micopy.index = 3; + cmenu.menuItems.add(_micopy); + + _mipaste = new MenuItem; + _mipaste.text = "&Paste"; + _mipaste.click ~= &menuPaste; + _mipaste.index = 4; + cmenu.menuItems.add(_mipaste); + + _midel = new MenuItem; + _midel.text = "&Delete"; + _midel.click ~= &menuDelete; + _midel.index = 5; + cmenu.menuItems.add(_midel); + + mi = new MenuItem; + mi.text = "-"; + mi.index = 6; + cmenu.menuItems.add(mi); + + _misel = new MenuItem; + _misel.text = "Select &All"; + _misel.click ~= &menuSelectAll; + _misel.index = 7; + cmenu.menuItems.add(_misel); + } + } + + + override @property Color backColor() // getter + { + if(Color.empty == backc) + return defaultBackColor; + return backc; + } + + alias backColor = Control.backColor; // Overload. + + + static @property Color defaultBackColor() // getter + { + return Color.systemColor(COLOR_WINDOW); + } + + + override @property Color foreColor() // getter + { + if(Color.empty == forec) + return defaultForeColor; + return forec; + } + + alias foreColor = Control.foreColor; // Overload. + + + static @property Color defaultForeColor() //getter + { + return Color.systemColor(COLOR_WINDOWTEXT); + } + + + override @property Cursor cursor() // getter + { + if(!wcurs) + return _defaultCursor; + return wcurs; + } + + alias cursor = Control.cursor; // Overload. + + + /// + int getFirstCharIndexFromLine(int line) + { + if(!isHandleCreated) + return -1; // ... + if(line < 0) + return -1; + return SendMessageA(hwnd, EM_LINEINDEX, line, 0).toI32; + } + + /// ditto + int getFirstCharIndexOfCurrentLine() + { + if(!isHandleCreated) + return -1; // ... + return SendMessageA(hwnd, EM_LINEINDEX, -1, 0).toI32; + } + + + /// + int getLineFromCharIndex(int charIndex) + { + if(!isHandleCreated) + return -1; // ... + if(charIndex < 0) + return -1; + return SendMessageA(hwnd, EM_LINEFROMCHAR, charIndex, 0).toI32; + } + + + /// + Point getPositionFromCharIndex(int charIndex) + { + if(!isHandleCreated) + return Point(0, 0); // ... + if(charIndex < 0) + return Point(0, 0); + POINT point; + SendMessageA(hwnd, EM_POSFROMCHAR, cast(WPARAM)&point, charIndex); + return Point(point.x, point.y); + } + + /// ditto + int getCharIndexFromPosition(Point pt) + { + if(!isHandleCreated) + return -1; // ... + if(!multiline) + return 0; + auto lresult = SendMessageA(hwnd, EM_CHARFROMPOS, 0, MAKELPARAM(pt.x, pt.y)); + if(-1 == lresult) + return -1; + return cast(int)cast(short)(lresult & 0xFFFF); + } + + + package static @property Cursor _defaultCursor() // getter + { + static Cursor def = null; + + if(!def) + { + synchronized + { + if(!def) + def = new SafeCursor(LoadCursorA(null, IDC_IBEAM)); + } + } + + return def; + } + + + protected override void onReflectedMessage(ref Message m) + { + super.onReflectedMessage(m); + + switch(m.msg) + { + case WM_COMMAND: + switch(HIWORD(m.wParam)) + { + case EN_CHANGE: + onTextChanged(EventArgs.empty); + break; + + default: + } + break; + + /+ + case WM_CTLCOLORSTATIC: + case WM_CTLCOLOREDIT: + /+ + //SetBkColor(cast(HDC)m.wParam, backColor.toRgb()); // ? + SetBkMode(cast(HDC)m.wParam, OPAQUE); // ? + +/ + break; + +/ + + default: + } + } + + + // processCmdKey returns true when keyData is a shortcut key (ctrl+C etc). + protected override bool processCmdKey(ref Message msg, Keys keyData) + { + // First call parent's ProcessCmdKey, since we don't to eat up + // the shortcut key we are not supported in TextBox. + bool returnedValue = super.processCmdKey(msg, keyData); + + // TODO: Implement + + // if (shortcutsEnabled == false && s_shortcutsToDisable !is null) + // { + // foreach (int shortcutValue in s_shortcutsToDisable) + // { + // if (keyData == shortcutValue || + // keyData == (shortcutValue | Keys.SHIFT)) + // { + // return true; + // } + // } + // } + + // // + // // There are a few keys that change the alignment of the text, but that + // // are not ignored by the native control when the ReadOnly property is set. + // // We need to workaround that. + // if (_textBoxFlags[readOnly]) + // { + // int k = keyData; + // if (k == Shortcut.CtrlL // align left + // || k == Shortcut.CtrlR // align right + // || k == Shortcut.CtrlE // align centre + // || k == Shortcut.CtrlJ) + // { // align justified + // return true; + // } + // } + + // if (!ReadOnly && (keyData == (Keys.CONTROL | Keys.BACK) || keyData == (Keys.CONTROL | Keys.SHIFT | Keys.BACK))) + // { + // if (selectionLength != 0) + // { + // SetSelectedTextInternal(string.Empty, clearUndo: false); + // } + // else if (SelectionStart != 0) + // { + // int boundaryStart = ClientUtils.GetWordBoundaryStart(Text, SelectionStart); + // int length = SelectionStart - boundaryStart; + // BeginUpdateInternal(); + // SelectionStart = boundaryStart; + // SelectionLength = length; + // EndUpdateInternal(); + // SetSelectedTextInternal(string.Empty, clearUndo: false); + // } + + // return true; + // } + + return returnedValue; + } + + /// isInputKey returns true when keyData is a regular input key. + // If keyData is input key, then window message is sended to wndProc() + // such as WM_KEYDOWN, WM_KEYUP, WM_CHAR and so on. + protected override bool isInputKey(Keys keyData) + { + if ((keyData & Keys.ALT) != Keys.ALT) + { + // In order to excepted for modifiers, + // do bit mask to extract only key code from key value. + switch (keyData & Keys.KEY_CODE) + { + case Keys.TAB: + // Single-line RichTextBox's want tab characters (see WM_GETDLGCODE), + // so we don't ask it + bool m = multiline; + bool a = _acceptsTab; + bool k = ((keyData & Keys.CONTROL) == 0); + if (m && a) + { + if (k) + return true; // TAB is input key because CONTROL is relesed. + else + return false; // TAB is not input key because CONTROL is pressed. + } + else + { + return false; // TAB is not input key. + } + + case Keys.ESCAPE: + if (multiline) + { + return false; + } + + break; + case Keys.BACK: + // TODO: implement + // if (!ReadOnly) + // { + // return true; + // } + + break; + case Keys.PAGE_UP: + case Keys.PAGE_DOWN: + case Keys.HOME: + case Keys.END: + return true; + default: + // Fall through to super.isInputKey() + } + } + + return super.isInputKey(keyData); + } + + /// Process dialog key (TAB, RETURN, ESC, UP, DOWN, LEFT, RIGHT and so on). + // Returns true when processed, otherwise call super class. + protected override bool processDialogKey(Keys keyData) + { + Keys keyCode = keyData & Keys.KEY_CODE; + + if (keyCode == Keys.TAB && _acceptsTab) + { + if ((keyData & Keys.CONTROL) != 0) + { + // Changes focus because pressed ctrl+TAB in accepts-tab mode. + return super.processDialogKey(keyData); + } + else + { + return true; // processed. + } + } + + // if (keyCode == Keys.TAB && _acceptsTab) && (keyData & Keys.CONTROL) != 0) + // { + // // When this control accepts Tabs, Ctrl-Tab is treated exactly like Tab. + // keyData &= ~Keys.CONTROL; + // } + + return super.processDialogKey(keyData); + } + + + protected override void prevWndProc(ref Message msg) + { + version(DFL_NO_MENUS) + { + // Don't prevent WM_CONTEXTMENU so at least it'll have a default menu. + } + else + { + if(msg.msg == WM_CONTEXTMENU) // Ignore the default context menu. + return; + } + + //msg.result = CallWindowProcA(textBoxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam); + msg.result = dfl.internal.utf.callWindowProc(textBoxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam); + } + + + // WinFormsに合わせて未定義とする + // + // protected override bool processKeyEventArgs(ref Message msg) // package + // { + // switch(msg.msg) + // { + // case WM_KEYDOWN: + // case WM_KEYUP: + // case WM_CHAR: + // if('\t' == msg.wParam) + // { + // // TODO: fix this. This case shouldn't be needed. + // if(_acceptsTab) + // { + // if(super.processKeyEventArgs(msg)) + // return true; // Handled. + // if(WM_KEYDOWN == msg.msg) + // { + // if(multiline) // Only multiline textboxes can have real tabs.. + // { + // //selectedText = "\t"; + // //SendMessageA(handle, EM_REPLACESEL, TRUE, cast(LPARAM)"\t".ptr); // Allow undo. // Crashes DMD 0.161. + // auto str = "\t".ptr; + // SendMessageA(handle, EM_REPLACESEL, TRUE, cast(LPARAM)str); // Allow undo. + // } + // } + // return true; // Handled. + // } + // } + // break; + + // default: + // } + // return super.processKeyEventArgs(msg); + // } + + + protected override void wndProc(ref Message msg) + { + switch(msg.msg) + { + // case WM_KEYDOWN: + // case WM_SYSKEYDOWN: + // case WM_CHAR: + // case WM_SYSCHAR: + // case WM_KEYUP: + // case WM_SYSKEYUP: + // Keys keyCode = cast(Keys)msg.wParam & Keys.KEY_CODE; + + // if (keyCode == Keys.TAB && _acceptsTab) + // { + // if ((keyCode & Keys.CONTROL) != 0) + // { + // // Changes focus because pressed ctrl+TAB in accepts-tab mode. + // return super.wndProc(msg); + // } + // else + // { + // return; // processed. + // } + // } + // break; + + case WM_GETDLGCODE: + // DLGC_WANTARROWS = 1 + // Want VK_LEFT, VK_RIGHT, VK_UP and VK_DOWN in WM_KEYDOWN. + // DLGC_WANTTAB = 2 + // Want VK_TAB in WM_KEYDOWN. + // DLGC_WANTALLKEYS = 4 + // Want VK_RETURN, VK_EXECUTE, VK_ESCAPE and VK_CANCEL in WM_KEYDOWN. + // DLGC_WANTCHARS = 0x80 + // Want WM_CHAR. + + // If this code is commented out, + // do not able to change focus and input TAB char by TAB key. + if(_acceptsTab) + { + msg.result |= DLGC_WANTTAB; + } + else + { + msg.result &= ~(DLGC_WANTTAB | DLGC_WANTALLKEYS); + } + + return; // Do not call super.wndProc() because processing TAB was done. + + default: + } + super.wndProc(msg); + } + + + protected override @property Size defaultSize() // getter + { + return Size(120, 23); // ? + } + + + protected final @property void hscroll(bool byes) // setter + { + _hscroll = byes; + + if(byes && (!_wrap || !multiline)) + _style(_style() | WS_HSCROLL | ES_AUTOHSCROLL); + } + + + protected final @property bool hscroll() // getter + { + return _hscroll; + } + + private: + package uint _lim = 30_000; // Documented as default. + bool _wrap = true; + bool _hscroll; + bool _acceptsTab = false; +}