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
36struct SetValuesStruct {
37    jobject scrollbar;
38    jint value;
39    jint visible;
40    jint min, max;
41
42};
43/************************************************************************
44 * AwtScrollbar fields
45 */
46
47jfieldID AwtScrollbar::lineIncrementID;
48jfieldID AwtScrollbar::pageIncrementID;
49jfieldID AwtScrollbar::orientationID;
50
51BOOL     AwtScrollbar::ms_isInsideMouseFilter = FALSE;
52int      AwtScrollbar::ms_instanceCounter = 0;
53HHOOK    AwtScrollbar::ms_hMouseFilter;
54
55/************************************************************************
56 * AwtScrollbar methods
57 */
58
59AwtScrollbar::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
78AwtScrollbar::~AwtScrollbar()
79{
80}
81
82void AwtScrollbar::Dispose()
83{
84    if (--ms_instanceCounter == 0) {
85        ::UnhookWindowsHookEx(ms_hMouseFilter);
86    }
87    AwtComponent::Dispose();
88}
89
90LPCTSTR
91AwtScrollbar::GetClassName() {
92    return TEXT("SCROLLBAR");  /* System provided scrollbar class */
93}
94
95/* Create a new AwtScrollbar object and window.   */
96AwtScrollbar *
97AwtScrollbar::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
156done:
157    env->DeleteLocalRef(target);
158    return c;
159}
160
161LRESULT CALLBACK
162AwtScrollbar::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
196LRESULT
197AwtScrollbar::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
222MsgRouting
223AwtScrollbar::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
241MsgRouting
242AwtScrollbar::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
262MsgRouting
263AwtScrollbar::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.
282void
283AwtScrollbar::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
299const char * const AwtScrollbar::SbNlineDown = "lineDown";
300const char * const AwtScrollbar::SbNlineUp   = "lineUp";
301const char * const AwtScrollbar::SbNpageDown = "pageDown";
302const char * const AwtScrollbar::SbNpageUp   = "pageUp";
303const char * const AwtScrollbar::SbNdrag     = "drag";
304const char * const AwtScrollbar::SbNdragEnd  = "dragEnd";
305const char * const AwtScrollbar::SbNwarp     = "warp";
306
307inline void
308AwtScrollbar::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
322MsgRouting
323AwtScrollbar::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
433MsgRouting
434AwtScrollbar::WmHScroll(UINT scrollCode, UINT pos, HWND hScrollbar)
435{
436    return WmVScroll(scrollCode, pos, hScrollbar);
437}
438
439void 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    }
468ret:
469    env->DeleteGlobalRef(self);
470
471    delete svs;
472}
473
474/************************************************************************
475 * Scrollbar native methods
476 */
477
478extern "C" {
479
480/*
481 * Class:     java_awt_Scrollbar
482 * Method:    initIDs
483 * Signature: ()V
484 */
485JNIEXPORT void JNICALL
486Java_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
511extern "C" {
512
513/*
514 * Class:     sun_awt_windows_WScrollbarPeer
515 * Method:    setValues
516 * Signature: (IIII)V
517 */
518JNIEXPORT void JNICALL
519Java_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 */
543JNIEXPORT void JNICALL
544Java_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 */
562JNIEXPORT void JNICALL
563Java_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 */
581JNIEXPORT void JNICALL
582Java_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 */
602JNIEXPORT jint JNICALL
603Java_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" */
613