/* * IP Address control * * Copyright 1999 Chris Morgan and * James Abbatiello * Copyright 1998, 1999 Eric Kohl * Copyright 1998 Alex Priem * * NOTES * * TODO: * -Edit control doesn't support the ES_CENTER style which prevents * this ipaddress control from having centered text look like the * windows ipaddress control * -Check all notifications/behavior. * -Optimization: * -include lpipsi in IPADDRESS_INFO. * -CurrentFocus: field that has focus at moment of processing. * -connect Rect32 rcClient. * -check ipaddress.cpp for missing features. * -refresh: draw '.' instead of setpixel. */ #include #include #include #include "winbase.h" #include "commctrl.h" #include "heap.h" #include "debugtools.h" DEFAULT_DEBUG_CHANNEL(ipaddress); typedef struct { BYTE LowerLimit[4]; BYTE UpperLimit[4]; RECT rcClient; INT uFocus; } IPADDRESS_INFO; typedef struct { WNDPROC wpOrigProc[4]; HWND hwndIP[4]; IPADDRESS_INFO *infoPtr; HWND hwnd; UINT uRefCount; } IP_SUBCLASS_INFO, *LPIP_SUBCLASS_INFO; #define IPADDRESS_GetInfoPtr(hwnd) ((IPADDRESS_INFO *)GetWindowLongA (hwnd, 0)) static BOOL IPADDRESS_SendNotify (HWND hwnd, UINT command); static BOOL IPADDRESS_SendIPAddressNotify (HWND hwnd, UINT field, BYTE newValue); /* property name of tooltip window handle */ #define IP_SUBCLASS_PROP "CCIP32SubclassInfo" static LRESULT CALLBACK IPADDRESS_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static VOID IPADDRESS_Refresh (HWND hwnd, HDC hdc) { RECT rcClient; HBRUSH hbr; COLORREF clr=GetSysColor (COLOR_3DDKSHADOW); int i,x,fieldsize; GetClientRect (hwnd, &rcClient); hbr = CreateSolidBrush (RGB(255,255,255)); DrawEdge (hdc, &rcClient, EDGE_SUNKEN, BF_RECT | BF_ADJUST); FillRect (hdc, &rcClient, hbr); DeleteObject (hbr); x=rcClient.left; fieldsize=(rcClient.right-rcClient.left) / 4; for (i=0; i<3; i++) { /* Should draw text "." here */ x+=fieldsize; SetPixel (hdc, x, 13, clr); SetPixel (hdc, x, 14, clr); SetPixel (hdc, x+1, 13, clr); SetPixel (hdc, x+1, 14, clr); } } static LRESULT IPADDRESS_Create (HWND hwnd, WPARAM wParam, LPARAM lParam) { IPADDRESS_INFO *infoPtr; RECT rcClient, edit; int i,fieldsize; LPIP_SUBCLASS_INFO lpipsi; infoPtr = (IPADDRESS_INFO *)COMCTL32_Alloc (sizeof(IPADDRESS_INFO)); SetWindowLongA (hwnd, 0, (DWORD)infoPtr); GetClientRect (hwnd, &rcClient); fieldsize=(rcClient.right-rcClient.left) /4; edit.top =rcClient.top+2; edit.bottom=rcClient.bottom-2; lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP); if (lpipsi == NULL) { lpipsi = (LPIP_SUBCLASS_INFO) COMCTL32_Alloc (sizeof(IP_SUBCLASS_INFO)); lpipsi->hwnd = hwnd; lpipsi->uRefCount++; SetPropA ((HWND)hwnd, IP_SUBCLASS_PROP, (HANDLE)lpipsi); /* infoPtr->lpipsi= lpipsi; */ } else WARN("IP-create called twice\n"); for (i=0; i<=3; i++) { infoPtr->LowerLimit[i]=0; infoPtr->UpperLimit[i]=255; edit.left=rcClient.left+i*fieldsize+6; edit.right=rcClient.left+(i+1)*fieldsize-2; lpipsi->hwndIP[i]= CreateWindowA ("edit", NULL, WS_CHILD | WS_VISIBLE | ES_CENTER, edit.left, edit.top, edit.right-edit.left, edit.bottom-edit.top, hwnd, (HMENU) 1, GetWindowLongA (hwnd, GWL_HINSTANCE), NULL); lpipsi->wpOrigProc[i]= (WNDPROC) SetWindowLongA (lpipsi->hwndIP[i],GWL_WNDPROC, (LONG) IPADDRESS_SubclassProc); SetPropA ((HWND)lpipsi->hwndIP[i], IP_SUBCLASS_PROP, (HANDLE)lpipsi); } lpipsi->infoPtr= infoPtr; return 0; } static LRESULT IPADDRESS_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam) { int i; IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd); LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP); for (i=0; i<=3; i++) { SetWindowLongA ((HWND)lpipsi->hwndIP[i], GWL_WNDPROC, (LONG)lpipsi->wpOrigProc[i]); } COMCTL32_Free (infoPtr); SetWindowLongA (hwnd, 0, 0); return 0; } static LRESULT IPADDRESS_KillFocus (HWND hwnd, WPARAM wParam, LPARAM lParam) { HDC hdc; TRACE("\n"); hdc = GetDC (hwnd); IPADDRESS_Refresh (hwnd, hdc); ReleaseDC (hwnd, hdc); IPADDRESS_SendIPAddressNotify (hwnd, 0, 0); /* FIXME: should use -1 */ IPADDRESS_SendNotify (hwnd, EN_KILLFOCUS); InvalidateRect (hwnd, NULL, TRUE); return 0; } static LRESULT IPADDRESS_Paint (HWND hwnd, WPARAM wParam) { HDC hdc; PAINTSTRUCT ps; hdc = wParam==0 ? BeginPaint (hwnd, &ps) : (HDC)wParam; IPADDRESS_Refresh (hwnd, hdc); if(!wParam) EndPaint (hwnd, &ps); return 0; } static LRESULT IPADDRESS_SetFocus (HWND hwnd, WPARAM wParam, LPARAM lParam) { HDC hdc; TRACE("\n"); hdc = GetDC (hwnd); IPADDRESS_Refresh (hwnd, hdc); ReleaseDC (hwnd, hdc); return 0; } static LRESULT IPADDRESS_Size (HWND hwnd, WPARAM wParam, LPARAM lParam) { /* IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd); */ TRACE("\n"); return 0; } static BOOL IPADDRESS_SendNotify (HWND hwnd, UINT command) { TRACE("%x\n",command); return (BOOL)SendMessageA (GetParent (hwnd), WM_COMMAND, MAKEWPARAM (GetWindowLongA (hwnd, GWL_ID),command), (LPARAM)hwnd); } static BOOL IPADDRESS_SendIPAddressNotify (HWND hwnd, UINT field, BYTE newValue) { NMIPADDRESS nmip; TRACE("%x %x\n",field,newValue); nmip.hdr.hwndFrom = hwnd; nmip.hdr.idFrom = GetWindowLongA (hwnd, GWL_ID); nmip.hdr.code = IPN_FIELDCHANGED; nmip.iField=field; nmip.iValue=newValue; return (BOOL)SendMessageA (GetParent (hwnd), WM_NOTIFY, (WPARAM)nmip.hdr.idFrom, (LPARAM)&nmip); } static LRESULT IPADDRESS_ClearAddress (HWND hwnd, WPARAM wParam, LPARAM lParam) { int i; HDC hdc; char buf[1]; LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd,IP_SUBCLASS_PROP); TRACE("\n"); buf[0]=0; for (i=0; i<=3; i++) SetWindowTextA (lpipsi->hwndIP[i],buf); hdc = GetDC (hwnd); IPADDRESS_Refresh (hwnd, hdc); ReleaseDC (hwnd, hdc); return 0; } static LRESULT IPADDRESS_IsBlank (HWND hwnd, WPARAM wParam, LPARAM lParam) { int i; char buf[20]; LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP); TRACE("\n"); for (i=0; i<=3; i++) { GetWindowTextA (lpipsi->hwndIP[i],buf,5); if (buf[0]) return 0; } return 1; } static LRESULT IPADDRESS_GetAddress (HWND hwnd, WPARAM wParam, LPARAM lParam) { char field[20]; int i,valid,fieldvalue; DWORD ip_addr; IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd); LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP); TRACE("\n"); valid=0; ip_addr=0; for (i=0; i<=3; i++) { GetWindowTextA (lpipsi->hwndIP[i],field,4); ip_addr*=256; if (field[0]) { field[3]=0; fieldvalue=atoi(field); if (fieldvalueLowerLimit[i]) fieldvalue=infoPtr->LowerLimit[i]; if (fieldvalue>infoPtr->UpperLimit[i]) fieldvalue=infoPtr->UpperLimit[i]; ip_addr+=fieldvalue; valid++; } } *(LPDWORD) lParam=ip_addr; return valid; } static LRESULT IPADDRESS_SetRange (HWND hwnd, WPARAM wParam, LPARAM lParam) { IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd); INT index; TRACE("\n"); index=(INT) wParam; if ((index<0) || (index>3)) return 0; infoPtr->LowerLimit[index]=lParam & 0xff; infoPtr->UpperLimit[index]=(lParam >>8) & 0xff; return 1; } static LRESULT IPADDRESS_SetAddress (HWND hwnd, WPARAM wParam, LPARAM lParam) { IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd); HDC hdc; LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP); int i,ip_address,value; char buf[20]; TRACE("\n"); ip_address=(DWORD) lParam; for (i=3; i>=0; i--) { value=ip_address & 0xff; if ((value>=infoPtr->LowerLimit[i]) && (value<=infoPtr->UpperLimit[i])) { sprintf (buf,"%d",value); SetWindowTextA (lpipsi->hwndIP[i],buf); IPADDRESS_SendNotify (hwnd, EN_CHANGE); } ip_address/=256; } hdc = GetDC (hwnd); /* & send notifications */ IPADDRESS_Refresh (hwnd, hdc); ReleaseDC (hwnd, hdc); return TRUE; } static LRESULT IPADDRESS_SetFocusToField (HWND hwnd, WPARAM wParam, LPARAM lParam) { /* IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd); */ LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP); INT index; index=(INT) wParam; TRACE(" %d\n", index); if ((index<0) || (index>3)) return 0; SetFocus (lpipsi->hwndIP[index]); return 1; } static LRESULT IPADDRESS_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam) { TRACE("\n"); SetFocus (hwnd); IPADDRESS_SendNotify (hwnd, EN_SETFOCUS); IPADDRESS_SetFocusToField (hwnd, 0, 0); return TRUE; } static INT IPADDRESS_CheckField (LPIP_SUBCLASS_INFO lpipsi, int currentfield) { int newField,fieldvalue; char field[20]; IPADDRESS_INFO *infoPtr=lpipsi->infoPtr; if(currentfield >= 0 && currentfield < 4) { TRACE("\n"); GetWindowTextA (lpipsi->hwndIP[currentfield],field,4); if (field[0]) { field[3]=0; newField=-1; fieldvalue=atoi(field); if (fieldvalue < infoPtr->LowerLimit[currentfield]) newField=infoPtr->LowerLimit[currentfield]; if (fieldvalue > infoPtr->UpperLimit[currentfield]) newField=infoPtr->UpperLimit[currentfield]; if (newField >= 0) { sprintf (field,"%d",newField); SetWindowTextA (lpipsi->hwndIP[currentfield], field); return TRUE; } } } return FALSE; } static INT IPADDRESS_GotoNextField (LPIP_SUBCLASS_INFO lpipsi, int currentfield) { if(currentfield >= -1 && currentfield < 4) { IPADDRESS_CheckField(lpipsi, currentfield); /* check the fields value */ if(currentfield < 3) { SetFocus (lpipsi->hwndIP[currentfield+1]); return TRUE; } } return FALSE; } /* period: move and select the text in the next field to the right if */ /* the current field is not empty(l!=0), we are not in the */ /* left most position, and nothing is selected(startsel==endsel) */ /* spacebar: same behavior as period */ /* alpha characters: completely ignored */ /* digits: accepted when field text length < 2 ignored otherwise. */ /* when 3 numbers have been entered into the field the value */ /* of the field is checked, if the field value exceeds the */ /* maximum value and is changed the field remains the current */ /* field, otherwise focus moves to the field to the right */ /* tab: change focus from the current ipaddress control to the next */ /* control in the tab order */ /* right arrow: move to the field on the right to the left most */ /* position in that field if no text is selected, */ /* we are in the right most position in the field, */ /* we are not in the right most field */ /* left arrow: move to the field on the left to the right most */ /* position in that field if no text is selected, */ /* we are in the left most position in the current field */ /* and we are not in the left most field */ /* backspace: delete the character to the left of the cursor position, */ /* if none are present move to the field on the left if */ /* we are not in the left most field and delete the right */ /* most digit in that field while keeping the cursor */ /* on the right side of the field */ LRESULT CALLBACK IPADDRESS_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { IPADDRESS_INFO *infoPtr; LPIP_SUBCLASS_INFO lpipsi = (LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd,IP_SUBCLASS_PROP); CHAR c = (CHAR)wParam; INT i, l, index, startsel, endsel; infoPtr = lpipsi->infoPtr; index=0; /* FIXME */ for (i=0; i<=3; i++) if (lpipsi->hwndIP[i]==hwnd) index=i; switch (uMsg) { case WM_CHAR: if(isdigit(c)) /* process all digits */ { int return_val; SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel); l = GetWindowTextLengthA (lpipsi->hwndIP[index]); if(l==2 && startsel==endsel) /* field is full after this key is processed */ { /* process the digit press before we check the field */ return_val = CallWindowProcA (lpipsi->wpOrigProc[index], hwnd, uMsg, wParam, lParam); /* if the field value was changed stay at the current field */ if(!IPADDRESS_CheckField(lpipsi, index)) IPADDRESS_GotoNextField (lpipsi,index); return return_val; } if(l > 2) /* field is full, stop key messages */ { lParam = 0; wParam = 0; } break; } if(c == '.') /* VK_PERIOD */ { l = GetWindowTextLengthA(lpipsi->hwndIP[index]); SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel); if(l != 0 && startsel==endsel && startsel != 0) { IPADDRESS_GotoNextField(lpipsi, index); SendMessageA(lpipsi->hwndIP[index+1], EM_SETSEL, (WPARAM)0, (LPARAM)3); } } /* stop all other characters */ wParam = 0; lParam = 0; break; case WM_KEYDOWN: if(c == VK_SPACE) { l = GetWindowTextLengthA(lpipsi->hwndIP[index]); SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel); if(l != 0 && startsel==endsel && startsel != 0) { IPADDRESS_GotoNextField(lpipsi, index); SendMessageA(lpipsi->hwndIP[index+1], EM_SETSEL, (WPARAM)0, (LPARAM)3); } } if(c == VK_RIGHT) { SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel); l = GetWindowTextLengthA (lpipsi->hwndIP[index]); if(startsel==endsel && startsel==l) { IPADDRESS_GotoNextField(lpipsi, index); SendMessageA(lpipsi->hwndIP[index+1], EM_SETSEL, (WPARAM)0,(LPARAM)0); } } if(c == VK_LEFT) { SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel); if(startsel==0 && startsel==endsel && index > 0) { IPADDRESS_GotoNextField(lpipsi, index - 2); l = GetWindowTextLengthA(lpipsi->hwndIP[index-1]); SendMessageA(lpipsi->hwndIP[index-1], EM_SETSEL, (WPARAM)l,(LPARAM)l); } } if(c == VK_BACK) /* VK_BACKSPACE */ { CHAR buf[4]; SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel); l = GetWindowTextLengthA (lpipsi->hwndIP[index]); if(startsel==endsel && startsel==0 && index > 0) { l = GetWindowTextLengthA(lpipsi->hwndIP[index-1]); if(l!=0) { GetWindowTextA (lpipsi->hwndIP[index-1],buf,4); buf[l-1] = '\0'; SetWindowTextA(lpipsi->hwndIP[index-1], buf); SendMessageA(lpipsi->hwndIP[index-1], EM_SETSEL, (WPARAM)l,(LPARAM)l); } IPADDRESS_GotoNextField(lpipsi, index - 2); } } break; default: break; } return CallWindowProcA (lpipsi->wpOrigProc[index], hwnd, uMsg, wParam, lParam); } static LRESULT WINAPI IPADDRESS_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { TRACE("hwnd=%x msg=%x wparam=%x lparam=%lx\n", hwnd, uMsg, wParam, lParam); if (!IPADDRESS_GetInfoPtr (hwnd) && (uMsg != WM_CREATE)) return DefWindowProcA (hwnd, uMsg, wParam, lParam); switch (uMsg) { case IPM_CLEARADDRESS: return IPADDRESS_ClearAddress (hwnd, wParam, lParam); case IPM_SETADDRESS: return IPADDRESS_SetAddress (hwnd, wParam, lParam); case IPM_GETADDRESS: return IPADDRESS_GetAddress (hwnd, wParam, lParam); case IPM_SETRANGE: return IPADDRESS_SetRange (hwnd, wParam, lParam); case IPM_SETFOCUS: return IPADDRESS_SetFocusToField (hwnd, wParam, lParam); case IPM_ISBLANK: return IPADDRESS_IsBlank (hwnd, wParam, lParam); case WM_CREATE: return IPADDRESS_Create (hwnd, wParam, lParam); case WM_DESTROY: return IPADDRESS_Destroy (hwnd, wParam, lParam); case WM_GETDLGCODE: return DLGC_WANTARROWS | DLGC_WANTCHARS; case WM_KILLFOCUS: return IPADDRESS_KillFocus (hwnd, wParam, lParam); case WM_LBUTTONDOWN: return IPADDRESS_LButtonDown (hwnd, wParam, lParam); case WM_PAINT: return IPADDRESS_Paint (hwnd, wParam); case WM_SETFOCUS: return IPADDRESS_SetFocus (hwnd, wParam, lParam); case WM_SIZE: return IPADDRESS_Size (hwnd, wParam, lParam); default: if (uMsg >= WM_USER) ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam); return DefWindowProcA (hwnd, uMsg, wParam, lParam); } return 0; } void IPADDRESS_Register (void) { WNDCLASSA wndClass; ZeroMemory (&wndClass, sizeof(WNDCLASSA)); wndClass.style = CS_GLOBALCLASS; wndClass.lpfnWndProc = (WNDPROC)IPADDRESS_WindowProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = sizeof(IPADDRESS_INFO *); wndClass.hCursor = LoadCursorA (0, IDC_ARROWA); wndClass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); wndClass.lpszClassName = WC_IPADDRESSA; RegisterClassA (&wndClass); } VOID IPADDRESS_Unregister (void) { UnregisterClassA (WC_IPADDRESSA, (HINSTANCE)NULL); }