1 /* 2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #include "awt_Toolkit.h" 27 #include "awt_TextArea.h" 28 #include "awt_TextComponent.h" 29 #include "awt_Canvas.h" 30 #include "awt_Window.h" 31 #include "awt_Frame.h" 32 33 /* IMPORTANT! Read the README.JNI file for notes on JNI converted AWT code. 34 */ 35 36 /***********************************************************************/ 37 // Struct for _ReplaceText() method 38 struct ReplaceTextStruct { 39 jobject textComponent; 40 jstring text; 41 int start, end; 42 }; 43 44 /************************************************************************ 45 * AwtTextArea fields 46 */ 47 48 jfieldID AwtTextArea::scrollbarVisibilityID; 49 50 /************************************************************************ 51 * AwtTextArea methods 52 */ 53 54 AwtTextArea::AwtTextArea() { 55 m_bCanUndo = FALSE; 56 m_lHDeltaAccum = 0; 57 m_lVDeltaAccum = 0; 58 } 59 60 AwtTextArea::~AwtTextArea() 61 { 62 } 63 64 void AwtTextArea::Dispose() 65 { 66 AwtTextComponent::Dispose(); 67 } 68 69 /* Create a new AwtTextArea object and window. */ 70 AwtTextArea* AwtTextArea::Create(jobject peer, jobject parent) 71 { 72 return (AwtTextArea*) AwtTextComponent::Create(peer, parent, true); 73 } 74 75 void AwtTextArea::EditSetSel(CHARRANGE &cr) { 76 SendMessage(EM_EXSETSEL, 0, reinterpret_cast<LPARAM>(&cr)); 77 // 6417581: force expected drawing 78 if (IS_WINVISTA && cr.cpMin == cr.cpMax) { 79 ::InvalidateRect(GetHWnd(), NULL, TRUE); 80 } 81 } 82 83 /* Count how many '\n's are there in jStr */ 84 size_t AwtTextArea::CountNewLines(JNIEnv *env, jstring jStr, size_t maxlen) 85 { 86 size_t nNewlines = 0; 87 88 if (jStr == NULL) { 89 return nNewlines; 90 } 91 /* 92 * Fix for BugTraq Id 4260109. 93 * Don't use TO_WSTRING since it allocates memory on the stack 94 * causing stack overflow when the text is very long. 95 */ 96 size_t length = env->GetStringLength(jStr) + 1; 97 WCHAR *string = new WCHAR[length]; 98 env->GetStringRegion(jStr, 0, static_cast<jsize>(length - 1), reinterpret_cast<jchar*>(string)); 99 string[length-1] = '\0'; 100 for (size_t i = 0; i < maxlen && i < length - 1; i++) { 101 if (string[i] == L'\n') { 102 nNewlines++; 103 } 104 } 105 delete[] string; 106 return nNewlines; 107 } 108 109 BOOL AwtTextArea::InheritsNativeMouseWheelBehavior() {return true;} 110 111 112 LRESULT 113 AwtTextArea::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { 114 115 LRESULT retValue = 0; 116 MsgRouting mr = mrDoDefault; 117 118 switch (message) { 119 case EM_SETCHARFORMAT: 120 case WM_SETFONT: 121 SetIgnoreEnChange(TRUE); 122 break; 123 } 124 125 retValue = AwtTextComponent::WindowProc(message, wParam, lParam); 126 127 switch (message) { 128 case EM_SETCHARFORMAT: 129 case WM_SETFONT: 130 SetIgnoreEnChange(FALSE); 131 break; 132 } 133 134 return retValue; 135 } 136 137 MsgRouting 138 AwtTextArea::WmNcHitTest(UINT x, UINT y, LRESULT& retVal) 139 { 140 if (::IsWindow(AwtWindow::GetModalBlocker(AwtComponent::GetTopLevelParentForWindow(GetHWnd())))) { 141 retVal = HTCLIENT; 142 return mrConsume; 143 } 144 return AwtTextComponent::WmNcHitTest(x, y, retVal); 145 } 146 147 148 MsgRouting 149 AwtTextArea::HandleEvent(MSG *msg, BOOL synthetic) 150 { 151 /* 152 * RichEdit 1.0 control starts internal message loop if the 153 * left mouse button is pressed while the cursor is not over 154 * the current selection or the current selection is empty. 155 * Because of this we don't receive WM_MOUSEMOVE messages 156 * while the left mouse button is pressed. To work around 157 * this behavior we process the relevant mouse messages 158 * by ourselves. 159 * By consuming WM_MOUSEMOVE messages we also don't give 160 * the RichEdit control a chance to recognize a drag gesture 161 * and initiate its own drag-n-drop operation. 162 * 163 * The workaround also allows us to implement synthetic focus mechanism. 164 * 165 */ 166 if (IsFocusingMouseMessage(msg)) { 167 CHARRANGE cr; 168 169 LONG lCurPos = EditGetCharFromPos(msg->pt); 170 171 EditGetSel(cr); 172 /* 173 * NOTE: Plain EDIT control always clears selection on mouse 174 * button press. We are clearing the current selection only if 175 * the mouse pointer is not over the selected region. 176 * In this case we sacrifice backward compatibility 177 * to allow dnd of the current selection. 178 */ 179 if (lCurPos < cr.cpMin || cr.cpMax <= lCurPos) { 180 if (msg->message == WM_LBUTTONDBLCLK) { 181 SetStartSelectionPos(static_cast<LONG>(SendMessage( 182 EM_FINDWORDBREAK, WB_MOVEWORDLEFT, lCurPos))); 183 SetEndSelectionPos(static_cast<LONG>(SendMessage( 184 EM_FINDWORDBREAK, WB_MOVEWORDRIGHT, lCurPos))); 185 } else { 186 SetStartSelectionPos(lCurPos); 187 SetEndSelectionPos(lCurPos); 188 } 189 cr.cpMin = GetStartSelectionPos(); 190 cr.cpMax = GetEndSelectionPos(); 191 EditSetSel(cr); 192 } 193 194 delete msg; 195 return mrConsume; 196 } else if (msg->message == WM_LBUTTONUP) { 197 198 /* 199 * If the left mouse button is pressed on the selected region 200 * we don't clear the current selection. We clear it on button 201 * release instead. This is to allow dnd of the current selection. 202 */ 203 if (GetStartSelectionPos() == -1 && GetEndSelectionPos() == -1) { 204 CHARRANGE cr; 205 206 LONG lCurPos = EditGetCharFromPos(msg->pt); 207 208 cr.cpMin = lCurPos; 209 cr.cpMax = lCurPos; 210 EditSetSel(cr); 211 } 212 213 /* 214 * Cleanup the state variables when left mouse button is released. 215 * These state variables are designed to reflect the selection state 216 * while the left mouse button is pressed and be set to -1 otherwise. 217 */ 218 SetStartSelectionPos(-1); 219 SetEndSelectionPos(-1); 220 SetLastSelectionPos(-1); 221 222 delete msg; 223 return mrConsume; 224 } else if (msg->message == WM_MOUSEMOVE && (msg->wParam & MK_LBUTTON)) { 225 226 /* 227 * We consume WM_MOUSEMOVE while the left mouse button is pressed, 228 * so we have to simulate selection autoscrolling when mouse is moved 229 * outside of the client area. 230 */ 231 POINT p; 232 RECT r; 233 BOOL bScrollDown = FALSE; 234 235 p.x = msg->pt.x; 236 p.y = msg->pt.y; 237 VERIFY(::GetClientRect(GetHWnd(), &r)); 238 239 if (p.y > r.bottom) { 240 bScrollDown = TRUE; 241 p.y = r.bottom - 1; 242 } 243 244 LONG lCurPos = EditGetCharFromPos(p); 245 246 if (GetStartSelectionPos() != -1 && 247 GetEndSelectionPos() != -1 && 248 lCurPos != GetLastSelectionPos()) { 249 250 CHARRANGE cr; 251 252 SetLastSelectionPos(lCurPos); 253 254 cr.cpMin = GetStartSelectionPos(); 255 cr.cpMax = GetLastSelectionPos(); 256 257 EditSetSel(cr); 258 } 259 if (bScrollDown == TRUE) { 260 SendMessage(EM_LINESCROLL, 0, 1); 261 } 262 delete msg; 263 return mrConsume; 264 } else if (msg->message == WM_MOUSEWHEEL) { 265 // 4417236: If there is an old version of RichEd32.dll which 266 // does not provide the mouse wheel scrolling we have to 267 // interpret WM_MOUSEWHEEL as a sequence of scroll messages. 268 // kdm@sparc.spb.su 269 UINT platfScrollLines = 3; 270 // Retrieve a number of scroll lines. 271 ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, 272 &platfScrollLines, 0); 273 274 if (platfScrollLines > 0) { 275 HWND hWnd = GetHWnd(); 276 LONG styles = ::GetWindowLong(hWnd, GWL_STYLE); 277 278 RECT rect; 279 // rect.left and rect.top are zero. 280 // rect.right and rect.bottom contain the width and height 281 VERIFY(::GetClientRect(hWnd, &rect)); 282 283 // calculate a number of visible lines 284 TEXTMETRIC tm; 285 HDC hDC = ::GetDC(hWnd); 286 DASSERT(hDC != NULL); 287 VERIFY(::GetTextMetrics(hDC, &tm)); 288 VERIFY(::ReleaseDC(hWnd, hDC)); 289 LONG visibleLines = rect.bottom / tm.tmHeight + 1; 290 291 LONG lineCount = static_cast<LONG>(::SendMessage(hWnd, 292 EM_GETLINECOUNT, 0, 0)); 293 BOOL sb_vert_disabled = (styles & WS_VSCROLL) == 0 294 || (lineCount <= visibleLines); 295 296 LONG *delta_accum = &m_lVDeltaAccum; 297 UINT wm_msg = WM_VSCROLL; 298 int sb_type = SB_VERT; 299 300 if (sb_vert_disabled && (styles & WS_HSCROLL)) { 301 delta_accum = &m_lHDeltaAccum; 302 wm_msg = WM_HSCROLL; 303 sb_type = SB_HORZ; 304 } 305 306 int delta = (short) HIWORD(msg->wParam); 307 *delta_accum += delta; 308 if (abs(*delta_accum) >= WHEEL_DELTA) { 309 if (platfScrollLines == WHEEL_PAGESCROLL) { 310 // Synthesize a page down or a page up message. 311 ::SendMessage(hWnd, wm_msg, 312 (delta > 0) ? SB_PAGEUP : SB_PAGEDOWN, 0); 313 *delta_accum = 0; 314 } else { 315 // We provide a friendly behavior of text scrolling. 316 // During of scrolling the text can be out of the client 317 // area's boundary. Here we try to prevent this behavior. 318 SCROLLINFO si; 319 ::ZeroMemory(&si, sizeof(si)); 320 si.cbSize = sizeof(SCROLLINFO); 321 si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE; 322 int actualScrollLines = 323 abs((int)(platfScrollLines * (*delta_accum / WHEEL_DELTA))); 324 for (int i = 0; i < actualScrollLines; i++) { 325 if (::GetScrollInfo(hWnd, sb_type, &si)) { 326 if ((wm_msg == WM_VSCROLL) 327 && ((*delta_accum < 0 328 && si.nPos >= (si.nMax - (int) si.nPage)) 329 || (*delta_accum > 0 330 && si.nPos <= si.nMin))) { 331 break; 332 } 333 } 334 // Here we don't send EM_LINESCROLL or EM_SCROLL 335 // messages to rich edit because it doesn't 336 // provide horizontal scrolling. 337 // So it's only possible to scroll by 1 line 338 // at a time to prevent scrolling when the 339 // scrollbar thumb reaches its boundary position. 340 ::SendMessage(hWnd, wm_msg, 341 (*delta_accum>0) ? SB_LINEUP : SB_LINEDOWN, 0); 342 } 343 *delta_accum %= WHEEL_DELTA; 344 } 345 } else { 346 *delta_accum = 0; 347 } 348 } 349 delete msg; 350 return mrConsume; 351 // 4417236: end of fix 352 } 353 354 return AwtTextComponent::HandleEvent(msg, synthetic); 355 } 356 357 358 /* Fix for 4776535, 4648702 359 * If width is 0 or 1 Windows hides the horizontal scroll bar even 360 * if the WS_HSCROLL style is set. It is a bug in Windows. 361 * As a workaround we should set an initial width to 2. 362 * kdm@sparc.spb.su 363 */ 364 void AwtTextArea::Reshape(int x, int y, int w, int h) 365 { 366 if (w < 2) { 367 w = 2; 368 } 369 AwtTextComponent::Reshape(x, y, w, h); 370 } 371 372 LONG AwtTextArea::getJavaSelPos(LONG orgPos) 373 { 374 long wlen; 375 long pos = orgPos; 376 long cur = 0; 377 long retval = 0; 378 LPTSTR wbuf; 379 380 if ((wlen = GetTextLength()) == 0) 381 return 0; 382 wbuf = new TCHAR[wlen + 1]; 383 GetText(wbuf, wlen + 1); 384 if (m_isLFonly == TRUE) { 385 wlen = RemoveCR(wbuf); 386 } 387 388 while (cur < pos && cur < wlen) { 389 if (wbuf[cur] == _T('\r') && wbuf[cur + 1] == _T('\n')) { 390 pos++; 391 } 392 cur++; 393 retval++; 394 } 395 delete[] wbuf; 396 return retval; 397 } 398 399 LONG AwtTextArea::getWin32SelPos(LONG orgPos) 400 { 401 if (GetTextLength() == 0) 402 return 0; 403 return orgPos; 404 } 405 406 void AwtTextArea::SetSelRange(LONG start, LONG end) 407 { 408 CHARRANGE cr; 409 cr.cpMin = getWin32SelPos(start); 410 cr.cpMax = getWin32SelPos(end); 411 EditSetSel(cr); 412 } 413 414 415 void AwtTextArea::_ReplaceText(void *param) 416 { 417 JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); 418 419 ReplaceTextStruct *rts = (ReplaceTextStruct *)param; 420 421 jobject textComponent = rts->textComponent; 422 jstring text = rts->text; 423 jint start = rts->start; 424 jint end = rts->end; 425 426 AwtTextComponent *c = NULL; 427 428 PDATA pData; 429 JNI_CHECK_PEER_GOTO(textComponent, done); 430 JNI_CHECK_NULL_GOTO(text, "null string", done); 431 432 c = (AwtTextComponent *)pData; 433 if (::IsWindow(c->GetHWnd())) 434 { 435 jsize length = env->GetStringLength(text) + 1; 436 // Bugid 4141477 - Can't use TO_WSTRING here because it uses alloca 437 // WCHAR* buffer = TO_WSTRING(text); 438 TCHAR *buffer = new TCHAR[length]; 439 env->GetStringRegion(text, 0, length-1, reinterpret_cast<jchar*>(buffer)); 440 buffer[length-1] = '\0'; 441 442 c->CheckLineSeparator(buffer); 443 c->RemoveCR(buffer); 444 // Fix for 5003402: added restoring/hiding selection to enable automatic scrolling 445 c->SendMessage(EM_HIDESELECTION, FALSE, TRUE); 446 c->SendMessage(EM_SETSEL, start, end); 447 c->SendMessage(EM_REPLACESEL, FALSE, (LPARAM)buffer); 448 c->SendMessage(EM_HIDESELECTION, TRUE, TRUE); 449 450 delete[] buffer; 451 } 452 453 done: 454 env->DeleteGlobalRef(textComponent); 455 env->DeleteGlobalRef(text); 456 457 delete rts; 458 } 459 460 461 /************************************************************************ 462 * TextArea native methods 463 */ 464 465 extern "C" { 466 467 /* 468 * Class: java_awt_TextArea 469 * Method: initIDs 470 * Signature: ()V 471 */ 472 JNIEXPORT void JNICALL 473 Java_java_awt_TextArea_initIDs(JNIEnv *env, jclass cls) 474 { 475 TRY; 476 477 AwtTextArea::scrollbarVisibilityID = 478 env->GetFieldID(cls, "scrollbarVisibility", "I"); 479 480 DASSERT(AwtTextArea::scrollbarVisibilityID != NULL); 481 482 CATCH_BAD_ALLOC; 483 } 484 485 } /* extern "C" */ 486 487 488 /************************************************************************ 489 * WTextAreaPeer native methods 490 */ 491 492 extern "C" { 493 494 /* 495 * Class: sun_awt_windows_WTextAreaPeer 496 * Method: create 497 * Signature: (Lsun/awt/windows/WComponentPeer;)V 498 */ 499 JNIEXPORT void JNICALL 500 Java_sun_awt_windows_WTextAreaPeer_create(JNIEnv *env, jobject self, 501 jobject parent) 502 { 503 TRY; 504 505 PDATA pData; 506 JNI_CHECK_PEER_RETURN(parent); 507 AwtToolkit::CreateComponent(self, parent, 508 (AwtToolkit::ComponentFactory) 509 AwtTextArea::Create); 510 JNI_CHECK_PEER_CREATION_RETURN(self); 511 512 CATCH_BAD_ALLOC; 513 } 514 515 /* 516 * Class: sun_awt_windows_WTextAreaPeer 517 * Method: replaceRange 518 * Signature: (Ljava/lang/String;II)V 519 */ 520 JNIEXPORT void JNICALL 521 Java_sun_awt_windows_WTextAreaPeer_replaceRange(JNIEnv *env, jobject self, 522 jstring text, 523 jint start, jint end) 524 { 525 TRY; 526 527 jobject selfGlobalRef = env->NewGlobalRef(self); 528 jstring textGlobalRef = (jstring)env->NewGlobalRef(text); 529 530 ReplaceTextStruct *rts = new ReplaceTextStruct; 531 rts->textComponent = selfGlobalRef; 532 rts->text = textGlobalRef; 533 rts->start = start; 534 rts->end = end; 535 536 AwtToolkit::GetInstance().SyncCall(AwtTextArea::_ReplaceText, rts); 537 // global refs and rts are deleted in _ReplaceText() 538 539 CATCH_BAD_ALLOC; 540 } 541 } /* extern "C" */