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 selection autoscrolling when mouse is moved 232 * outside of the client area. 233 */ 234 POINT p; 235 RECT r; 236 BOOL bScrollDown = FALSE; 237 238 p.x = msg->pt.x; 239 p.y = msg->pt.y; 240 VERIFY(::GetClientRect(GetHWnd(), &r)); 241 242 if (p.y > r.bottom) { 243 bScrollDown = TRUE; 244 p.y = r.bottom - 1; 245 } 246 247 LONG lCurPos = EditGetCharFromPos(p); 248 249 if (GetStartSelectionPos() != -1 && 250 GetEndSelectionPos() != -1 && 251 lCurPos != GetLastSelectionPos()) { 252 253 CHARRANGE cr; 254 255 SetLastSelectionPos(lCurPos); 256 257 cr.cpMin = GetStartSelectionPos(); 258 cr.cpMax = GetLastSelectionPos(); 259 260 EditSetSel(cr); 261 } 262 if (bScrollDown == TRUE) { 263 SendMessage(EM_LINESCROLL, 0, 1); 264 } 265 delete msg; 266 return mrConsume; 267 } else if (msg->message == WM_MOUSEWHEEL) { 268 // 4417236: If there is an old version of RichEd32.dll which 269 // does not provide the mouse wheel scrolling we have to 270 // interpret WM_MOUSEWHEEL as a sequence of scroll messages. 271 // kdm@sparc.spb.su 272 UINT platfScrollLines = 3; 273 // Retrieve a number of scroll lines. 274 ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, 275 &platfScrollLines, 0); 276 277 if (platfScrollLines > 0) { 278 HWND hWnd = GetHWnd(); 279 LONG styles = ::GetWindowLong(hWnd, GWL_STYLE); 280 281 RECT rect; 282 // rect.left and rect.top are zero. 283 // rect.right and rect.bottom contain the width and height 284 VERIFY(::GetClientRect(hWnd, &rect)); 285 286 // calculate a number of visible lines 287 TEXTMETRIC tm; 288 HDC hDC = ::GetDC(hWnd); 289 DASSERT(hDC != NULL); 290 VERIFY(::GetTextMetrics(hDC, &tm)); 291 VERIFY(::ReleaseDC(hWnd, hDC)); 292 LONG visibleLines = rect.bottom / tm.tmHeight + 1; 293 294 LONG lineCount = static_cast<LONG>(::SendMessage(hWnd, 295 EM_GETLINECOUNT, 0, 0)); 296 BOOL sb_vert_disabled = (styles & WS_VSCROLL) == 0 297 || (lineCount <= visibleLines); 298 299 LONG *delta_accum = &m_lVDeltaAccum; 300 UINT wm_msg = WM_VSCROLL; 301 int sb_type = SB_VERT; 302 303 if (sb_vert_disabled && (styles & WS_HSCROLL)) { 304 delta_accum = &m_lHDeltaAccum; 305 wm_msg = WM_HSCROLL; 306 sb_type = SB_HORZ; 307 } 308 309 int delta = (short) HIWORD(msg->wParam); 310 *delta_accum += delta; 311 if (abs(*delta_accum) >= WHEEL_DELTA) { 312 if (platfScrollLines == WHEEL_PAGESCROLL) { 313 // Synthesize a page down or a page up message. 314 ::SendMessage(hWnd, wm_msg, 315 (delta > 0) ? SB_PAGEUP : SB_PAGEDOWN, 0); 316 *delta_accum = 0; 317 } else { 318 // We provide a friendly behavior of text scrolling. 319 // During of scrolling the text can be out of the client 320 // area's boundary. Here we try to prevent this behavior. 321 SCROLLINFO si; 322 ::ZeroMemory(&si, sizeof(si)); 323 si.cbSize = sizeof(SCROLLINFO); 324 si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE; 325 int actualScrollLines = 326 abs((int)(platfScrollLines * (*delta_accum / WHEEL_DELTA))); 327 for (int i = 0; i < actualScrollLines; i++) { 328 if (::GetScrollInfo(hWnd, sb_type, &si)) { 329 if ((wm_msg == WM_VSCROLL) 330 && ((*delta_accum < 0 331 && si.nPos >= (si.nMax - (int) si.nPage)) 332 || (*delta_accum > 0 333 && si.nPos <= si.nMin))) { 334 break; 335 } 336 } 337 // Here we don't send EM_LINESCROLL or EM_SCROLL 338 // messages to rich edit because it doesn't 339 // provide horizontal scrolling. 340 // So it's only possible to scroll by 1 line 341 // at a time to prevent scrolling when the 342 // scrollbar thumb reaches its boundary position. 343 ::SendMessage(hWnd, wm_msg, 344 (*delta_accum>0) ? SB_LINEUP : SB_LINEDOWN, 0); 345 } 346 *delta_accum %= WHEEL_DELTA; 347 } 348 } else { 349 *delta_accum = 0; 350 } 351 } 352 delete msg; 353 return mrConsume; 354 // 4417236: end of fix 355 } 356 357 return AwtTextComponent::HandleEvent(msg, synthetic); 358 } 359 360 361 /* Fix for 4776535, 4648702 362 * If width is 0 or 1 Windows hides the horizontal scroll bar even 363 * if the WS_HSCROLL style is set. It is a bug in Windows. 364 * As a workaround we should set an initial width to 2. 365 * kdm@sparc.spb.su 366 */ 367 void AwtTextArea::Reshape(int x, int y, int w, int h) 368 { 369 if (w < 2) { 370 w = 2; 371 } 372 AwtTextComponent::Reshape(x, y, w, h); 373 } 374 375 LONG AwtTextArea::getJavaSelPos(LONG orgPos) 376 { 377 long wlen; 378 long pos = orgPos; 379 long cur = 0; 380 long retval = 0; 381 LPTSTR wbuf; 382 383 if ((wlen = GetTextLength()) == 0) 384 return 0; 385 wbuf = new TCHAR[wlen + 1]; 386 GetText(wbuf, wlen + 1); 387 if (m_isLFonly == TRUE) { 388 wlen = RemoveCR(wbuf); 389 } 390 391 while (cur < pos && cur < wlen) { 392 if (wbuf[cur] == _T('\r') && wbuf[cur + 1] == _T('\n')) { 393 pos++; 394 } 395 cur++; 396 retval++; 397 } 398 delete[] wbuf; 399 return retval; 400 } 401 402 LONG AwtTextArea::getWin32SelPos(LONG orgPos) 403 { 404 if (GetTextLength() == 0) 405 return 0; 406 return orgPos; 407 } 408 409 void AwtTextArea::SetSelRange(LONG start, LONG end) 410 { 411 CHARRANGE cr; 412 cr.cpMin = getWin32SelPos(start); 413 cr.cpMax = getWin32SelPos(end); 414 EditSetSel(cr); 415 } 416 417 418 void AwtTextArea::_ReplaceText(void *param) 419 { 420 JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); 421 422 ReplaceTextStruct *rts = (ReplaceTextStruct *)param; 423 424 jobject textComponent = rts->textComponent; 425 jstring text = rts->text; 426 jint start = rts->start; 427 jint end = rts->end; 428 429 AwtTextComponent *c = NULL; 430 431 PDATA pData; 432 JNI_CHECK_PEER_GOTO(textComponent, done); 433 JNI_CHECK_NULL_GOTO(text, "null string", done); 434 435 c = (AwtTextComponent *)pData; 436 if (::IsWindow(c->GetHWnd())) 437 { 438 jsize length = env->GetStringLength(text) + 1; 439 // Bugid 4141477 - Can't use TO_WSTRING here because it uses alloca 440 // WCHAR* buffer = TO_WSTRING(text); 441 TCHAR *buffer = new TCHAR[length]; 442 env->GetStringRegion(text, 0, length-1, reinterpret_cast<jchar*>(buffer)); 443 buffer[length-1] = '\0'; 444 445 c->CheckLineSeparator(buffer); 446 c->RemoveCR(buffer); 447 // Fix for 5003402: added restoring/hiding selection to enable automatic scrolling 448 c->SendMessage(EM_HIDESELECTION, FALSE, TRUE); 449 c->SendMessage(EM_SETSEL, start, end); 450 c->SendMessage(EM_REPLACESEL, FALSE, (LPARAM)buffer); 451 c->SendMessage(EM_HIDESELECTION, TRUE, TRUE); 452 453 delete[] buffer; 454 } 455 456 done: 457 env->DeleteGlobalRef(textComponent); 458 env->DeleteGlobalRef(text); 459 460 delete rts; 461 } 462 463 464 /************************************************************************ 465 * TextArea native methods 466 */ 467 468 extern "C" { 469 470 /* 471 * Class: java_awt_TextArea 472 * Method: initIDs 473 * Signature: ()V 474 */ 475 JNIEXPORT void JNICALL 476 Java_java_awt_TextArea_initIDs(JNIEnv *env, jclass cls) 477 { 478 TRY; 479 480 AwtTextArea::scrollbarVisibilityID = 481 env->GetFieldID(cls, "scrollbarVisibility", "I"); 482 483 DASSERT(AwtTextArea::scrollbarVisibilityID != NULL); 484 485 CATCH_BAD_ALLOC; 486 } 487 488 } /* extern "C" */ 489 490 491 /************************************************************************ 492 * WTextAreaPeer native methods 493 */ 494 495 extern "C" { 496 497 /* 498 * Class: sun_awt_windows_WTextAreaPeer 499 * Method: create 500 * Signature: (Lsun/awt/windows/WComponentPeer;)V 501 */ 502 JNIEXPORT void JNICALL 503 Java_sun_awt_windows_WTextAreaPeer_create(JNIEnv *env, jobject self, 504 jobject parent) 505 { 506 TRY; 507 508 PDATA pData; 509 JNI_CHECK_PEER_RETURN(parent); 510 AwtToolkit::CreateComponent(self, parent, 511 (AwtToolkit::ComponentFactory) 512 AwtTextArea::Create); 513 JNI_CHECK_PEER_CREATION_RETURN(self); 514 515 CATCH_BAD_ALLOC; 516 } 517 518 /* 519 * Class: sun_awt_windows_WTextAreaPeer 520 * Method: replaceRange 521 * Signature: (Ljava/lang/String;II)V 522 */ 523 JNIEXPORT void JNICALL 524 Java_sun_awt_windows_WTextAreaPeer_replaceRange(JNIEnv *env, jobject self, 525 jstring text, 526 jint start, jint end) 527 { 528 TRY; 529 530 jobject selfGlobalRef = env->NewGlobalRef(self); 531 jstring textGlobalRef = (jstring)env->NewGlobalRef(text); 532 533 ReplaceTextStruct *rts = new ReplaceTextStruct; 534 rts->textComponent = selfGlobalRef; 535 rts->text = textGlobalRef; 536 rts->start = start; 537 rts->end = end; 538 539 AwtToolkit::GetInstance().SyncCall(AwtTextArea::_ReplaceText, rts); 540 // global refs and rts are deleted in _ReplaceText() 541 542 CATCH_BAD_ALLOC; 543 } 544 } /* extern "C" */