📄 updown.c
字号:
/*
* Updown control
*
* Copyright 1997, 2002 Dimitrie O. Paun
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* NOTE
*
* This code was audited for completeness against the documented features
* of Comctl32.dll version 6.0 on Sep. 9, 2002, by Dimitrie O. Paun.
*
* Unless otherwise noted, we believe this code to be complete, as per
* the specification mentioned above.
* If you discover missing features, or bugs, please note them below.
*
*/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "commctrl.h"
#include "comctl32.h"
#include "uxtheme.h"
#include "tmschema.h"
#include "wine/unicode.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(updown);
typedef struct
{
HWND Self; /* Handle to this up-down control */
HWND Notify; /* Handle to the parent window */
DWORD dwStyle; /* The GWL_STYLE for this window */
UINT AccelCount; /* Number of elements in AccelVect */
UDACCEL* AccelVect; /* Vector containing AccelCount elements */
INT AccelIndex; /* Current accel index, -1 if not accel'ing */
INT Base; /* Base to display nr in the buddy window */
INT CurVal; /* Current up-down value */
INT MinVal; /* Minimum up-down value */
INT MaxVal; /* Maximum up-down value */
HWND Buddy; /* Handle to the buddy window */
INT BuddyType; /* Remembers the buddy type BUDDY_TYPE_* */
INT Flags; /* Internal Flags FLAG_* */
BOOL UnicodeFormat; /* Marks the use of Unicode internally */
} UPDOWN_INFO;
/* Control configuration constants */
#define INITIAL_DELAY 500 /* initial timer until auto-inc kicks in */
#define AUTOPRESS_DELAY 250 /* time to keep arrow pressed on KEY_DOWN */
#define REPEAT_DELAY 50 /* delay between auto-increments */
#define DEFAULT_WIDTH 14 /* default width of the ctrl */
#define DEFAULT_XSEP 0 /* default separation between buddy and ctrl */
#define DEFAULT_ADDTOP 0 /* amount to extend above the buddy window */
#define DEFAULT_ADDBOT 0 /* amount to extend below the buddy window */
#define DEFAULT_BUDDYBORDER 2 /* Width/height of the buddy border */
#define DEFAULT_BUDDYSPACER 2 /* Spacer between the buddy and the ctrl */
#define DEFAULT_BUDDYBORDER_THEMED 1 /* buddy border when theming is enabled */
#define DEFAULT_BUDDYSPACER_THEMED 0 /* buddy spacer when theming is enabled */
/* Work constants */
#define FLAG_INCR 0x01
#define FLAG_DECR 0x02
#define FLAG_MOUSEIN 0x04
#define FLAG_PRESSED 0x08
#define FLAG_ARROW (FLAG_INCR | FLAG_DECR)
#define BUDDY_TYPE_UNKNOWN 0
#define BUDDY_TYPE_LISTBOX 1
#define BUDDY_TYPE_EDIT 2
#define TIMER_AUTOREPEAT 1
#define TIMER_ACCEL 2
#define TIMER_AUTOPRESS 3
#define UPDOWN_GetInfoPtr(hwnd) ((UPDOWN_INFO *)GetWindowLongPtrW (hwnd,0))
#define COUNT_OF(a) (sizeof(a)/sizeof(a[0]))
static const WCHAR BUDDY_UPDOWN_HWND[] = { 'b', 'u', 'd', 'd', 'y', 'U', 'p', 'D', 'o', 'w', 'n', 'H', 'W', 'N', 'D', 0 };
static const WCHAR BUDDY_SUPERCLASS_WNDPROC[] = { 'b', 'u', 'd', 'd', 'y', 'S', 'u', 'p', 'p', 'e', 'r',
'C', 'l', 'a', 's', 's', 'W', 'n', 'd', 'P', 'r', 'o', 'c', 0 };
static void UPDOWN_DoAction (UPDOWN_INFO *infoPtr, int delta, int action);
/***********************************************************************
* UPDOWN_IsBuddyEdit
* Tests if our buddy is an edit control.
*/
static inline BOOL UPDOWN_IsBuddyEdit(const UPDOWN_INFO *infoPtr)
{
return infoPtr->BuddyType == BUDDY_TYPE_EDIT;
}
/***********************************************************************
* UPDOWN_IsBuddyListbox
* Tests if our buddy is a listbox control.
*/
static inline BOOL UPDOWN_IsBuddyListbox(const UPDOWN_INFO *infoPtr)
{
return infoPtr->BuddyType == BUDDY_TYPE_LISTBOX;
}
/***********************************************************************
* UPDOWN_InBounds
* Tests if a given value 'val' is between the Min&Max limits
*/
static BOOL UPDOWN_InBounds(const UPDOWN_INFO *infoPtr, int val)
{
if(infoPtr->MaxVal > infoPtr->MinVal)
return (infoPtr->MinVal <= val) && (val <= infoPtr->MaxVal);
else
return (infoPtr->MaxVal <= val) && (val <= infoPtr->MinVal);
}
/***********************************************************************
* UPDOWN_OffsetVal
* Change the current value by delta.
* It returns TRUE is the value was changed successfuly, or FALSE
* if the value was not changed, as it would go out of bounds.
*/
static BOOL UPDOWN_OffsetVal(UPDOWN_INFO *infoPtr, int delta)
{
/* check if we can do the modification first */
if(!UPDOWN_InBounds (infoPtr, infoPtr->CurVal+delta)) {
if (infoPtr->dwStyle & UDS_WRAP) {
delta += (delta < 0 ? -1 : 1) *
(infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1) *
(infoPtr->MinVal - infoPtr->MaxVal) +
(delta < 0 ? 1 : -1);
} else return FALSE;
}
infoPtr->CurVal += delta;
return TRUE;
}
/***********************************************************************
* UPDOWN_HasBuddyBorder
*
* When we have a buddy set and that we are aligned on our buddy, we
* want to draw a sunken edge to make like we are part of that control.
*/
static BOOL UPDOWN_HasBuddyBorder(const UPDOWN_INFO *infoPtr)
{
return ( ((infoPtr->dwStyle & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT)) != 0) &&
UPDOWN_IsBuddyEdit(infoPtr) );
}
/***********************************************************************
* UPDOWN_GetArrowRect
* wndPtr - pointer to the up-down wnd
* rect - will hold the rectangle
* arrow - FLAG_INCR to get the "increment" rect (up or right)
* FLAG_DECR to get the "decrement" rect (down or left)
* If both flags are pressent, the envelope is returned.
*/
static void UPDOWN_GetArrowRect (const UPDOWN_INFO* infoPtr, RECT *rect, int arrow)
{
HTHEME theme = GetWindowTheme (infoPtr->Self);
const int border = theme ? DEFAULT_BUDDYBORDER_THEMED : DEFAULT_BUDDYBORDER;
const int spacer = theme ? DEFAULT_BUDDYSPACER_THEMED : DEFAULT_BUDDYSPACER;
GetClientRect (infoPtr->Self, rect);
/*
* Make sure we calculate the rectangle to fit even if we draw the
* border.
*/
if (UPDOWN_HasBuddyBorder(infoPtr)) {
if (infoPtr->dwStyle & UDS_ALIGNLEFT)
rect->left += border;
else
rect->right -= border;
InflateRect(rect, 0, -border);
}
/* now figure out if we need a space away from the buddy */
if (IsWindow(infoPtr->Buddy) ) {
if (infoPtr->dwStyle & UDS_ALIGNLEFT) rect->right -= spacer;
else rect->left += spacer;
}
/*
* We're calculating the midpoint to figure-out where the
* separation between the buttons will lay. We make sure that we
* round the uneven numbers by adding 1.
*/
if (infoPtr->dwStyle & UDS_HORZ) {
int len = rect->right - rect->left + 1; /* compute the width */
if (arrow & FLAG_INCR)
rect->left = rect->left + len/2;
if (arrow & FLAG_DECR)
rect->right = rect->left + len/2 - (theme ? 0 : 1);
} else {
int len = rect->bottom - rect->top + 1; /* compute the height */
if (arrow & FLAG_INCR)
rect->bottom = rect->top + len/2 - (theme ? 0 : 1);
if (arrow & FLAG_DECR)
rect->top = rect->top + len/2;
}
}
/***********************************************************************
* UPDOWN_GetArrowFromPoint
* Returns the rectagle (for the up or down arrow) that contains pt.
* If it returns the up rect, it returns FLAG_INCR.
* If it returns the down rect, it returns FLAG_DECR.
*/
static INT UPDOWN_GetArrowFromPoint (const UPDOWN_INFO *infoPtr, RECT *rect, POINT pt)
{
UPDOWN_GetArrowRect (infoPtr, rect, FLAG_INCR);
if(PtInRect(rect, pt)) return FLAG_INCR;
UPDOWN_GetArrowRect (infoPtr, rect, FLAG_DECR);
if(PtInRect(rect, pt)) return FLAG_DECR;
return 0;
}
/***********************************************************************
* UPDOWN_GetThousandSep
* Returns the thousand sep. If an error occurs, it returns ','.
*/
static WCHAR UPDOWN_GetThousandSep(void)
{
WCHAR sep[2];
if(GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, sep, 2) != 1)
sep[0] = ',';
return sep[0];
}
/***********************************************************************
* UPDOWN_GetBuddyInt
* Tries to read the pos from the buddy window and if it succeeds,
* it stores it in the control's CurVal
* returns:
* TRUE - if it read the integer from the buddy successfully
* FALSE - if an error occurred
*/
static BOOL UPDOWN_GetBuddyInt (UPDOWN_INFO *infoPtr)
{
WCHAR txt[20], sep, *src, *dst;
int newVal;
if (!((infoPtr->dwStyle & UDS_SETBUDDYINT) && IsWindow(infoPtr->Buddy)))
return FALSE;
/*if the buddy is a list window, we must set curr index */
if (UPDOWN_IsBuddyListbox(infoPtr)) {
newVal = SendMessageW(infoPtr->Buddy, LB_GETCARETINDEX, 0, 0);
if(newVal < 0) return FALSE;
} else {
/* we have a regular window, so will get the text */
/* note that a zero-length string is a legitimate value for 'txt',
* and ought to result in a successful conversion to '0'. */
if (GetWindowTextW(infoPtr->Buddy, txt, COUNT_OF(txt)) < 0)
return FALSE;
sep = UPDOWN_GetThousandSep();
/* now get rid of the separators */
for(src = dst = txt; *src; src++)
if(*src != sep) *dst++ = *src;
*dst = 0;
/* try to convert the number and validate it */
newVal = strtolW(txt, &src, infoPtr->Base);
if(*src || !UPDOWN_InBounds (infoPtr, newVal)) return FALSE;
}
TRACE("new value(%d) from buddy (old=%d)\n", newVal, infoPtr->CurVal);
infoPtr->CurVal = newVal;
return TRUE;
}
/***********************************************************************
* UPDOWN_SetBuddyInt
* Tries to set the pos to the buddy window based on current pos
* returns:
* TRUE - if it set the caption of the buddy successfully
* FALSE - if an error occurred
*/
static BOOL UPDOWN_SetBuddyInt (const UPDOWN_INFO *infoPtr)
{
WCHAR fmt[3] = { '%', 'd', '\0' };
WCHAR txt[20];
int len;
if (!((infoPtr->dwStyle & UDS_SETBUDDYINT) && IsWindow(infoPtr->Buddy)))
return FALSE;
TRACE("set new value(%d) to buddy.\n", infoPtr->CurVal);
/*if the buddy is a list window, we must set curr index */
if (UPDOWN_IsBuddyListbox(infoPtr)) {
return SendMessageW(infoPtr->Buddy, LB_SETCURSEL, infoPtr->CurVal, 0) != LB_ERR;
}
/* Regular window, so set caption to the number */
if (infoPtr->Base == 16) fmt[1] = 'X';
len = wsprintfW(txt, fmt, infoPtr->CurVal);
/* Do thousands separation if necessary */
if (!(infoPtr->dwStyle & UDS_NOTHOUSANDS) && (len > 3)) {
WCHAR tmp[COUNT_OF(txt)], *src = tmp, *dst = txt;
WCHAR sep = UPDOWN_GetThousandSep();
int start = len % 3;
memcpy(tmp, txt, sizeof(txt));
if (start == 0) start = 3;
dst += start;
src += start;
for (len=0; *src; len++) {
if (len % 3 == 0) *dst++ = sep;
*dst++ = *src++;
}
*dst = 0;
}
return SetWindowTextW(infoPtr->Buddy, txt);
}
/***********************************************************************
* UPDOWN_DrawBuddyBackground
*
* Draw buddy background for visual integration.
*/
static BOOL UPDOWN_DrawBuddyBackground (const UPDOWN_INFO *infoPtr, HDC hdc)
{
RECT br;
HTHEME buddyTheme = GetWindowTheme (infoPtr->Buddy);
if (!buddyTheme) return FALSE;
GetClientRect (infoPtr->Buddy, &br);
MapWindowPoints (infoPtr->Buddy, infoPtr->Self, (POINT*)&br, 2);
/* FIXME: take disabled etc. into account */
DrawThemeBackground (buddyTheme, hdc, 0, 0, &br, NULL);
return TRUE;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -