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