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" */