/* * tkWinScrollbar.c -- * * This file implements the Windows specific portion of the scrollbar * widget. * * Copyright (c) 1996 by Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkWinScrlbr.c,v 1.1.1.1 2007/07/10 15:05:18 duncan Exp $ */ #include "tkWinInt.h" #include "tkScrollbar.h" /* * The following constant is used to specify the maximum scroll position. * This value is limited by the Win32 API to either 16-bits or 32-bits, * depending on the context. For now we'll just use a value small * enough to fit in 16-bits, but which gives us 4-digits of precision. */ #define MAX_SCROLL 10000 /* * Declaration of Windows specific scrollbar structure. */ typedef struct WinScrollbar { TkScrollbar info; /* Generic scrollbar info. */ WNDPROC oldProc; /* Old window procedure. */ int lastVertical; /* 1 if was vertical at last refresh. */ HWND hwnd; /* Current window handle. */ int winFlags; /* Various flags; see below. */ } WinScrollbar; /* * Flag bits for native scrollbars: * * IN_MODAL_LOOP: Non-zero means this scrollbar is in the middle * of a modal loop. * ALREADY_DEAD: Non-zero means this scrollbar has been * destroyed, but has not been cleaned up. */ #define IN_MODAL_LOOP 1 #define ALREADY_DEAD 2 /* * Cached system metrics used to determine scrollbar geometry. */ static int initialized = 0; static int hArrowWidth, hThumb; /* Horizontal control metrics. */ static int vArrowWidth, vArrowHeight, vThumb; /* Vertical control metrics. */ TCL_DECLARE_MUTEX(winScrlbrMutex) /* * This variable holds the default width for a scrollbar in string * form for use in a Tk_ConfigSpec. */ static char defWidth[TCL_INTEGER_SPACE]; /* * Declarations for functions defined in this file. */ static Window CreateProc _ANSI_ARGS_((Tk_Window tkwin, Window parent, ClientData instanceData)); static void ModalLoopProc _ANSI_ARGS_((Tk_Window tkwin, XEvent *eventPtr)); static int ScrollbarBindProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, XEvent *eventPtr, Tk_Window tkwin, KeySym keySym)); static LRESULT CALLBACK ScrollbarProc _ANSI_ARGS_((HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)); static void UpdateScrollbar _ANSI_ARGS_(( WinScrollbar *scrollPtr)); static void UpdateScrollbarMetrics _ANSI_ARGS_((void)); /* * The class procedure table for the scrollbar widget. */ Tk_ClassProcs tkpScrollbarProcs = { sizeof(Tk_ClassProcs), /* size */ NULL, /* worldChangedProc */ CreateProc, /* createProc */ ModalLoopProc, /* modalProc */ }; /* *---------------------------------------------------------------------- * * TkpCreateScrollbar -- * * Allocate a new TkScrollbar structure. * * Results: * Returns a newly allocated TkScrollbar structure. * * Side effects: * Registers an event handler for the widget. * *---------------------------------------------------------------------- */ TkScrollbar * TkpCreateScrollbar(tkwin) Tk_Window tkwin; { WinScrollbar *scrollPtr; TkWindow *winPtr = (TkWindow *)tkwin; if (!initialized) { Tcl_MutexLock(&winScrlbrMutex); UpdateScrollbarMetrics(); initialized = 1; Tcl_MutexUnlock(&winScrlbrMutex); } scrollPtr = (WinScrollbar *) ckalloc(sizeof(WinScrollbar)); scrollPtr->winFlags = 0; scrollPtr->hwnd = NULL; Tk_CreateEventHandler(tkwin, ExposureMask|StructureNotifyMask|FocusChangeMask, TkScrollbarEventProc, (ClientData) scrollPtr); if (!Tcl_GetAssocData(winPtr->mainPtr->interp, "TkScrollbar", NULL)) { Tcl_SetAssocData(winPtr->mainPtr->interp, "TkScrollbar", NULL, (ClientData)1); TkCreateBindingProcedure(winPtr->mainPtr->interp, winPtr->mainPtr->bindingTable, (ClientData)Tk_GetUid("Scrollbar"), "", ScrollbarBindProc, NULL, NULL); } return (TkScrollbar*) scrollPtr; } /* *---------------------------------------------------------------------- * * UpdateScrollbar -- * * This function updates the position and size of the scrollbar * thumb based on the current settings. * * Results: * None. * * Side effects: * Moves the thumb. * *---------------------------------------------------------------------- */ static void UpdateScrollbar(scrollPtr) WinScrollbar *scrollPtr; { SCROLLINFO scrollInfo; double thumbSize; /* * Update the current scrollbar position and shape. */ scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; scrollInfo.cbSize = sizeof(scrollInfo); scrollInfo.nMin = 0; scrollInfo.nMax = MAX_SCROLL; thumbSize = (scrollPtr->info.lastFraction - scrollPtr->info.firstFraction); scrollInfo.nPage = ((UINT) (thumbSize * (double) MAX_SCROLL)) + 1; if (thumbSize < 1.0) { scrollInfo.nPos = (int) ((scrollPtr->info.firstFraction / (1.0-thumbSize)) * (MAX_SCROLL - (scrollInfo.nPage - 1))); } else { scrollInfo.nPos = 0; /* * Disable the scrollbar when there is nothing to scroll. * This is standard Windows style (see eg Notepad). * Also prevents possible crash on XP+ systems [Bug #624116]. */ scrollInfo.fMask |= SIF_DISABLENOSCROLL; } SetScrollInfo(scrollPtr->hwnd, SB_CTL, &scrollInfo, TRUE); } /* *---------------------------------------------------------------------- * * CreateProc -- * * This function creates a new Scrollbar control, subclasses * the instance, and generates a new Window object. * * Results: * Returns the newly allocated Window object, or None on failure. * * Side effects: * Causes a new Scrollbar control to come into existence. * *---------------------------------------------------------------------- */ static Window CreateProc(tkwin, parentWin, instanceData) Tk_Window tkwin; /* Token for window. */ Window parentWin; /* Parent of new window. */ ClientData instanceData; /* Scrollbar instance data. */ { DWORD style; Window window; HWND parent; TkWindow *winPtr; WinScrollbar *scrollPtr = (WinScrollbar *)instanceData; parent = Tk_GetHWND(parentWin); if (scrollPtr->info.vertical) { style = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBS_VERT | SBS_RIGHTALIGN; } else { style = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBS_HORZ | SBS_BOTTOMALIGN; } scrollPtr->hwnd = CreateWindow("SCROLLBAR", NULL, style, Tk_X(tkwin), Tk_Y(tkwin), Tk_Width(tkwin), Tk_Height(tkwin), parent, NULL, Tk_GetHINSTANCE(), NULL); /* * Ensure new window is inserted into the stacking order at the correct * place. */ SetWindowPos(scrollPtr->hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); for (winPtr = ((TkWindow*)tkwin)->nextPtr; winPtr != NULL; winPtr = winPtr->nextPtr) { if ((winPtr->window != None) && !(winPtr->flags & TK_TOP_HIERARCHY)) { TkWinSetWindowPos(scrollPtr->hwnd, Tk_GetHWND(winPtr->window), Below); break; } } scrollPtr->lastVertical = scrollPtr->info.vertical; #ifdef _WIN64 scrollPtr->oldProc = (WNDPROC)SetWindowLongPtr(scrollPtr->hwnd, GWLP_WNDPROC, (LONG_PTR) ScrollbarProc); #else scrollPtr->oldProc = (WNDPROC)SetWindowLong(scrollPtr->hwnd, GWL_WNDPROC, (DWORD) ScrollbarProc); #endif window = Tk_AttachHWND(tkwin, scrollPtr->hwnd); UpdateScrollbar(scrollPtr); return window; } /* *-------------------------------------------------------------- * * TkpDisplayScrollbar -- * * This procedure redraws the contents of a scrollbar window. * It is invoked as a do-when-idle handler, so it only runs * when there's nothing else for the application to do. * * Results: * None. * * Side effects: * Information appears on the screen. * *-------------------------------------------------------------- */ void TkpDisplayScrollbar(clientData) ClientData clientData; /* Information about window. */ { WinScrollbar *scrollPtr = (WinScrollbar *) clientData; Tk_Window tkwin = scrollPtr->info.tkwin; scrollPtr->info.flags &= ~REDRAW_PENDING; if ((tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } /* * Destroy and recreate the scrollbar control if the orientation * has changed. */ if (scrollPtr->lastVertical != scrollPtr->info.vertical) { HWND hwnd = Tk_GetHWND(Tk_WindowId(tkwin)); #ifdef _WIN64 SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) scrollPtr->oldProc); #else SetWindowLong(hwnd, GWL_WNDPROC, (DWORD) scrollPtr->oldProc); #endif DestroyWindow(hwnd); CreateProc(tkwin, Tk_WindowId(Tk_Parent(tkwin)), (ClientData) scrollPtr); } else { UpdateScrollbar(scrollPtr); } } /* *---------------------------------------------------------------------- * * TkpDestroyScrollbar -- * * Free data structures associated with the scrollbar control. * * Results: * None. * * Side effects: * Restores the default control state. * *---------------------------------------------------------------------- */ void TkpDestroyScrollbar(scrollPtr) TkScrollbar *scrollPtr; { WinScrollbar *winScrollPtr = (WinScrollbar *)scrollPtr; HWND hwnd = winScrollPtr->hwnd; if (hwnd) { #ifdef _WIN64 SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) winScrollPtr->oldProc); #else SetWindowLong(hwnd, GWL_WNDPROC, (DWORD) winScrollPtr->oldProc); #endif if (winScrollPtr->winFlags & IN_MODAL_LOOP) { ((TkWindow *)scrollPtr->tkwin)->flags |= TK_DONT_DESTROY_WINDOW; SetParent(hwnd, NULL); } } winScrollPtr->winFlags |= ALREADY_DEAD; } /* *---------------------------------------------------------------------- * * UpdateScrollbarMetrics -- * * This function retrieves the current system metrics for a * scrollbar. * * Results: * None. * * Side effects: * Updates the geometry cache info for all scrollbars. * *---------------------------------------------------------------------- */ void UpdateScrollbarMetrics() { Tk_ConfigSpec *specPtr; hArrowWidth = GetSystemMetrics(SM_CXHSCROLL); hThumb = GetSystemMetrics(SM_CXHTHUMB); vArrowWidth = GetSystemMetrics(SM_CXVSCROLL); vArrowHeight = GetSystemMetrics(SM_CYVSCROLL); vThumb = GetSystemMetrics(SM_CYVTHUMB); sprintf(defWidth, "%d", vArrowWidth); for (specPtr = tkpScrollbarConfigSpecs; specPtr->type != TK_CONFIG_END; specPtr++) { if (specPtr->offset == Tk_Offset(TkScrollbar, width)) { specPtr->defValue = defWidth; } } } /* *---------------------------------------------------------------------- * * TkpComputeScrollbarGeometry -- * * After changes in a scrollbar's size or configuration, this * procedure recomputes various geometry information used in * displaying the scrollbar. * * Results: * None. * * Side effects: * The scrollbar will be displayed differently. * *---------------------------------------------------------------------- */ void TkpComputeScrollbarGeometry(scrollPtr) register TkScrollbar *scrollPtr; /* Scrollbar whose geometry may * have changed. */ { int fieldLength, minThumbSize; /* * Windows doesn't use focus rings on scrollbars, but we still * perform basic sanity checks to appease backwards compatibility. */ if (scrollPtr->highlightWidth < 0) { scrollPtr->highlightWidth = 0; } if (scrollPtr->vertical) { scrollPtr->arrowLength = vArrowHeight; fieldLength = Tk_Height(scrollPtr->tkwin); minThumbSize = vThumb; } else { scrollPtr->arrowLength = hArrowWidth; fieldLength = Tk_Width(scrollPtr->tkwin); minThumbSize = hThumb; } fieldLength -= 2*scrollPtr->arrowLength; if (fieldLength < 0) { fieldLength = 0; } scrollPtr->sliderFirst = (int) ((double)fieldLength * scrollPtr->firstFraction); scrollPtr->sliderLast = (int) ((double)fieldLength * scrollPtr->lastFraction); /* * Adjust the slider so that some piece of it is always * displayed in the scrollbar and so that it has at least * a minimal width (so it can be grabbed with the mouse). */ if (scrollPtr->sliderFirst > fieldLength) { scrollPtr->sliderFirst = fieldLength; } if (scrollPtr->sliderFirst < 0) { scrollPtr->sliderFirst = 0; } if (scrollPtr->sliderLast < (scrollPtr->sliderFirst + minThumbSize)) { scrollPtr->sliderLast = scrollPtr->sliderFirst + minThumbSize; } if (scrollPtr->sliderLast > fieldLength) { scrollPtr->sliderLast = fieldLength; } scrollPtr->sliderFirst += scrollPtr->arrowLength; scrollPtr->sliderLast += scrollPtr->arrowLength; /* * Register the desired geometry for the window (leave enough space * for the two arrows plus a minimum-size slider, plus border around * the whole window, if any). Then arrange for the window to be * redisplayed. */ if (scrollPtr->vertical) { Tk_GeometryRequest(scrollPtr->tkwin, scrollPtr->width, 2*scrollPtr->arrowLength + minThumbSize); } else { Tk_GeometryRequest(scrollPtr->tkwin, 2*scrollPtr->arrowLength + minThumbSize, scrollPtr->width); } Tk_SetInternalBorder(scrollPtr->tkwin, 0); } /* *---------------------------------------------------------------------- * * ScrollbarProc -- * * This function is call by Windows whenever an event occurs on * a scrollbar control created by Tk. * * Results: * Standard Windows return value. * * Side effects: * May generate events. * *---------------------------------------------------------------------- */ static LRESULT CALLBACK ScrollbarProc(hwnd, message, wParam, lParam) HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; { LRESULT result; POINT point; WinScrollbar *scrollPtr; Tk_Window tkwin = Tk_HWNDToWindow(hwnd); if (tkwin == NULL) { panic("ScrollbarProc called on an invalid HWND"); } scrollPtr = (WinScrollbar *)((TkWindow*)tkwin)->instanceData; switch(message) { case WM_HSCROLL: case WM_VSCROLL: { Tcl_Interp *interp; Tcl_DString cmdString; int command = LOWORD(wParam); int code; GetCursorPos(&point); Tk_TranslateWinEvent(NULL, WM_MOUSEMOVE, 0, MAKELPARAM(point.x, point.y), &result); if (command == SB_ENDSCROLL) { return 0; } /* * Bail out immediately if there isn't a command to invoke. */ if (scrollPtr->info.commandSize == 0) { Tcl_ServiceAll(); return 0; } Tcl_DStringInit(&cmdString); Tcl_DStringAppend(&cmdString, scrollPtr->info.command, scrollPtr->info.commandSize); if (command == SB_LINELEFT || command == SB_LINERIGHT) { Tcl_DStringAppendElement(&cmdString, "scroll"); Tcl_DStringAppendElement(&cmdString, (command == SB_LINELEFT ) ? "-1" : "1"); Tcl_DStringAppendElement(&cmdString, "units"); } else if (command == SB_PAGELEFT || command == SB_PAGERIGHT) { Tcl_DStringAppendElement(&cmdString, "scroll"); Tcl_DStringAppendElement(&cmdString, (command == SB_PAGELEFT ) ? "-1" : "1"); Tcl_DStringAppendElement(&cmdString, "pages"); } else { char valueString[TCL_DOUBLE_SPACE]; double pos = 0.0; switch (command) { case SB_THUMBPOSITION: pos = ((double)HIWORD(wParam)) / MAX_SCROLL; break; case SB_THUMBTRACK: pos = ((double)HIWORD(wParam)) / MAX_SCROLL; break; case SB_TOP: pos = 0.0; break; case SB_BOTTOM: pos = 1.0; break; } sprintf(valueString, "%g", pos); Tcl_DStringAppendElement(&cmdString, "moveto"); Tcl_DStringAppendElement(&cmdString, valueString); } interp = scrollPtr->info.interp; code = Tcl_GlobalEval(interp, cmdString.string); if (code != TCL_OK && code != TCL_CONTINUE && code != TCL_BREAK) { Tcl_AddErrorInfo(interp, "\n (scrollbar command)"); Tcl_BackgroundError(interp); } Tcl_DStringFree(&cmdString); Tcl_ServiceAll(); return 0; } default: if (Tk_TranslateWinEvent(hwnd, message, wParam, lParam, &result)) { return result; } } return CallWindowProc(scrollPtr->oldProc, hwnd, message, wParam, lParam); } /* *---------------------------------------------------------------------- * * TkpConfigureScrollbar -- * * This procedure is called after the generic code has finished * processing configuration options, in order to configure * platform specific options. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkpConfigureScrollbar(scrollPtr) register TkScrollbar *scrollPtr; /* Information about widget; may or * may not already have values for * some fields. */ { } /* *-------------------------------------------------------------- * * ScrollbarBindProc -- * * This procedure is invoked when the default * binding on the Scrollbar bind tag fires. * * Results: * None. * * Side effects: * The event enters a modal loop. * *-------------------------------------------------------------- */ static int ScrollbarBindProc(clientData, interp, eventPtr, tkwin, keySym) ClientData clientData; Tcl_Interp *interp; XEvent *eventPtr; Tk_Window tkwin; KeySym keySym; { TkWindow *winPtr = (TkWindow*)tkwin; if (eventPtr->type == ButtonPress) { winPtr->flags |= TK_DEFER_MODAL; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ModalLoopProc -- * * This function is invoked at the end of the event processing * whenever the ScrollbarBindProc has been invoked for a ButtonPress * event. * * Results: * None. * * Side effects: * Enters a modal loop. * *---------------------------------------------------------------------- */ static void ModalLoopProc(tkwin, eventPtr) Tk_Window tkwin; XEvent *eventPtr; { TkWindow *winPtr = (TkWindow*)tkwin; WinScrollbar *scrollPtr = (WinScrollbar *) winPtr->instanceData; int oldMode; if (scrollPtr->hwnd) { Tcl_Preserve((ClientData)scrollPtr); scrollPtr->winFlags |= IN_MODAL_LOOP; oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); TkWinResendEvent(scrollPtr->oldProc, scrollPtr->hwnd, eventPtr); (void) Tcl_SetServiceMode(oldMode); scrollPtr->winFlags &= ~IN_MODAL_LOOP; if (scrollPtr->hwnd && scrollPtr->winFlags & ALREADY_DEAD) { DestroyWindow(scrollPtr->hwnd); } Tcl_Release((ClientData)scrollPtr); } } /* *-------------------------------------------------------------- * * TkpScrollbarPosition -- * * Determine the scrollbar element corresponding to a * given position. * * Results: * One of TOP_ARROW, TOP_GAP, etc., indicating which element * of the scrollbar covers the position given by (x, y). If * (x,y) is outside the scrollbar entirely, then OUTSIDE is * returned. * * Side effects: * None. * *-------------------------------------------------------------- */ int TkpScrollbarPosition(scrollPtr, x, y) register TkScrollbar *scrollPtr; /* Scrollbar widget record. */ int x, y; /* Coordinates within scrollPtr's * window. */ { int length, width, tmp; if (scrollPtr->vertical) { length = Tk_Height(scrollPtr->tkwin); width = Tk_Width(scrollPtr->tkwin); } else { tmp = x; x = y; y = tmp; length = Tk_Width(scrollPtr->tkwin); width = Tk_Height(scrollPtr->tkwin); } if ((x < scrollPtr->inset) || (x >= (width - scrollPtr->inset)) || (y < scrollPtr->inset) || (y >= (length - scrollPtr->inset))) { return OUTSIDE; } /* * All of the calculations in this procedure mirror those in * TkpDisplayScrollbar. Be sure to keep the two consistent. */ if (y < (scrollPtr->inset + scrollPtr->arrowLength)) { return TOP_ARROW; } if (y < scrollPtr->sliderFirst) { return TOP_GAP; } if (y < scrollPtr->sliderLast) { return SLIDER; } if (y >= (length - (scrollPtr->arrowLength + scrollPtr->inset))) { return BOTTOM_ARROW; } return BOTTOM_GAP; }