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 // Fix for 5003402: added restoring/hiding selection to enable automatic scrolling 77 SendMessage(EM_HIDESELECTION, FALSE, TRUE); 78 SendMessage(EM_EXSETSEL, 0, reinterpret_cast<LPARAM>(&cr)); 79 SendMessage(EM_HIDESELECTION, TRUE, TRUE); 80 // 6417581: force expected drawing 81 if (IS_WINVISTA && cr.cpMin == cr.cpMax) { 82 ::InvalidateRect(GetHWnd(), NULL, TRUE); 83 } 84 } 85 86 /* Count how many '\n's are there in jStr */ 87 size_t AwtTextArea::CountNewLines(JNIEnv *env, jstring jStr, size_t maxlen) 88 { 89 size_t nNewlines = 0; 90 91 if (jStr == NULL) { 92 return nNewlines; 93 } 94 /* 95 * Fix for BugTraq Id 4260109. 96 * Don't use TO_WSTRING since it allocates memory on the stack 97 * causing stack overflow when the text is very long. 98 */ 99 size_t length = env->GetStringLength(jStr) + 1; 100 WCHAR *string = new WCHAR[length]; 101 env->GetStringRegion(jStr, 0, static_cast<jsize>(length - 1), reinterpret_cast<jchar*>(string)); 102 string[length-1] = '\0'; 103 for (size_t i = 0; i < maxlen && i < length - 1; i++) { 104 if (string[i] == L'\n') { 105 nNewlines++; 106 } 107 } 108 delete[] string; 109 return nNewlines; 110 } 111 112 BOOL AwtTextArea::InheritsNativeMouseWheelBehavior() {return true;} 113 114 115 LRESULT 116 AwtTextArea::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { 117 118 LRESULT retValue = 0; 119 MsgRouting mr = mrDoDefault; 120 121 switch (message) { 122 case EM_SETCHARFORMAT: 123 case WM_SETFONT: 124 SetIgnoreEnChange(TRUE); 125 break; 126 } 127 128 retValue = AwtTextComponent::WindowProc(message, wParam, lParam); 129 130 switch (message) { 131 case EM_SETCHARFORMAT: 132 case WM_SETFONT: 133 SetIgnoreEnChange(FALSE); 134 break; 135 } 136 137 return retValue; 138 } 139 140 MsgRouting 141 AwtTextArea::WmNcHitTest(UINT x, UINT y, LRESULT& retVal) 142 { 143 if (::IsWindow(AwtWindow::GetModalBlocker(AwtComponent::GetTopLevelParentForWindow(GetHWnd())))) { 144 retVal = HTCLIENT; 145 return mrConsume; 146 } 147 return AwtTextComponent::WmNcHitTest(x, y, retVal); 148 } 149 150 151 MsgRouting 152 AwtTextArea::HandleEvent(MSG *msg, BOOL synthetic) 153 { 154 /* 155 * RichEdit 1.0 control starts internal message loop if the 156 * left mouse button is pressed while the cursor is not over 157 * the current selection or the current selection is empty. 158 * Because of this we don't receive WM_MOUSEMOVE messages 159 * while the left mouse button is pressed. To work around 160 * this behavior we process the relevant mouse messages 161 * by ourselves. 162 * By consuming WM_MOUSEMOVE messages we also don't give 163 * the RichEdit control a chance to recognize a drag gesture 164 * and initiate its own drag-n-drop operation. 165 * 166 * The workaround also allows us to implement synthetic focus mechanism. 167 * 168 */ 169 if (IsFocusingMouseMessage(msg)) { 170 CHARRANGE cr; 171 172 LONG lCurPos = EditGetCharFromPos(msg->pt); 173 174 EditGetSel(cr); 175 /* 176 * NOTE: Plain EDIT control always clears selection on mouse 177 * button press. We are clearing the current selection only if 178 * the mouse pointer is not over the selected region. 179 * In this case we sacrifice backward compatibility 180 * to allow dnd of the current selection. 181 */ 182 if (lCurPos < cr.cpMin || cr.cpMax <= lCurPos) { 183 if (msg->message == WM_LBUTTONDBLCLK) { 184 SetStartSelectionPos(static_cast<LONG>(SendMessage( 185 EM_FINDWORDBREAK, WB_MOVEWORDLEFT, lCurPos))); 186 SetEndSelectionPos(static_cast<LONG>(SendMessage( 187 EM_FINDWORDBREAK, WB_MOVEWORDRIGHT, lCurPos))); 188 } else { 189 SetStartSelectionPos(lCurPos); 190 SetEndSelectionPos(lCurPos); 191 } 192 cr.cpMin = GetStartSelectionPos(); 193 cr.cpMax = GetEndSelectionPos(); 194 EditSetSel(cr); 195 } 196 197 delete msg; 198 return mrConsume; 199 } else if (msg->message == WM_LBUTTONUP) { 200 201 /* 202 * If the left mouse button is pressed on the selected region 203 * we don't clear the current selection. We clear it on button 204 * release instead. This is to allow dnd of the current selection. 205 */ 206 if (GetStartSelectionPos() == -1 && GetEndSelectionPos() == -1) { 207 CHARRANGE cr; 208 209 LONG lCurPos = EditGetCharFromPos(msg->pt); 210 211 cr.cpMin = lCurPos; 212 cr.cpMax = lCurPos; 213 EditSetSel(cr); 214 } 215 216 /* 217 * Cleanup the state variables when left mouse button is released. 218 * These state variables are designed to reflect the selection state 219 * while the left mouse button is pressed and be set to -1 otherwise. 220 */ 221 SetStartSelectionPos(-1); 222 SetEndSelectionPos(-1); 223 SetLastSelectionPos(-1); 224 225 delete msg; 226 return mrConsume; 227 } else if (msg->message == WM_MOUSEMOVE && (msg->wParam & MK_LBUTTON)) { 228 229 /* 230 * We consume WM_MOUSEMOVE while the left mouse button is pressed, 231 * so we have to simulate autoscrolling when mouse is moved outside 232 * of the client area. 233 */ 234 POINT p; 235 RECT r; 236 BOOL bScrollLeft = FALSE; 237 BOOL bScrollRight = FALSE; 238 BOOL bScrollUp = FALSE; 239 BOOL bScrollDown = FALSE; 240 241 p.x = msg->pt.x; 242 p.y = msg->pt.y; 243 VERIFY(::GetClientRect(GetHWnd(), &r)); 244 245 if (p.x < 0) { 246 bScrollLeft = TRUE; 247 p.x = 0; 248 } else if (p.x > r.right) { 249 bScrollRight = TRUE; 250 p.x = r.right - 1; 251 } 252 if (p.y < 0) { 253 bScrollUp = TRUE; 254 p.y = 0; 255 } else if (p.y > r.bottom) { 256 bScrollDown = TRUE; 257 p.y = r.bottom - 1; 258 } 259 260 LONG lCurPos = EditGetCharFromPos(p); 261 262 if (GetStartSelectionPos() != -1 && 263 GetEndSelectionPos() != -1 && 264 lCurPos != GetLastSelectionPos()) { 265 266 CHARRANGE cr; 267 268 SetLastSelectionPos(lCurPos); 269 270 cr.cpMin = GetStartSelectionPos(); 271 cr.cpMax = GetLastSelectionPos(); 272 273 EditSetSel(cr); 274 } 275 276 if (bScrollLeft == TRUE || bScrollRight == TRUE) { 277 SCROLLINFO si; 278 memset(&si, 0, sizeof(si)); 279 si.cbSize = sizeof(si); 280 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; 281 282 VERIFY(::GetScrollInfo(GetHWnd(), SB_HORZ, &si)); 283 if (bScrollLeft == TRUE) { 284 si.nPos = si.nPos - si.nPage / 2; 285 si.nPos = max(si.nMin, si.nPos); 286 } else if (bScrollRight == TRUE) { 287 si.nPos = si.nPos + si.nPage / 2; 288 si.nPos = min(si.nPos, si.nMax); 289 } 290 /* 291 * Okay to use 16-bit position since RichEdit control adjusts 292 * its scrollbars so that their range is always 16-bit. 293 */ 294 DASSERT(abs(si.nPos) < 0x8000); 295 SendMessage(WM_HSCROLL, 296 MAKEWPARAM(SB_THUMBPOSITION, LOWORD(si.nPos))); 297 } 298 if (bScrollUp == TRUE) { 299 SendMessage(EM_LINESCROLL, 0, -1); 300 } else if (bScrollDown == TRUE) { 301 SendMessage(EM_LINESCROLL, 0, 1); 302 } 303 delete msg; 304 return mrConsume; 305 } else if (msg->message == WM_MOUSEWHEEL) { 306 // 4417236: If there is an old version of RichEd32.dll which 307 // does not provide the mouse wheel scrolling we have to 308 // interpret WM_MOUSEWHEEL as a sequence of scroll messages. 309 // kdm@sparc.spb.su 310 UINT platfScrollLines = 3; 311 // Retrieve a number of scroll lines. 312 ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, 313 &platfScrollLines, 0); 314 315 if (platfScrollLines > 0) { 316 HWND hWnd = GetHWnd(); 317 LONG styles = ::GetWindowLong(hWnd, GWL_STYLE); 318 319 RECT rect; 320 // rect.left and rect.top are zero. 321 // rect.right and rect.bottom contain the width and height 322 VERIFY(::GetClientRect(hWnd, &rect)); 323 324 // calculate a number of visible lines 325 TEXTMETRIC tm; 326 HDC hDC = ::GetDC(hWnd); 327 DASSERT(hDC != NULL); 328 VERIFY(::GetTextMetrics(hDC, &tm)); 329 VERIFY(::ReleaseDC(hWnd, hDC)); 330 LONG visibleLines = rect.bottom / tm.tmHeight + 1; 331 332 LONG lineCount = static_cast<LONG>(::SendMessage(hWnd, 333 EM_GETLINECOUNT, 0, 0)); 334 BOOL sb_vert_disabled = (styles & WS_VSCROLL) == 0 335 || (lineCount <= visibleLines); 336 337 LONG *delta_accum = &m_lVDeltaAccum; 338 UINT wm_msg = WM_VSCROLL; 339 int sb_type = SB_VERT; 340 341 if (sb_vert_disabled && (styles & WS_HSCROLL)) { 342 delta_accum = &m_lHDeltaAccum; 343 wm_msg = WM_HSCROLL; 344 sb_type = SB_HORZ; 345 } 346 347 int delta = (short) HIWORD(msg->wParam); 348 *delta_accum += delta; 349 if (abs(*delta_accum) >= WHEEL_DELTA) { 350 if (platfScrollLines == WHEEL_PAGESCROLL) { 351 // Synthesize a page down or a page up message. 352 ::SendMessage(hWnd, wm_msg, 353 (delta > 0) ? SB_PAGEUP : SB_PAGEDOWN, 0); 354 *delta_accum = 0; 355 } else { 356 // We provide a friendly behavior of text scrolling. 357 // During of scrolling the text can be out of the client 358 // area's boundary. Here we try to prevent this behavior. 359 SCROLLINFO si; 360 ::ZeroMemory(&si, sizeof(si)); 361 si.cbSize = sizeof(SCROLLINFO); 362 si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE; 363 int actualScrollLines = 364 abs((int)(platfScrollLines * (*delta_accum / WHEEL_DELTA))); 365 for (int i = 0; i < actualScrollLines; i++) { 366 if (::GetScrollInfo(hWnd, sb_type, &si)) { 367 if ((wm_msg == WM_VSCROLL) 368 && ((*delta_accum < 0 369 && si.nPos >= (si.nMax - (int) si.nPage)) 370 || (*delta_accum > 0 371 && si.nPos <= si.nMin))) { 372 break; 373 } 374 } 375 // Here we don't send EM_LINESCROLL or EM_SCROLL 376 // messages to rich edit because it doesn't 377 // provide horizontal scrolling. 378 // So it's only possible to scroll by 1 line 379 // at a time to prevent scrolling when the 380 // scrollbar thumb reaches its boundary position. 381 ::SendMessage(hWnd, wm_msg, 382 (*delta_accum>0) ? SB_LINEUP : SB_LINEDOWN, 0); 383 } 384 *delta_accum %= WHEEL_DELTA; 385 } 386 } else { 387 *delta_accum = 0; 388 } 389 } 390 delete msg; 391 return mrConsume; 392 // 4417236: end of fix 393 } 394 395 return AwtTextComponent::HandleEvent(msg, synthetic); 396 } 397 398 399 /* Fix for 4776535, 4648702 400 * If width is 0 or 1 Windows hides the horizontal scroll bar even 401 * if the WS_HSCROLL style is set. It is a bug in Windows. 402 * As a workaround we should set an initial width to 2. 403 * kdm@sparc.spb.su 404 */ 405 void AwtTextArea::Reshape(int x, int y, int w, int h) 406 { 407 if (w < 2) { 408 w = 2; 409 } 410 AwtTextComponent::Reshape(x, y, w, h); 411 } 412 413 LONG AwtTextArea::getJavaSelPos(LONG orgPos) 414 { 415 long wlen; 416 long pos = orgPos; 417 long cur = 0; 418 long retval = 0; 419 LPTSTR wbuf; 420 421 if ((wlen = GetTextLength()) == 0) 422 return 0; 423 wbuf = new TCHAR[wlen + 1]; 424 GetText(wbuf, wlen + 1); 425 if (m_isLFonly == TRUE) { 426 wlen = RemoveCR(wbuf); 427 } 428 429 while (cur < pos && cur < wlen) { 430 if (wbuf[cur] == _T('\r') && wbuf[cur + 1] == _T('\n')) { 431 pos++; 432 } 433 cur++; 434 retval++; 435 } 436 delete[] wbuf; 437 return retval; 438 } 439 440 LONG AwtTextArea::getWin32SelPos(LONG orgPos) 441 { 442 if (GetTextLength() == 0) 443 return 0; 444 return orgPos; 445 } 446 447 void AwtTextArea::SetSelRange(LONG start, LONG end) 448 { 449 CHARRANGE cr; 450 cr.cpMin = getWin32SelPos(start); 451 cr.cpMax = getWin32SelPos(end); 452 EditSetSel(cr); 453 } 454 455 456 void AwtTextArea::_ReplaceText(void *param) 457 { 458 JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); 459 460 ReplaceTextStruct *rts = (ReplaceTextStruct *)param; 461 462 jobject textComponent = rts->textComponent; 463 jstring text = rts->text; 464 jint start = rts->start; 465 jint end = rts->end; 466 467 AwtTextComponent *c = NULL; 468 469 PDATA pData; 470 JNI_CHECK_PEER_GOTO(textComponent, done); 471 JNI_CHECK_NULL_GOTO(text, "null string", done); 472 473 c = (AwtTextComponent *)pData; 474 if (::IsWindow(c->GetHWnd())) 475 { 476 jsize length = env->GetStringLength(text) + 1; 477 // Bugid 4141477 - Can't use TO_WSTRING here because it uses alloca 478 // WCHAR* buffer = TO_WSTRING(text); 479 TCHAR *buffer = new TCHAR[length]; 480 env->GetStringRegion(text, 0, length-1, reinterpret_cast<jchar*>(buffer)); 481 buffer[length-1] = '\0'; 482 483 c->CheckLineSeparator(buffer); 484 c->RemoveCR(buffer); 485 // Fix for 5003402: added restoring/hiding selection to enable automatic scrolling 486 c->SendMessage(EM_HIDESELECTION, FALSE, TRUE); 487 c->SendMessage(EM_SETSEL, start, end); 488 c->SendMessage(EM_REPLACESEL, FALSE, (LPARAM)buffer); 489 c->SendMessage(EM_HIDESELECTION, TRUE, TRUE); 490 491 delete[] buffer; 492 } 493 494 done: 495 env->DeleteGlobalRef(textComponent); 496 env->DeleteGlobalRef(text); 497 498 delete rts; 499 } 500 501 502 /************************************************************************ 503 * TextArea native methods 504 */ 505 506 extern "C" { 507 508 /* 509 * Class: java_awt_TextArea 510 * Method: initIDs 511 * Signature: ()V 512 */ 513 JNIEXPORT void JNICALL 514 Java_java_awt_TextArea_initIDs(JNIEnv *env, jclass cls) 515 { 516 TRY; 517 518 AwtTextArea::scrollbarVisibilityID = 519 env->GetFieldID(cls, "scrollbarVisibility", "I"); 520 521 DASSERT(AwtTextArea::scrollbarVisibilityID != NULL); 522 523 CATCH_BAD_ALLOC; 524 } 525 526 } /* extern "C" */ 527 528 529 /************************************************************************ 530 * WTextAreaPeer native methods 531 */ 532 533 extern "C" { 534 535 /* 536 * Class: sun_awt_windows_WTextAreaPeer 537 * Method: create 538 * Signature: (Lsun/awt/windows/WComponentPeer;)V 539 */ 540 JNIEXPORT void JNICALL 541 Java_sun_awt_windows_WTextAreaPeer_create(JNIEnv *env, jobject self, 542 jobject parent) 543 { 544 TRY; 545 546 AwtToolkit::CreateComponent(self, parent, 547 (AwtToolkit::ComponentFactory) 548 AwtTextArea::Create); 549 550 CATCH_BAD_ALLOC; 551 } 552 553 /* 554 * Class: sun_awt_windows_WTextAreaPeer 555 * Method: replaceRange 556 * Signature: (Ljava/lang/String;II)V 557 */ 558 JNIEXPORT void JNICALL 559 Java_sun_awt_windows_WTextAreaPeer_replaceRange(JNIEnv *env, jobject self, 560 jstring text, 561 jint start, jint end) 562 { 563 TRY; 564 565 jobject selfGlobalRef = env->NewGlobalRef(self); 566 jstring textGlobalRef = (jstring)env->NewGlobalRef(text); 567 568 ReplaceTextStruct *rts = new ReplaceTextStruct; 569 rts->textComponent = selfGlobalRef; 570 rts->text = textGlobalRef; 571 rts->start = start; 572 rts->end = end; 573 574 AwtToolkit::GetInstance().SyncCall(AwtTextArea::_ReplaceText, rts); 575 // global refs and rts are deleted in _ReplaceText() 576 577 CATCH_BAD_ALLOC; 578 } 579 } /* extern "C" */