| /* |
| * Copyright (c) 1996, 2009, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| #include "awt_Toolkit.h" |
| #include "awt_TextArea.h" |
| #include "awt_TextComponent.h" |
| #include "awt_Canvas.h" |
| #include "awt_Window.h" |
| #include "awt_Frame.h" |
| |
| /* IMPORTANT! Read the README.JNI file for notes on JNI converted AWT code. |
| */ |
| |
| /***********************************************************************/ |
| // Struct for _ReplaceText() method |
| struct ReplaceTextStruct { |
| jobject textComponent; |
| jstring text; |
| int start, end; |
| }; |
| |
| /************************************************************************ |
| * AwtTextArea fields |
| */ |
| |
| jfieldID AwtTextArea::scrollbarVisibilityID; |
| |
| WNDPROC AwtTextArea::sm_pDefWindowProc = NULL; |
| |
| /************************************************************************ |
| * AwtTextArea methods |
| */ |
| |
| AwtTextArea::AwtTextArea() { |
| m_bIgnoreEnChange = FALSE; |
| m_bCanUndo = FALSE; |
| m_hEditCtrl = NULL; |
| m_lHDeltaAccum = 0; |
| m_lVDeltaAccum = 0; |
| } |
| |
| AwtTextArea::~AwtTextArea() |
| { |
| } |
| |
| void AwtTextArea::Dispose() |
| { |
| if (m_hEditCtrl != NULL) { |
| VERIFY(::DestroyWindow(m_hEditCtrl)); |
| m_hEditCtrl = NULL; |
| } |
| AwtTextComponent::Dispose(); |
| } |
| |
| LPCTSTR AwtTextArea::GetClassName() { |
| static BOOL richedLibraryLoaded = FALSE; |
| if (!richedLibraryLoaded) { |
| ::LoadLibrary(TEXT("RICHED20.DLL")); |
| richedLibraryLoaded = TRUE; |
| } |
| return RICHEDIT_CLASS; |
| } |
| |
| /* Create a new AwtTextArea object and window. */ |
| AwtTextArea* AwtTextArea::Create(jobject peer, jobject parent) |
| { |
| JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); |
| |
| jobject target = NULL; |
| AwtTextArea* c = NULL; |
| |
| try { |
| if (env->EnsureLocalCapacity(1) < 0) { |
| return NULL; |
| } |
| |
| PDATA pData; |
| AwtCanvas* awtParent; |
| JNI_CHECK_PEER_GOTO(parent, done); |
| |
| awtParent = (AwtCanvas*)pData; |
| JNI_CHECK_NULL_GOTO(awtParent, "null awtParent", done); |
| |
| target = env->GetObjectField(peer, AwtObject::targetID); |
| JNI_CHECK_NULL_GOTO(target, "null target", done); |
| |
| c = new AwtTextArea(); |
| |
| { |
| /* Adjust style for scrollbar visibility and word wrap */ |
| DWORD scroll_style; |
| jint scrollbarVisibility = |
| env->GetIntField(target, AwtTextArea::scrollbarVisibilityID); |
| |
| switch (scrollbarVisibility) { |
| case java_awt_TextArea_SCROLLBARS_NONE: |
| scroll_style = ES_AUTOVSCROLL; |
| break; |
| case java_awt_TextArea_SCROLLBARS_VERTICAL_ONLY: |
| scroll_style = WS_VSCROLL | ES_AUTOVSCROLL; |
| break; |
| case java_awt_TextArea_SCROLLBARS_HORIZONTAL_ONLY: |
| scroll_style = WS_HSCROLL | ES_AUTOHSCROLL | ES_AUTOVSCROLL; |
| break; |
| case java_awt_TextArea_SCROLLBARS_BOTH: |
| scroll_style = WS_VSCROLL | WS_HSCROLL | |
| ES_AUTOVSCROLL | ES_AUTOHSCROLL; |
| break; |
| } |
| |
| /* |
| * Specify ES_DISABLENOSCROLL - RichEdit control style to disable |
| * scrollbars instead of hiding them when not needed. |
| */ |
| DWORD style = WS_CHILD | WS_CLIPSIBLINGS | ES_LEFT | ES_MULTILINE | |
| ES_WANTRETURN | scroll_style | ES_DISABLENOSCROLL; |
| DWORD exStyle = WS_EX_CLIENTEDGE; |
| if (GetRTL()) { |
| exStyle |= WS_EX_RIGHT | WS_EX_LEFTSCROLLBAR; |
| if (GetRTLReadingOrder()) |
| exStyle |= WS_EX_RTLREADING; |
| } |
| |
| jint x = env->GetIntField(target, AwtComponent::xID); |
| jint y = env->GetIntField(target, AwtComponent::yID); |
| jint width = env->GetIntField(target, AwtComponent::widthID); |
| jint height = env->GetIntField(target, AwtComponent::heightID); |
| |
| c->CreateHWnd(env, L"", style, exStyle, |
| x, y, width, height, |
| awtParent->GetHWnd(), |
| reinterpret_cast<HMENU>(static_cast<INT_PTR>( |
| awtParent->CreateControlID())), |
| ::GetSysColor(COLOR_WINDOWTEXT), |
| ::GetSysColor(COLOR_WINDOW), |
| peer); |
| |
| // Fix for 4753116. |
| // If it is not win95 (we are using Richedit 2.0) |
| // we set plain text mode, in which the control is |
| // similar to a standard edit control: |
| // - The text in a plain text control can have only |
| // one format. |
| // - The user cannot paste rich text formats, such as RTF |
| // or embedded objects into a plain text control. |
| // - Rich text mode controls always have a default |
| // end-of-document marker or carriage return, |
| // to format paragraphs. |
| // kdm@sparc.spb.su |
| c->SendMessage(EM_SETTEXTMODE, TM_PLAINTEXT, 0); |
| |
| c->m_backgroundColorSet = TRUE; |
| /* suppress inheriting parent's color. */ |
| c->UpdateBackground(env, target); |
| c->SendMessage(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, |
| MAKELPARAM(1, 1)); |
| /* |
| * Fix for BugTraq Id 4260109. |
| * Set the text limit to the maximum. |
| * Use EM_EXLIMITTEXT for RichEdit controls. |
| * For some reason RichEdit 1.0 becomes read-only if the |
| * specified limit is greater than 0x7FFFFFFD. |
| */ |
| c->SendMessage(EM_EXLIMITTEXT, 0, 0x7FFFFFFD); |
| |
| /* Unregister RichEdit built-in drop target. */ |
| VERIFY(::RevokeDragDrop(c->GetHWnd()) != DRAGDROP_E_INVALIDHWND); |
| |
| /* To enforce CF_TEXT format for paste operations. */ |
| VERIFY(c->SendMessage(EM_SETOLECALLBACK, 0, |
| (LPARAM)&GetOleCallback())); |
| |
| c->SendMessage(EM_SETEVENTMASK, 0, ENM_CHANGE); |
| } |
| } catch (...) { |
| env->DeleteLocalRef(target); |
| throw; |
| } |
| |
| done: |
| env->DeleteLocalRef(target); |
| |
| return c; |
| } |
| |
| void AwtTextArea::EditSetSel(CHARRANGE &cr) { |
| // Fix for 5003402: added restoring/hiding selection to enable automatic scrolling |
| SendMessage(EM_HIDESELECTION, FALSE, TRUE); |
| SendMessage(EM_EXSETSEL, 0, reinterpret_cast<LPARAM>(&cr)); |
| SendMessage(EM_HIDESELECTION, TRUE, TRUE); |
| // 6417581: force expected drawing |
| if (IS_WINVISTA && cr.cpMin == cr.cpMax) { |
| ::InvalidateRect(GetHWnd(), NULL, TRUE); |
| } |
| } |
| |
| void AwtTextArea::EditGetSel(CHARRANGE &cr) { |
| SendMessage(EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&cr)); |
| } |
| |
| LONG AwtTextArea::EditGetCharFromPos(POINT& pt) { |
| return static_cast<LONG>(SendMessage(EM_CHARFROMPOS, 0, |
| reinterpret_cast<LPARAM>(&pt))); |
| } |
| |
| /* Count how many '\n's are there in jStr */ |
| size_t AwtTextArea::CountNewLines(JNIEnv *env, jstring jStr, size_t maxlen) |
| { |
| size_t nNewlines = 0; |
| |
| if (jStr == NULL) { |
| return nNewlines; |
| } |
| /* |
| * Fix for BugTraq Id 4260109. |
| * Don't use TO_WSTRING since it allocates memory on the stack |
| * causing stack overflow when the text is very long. |
| */ |
| size_t length = env->GetStringLength(jStr) + 1; |
| WCHAR *string = new WCHAR[length]; |
| env->GetStringRegion(jStr, 0, static_cast<jsize>(length - 1), reinterpret_cast<jchar*>(string)); |
| string[length-1] = '\0'; |
| for (size_t i = 0; i < maxlen && i < length - 1; i++) { |
| if (string[i] == L'\n') { |
| nNewlines++; |
| } |
| } |
| delete[] string; |
| return nNewlines; |
| } |
| |
| BOOL AwtTextArea::InheritsNativeMouseWheelBehavior() {return true;} |
| |
| MsgRouting |
| AwtTextArea::PreProcessMsg(MSG& msg) |
| { |
| MsgRouting mr = mrPassAlong; |
| static BOOL bPassAlongWmLButtonUp = TRUE; |
| |
| if (msg.message == WM_LBUTTONDBLCLK) { |
| bPassAlongWmLButtonUp = FALSE; |
| } |
| |
| /* |
| * For some reason RichEdit 1.0 filters out WM_LBUTTONUP after |
| * WM_LBUTTONDBLCLK. To work around this "feature" we send WM_LBUTTONUP |
| * directly to the window procedure and consume instead of passing it |
| * to the next hook. |
| */ |
| if (msg.message == WM_LBUTTONUP && bPassAlongWmLButtonUp == FALSE) { |
| SendMessage(WM_LBUTTONUP, msg.wParam, msg.lParam); |
| bPassAlongWmLButtonUp = TRUE; |
| mr = mrConsume; |
| } |
| |
| if (mr == mrPassAlong) { |
| mr = AwtComponent::PreProcessMsg(msg); |
| } |
| |
| return mr; |
| } |
| |
| LRESULT |
| AwtTextArea::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { |
| |
| LRESULT retValue = 0; |
| MsgRouting mr = mrDoDefault; |
| |
| switch (message) { |
| case WM_PRINTCLIENT: |
| { |
| FORMATRANGE fr; |
| HDC hPrinterDC = (HDC)wParam; |
| int nHorizRes = ::GetDeviceCaps(hPrinterDC, HORZRES); |
| int nVertRes = ::GetDeviceCaps(hPrinterDC, VERTRES); |
| int nLogPixelsX = ::GetDeviceCaps(hPrinterDC, LOGPIXELSX); |
| int nLogPixelsY = ::GetDeviceCaps(hPrinterDC, LOGPIXELSY); |
| |
| // Ensure the printer DC is in MM_TEXT mode. |
| ::SetMapMode ( hPrinterDC, MM_TEXT ); |
| |
| // Rendering to the same DC we are measuring. |
| ::ZeroMemory(&fr, sizeof(fr)); |
| fr.hdc = fr.hdcTarget = hPrinterDC; |
| // Set up the page. |
| fr.rcPage.left = fr.rcPage.top = 0; |
| fr.rcPage.right = (nHorizRes/nLogPixelsX) * 1440; // in twips |
| fr.rcPage.bottom = (nVertRes/nLogPixelsY) * 1440; |
| fr.rc.left = fr.rcPage.left; |
| fr.rc.top = fr.rcPage.top; |
| fr.rc.right = fr.rcPage.right; |
| fr.rc.bottom = fr.rcPage.bottom; |
| |
| // start printing from the first visible line |
| LRESULT nLine = SendMessage(EM_GETFIRSTVISIBLELINE, 0, 0); |
| LONG startCh = static_cast<LONG>(SendMessage(EM_LINEINDEX, |
| (WPARAM)nLine, 0)); |
| fr.chrg.cpMin = startCh; |
| fr.chrg.cpMax = -1; |
| |
| SendMessage(EM_FORMATRANGE, TRUE, (LPARAM)&fr); |
| } |
| |
| break; |
| case EM_SETCHARFORMAT: |
| case WM_SETFONT: |
| SetIgnoreEnChange(TRUE); |
| break; |
| } |
| |
| retValue = AwtComponent::WindowProc(message, wParam, lParam); |
| |
| switch (message) { |
| case EM_SETCHARFORMAT: |
| case WM_SETFONT: |
| SetIgnoreEnChange(FALSE); |
| break; |
| } |
| |
| return retValue; |
| } |
| |
| /* |
| * This routine is a window procedure for the subclass of the standard edit control |
| * used to generate context menu. RichEdit controls don't have built-in context menu. |
| * To implement this functionality we have to create an invisible edit control and |
| * forward WM_CONTEXTMENU messages from a RichEdit control to this helper edit control. |
| * While the edit control context menu is active we intercept the message generated in |
| * response to particular item selection and forward it back to the RichEdit control. |
| * (See AwtTextArea::WmContextMenu for more details). |
| */ |
| LRESULT |
| AwtTextArea::EditProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { |
| |
| static BOOL bContextMenuActive = FALSE; |
| |
| LRESULT retValue = 0; |
| MsgRouting mr = mrDoDefault; |
| |
| DASSERT(::IsWindow(::GetParent(hWnd))); |
| |
| switch (message) { |
| case WM_UNDO: |
| case WM_CUT: |
| case WM_COPY: |
| case WM_PASTE: |
| case WM_CLEAR: |
| case EM_SETSEL: |
| if (bContextMenuActive) { |
| ::SendMessage(::GetParent(hWnd), message, wParam, lParam); |
| mr = mrConsume; |
| } |
| break; |
| case WM_CONTEXTMENU: |
| bContextMenuActive = TRUE; |
| break; |
| } |
| |
| if (mr == mrDoDefault) { |
| DASSERT(sm_pDefWindowProc != NULL); |
| retValue = ::CallWindowProc(sm_pDefWindowProc, |
| hWnd, message, wParam, lParam); |
| } |
| |
| if (message == WM_CONTEXTMENU) { |
| bContextMenuActive = FALSE; |
| } |
| |
| return retValue; |
| } |
| |
| MsgRouting |
| AwtTextArea::WmContextMenu(HWND hCtrl, UINT xPos, UINT yPos) { |
| /* Use the system provided edit control class to generate context menu. */ |
| if (m_hEditCtrl == NULL) { |
| DWORD dwStyle = WS_CHILD; |
| DWORD dwExStyle = 0; |
| m_hEditCtrl = ::CreateWindowEx(dwExStyle, |
| L"EDIT", |
| L"TEXT", |
| dwStyle, |
| 0, 0, 0, 0, |
| GetHWnd(), |
| reinterpret_cast<HMENU>( |
| static_cast<INT_PTR>( |
| CreateControlID())), |
| AwtToolkit::GetInstance().GetModuleHandle(), |
| NULL); |
| DASSERT(m_hEditCtrl != NULL); |
| if (sm_pDefWindowProc == NULL) { |
| sm_pDefWindowProc = (WNDPROC)::GetWindowLongPtr(m_hEditCtrl, |
| GWLP_WNDPROC); |
| } |
| ::SetLastError(0); |
| INT_PTR ret = ::SetWindowLongPtr(m_hEditCtrl, GWLP_WNDPROC, |
| (INT_PTR)AwtTextArea::EditProc); |
| DASSERT(ret != 0 || ::GetLastError() == 0); |
| } |
| |
| /* |
| * Tricks on the edit control to ensure that its context menu has |
| * the correct set of enabled items according to the RichEdit state. |
| */ |
| ::SetWindowText(m_hEditCtrl, TEXT("TEXT")); |
| |
| if (m_bCanUndo == TRUE && SendMessage(EM_CANUNDO)) { |
| /* Enable 'Undo' item. */ |
| ::SendMessage(m_hEditCtrl, WM_CHAR, 'A', 0); |
| } |
| |
| { |
| /* |
| * Initial selection for the edit control - (0,1). |
| * This enables 'Cut', 'Copy' and 'Delete' and 'Select All'. |
| */ |
| INT nStart = 0; |
| INT nEnd = 1; |
| if (SendMessage(EM_SELECTIONTYPE) == SEL_EMPTY) { |
| /* |
| * RichEdit selection is empty - clear selection of the edit control. |
| * This disables 'Cut', 'Copy' and 'Delete'. |
| */ |
| nStart = -1; |
| nEnd = 0; |
| } else { |
| |
| CHARRANGE cr; |
| EditGetSel(cr); |
| /* Check if all the text is selected. */ |
| if (cr.cpMin == 0) { |
| |
| int len = ::GetWindowTextLength(GetHWnd()); |
| if (cr.cpMin == 0 && cr.cpMax >= len) { |
| /* |
| * All the text is selected in RichEdit - select all the |
| * text in the edit control. This disables 'Select All'. |
| */ |
| nStart = 0; |
| nEnd = -1; |
| } |
| } |
| } |
| ::SendMessage(m_hEditCtrl, EM_SETSEL, (WPARAM)nStart, (LPARAM)nEnd); |
| } |
| |
| /* Disable 'Paste' item if the RichEdit control is read-only. */ |
| ::SendMessage(m_hEditCtrl, EM_SETREADONLY, |
| GetStyle() & ES_READONLY ? TRUE : FALSE, 0); |
| |
| POINT p; |
| p.x = xPos; |
| p.y = yPos; |
| |
| /* |
| * If the context menu is requested with SHIFT+F10 or VK_APPS key, |
| * we position its top left corner to the center of the RichEdit |
| * client rect. |
| */ |
| if (p.x == -1 && p.y == -1) { |
| RECT r; |
| VERIFY(::GetClientRect(GetHWnd(), &r)); |
| p.x = (r.left + r.right) / 2; |
| p.y = (r.top + r.bottom) / 2; |
| VERIFY(::ClientToScreen(GetHWnd(), &p)); |
| } |
| |
| // The context menu steals focus from the proxy. |
| // So, set the focus-restore flag up. |
| SetRestoreFocus(TRUE); |
| ::SendMessage(m_hEditCtrl, WM_CONTEXTMENU, (WPARAM)m_hEditCtrl, MAKELPARAM(p.x, p.y)); |
| SetRestoreFocus(FALSE); |
| |
| return mrConsume; |
| } |
| |
| MsgRouting |
| AwtTextArea::WmNcHitTest(UINT x, UINT y, LRESULT& retVal) |
| { |
| if (::IsWindow(AwtWindow::GetModalBlocker(AwtComponent::GetTopLevelParentForWindow(GetHWnd())))) { |
| retVal = HTCLIENT; |
| return mrConsume; |
| } |
| return AwtTextComponent::WmNcHitTest(x, y, retVal); |
| } |
| |
| |
| MsgRouting |
| AwtTextArea::WmNotify(UINT notifyCode) |
| { |
| if (notifyCode == EN_CHANGE) { |
| /* |
| * Ignore notifications if the text hasn't been changed. |
| * EN_CHANGE sent on character formatting changes as well. |
| */ |
| if (m_bIgnoreEnChange == FALSE) { |
| m_bCanUndo = TRUE; |
| DoCallback("valueChanged", "()V"); |
| } else { |
| m_bCanUndo = FALSE; |
| } |
| } |
| return mrDoDefault; |
| } |
| |
| MsgRouting |
| AwtTextArea::HandleEvent(MSG *msg, BOOL synthetic) |
| { |
| MsgRouting returnVal; |
| /* |
| * RichEdit 1.0 control starts internal message loop if the |
| * left mouse button is pressed while the cursor is not over |
| * the current selection or the current selection is empty. |
| * Because of this we don't receive WM_MOUSEMOVE messages |
| * while the left mouse button is pressed. To work around |
| * this behavior we process the relevant mouse messages |
| * by ourselves. |
| * By consuming WM_MOUSEMOVE messages we also don't give |
| * the RichEdit control a chance to recognize a drag gesture |
| * and initiate its own drag-n-drop operation. |
| * |
| * The workaround also allows us to implement synthetic focus mechanism. |
| * |
| */ |
| if (IsFocusingMouseMessage(msg)) { |
| CHARRANGE cr; |
| |
| LONG lCurPos = EditGetCharFromPos(msg->pt); |
| |
| EditGetSel(cr); |
| /* |
| * NOTE: Plain EDIT control always clears selection on mouse |
| * button press. We are clearing the current selection only if |
| * the mouse pointer is not over the selected region. |
| * In this case we sacrifice backward compatibility |
| * to allow dnd of the current selection. |
| */ |
| if (lCurPos < cr.cpMin || cr.cpMax <= lCurPos) { |
| if (msg->message == WM_LBUTTONDBLCLK) { |
| SetStartSelectionPos(static_cast<LONG>(SendMessage( |
| EM_FINDWORDBREAK, WB_MOVEWORDLEFT, lCurPos))); |
| SetEndSelectionPos(static_cast<LONG>(SendMessage( |
| EM_FINDWORDBREAK, WB_MOVEWORDRIGHT, lCurPos))); |
| } else { |
| SetStartSelectionPos(lCurPos); |
| SetEndSelectionPos(lCurPos); |
| } |
| cr.cpMin = GetStartSelectionPos(); |
| cr.cpMax = GetEndSelectionPos(); |
| EditSetSel(cr); |
| } |
| |
| delete msg; |
| return mrConsume; |
| } else if (msg->message == WM_LBUTTONUP) { |
| |
| /* |
| * If the left mouse button is pressed on the selected region |
| * we don't clear the current selection. We clear it on button |
| * release instead. This is to allow dnd of the current selection. |
| */ |
| if (GetStartSelectionPos() == -1 && GetEndSelectionPos() == -1) { |
| CHARRANGE cr; |
| |
| LONG lCurPos = EditGetCharFromPos(msg->pt); |
| |
| cr.cpMin = lCurPos; |
| cr.cpMax = lCurPos; |
| EditSetSel(cr); |
| } |
| |
| /* |
| * Cleanup the state variables when left mouse button is released. |
| * These state variables are designed to reflect the selection state |
| * while the left mouse button is pressed and be set to -1 otherwise. |
| */ |
| SetStartSelectionPos(-1); |
| SetEndSelectionPos(-1); |
| SetLastSelectionPos(-1); |
| |
| delete msg; |
| return mrConsume; |
| } else if (msg->message == WM_MOUSEMOVE && (msg->wParam & MK_LBUTTON)) { |
| |
| /* |
| * We consume WM_MOUSEMOVE while the left mouse button is pressed, |
| * so we have to simulate autoscrolling when mouse is moved outside |
| * of the client area. |
| */ |
| POINT p; |
| RECT r; |
| BOOL bScrollLeft = FALSE; |
| BOOL bScrollRight = FALSE; |
| BOOL bScrollUp = FALSE; |
| BOOL bScrollDown = FALSE; |
| |
| p.x = msg->pt.x; |
| p.y = msg->pt.y; |
| VERIFY(::GetClientRect(GetHWnd(), &r)); |
| |
| if (p.x < 0) { |
| bScrollLeft = TRUE; |
| p.x = 0; |
| } else if (p.x > r.right) { |
| bScrollRight = TRUE; |
| p.x = r.right - 1; |
| } |
| if (p.y < 0) { |
| bScrollUp = TRUE; |
| p.y = 0; |
| } else if (p.y > r.bottom) { |
| bScrollDown = TRUE; |
| p.y = r.bottom - 1; |
| } |
| |
| LONG lCurPos = EditGetCharFromPos(p); |
| |
| if (GetStartSelectionPos() != -1 && |
| GetEndSelectionPos() != -1 && |
| lCurPos != GetLastSelectionPos()) { |
| |
| CHARRANGE cr; |
| |
| SetLastSelectionPos(lCurPos); |
| |
| cr.cpMin = GetStartSelectionPos(); |
| cr.cpMax = GetLastSelectionPos(); |
| |
| EditSetSel(cr); |
| } |
| |
| if (bScrollLeft == TRUE || bScrollRight == TRUE) { |
| SCROLLINFO si; |
| memset(&si, 0, sizeof(si)); |
| si.cbSize = sizeof(si); |
| si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; |
| |
| VERIFY(::GetScrollInfo(GetHWnd(), SB_HORZ, &si)); |
| if (bScrollLeft == TRUE) { |
| si.nPos = si.nPos - si.nPage / 2; |
| si.nPos = max(si.nMin, si.nPos); |
| } else if (bScrollRight == TRUE) { |
| si.nPos = si.nPos + si.nPage / 2; |
| si.nPos = min(si.nPos, si.nMax); |
| } |
| /* |
| * Okay to use 16-bit position since RichEdit control adjusts |
| * its scrollbars so that their range is always 16-bit. |
| */ |
| DASSERT(abs(si.nPos) < 0x8000); |
| SendMessage(WM_HSCROLL, |
| MAKEWPARAM(SB_THUMBPOSITION, LOWORD(si.nPos))); |
| } |
| if (bScrollUp == TRUE) { |
| SendMessage(EM_LINESCROLL, 0, -1); |
| } else if (bScrollDown == TRUE) { |
| SendMessage(EM_LINESCROLL, 0, 1); |
| } |
| delete msg; |
| return mrConsume; |
| } else if (msg->message == WM_RBUTTONUP || |
| (msg->message == WM_SYSKEYDOWN && msg->wParam == VK_F10 && |
| HIBYTE(::GetKeyState(VK_SHIFT)))) { |
| POINT p; |
| if (msg->message == WM_RBUTTONUP) { |
| VERIFY(::GetCursorPos(&p)); |
| } else { |
| p.x = -1; |
| p.y = -1; |
| } |
| |
| if (!::PostMessage(GetHWnd(), WM_CONTEXTMENU, (WPARAM)GetHWnd(), |
| MAKELPARAM(p.x, p.y))) { |
| JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); |
| JNU_ThrowInternalError(env, "Message not posted, native event queue may be full."); |
| env->ExceptionDescribe(); |
| env->ExceptionClear(); |
| } |
| delete msg; |
| return mrConsume; |
| } else if (msg->message == WM_MOUSEWHEEL) { |
| // 4417236: If there is an old version of RichEd32.dll which |
| // does not provide the mouse wheel scrolling we have to |
| // interpret WM_MOUSEWHEEL as a sequence of scroll messages. |
| // kdm@sparc.spb.su |
| UINT platfScrollLines = 3; |
| // Retrieve a number of scroll lines. |
| ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, |
| &platfScrollLines, 0); |
| |
| if (platfScrollLines > 0) { |
| HWND hWnd = GetHWnd(); |
| LONG styles = ::GetWindowLong(hWnd, GWL_STYLE); |
| |
| RECT rect; |
| // rect.left and rect.top are zero. |
| // rect.right and rect.bottom contain the width and height |
| VERIFY(::GetClientRect(hWnd, &rect)); |
| |
| // calculate a number of visible lines |
| TEXTMETRIC tm; |
| HDC hDC = ::GetDC(hWnd); |
| DASSERT(hDC != NULL); |
| VERIFY(::GetTextMetrics(hDC, &tm)); |
| VERIFY(::ReleaseDC(hWnd, hDC)); |
| LONG visibleLines = rect.bottom / tm.tmHeight + 1; |
| |
| LONG lineCount = static_cast<LONG>(::SendMessage(hWnd, |
| EM_GETLINECOUNT, 0, 0)); |
| BOOL sb_vert_disabled = (styles & WS_VSCROLL) == 0 |
| || (lineCount <= visibleLines); |
| |
| LONG *delta_accum = &m_lVDeltaAccum; |
| UINT wm_msg = WM_VSCROLL; |
| int sb_type = SB_VERT; |
| |
| if (sb_vert_disabled && (styles & WS_HSCROLL)) { |
| delta_accum = &m_lHDeltaAccum; |
| wm_msg = WM_HSCROLL; |
| sb_type = SB_HORZ; |
| } |
| |
| int delta = (short) HIWORD(msg->wParam); |
| *delta_accum += delta; |
| if (abs(*delta_accum) >= WHEEL_DELTA) { |
| if (platfScrollLines == WHEEL_PAGESCROLL) { |
| // Synthesize a page down or a page up message. |
| ::SendMessage(hWnd, wm_msg, |
| (delta > 0) ? SB_PAGEUP : SB_PAGEDOWN, 0); |
| *delta_accum = 0; |
| } else { |
| // We provide a friendly behavior of text scrolling. |
| // During of scrolling the text can be out of the client |
| // area's boundary. Here we try to prevent this behavior. |
| SCROLLINFO si; |
| ::ZeroMemory(&si, sizeof(si)); |
| si.cbSize = sizeof(SCROLLINFO); |
| si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE; |
| int actualScrollLines = |
| abs((int)(platfScrollLines * (*delta_accum / WHEEL_DELTA))); |
| for (int i = 0; i < actualScrollLines; i++) { |
| if (::GetScrollInfo(hWnd, sb_type, &si)) { |
| if ((wm_msg == WM_VSCROLL) |
| && ((*delta_accum < 0 |
| && si.nPos >= (si.nMax - (int) si.nPage)) |
| || (*delta_accum > 0 |
| && si.nPos <= si.nMin))) { |
| break; |
| } |
| } |
| // Here we don't send EM_LINESCROLL or EM_SCROLL |
| // messages to rich edit because it doesn't |
| // provide horizontal scrolling. |
| // So it's only possible to scroll by 1 line |
| // at a time to prevent scrolling when the |
| // scrollbar thumb reaches its boundary position. |
| ::SendMessage(hWnd, wm_msg, |
| (*delta_accum>0) ? SB_LINEUP : SB_LINEDOWN, 0); |
| } |
| *delta_accum %= WHEEL_DELTA; |
| } |
| } else { |
| *delta_accum = 0; |
| } |
| } |
| delete msg; |
| return mrConsume; |
| // 4417236: end of fix |
| } |
| |
| /* |
| * Store the 'synthetic' parameter so that the WM_PASTE security check |
| * happens only for synthetic events. |
| */ |
| m_synthetic = synthetic; |
| returnVal = AwtComponent::HandleEvent(msg, synthetic); |
| m_synthetic = FALSE; |
| |
| return returnVal; |
| } |
| |
| /* |
| * WM_CTLCOLOR is not sent by rich edit controls. |
| * Use EM_SETCHARFORMAT and EM_SETBKGNDCOLOR to set |
| * respectively foreground and background color. |
| */ |
| void AwtTextArea::SetColor(COLORREF c) { |
| AwtComponent::SetColor(c); |
| |
| CHARFORMAT cf; |
| memset(&cf, 0, sizeof(cf)); |
| cf.cbSize = sizeof(cf); |
| cf.dwMask = CFM_COLOR; |
| |
| cf.crTextColor = ::IsWindowEnabled(GetHWnd()) ? GetColor() : ::GetSysColor(COLOR_3DSHADOW); |
| |
| /* |
| * The documentation for EM_GETCHARFORMAT is not exactly |
| * correct. It appears that wParam has the same meaning |
| * as for EM_SETCHARFORMAT. Our task is to secure that |
| * all the characters in the control have the required |
| * formatting. That's why we use SCF_ALL. |
| */ |
| VERIFY(SendMessage(EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&cf)); |
| VERIFY(SendMessage(EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM)&cf)); |
| } |
| |
| /* |
| * In responce to EM_SETBKGNDCOLOR rich edit changes |
| * its bg color and repaints itself so we don't need |
| * to force repaint. |
| */ |
| void AwtTextArea::SetBackgroundColor(COLORREF c) { |
| AwtComponent::SetBackgroundColor(c); |
| SendMessage(EM_SETBKGNDCOLOR, (WPARAM)FALSE, (LPARAM)GetBackgroundColor()); |
| } |
| |
| /* |
| * Disabled edit control has grayed foreground. |
| * Disabled RichEdit 1.0 control has original foreground. |
| * Thus we have to set grayed foreground manually. |
| */ |
| void AwtTextArea::Enable(BOOL bEnable) |
| { |
| AwtComponent::Enable(bEnable); |
| |
| SetColor(GetColor()); |
| } |
| |
| |
| /* Fix for 4776535, 4648702 |
| * If width is 0 or 1 Windows hides the horizontal scroll bar even |
| * if the WS_HSCROLL style is set. It is a bug in Windows. |
| * As a workaround we should set an initial width to 2. |
| * kdm@sparc.spb.su |
| */ |
| void AwtTextArea::Reshape(int x, int y, int w, int h) |
| { |
| if (w < 2) { |
| w = 2; |
| } |
| AwtTextComponent::Reshape(x, y, w, h); |
| } |
| |
| LONG AwtTextArea::getJavaSelPos(LONG orgPos) |
| { |
| long wlen; |
| long pos = orgPos; |
| long cur = 0; |
| long retval = 0; |
| LPTSTR wbuf; |
| |
| if ((wlen = GetTextLength()) == 0) |
| return 0; |
| wbuf = new TCHAR[wlen + 1]; |
| GetText(wbuf, wlen + 1); |
| if (m_isLFonly == TRUE) { |
| wlen = RemoveCR(wbuf); |
| } |
| |
| while (cur < pos && cur < wlen) { |
| if (wbuf[cur] == _T('\r') && wbuf[cur + 1] == _T('\n')) { |
| pos++; |
| } |
| cur++; |
| retval++; |
| } |
| delete[] wbuf; |
| return retval; |
| } |
| |
| LONG AwtTextArea::getWin32SelPos(LONG orgPos) |
| { |
| if (GetTextLength() == 0) |
| return 0; |
| return orgPos; |
| } |
| |
| void AwtTextArea::SetSelRange(LONG start, LONG end) |
| { |
| CHARRANGE cr; |
| cr.cpMin = getWin32SelPos(start); |
| cr.cpMax = getWin32SelPos(end); |
| EditSetSel(cr); |
| } |
| |
| |
| void AwtTextArea::_ReplaceText(void *param) |
| { |
| JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); |
| |
| ReplaceTextStruct *rts = (ReplaceTextStruct *)param; |
| |
| jobject textComponent = rts->textComponent; |
| jstring text = rts->text; |
| jint start = rts->start; |
| jint end = rts->end; |
| |
| AwtTextComponent *c = NULL; |
| |
| PDATA pData; |
| JNI_CHECK_PEER_GOTO(textComponent, done); |
| JNI_CHECK_NULL_GOTO(text, "null string", done); |
| |
| c = (AwtTextComponent *)pData; |
| if (::IsWindow(c->GetHWnd())) |
| { |
| jsize length = env->GetStringLength(text) + 1; |
| // Bugid 4141477 - Can't use TO_WSTRING here because it uses alloca |
| // WCHAR* buffer = TO_WSTRING(text); |
| TCHAR *buffer = new TCHAR[length]; |
| env->GetStringRegion(text, 0, length-1, reinterpret_cast<jchar*>(buffer)); |
| buffer[length-1] = '\0'; |
| |
| c->CheckLineSeparator(buffer); |
| c->RemoveCR(buffer); |
| // Fix for 5003402: added restoring/hiding selection to enable automatic scrolling |
| c->SendMessage(EM_HIDESELECTION, FALSE, TRUE); |
| c->SendMessage(EM_SETSEL, start, end); |
| c->SendMessage(EM_REPLACESEL, FALSE, (LPARAM)buffer); |
| c->SendMessage(EM_HIDESELECTION, TRUE, TRUE); |
| |
| delete[] buffer; |
| } |
| |
| done: |
| env->DeleteGlobalRef(textComponent); |
| env->DeleteGlobalRef(text); |
| |
| delete rts; |
| } |
| |
| |
| /************************************************************************ |
| * TextArea native methods |
| */ |
| |
| extern "C" { |
| |
| /* |
| * Class: java_awt_TextArea |
| * Method: initIDs |
| * Signature: ()V |
| */ |
| JNIEXPORT void JNICALL |
| Java_java_awt_TextArea_initIDs(JNIEnv *env, jclass cls) |
| { |
| TRY; |
| |
| AwtTextArea::scrollbarVisibilityID = |
| env->GetFieldID(cls, "scrollbarVisibility", "I"); |
| |
| DASSERT(AwtTextArea::scrollbarVisibilityID != NULL); |
| |
| CATCH_BAD_ALLOC; |
| } |
| |
| } /* extern "C" */ |
| |
| |
| /************************************************************************ |
| * WTextAreaPeer native methods |
| */ |
| |
| extern "C" { |
| |
| /* |
| * Class: sun_awt_windows_WTextAreaPeer |
| * Method: create |
| * Signature: (Lsun/awt/windows/WComponentPeer;)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_sun_awt_windows_WTextAreaPeer_create(JNIEnv *env, jobject self, |
| jobject parent) |
| { |
| TRY; |
| |
| PDATA pData; |
| JNI_CHECK_PEER_RETURN(parent); |
| AwtToolkit::CreateComponent(self, parent, |
| (AwtToolkit::ComponentFactory) |
| AwtTextArea::Create); |
| JNI_CHECK_PEER_CREATION_RETURN(self); |
| |
| CATCH_BAD_ALLOC; |
| } |
| |
| /* |
| * Class: sun_awt_windows_WTextAreaPeer |
| * Method: replaceText |
| * Signature: (Ljava/lang/String;II)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_sun_awt_windows_WTextAreaPeer_replaceText(JNIEnv *env, jobject self, |
| jstring text, |
| jint start, jint end) |
| { |
| TRY; |
| |
| jobject selfGlobalRef = env->NewGlobalRef(self); |
| jstring textGlobalRef = (jstring)env->NewGlobalRef(text); |
| |
| ReplaceTextStruct *rts = new ReplaceTextStruct; |
| rts->textComponent = selfGlobalRef; |
| rts->text = textGlobalRef; |
| rts->start = start; |
| rts->end = end; |
| |
| AwtToolkit::GetInstance().SyncCall(AwtTextArea::_ReplaceText, rts); |
| // global refs and rts are deleted in _ReplaceText() |
| |
| CATCH_BAD_ALLOC; |
| } |
| |
| /* |
| * Class: sun_awt_windows_WTextAreaPeer |
| * Method: insertText |
| * Signature: (Ljava/lang/String;I)V |
| */ |
| JNIEXPORT void JNICALL |
| Java_sun_awt_windows_WTextAreaPeer_insertText(JNIEnv *env, jobject self, |
| jstring text, jint pos) |
| { |
| Java_sun_awt_windows_WTextAreaPeer_replaceText(env, self, text, pos, pos); |
| } |
| |
| } /* extern "C" */ |
| |
| |
| AwtTextArea::OleCallback AwtTextArea::sm_oleCallback; |
| |
| /************************************************************************ |
| * Inner class OleCallback definition. |
| */ |
| |
| AwtTextArea::OleCallback::OleCallback() { |
| m_refs = 0; |
| AddRef(); |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::QueryInterface(REFIID riid, LPVOID * ppvObj) { |
| |
| TRY; |
| |
| if (::IsEqualIID(riid, IID_IUnknown)) { |
| *ppvObj = (void __RPC_FAR *__RPC_FAR)(IUnknown*)this; |
| AddRef(); |
| return S_OK; |
| } else if (::IsEqualIID(riid, IID_IRichEditOleCallback)) { |
| *ppvObj = (void __RPC_FAR *__RPC_FAR)(IRichEditOleCallback*)this; |
| AddRef(); |
| return S_OK; |
| } else { |
| *ppvObj = NULL; |
| return E_NOINTERFACE; |
| } |
| |
| CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY); |
| } |
| |
| STDMETHODIMP_(ULONG) |
| AwtTextArea::OleCallback::AddRef() { |
| return ++m_refs; |
| } |
| |
| STDMETHODIMP_(ULONG) |
| AwtTextArea::OleCallback::Release() { |
| int refs; |
| |
| if ((refs = --m_refs) == 0) delete this; |
| |
| return (ULONG)refs; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::GetNewStorage(LPSTORAGE FAR * ppstg) { |
| return E_NOTIMPL; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::GetInPlaceContext(LPOLEINPLACEFRAME FAR * ppipframe, |
| LPOLEINPLACEUIWINDOW FAR* ppipuiDoc, |
| LPOLEINPLACEFRAMEINFO pipfinfo) |
| { |
| return E_NOTIMPL; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::ShowContainerUI(BOOL fShow) { |
| return E_NOTIMPL; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::QueryInsertObject(LPCLSID pclsid, |
| LPSTORAGE pstg, |
| LONG cp) { |
| return NOERROR; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::DeleteObject(LPOLEOBJECT poleobj) { |
| return NOERROR; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::QueryAcceptData(LPDATAOBJECT pdataobj, |
| CLIPFORMAT *pcfFormat, |
| DWORD reco, |
| BOOL fReally, |
| HGLOBAL hMetaPict) { |
| if (reco == RECO_PASTE) { |
| // If CF_TEXT format is available edit controls will select it, |
| // otherwise if it is CF_UNICODETEXT is available it will be |
| // selected, otherwise if CF_OEMTEXT is available it will be selected. |
| if (::IsClipboardFormatAvailable(CF_TEXT)) { |
| *pcfFormat = CF_TEXT; |
| } else if (::IsClipboardFormatAvailable(CF_UNICODETEXT)) { |
| *pcfFormat = CF_UNICODETEXT; |
| } else if (::IsClipboardFormatAvailable(CF_OEMTEXT)) { |
| *pcfFormat = CF_OEMTEXT; |
| } else { |
| // Don't allow rich edit to paste clipboard data |
| // in other formats. |
| *pcfFormat = CF_TEXT; |
| } |
| } |
| |
| return NOERROR; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::ContextSensitiveHelp(BOOL fEnterMode) { |
| return NOERROR; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::GetClipboardData(CHARRANGE *pchrg, |
| DWORD reco, |
| LPDATAOBJECT *ppdataobj) { |
| return E_NOTIMPL; |
| } |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::GetDragDropEffect(BOOL fDrag, |
| DWORD grfKeyState, |
| LPDWORD pdwEffect) { |
| |
| return E_NOTIMPL; |
| } |
| |
| |
| STDMETHODIMP |
| AwtTextArea::OleCallback::GetContextMenu(WORD seltype, |
| LPOLEOBJECT lpoleobj, |
| CHARRANGE FAR * lpchrg, |
| HMENU FAR * lphmenu) { |
| return E_NOTIMPL; |
| } |