1 /*
   2  * Copyright (c) 1996, 2014, 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_Scrollbar.h"
  28 #include "awt_Canvas.h"
  29 #include "awt_Window.h"
  30 
  31 /* IMPORTANT! Read the README.JNI file for notes on JNI converted AWT code.
  32  */
  33 
  34 /***********************************************************************/
  35 // struct for _SetValues() method
  36 struct SetValuesStruct {
  37     jobject scrollbar;
  38     jint value;
  39     jint visible;
  40     jint min, max;
  41 
  42 };
  43 /************************************************************************
  44  * AwtScrollbar fields
  45  */
  46 
  47 jfieldID AwtScrollbar::lineIncrementID;
  48 jfieldID AwtScrollbar::pageIncrementID;
  49 jfieldID AwtScrollbar::orientationID;
  50 
  51 BOOL     AwtScrollbar::ms_isInsideMouseFilter = FALSE;
  52 int      AwtScrollbar::ms_instanceCounter = 0;
  53 HHOOK    AwtScrollbar::ms_hMouseFilter;
  54 
  55 /************************************************************************
  56  * AwtScrollbar methods
  57  */
  58 
  59 AwtScrollbar::AwtScrollbar() {
  60     m_orientation = SB_HORZ;
  61     m_lineIncr = 0;
  62     m_pageIncr = 0;
  63     m_prevCallback = NULL;
  64     m_prevCallbackPos = 0;
  65     ms_instanceCounter++;
  66 
  67     /*
  68      * Fix for 4515085.
  69      * Use the hook to process WM_LBUTTONUP message.
  70      */
  71     if (AwtScrollbar::ms_instanceCounter == 1) {
  72         AwtScrollbar::ms_hMouseFilter =
  73             ::SetWindowsHookEx(WH_MOUSE, (HOOKPROC)AwtScrollbar::MouseFilter,
  74                                0, AwtToolkit::MainThread());
  75     }
  76 }
  77 
  78 AwtScrollbar::~AwtScrollbar()
  79 {
  80 }
  81 
  82 void AwtScrollbar::Dispose()
  83 {
  84     if (--ms_instanceCounter == 0) {
  85         ::UnhookWindowsHookEx(ms_hMouseFilter);
  86     }
  87     AwtComponent::Dispose();
  88 }
  89 
  90 LPCTSTR
  91 AwtScrollbar::GetClassName() {
  92     return TEXT("SCROLLBAR");  /* System provided scrollbar class */
  93 }
  94 
  95 /* Create a new AwtScrollbar object and window.   */
  96 AwtScrollbar *
  97 AwtScrollbar::Create(jobject peer, jobject parent)
  98 {
  99     JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
 100 
 101     jobject target = NULL;
 102     AwtScrollbar* c = NULL;
 103 
 104     try {
 105         if (env->EnsureLocalCapacity(1) < 0) {
 106             return NULL;
 107         }
 108 
 109         PDATA pData;
 110         AwtCanvas* awtParent;
 111         JNI_CHECK_PEER_GOTO(parent, done);
 112 
 113         awtParent = (AwtCanvas*)pData;
 114         JNI_CHECK_NULL_GOTO(awtParent, "null awtParent", done);
 115 
 116         target = env->GetObjectField(peer, AwtObject::targetID);
 117         JNI_CHECK_NULL_GOTO(target, "null target", done);
 118 
 119         c = new AwtScrollbar();
 120 
 121         {
 122             jint orientation =
 123                 env->GetIntField(target, AwtScrollbar::orientationID);
 124             c->m_orientation = (orientation == java_awt_Scrollbar_VERTICAL) ?
 125                 SB_VERT : SB_HORZ;
 126             c->m_lineIncr =
 127                 env->GetIntField(target, AwtScrollbar::lineIncrementID);
 128             c->m_pageIncr =
 129                 env->GetIntField(target, AwtScrollbar::pageIncrementID);
 130 
 131             DWORD style = WS_CHILD | WS_CLIPSIBLINGS |
 132                 c->m_orientation;/* Note: SB_ and SBS_ are the same here */
 133 
 134             jint x = env->GetIntField(target, AwtComponent::xID);
 135             jint y = env->GetIntField(target, AwtComponent::yID);
 136             jint width = env->GetIntField(target, AwtComponent::widthID);
 137             jint height = env->GetIntField(target, AwtComponent::heightID);
 138 
 139             c->CreateHWnd(env, L"", style, 0,
 140                           x, y, width, height,
 141                           awtParent->GetHWnd(),
 142                           reinterpret_cast<HMENU>(static_cast<INT_PTR>(
 143                 awtParent->CreateControlID())),
 144                           ::GetSysColor(COLOR_SCROLLBAR),
 145                           ::GetSysColor(COLOR_SCROLLBAR),
 146                           peer);
 147             c->m_backgroundColorSet = TRUE;
 148             /* suppress inheriting parent's color. */
 149             c->UpdateBackground(env, target);
 150         }
 151     } catch (...) {
 152         env->DeleteLocalRef(target);
 153         throw;
 154     }
 155 
 156 done:
 157     env->DeleteLocalRef(target);
 158     return c;
 159 }
 160 
 161 LRESULT CALLBACK
 162 AwtScrollbar::MouseFilter(int nCode, WPARAM wParam, LPARAM lParam)
 163 {
 164     if (((UINT)wParam == WM_LBUTTONUP || (UINT)wParam == WM_MOUSEMOVE) &&
 165         ms_isInsideMouseFilter != TRUE &&
 166         nCode >= 0)
 167     {
 168         HWND hwnd = ((PMOUSEHOOKSTRUCT)lParam)->hwnd;
 169         AwtComponent *comp = AwtComponent::GetComponent(hwnd);
 170 
 171         if (comp != NULL && comp->IsScrollbar()) {
 172             MSG msg;
 173             LPMSG lpMsg = (LPMSG)&msg;
 174             UINT msgID = (UINT)wParam;
 175 
 176             ms_isInsideMouseFilter = TRUE;
 177 
 178             // Peek the message to get wParam containing the message's flags.
 179             // <::PeekMessage> will call this hook again. To prevent recursive
 180             // processing the <ms_isInsideMouseFilter> flag is used.
 181             // Calling <::PeekMessage> is not so good desision but is the only one
 182             // found to get those flags (used further in Java event creation).
 183             // WARNING! If you are about to add new hook of WM_MOUSE type make
 184             // it ready for recursive call, otherwise modify this one.
 185             if (::PeekMessage(lpMsg, hwnd, msgID, msgID, PM_NOREMOVE)) {
 186                 comp->WindowProc(msgID, lpMsg->wParam, lpMsg->lParam);
 187             }
 188 
 189             ms_isInsideMouseFilter = FALSE;
 190         }
 191     }
 192     return ::CallNextHookEx(AwtScrollbar::ms_hMouseFilter, nCode, wParam, lParam);
 193 }
 194 
 195 
 196 LRESULT
 197 AwtScrollbar::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
 198 {
 199     // Delegate real work to super
 200     LRESULT retValue = AwtComponent::WindowProc(message, wParam, lParam);
 201 
 202     // After-hooks for workarounds
 203     switch (message) {
 204 
 205       // Work around a windows bug described in KB article Q73839.
 206       // Need to update focus indicator on scrollbar if thumb
 207       // proportion or thumb position was changed.
 208 
 209       case WM_SIZE:
 210       case SBM_SETSCROLLINFO:
 211       case SBM_SETRANGE:
 212       case SBM_SETRANGEREDRAW:
 213           if (AwtComponent::sm_focusOwner == GetHWnd()) {
 214               UpdateFocusIndicator();
 215           }
 216           break;
 217     }
 218 
 219     return retValue;
 220 }
 221 
 222 MsgRouting
 223 AwtScrollbar::WmNcHitTest(UINT x, UINT y, LRESULT& retVal)
 224 {
 225     if (::IsWindow(AwtWindow::GetModalBlocker(AwtComponent::GetTopLevelParentForWindow(GetHWnd())))) {
 226         retVal = HTCLIENT;
 227         return mrConsume;
 228     }
 229     return AwtComponent::WmNcHitTest(x, y, retVal);
 230 }
 231 
 232 // Fix for a race condition when the WM_LBUTTONUP is picked by the AWT
 233 // message loop before(!) the windows internal message loop for the
 234 // scrollbar is started in response to WM_LBUTTONDOWN.  See KB article
 235 // Q102552.
 236 //
 237 // Note that WM_LBUTTONUP is processed by the windows internal message
 238 // loop.  May be we can synthesize a MOUSE_RELEASED event but that
 239 // seems kludgey, so we'd better left this as is for now.
 240 
 241 MsgRouting
 242 AwtScrollbar::WmMouseDown(UINT flags, int x, int y, int button)
 243 {
 244     // We pass the WM_LBUTTONDOWN up to Java, but we process it
 245     // immediately as well to avoid the race.  Later when this press
 246     // event returns to us wrapped into a WM_AWT_HANDLE_EVENT we
 247     // ignore it in the HandleEvent below.  This means that we can not
 248     // consume the mouse press in the Java world.
 249 
 250     MsgRouting usualRoute = AwtComponent::WmMouseDown(flags, x, y, button);
 251 
 252     if (::IsWindow(AwtWindow::GetModalBlocker(AwtComponent::GetTopLevelParentForWindow(GetHWnd())))) {
 253         return mrConsume;
 254     }
 255 
 256     if (button == LEFT_BUTTON)
 257         return mrDoDefault;    // Force immediate processing to avoid the race.
 258     else
 259         return usualRoute;
 260 }
 261 
 262 MsgRouting
 263 AwtScrollbar::HandleEvent(MSG *msg, BOOL synthetic)
 264 {
 265     // SCROLLBAR control doesn't cause activation on mouse/key events,
 266     // so we can safely (for synthetic focus) pass them to the system proc.
 267 
 268     if (IsFocusingMouseMessage(msg)) {
 269         // Left button press was already routed to default window
 270         // procedure in the WmMouseDown above.  Propagating synthetic
 271         // press seems like a bad idea as internal message loop
 272         // doesn't know how to unwrap synthetic release.
 273         delete msg;
 274         return mrConsume;
 275     }
 276     return AwtComponent::HandleEvent(msg, synthetic);
 277 }
 278 
 279 // Work around a windows bug descrbed in KB article Q73839.  Reset
 280 // focus on scrollbars to update focus indicator.  The article advises
 281 // to disable/enable the scrollbar.
 282 void
 283 AwtScrollbar::UpdateFocusIndicator()
 284 {
 285     if (IsFocusable()) {
 286         // todo: doesn't work
 287         SendMessage((WPARAM)ESB_DISABLE_BOTH);
 288         SendMessage((WPARAM)ESB_ENABLE_BOTH);
 289     }
 290 }
 291 
 292 // In a windows app one would call SetScrollInfo from WM_[HV]SCROLL
 293 // handler directly.  Since we call SetScrollInfo from Java world
 294 // after scroll handler is over next WM_[HV]SCROLL event can be
 295 // delivered before SetScrollInfo was called in response to the
 296 // previous one and thus we would fire exactly the same event which
 297 // will only contribute to the growth of the backlog of scroll events.
 298 
 299 const char * const AwtScrollbar::SbNlineDown = "lineDown";
 300 const char * const AwtScrollbar::SbNlineUp   = "lineUp";
 301 const char * const AwtScrollbar::SbNpageDown = "pageDown";
 302 const char * const AwtScrollbar::SbNpageUp   = "pageUp";
 303 const char * const AwtScrollbar::SbNdrag     = "drag";
 304 const char * const AwtScrollbar::SbNdragEnd  = "dragEnd";
 305 const char * const AwtScrollbar::SbNwarp     = "warp";
 306 
 307 inline void
 308 AwtScrollbar::DoScrollCallbackCoalesce(const char* methodName, int newPos)
 309 {
 310     if (methodName == m_prevCallback && newPos == m_prevCallbackPos) {
 311         DTRACE_PRINTLN2("AwtScrollbar: ignoring duplicate callback %s(%d)",
 312                         methodName, newPos);
 313     }
 314     else {
 315         DoCallback(methodName, "(I)V", newPos);
 316         m_prevCallback = methodName;
 317         m_prevCallbackPos = newPos;
 318     }
 319 }
 320 
 321 
 322 MsgRouting
 323 AwtScrollbar::WmVScroll(UINT scrollCode, UINT pos, HWND hScrollbar)
 324 {
 325     int minVal, maxVal;    // scrollbar range
 326     int minPos, maxPos;    // thumb positions (max depends on visible amount)
 327     int curPos, newPos;
 328 
 329     // For drags we have old (static) and new (dynamic) thumb positions
 330     int dragP = (scrollCode == SB_THUMBTRACK
 331               || scrollCode == SB_THUMBPOSITION);
 332     int thumbPos;
 333 
 334     SCROLLINFO si;
 335     si.cbSize = sizeof si;
 336     si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE;
 337 
 338     // From, _Win32 Programming_, by Rector and Newcommer, p. 185:
 339     // "In some of the older documentation on Win32 scroll bars,
 340     // including that published by Microsoft, you may read that
 341     // you *cannot* obtain the scroll position while in a handler.
 342     // The SIF_TRACKPOS flag was added after this documentation
 343     // was published.  Beware of this older documentation; it may
 344     // have other obsolete features."
 345     if (dragP) {
 346         si.fMask |= SIF_TRACKPOS;
 347     }
 348 
 349     VERIFY(::GetScrollInfo(GetHWnd(), SB_CTL, &si));
 350     curPos = si.nPos;
 351     minPos = minVal = si.nMin;
 352 
 353     // Upper bound of the range.  Note that adding 1 here is safe
 354     // and won't cause a wrap, since we have substracted 1 in the
 355     // SetValues above.
 356     maxVal = si.nMax + 1;
 357 
 358     // Meaningful maximum position is maximum - visible.
 359     maxPos = maxVal - si.nPage;
 360 
 361     // XXX: Documentation for SBM_SETRANGE says that scrollbar
 362     // range is limited by MAXLONG, which is 2**31, but when a
 363     // scroll range is greater than that, thumbPos is reported
 364     // incorrectly due to integer arithmetic wrap(s).
 365     thumbPos = dragP ? si.nTrackPos : curPos;
 366 
 367     // NB: Beware arithmetic wrap when calculating newPos
 368     switch (scrollCode) {
 369 
 370       case SB_LINEUP:
 371           if ((__int64)curPos - m_lineIncr > minPos)
 372               newPos = curPos - m_lineIncr;
 373           else
 374               newPos = minPos;
 375           if (newPos != curPos)
 376               DoScrollCallbackCoalesce(SbNlineUp, newPos);
 377           break;
 378 
 379       case SB_LINEDOWN:
 380           if ((__int64)curPos + m_lineIncr < maxPos)
 381               newPos = curPos + m_lineIncr;
 382           else
 383               newPos = maxPos;
 384           if (newPos != curPos)
 385               DoScrollCallbackCoalesce(SbNlineDown, newPos);
 386           break;
 387 
 388       case SB_PAGEUP:
 389           if ((__int64)curPos - m_pageIncr > minPos)
 390               newPos = curPos - m_pageIncr;
 391           else
 392               newPos = minPos;
 393           if (newPos != curPos)
 394               DoScrollCallbackCoalesce(SbNpageUp, newPos);
 395           break;
 396 
 397       case SB_PAGEDOWN:
 398           if ((__int64)curPos + m_pageIncr < maxPos)
 399               newPos = curPos + m_pageIncr;
 400           else
 401               newPos = maxPos;
 402           if (newPos != curPos)
 403               DoScrollCallbackCoalesce(SbNpageDown, newPos);
 404           break;
 405 
 406       case SB_TOP:
 407           if (minPos != curPos)
 408               DoScrollCallbackCoalesce(SbNwarp, minPos);
 409           break;
 410 
 411       case SB_BOTTOM:
 412           if (maxPos != curPos)
 413               DoScrollCallbackCoalesce(SbNwarp, maxPos);
 414           break;
 415 
 416       case SB_THUMBTRACK:
 417           if (thumbPos != curPos)
 418               DoScrollCallbackCoalesce(SbNdrag, thumbPos);
 419           break;
 420 
 421       case SB_THUMBPOSITION:
 422           DoScrollCallbackCoalesce(SbNdragEnd, thumbPos);
 423           break;
 424 
 425       case SB_ENDSCROLL:
 426           // reset book-keeping info
 427           m_prevCallback = NULL;
 428           break;
 429     }
 430     return mrDoDefault;
 431 }
 432 
 433 MsgRouting
 434 AwtScrollbar::WmHScroll(UINT scrollCode, UINT pos, HWND hScrollbar)
 435 {
 436     return WmVScroll(scrollCode, pos, hScrollbar);
 437 }
 438 
 439 void AwtScrollbar::_SetValues(void *param)
 440 {
 441     JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
 442 
 443     SetValuesStruct *svs = (SetValuesStruct *)param;
 444     jobject self = svs->scrollbar;
 445 
 446     SCROLLINFO si;
 447     si.cbSize = sizeof si;
 448     si.fMask  = SIF_POS | SIF_PAGE | SIF_RANGE;
 449     si.nMin   = svs->min;
 450     si.nMax   = svs->max - 1;
 451     si.nPage  = svs->visible;
 452     si.nPos   = svs->value;
 453 
 454     AwtScrollbar *sb = NULL;
 455 
 456     PDATA pData;
 457     JNI_CHECK_PEER_GOTO(self, ret);
 458     sb = (AwtScrollbar *)pData;
 459     if (::IsWindow(sb->GetHWnd()))
 460     {
 461         BOOL update_p = ::IsWindowEnabled(sb->GetHWnd()); // don't redraw if disabled
 462         DTRACE_PRINTLN5("AwtScrollbar::SetValues(val = %d, vis = %d,"//(ctd.)
 463                         " min = %d, max = %d)%s",
 464             svs->value, svs->visible, svs->min, svs->max,
 465             update_p ? "" : " - NOT redrawing");
 466         ::SetScrollInfo(sb->GetHWnd(), SB_CTL, &si, update_p);
 467     }
 468 ret:
 469     env->DeleteGlobalRef(self);
 470 
 471     delete svs;
 472 }
 473 
 474 /************************************************************************
 475  * Scrollbar native methods
 476  */
 477 
 478 extern "C" {
 479 
 480 /*
 481  * Class:     java_awt_Scrollbar
 482  * Method:    initIDs
 483  * Signature: ()V
 484  */
 485 JNIEXPORT void JNICALL
 486 Java_java_awt_Scrollbar_initIDs(JNIEnv *env, jclass cls)
 487 {
 488     TRY;
 489 
 490     AwtScrollbar::lineIncrementID = env->GetFieldID(cls, "lineIncrement", "I");
 491     DASSERT(AwtScrollbar::lineIncrementID != NULL);
 492     CHECK_NULL(AwtScrollbar::lineIncrementID);
 493 
 494     AwtScrollbar::pageIncrementID = env->GetFieldID(cls, "pageIncrement", "I");
 495     DASSERT(AwtScrollbar::pageIncrementID != NULL);
 496     CHECK_NULL(AwtScrollbar::pageIncrementID);
 497 
 498     AwtScrollbar::orientationID = env->GetFieldID(cls, "orientation", "I");
 499     DASSERT(AwtScrollbar::orientationID != NULL);
 500 
 501     CATCH_BAD_ALLOC;
 502 }
 503 
 504 } /* extern "C" */
 505 
 506 
 507 /************************************************************************
 508  * WScrollbarPeer native methods
 509  */
 510 
 511 extern "C" {
 512 
 513 /*
 514  * Class:     sun_awt_windows_WScrollbarPeer
 515  * Method:    setValues
 516  * Signature: (IIII)V
 517  */
 518 JNIEXPORT void JNICALL
 519 Java_sun_awt_windows_WScrollbarPeer_setValues(JNIEnv *env, jobject self,
 520                                               jint value, jint visible,
 521                                               jint minimum, jint maximum)
 522 {
 523     TRY;
 524 
 525     SetValuesStruct *svs = new SetValuesStruct;
 526     svs->scrollbar = env->NewGlobalRef(self);
 527     svs->value = value;
 528     svs->visible = visible;
 529     svs->min = minimum;
 530     svs->max = maximum;
 531 
 532     AwtToolkit::GetInstance().SyncCall(AwtScrollbar::_SetValues, svs);
 533     // global ref and svs are deleted in _SetValues
 534 
 535     CATCH_BAD_ALLOC;
 536 }
 537 
 538 /*
 539  * Class:     sun_awt_windows_WScrollbarPeer
 540  * Method:    setLineIncrement
 541  * Signature: (I)V
 542  */
 543 JNIEXPORT void JNICALL
 544 Java_sun_awt_windows_WScrollbarPeer_setLineIncrement(JNIEnv *env, jobject self,
 545                                                      jint increment)
 546 {
 547     TRY;
 548 
 549     PDATA pData;
 550     JNI_CHECK_PEER_RETURN(self);
 551     AwtScrollbar* c = (AwtScrollbar*)pData;
 552     c->SetLineIncrement(increment);
 553 
 554     CATCH_BAD_ALLOC;
 555 }
 556 
 557 /*
 558  * Class:     sun_awt_windows_WScrollbarPeer
 559  * Method:    setPageIncrement
 560  * Signature: (I)V
 561  */
 562 JNIEXPORT void JNICALL
 563 Java_sun_awt_windows_WScrollbarPeer_setPageIncrement(JNIEnv *env, jobject self,
 564                                                      jint increment)
 565 {
 566     TRY;
 567 
 568     PDATA pData;
 569     JNI_CHECK_PEER_RETURN(self);
 570     AwtScrollbar* c = (AwtScrollbar*)pData;
 571     c->SetPageIncrement(increment);
 572 
 573     CATCH_BAD_ALLOC;
 574 }
 575 
 576 /*
 577  * Class:     sun_awt_windows_WScrollbarPeer
 578  * Method:    create
 579  * Signature: (Lsun/awt/windows/WComponentPeer;)V
 580  */
 581 JNIEXPORT void JNICALL
 582 Java_sun_awt_windows_WScrollbarPeer_create(JNIEnv *env, jobject self,
 583                                            jobject parent)
 584 {
 585     TRY;
 586 
 587     PDATA pData;
 588     JNI_CHECK_PEER_RETURN(parent);
 589     AwtToolkit::CreateComponent(self, parent,
 590                                 (AwtToolkit::ComponentFactory)
 591                                 AwtScrollbar::Create);
 592     JNI_CHECK_PEER_CREATION_RETURN(self);
 593 
 594     CATCH_BAD_ALLOC;
 595 }
 596 
 597 /*
 598  * Class:     sun_awt_windows_WScrollbarPeer
 599  * Method:    getScrollbarSize
 600  * Signature: (I)I
 601  */
 602 JNIEXPORT jint JNICALL
 603 Java_sun_awt_windows_WScrollbarPeer_getScrollbarSize(JNIEnv *env, jclass clazz, jint orientation)
 604 {
 605     if (orientation == java_awt_Scrollbar_VERTICAL) {
 606         return ::GetSystemMetrics(SM_CXVSCROLL);
 607     } else {
 608         return ::GetSystemMetrics(SM_CYHSCROLL);
 609     }
 610 }
 611 
 612 } /* extern "C" */