blob: 5cdbad959fcfa59ef97160519fc75451e7f42992 [file] [log] [blame]
/*
* 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;
}