ipaddress.c 15.5 KB
Newer Older
1 2
/*
 * IP Address control
3
 *
4 5 6
 * Copyright 2002 Dimitrie O. Paun
 * Copyright 1999 Chris Morgan<cmorgan@wpi.edu>
 * Copyright 1999 James Abbatiello<abbeyj@wpi.edu>
7
 * Copyright 1998, 1999 Eric Kohl
8
 * Copyright 1998 Alex Priem <alexp@sci.kun.nl>
9
 *
10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
24 25 26 27 28
 * 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.
 * 
Robert Shearman's avatar
Robert Shearman committed
29
 * Unless otherwise noted, we believe this code to be complete, as per
30 31 32
 * the specification mentioned above.
 * If you discover missing features, or bugs, please note them below.
 * 
33 34
 */

35 36
#include <ctype.h>
#include <stdlib.h>
37
#include <stdarg.h>
38
#include <stdio.h>
39
#include <string.h>
40

41
#include "windef.h"
42
#include "winbase.h"
43 44 45
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
46
#include "commctrl.h"
47
#include "comctl32.h"
48
#include "wine/unicode.h"
49
#include "wine/debug.h"
50

51
WINE_DEFAULT_DEBUG_CHANNEL(ipaddress);
52

53 54
typedef struct
{
55 56 57 58 59
    HWND     EditHwnd;
    INT      LowerLimit;
    INT      UpperLimit;
    WNDPROC  OrigProc;
} IPPART_INFO;
60 61 62

typedef struct
{
63
    HWND	Self;
64
    HWND	Notify;
65 66
    IPPART_INFO	Part[4];
} IPADDRESS_INFO;
67

68 69 70
static const WCHAR IP_SUBCLASS_PROP[] = 
    { 'C', 'C', 'I', 'P', '3', '2', 'S', 'u', 'b', 'c', 'l', 'a', 's', 's', 'I', 'n', 'f', 'o', 0 };

71 72 73 74
#define POS_DEFAULT	0
#define POS_LEFT	1
#define POS_RIGHT	2
#define POS_SELALL	3
75 76

static LRESULT CALLBACK
77
IPADDRESS_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
78

79
static LRESULT IPADDRESS_Notify (IPADDRESS_INFO *infoPtr, UINT command)
80
{
81
    HWND hwnd = infoPtr->Self;
82

83
    TRACE("(command=%x)\n", command);
84

85
    return SendMessageW (infoPtr->Notify, WM_COMMAND,
86
             MAKEWPARAM (GetWindowLongPtrW (hwnd, GWLP_ID), command), (LPARAM)hwnd);
87 88
}

89
static INT IPADDRESS_IPNotify (IPADDRESS_INFO *infoPtr, INT field, INT value)
90
{
91
    NMIPADDRESS nmip;
92

93
    TRACE("(field=%x, value=%d)\n", field, value);
94

95
    nmip.hdr.hwndFrom = infoPtr->Self;
96
    nmip.hdr.idFrom   = GetWindowLongPtrW (infoPtr->Self, GWLP_ID);
97
    nmip.hdr.code     = IPN_FIELDCHANGED;
98

99 100
    nmip.iField = field;
    nmip.iValue = value;
101

102
    SendMessageW (infoPtr->Notify, WM_NOTIFY,
103
                  (WPARAM)nmip.hdr.idFrom, (LPARAM)&nmip);
104

105
    TRACE("<-- %d\n", nmip.iValue);
106

107
    return nmip.iValue;
108
}
109 110


111
static int IPADDRESS_GetPartIndex(IPADDRESS_INFO *infoPtr, HWND hwnd)
112
{
113
    int i;
114

115
    TRACE("(hwnd=%p)\n", hwnd);
116 117

    for (i = 0; i < 4; i++)
118
        if (infoPtr->Part[i].EditHwnd == hwnd) return i;
119

120
    ERR("We subclassed the wrong window! (hwnd=%p)\n", hwnd);
121
    return -1;
122 123 124
}


125
static LRESULT IPADDRESS_Draw (IPADDRESS_INFO *infoPtr, HDC hdc)
126
{
127
    static const WCHAR dotW[] = { '.', 0 };
128 129 130
    RECT rect, rcPart;
    POINT pt;
    int i;
131

132
    TRACE("\n");
133

134 135 136 137 138 139 140
    GetClientRect (infoPtr->Self, &rect);
    DrawEdge (hdc, &rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);

    for (i = 0; i < 3; i++) {
        GetWindowRect (infoPtr->Part[i].EditHwnd, &rcPart);
	pt.x = rcPart.right;
	ScreenToClient(infoPtr->Self, &pt);
141
	rect.left = pt.x;
142 143 144 145
	GetWindowRect (infoPtr->Part[i+1].EditHwnd, &rcPart);
	pt.x = rcPart.left;
	ScreenToClient(infoPtr->Self, &pt);
	rect.right = pt.x;
146
	DrawTextW(hdc, dotW, 1, &rect, DT_SINGLELINE | DT_CENTER | DT_BOTTOM);
147
    }
148

149
    return 0;
150
}
151 152


153
static LRESULT IPADDRESS_Create (HWND hwnd, LPCREATESTRUCTA lpCreate)
154
{
155
    static const WCHAR EDIT[] = { 'E', 'd', 'i', 't', 0 };
156 157 158 159 160
    IPADDRESS_INFO *infoPtr;
    RECT rcClient, edit;
    int i, fieldsize;

    TRACE("\n");
161

162 163
    SetWindowLongW (hwnd, GWL_STYLE,
		    GetWindowLongW(hwnd, GWL_STYLE) & ~WS_BORDER);
164

165
    infoPtr = (IPADDRESS_INFO *)Alloc (sizeof(IPADDRESS_INFO));
166
    if (!infoPtr) return -1;
167
    SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
168

169
    GetClientRect (hwnd, &rcClient);
170

171
    fieldsize = (rcClient.right - rcClient.left) / 4;
172

173 174
    edit.top    = rcClient.top + 2;
    edit.bottom = rcClient.bottom - 2;
175

176
    infoPtr->Self = hwnd;
177
    infoPtr->Notify = lpCreate->hwndParent;
178

179 180
    for (i = 0; i < 4; i++) {
	IPPART_INFO* part = &infoPtr->Part[i];
181

182 183 184 185 186 187
	part->LowerLimit = 0;
	part->UpperLimit = 255;
        edit.left = rcClient.left + i*fieldsize + 6;
        edit.right = rcClient.left + (i+1)*fieldsize - 2;
        part->EditHwnd =
		CreateWindowW (EDIT, NULL, WS_CHILD | WS_VISIBLE | ES_CENTER,
188 189
                               edit.left, edit.top, edit.right - edit.left,
			       edit.bottom - edit.top, hwnd, (HMENU) 1,
190
			       (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), NULL);
191
	SetPropW(part->EditHwnd, IP_SUBCLASS_PROP, hwnd);
192
        part->OrigProc = (WNDPROC)
193 194
		SetWindowLongPtrW (part->EditHwnd, GWLP_WNDPROC,
				(DWORD_PTR)IPADDRESS_SubclassProc);
195
    }
196

197
    return 0;
198
}
199 200


201
static LRESULT IPADDRESS_Destroy (IPADDRESS_INFO *infoPtr)
202
{
203
    int i;
204

205
    TRACE("\n");
206

207 208
    for (i = 0; i < 4; i++) {
	IPPART_INFO* part = &infoPtr->Part[i];
209
        SetWindowLongPtrW (part->EditHwnd, GWLP_WNDPROC, (DWORD_PTR)part->OrigProc);
210
    }
211

212
    SetWindowLongPtrW (infoPtr->Self, 0, 0);
213
    Free (infoPtr);
214
    return 0;
215 216 217
}


218
static LRESULT IPADDRESS_Paint (IPADDRESS_INFO *infoPtr, HDC hdc)
219
{
220
    PAINTSTRUCT ps;
221

222
    TRACE("\n");
223

224
    if (hdc) return IPADDRESS_Draw (infoPtr, hdc);
225

226 227 228 229
    hdc = BeginPaint (infoPtr->Self, &ps);
    IPADDRESS_Draw (infoPtr, hdc);
    EndPaint (infoPtr->Self, &ps);
    return 0;
230 231 232
}


233
static BOOL IPADDRESS_IsBlank (IPADDRESS_INFO *infoPtr)
234
{
235
    int i;
236

237
    TRACE("\n");
238

239 240 241 242
    for (i = 0; i < 4; i++)
        if (GetWindowTextLengthW (infoPtr->Part[i].EditHwnd)) return FALSE;

    return TRUE;
243 244
}

245

246
static int IPADDRESS_GetAddress (IPADDRESS_INFO *infoPtr, LPDWORD ip_address)
247
{
248 249 250
    WCHAR field[5];
    int i, invalid = 0;
    DWORD ip_addr = 0;
251

252
    TRACE("\n");
253

254 255
    for (i = 0; i < 4; i++) {
        ip_addr *= 256;
256
        if (GetWindowTextW (infoPtr->Part[i].EditHwnd, field, 4))
257
  	    ip_addr += atolW(field);
258 259 260 261
	else
	    invalid++;
    }
    *ip_address = ip_addr;
262

263
    return 4 - invalid;
264 265
}

266

267
static BOOL IPADDRESS_SetRange (IPADDRESS_INFO *infoPtr, int index, WORD range)
268
{
269
    TRACE("\n");
270

271
    if ( (index < 0) || (index > 3) ) return FALSE;
272

273 274 275 276
    infoPtr->Part[index].LowerLimit = range & 0xFF;
    infoPtr->Part[index].UpperLimit = (range >> 8)  & 0xFF;

    return TRUE;
277 278 279
}


280
static void IPADDRESS_ClearAddress (IPADDRESS_INFO *infoPtr)
281
{
Alexandre Julliard's avatar
Alexandre Julliard committed
282
    WCHAR nil[1] = { 0 };
283
    int i;
284

285
    TRACE("\n");
286

287
    for (i = 0; i < 4; i++)
288
        SetWindowTextW (infoPtr->Part[i].EditHwnd, nil);
289 290
}

291

292
static LRESULT IPADDRESS_SetAddress (IPADDRESS_INFO *infoPtr, DWORD ip_address)
293
{
294 295
    WCHAR buf[20];
    static const WCHAR fmt[] = { '%', 'd', 0 };
296 297 298
    int i;

    TRACE("\n");
299

300 301 302 303
    for (i = 3; i >= 0; i--) {
	IPPART_INFO* part = &infoPtr->Part[i];
        int value = ip_address & 0xff;
	if ( (value >= part->LowerLimit) && (value <= part->UpperLimit) ) {
304
	    wsprintfW (buf, fmt, value);
305 306 307 308 309
	    SetWindowTextW (part->EditHwnd, buf);
	    IPADDRESS_Notify (infoPtr, EN_CHANGE);
        }
        ip_address >>= 8;
    }
310

311
    return TRUE;
312
}
313 314


315
static void IPADDRESS_SetFocusToField (IPADDRESS_INFO *infoPtr, INT index)
316
{
317
    TRACE("(index=%d)\n", index);
318

319 320 321 322 323
    if (index > 3) {
	for (index = 0; index < 4; index++)
	    if (!GetWindowTextLengthW(infoPtr->Part[index].EditHwnd)) break;
    }
    if (index < 9 || index > 3) index = 0;
324

325
    SetFocus (infoPtr->Part[index].EditHwnd);
326 327 328
}


329
static BOOL IPADDRESS_ConstrainField (IPADDRESS_INFO *infoPtr, int currentfield)
330
{
331
    IPPART_INFO *part = &infoPtr->Part[currentfield];
332 333
    WCHAR field[10];
    static const WCHAR fmt[] = { '%', 'd', 0 };
334
    int curValue, newValue;
335

336
    TRACE("(currentfield=%d)\n", currentfield);
337

338
    if (currentfield < 0 || currentfield > 3) return FALSE;
339

340
    if (!GetWindowTextW (part->EditHwnd, field, 4)) return FALSE;
341

342
    curValue = atoiW(field);
343
    TRACE("  curValue=%d\n", curValue);
344

345 346 347 348 349
    newValue = IPADDRESS_IPNotify(infoPtr, currentfield, curValue);
    TRACE("  newValue=%d\n", newValue);

    if (newValue < part->LowerLimit) newValue = part->LowerLimit;
    if (newValue > part->UpperLimit) newValue = part->UpperLimit;
350

351
    if (newValue == curValue) return FALSE;
352

353
    wsprintfW (field, fmt, newValue);
354 355
    TRACE("  field='%s'\n", debugstr_w(field));
    return SetWindowTextW (part->EditHwnd, field);
356
}
357 358


359
static BOOL IPADDRESS_GotoNextField (IPADDRESS_INFO *infoPtr, int cur, int sel)
360
{
361 362 363 364
    TRACE("\n");

    if(cur >= -1 && cur < 4) {
	IPADDRESS_ConstrainField(infoPtr, cur);
365

366 367 368 369 370 371 372 373 374 375 376 377 378
	if(cur < 3) {
	    IPPART_INFO *next = &infoPtr->Part[cur + 1];
	    int start = 0, end = 0;
            SetFocus (next->EditHwnd);
	    if (sel != POS_DEFAULT) {
		if (sel == POS_RIGHT)
		    start = end = GetWindowTextLengthW(next->EditHwnd);
		else if (sel == POS_SELALL)
		    end = -1;
	        SendMessageW(next->EditHwnd, EM_SETSEL, start, end);
	    }
	    return TRUE;
	}
379

380
    }
381
    return FALSE;
382 383 384
}


385 386 387 388 389 390 391 392 393
/*
 * 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
 *
394 395 396 397 398
 * 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
399
 *
400 401
 * tab: change focus from the current ipaddress control to the next
 *      control in the tab order
402
 *
403 404 405 406
 * 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
407
 *
408 409 410 411
 * 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
412
 *
413 414 415 416 417
 * 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
418
 */
419
LRESULT CALLBACK
420
IPADDRESS_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
421
{
422 423
    HWND Self = (HWND)GetPropW (hwnd, IP_SUBCLASS_PROP);
    IPADDRESS_INFO *infoPtr = (IPADDRESS_INFO *)GetWindowLongPtrW (Self, 0);
424 425 426
    CHAR c = (CHAR)wParam;
    INT index, len = 0, startsel, endsel;
    IPPART_INFO *part;
427

428
    TRACE("(hwnd=%p msg=0x%x wparam=0x%x lparam=0x%lx)\n", hwnd, uMsg, wParam, lParam);
429

430 431
    if ( (index = IPADDRESS_GetPartIndex(infoPtr, hwnd)) < 0) return 0;
    part = &infoPtr->Part[index];
432

433 434 435
    if (uMsg == WM_CHAR || uMsg == WM_KEYDOWN) {
	len = GetWindowTextLengthW (hwnd);
	SendMessageW(hwnd, EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel);
436
    }
437
    switch (uMsg) {
438
 	case WM_CHAR:
439 440 441 442
 	    if(isdigit(c)) {
		if(len == 2 && startsel==endsel && endsel==len) {
		    /* process the digit press before we check the field */
		    int return_val = CallWindowProcW (part->OrigProc, hwnd, uMsg, wParam, lParam);
443

444 445 446 447 448 449 450
		    /* if the field value was changed stay at the current field */
		    if(!IPADDRESS_ConstrainField(infoPtr, index))
			IPADDRESS_GotoNextField (infoPtr, index, POS_DEFAULT);

		    return return_val;
		} else if (len == 3 && startsel==endsel && endsel==len)
		    IPADDRESS_GotoNextField (infoPtr, index, POS_SELALL);
451
		else if (len < 3) break;
452 453
	    } else if(c == '.' || c == ' ') {
		if(len && startsel==endsel && startsel != 0) {
454
		    IPADDRESS_GotoNextField(infoPtr, index, POS_SELALL);
455 456 457
		}
 	    } else if (c == VK_BACK) break;
	    return 0;
458

459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
	case WM_KEYDOWN:
	    switch(c) {
		case VK_RIGHT:
		    if(startsel==endsel && startsel==len) {
			IPADDRESS_GotoNextField(infoPtr, index, POS_LEFT);
			return 0;
		    }
		    break;
		case VK_LEFT:
		    if(startsel==0 && startsel==endsel && index > 0) {
			IPADDRESS_GotoNextField(infoPtr, index - 2, POS_RIGHT);
			return 0;
		    }
		    break;
		case VK_BACK:
		    if(startsel==endsel && startsel==0 && index > 0) {
			IPPART_INFO *prev = &infoPtr->Part[index-1];
			WCHAR val[10];
477

478 479 480 481
			if(GetWindowTextW(prev->EditHwnd, val, 5)) {
			    val[lstrlenW(val) - 1] = 0;
			    SetWindowTextW(prev->EditHwnd, val);
			}
482

483 484 485 486
			IPADDRESS_GotoNextField(infoPtr, index - 2, POS_RIGHT);
			return 0;
		    }
		    break;
487
	    }
488 489 490 491 492 493 494 495 496 497 498
	    break;
	case WM_KILLFOCUS:
	    if (IPADDRESS_GetPartIndex(infoPtr, (HWND)wParam) < 0)
		IPADDRESS_Notify(infoPtr, EN_KILLFOCUS);
	    break;
	case WM_SETFOCUS:
	    if (IPADDRESS_GetPartIndex(infoPtr, (HWND)wParam) < 0)
		IPADDRESS_Notify(infoPtr, EN_SETFOCUS);
	    break;
    }
    return CallWindowProcW (part->OrigProc, hwnd, uMsg, wParam, lParam);
499
}
500

501

502
static LRESULT WINAPI
503
IPADDRESS_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
504
{
505
    IPADDRESS_INFO *infoPtr = (IPADDRESS_INFO *)GetWindowLongPtrW (hwnd, 0);
506

507
    TRACE("(hwnd=%p msg=0x%x wparam=0x%x lparam=0x%lx)\n", hwnd, uMsg, wParam, lParam);
508

509 510
    if (!infoPtr && (uMsg != WM_CREATE))
        return DefWindowProcW (hwnd, uMsg, wParam, lParam);
511

512 513 514
    switch (uMsg)
    {
	case WM_CREATE:
515
	    return IPADDRESS_Create (hwnd, (LPCREATESTRUCTA)lParam);
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554

	case WM_DESTROY:
	    return IPADDRESS_Destroy (infoPtr);

	case WM_PAINT:
	    return IPADDRESS_Paint (infoPtr, (HDC)wParam);

	case WM_COMMAND:
	    switch(wParam >> 16) {
		case EN_CHANGE:
		    IPADDRESS_Notify(infoPtr, EN_CHANGE);
		    break;
		case EN_KILLFOCUS:
		    IPADDRESS_ConstrainField(infoPtr, IPADDRESS_GetPartIndex(infoPtr, (HWND)lParam));
		    break;
	    }
	    break;

        case IPM_CLEARADDRESS:
            IPADDRESS_ClearAddress (infoPtr);
	    break;

        case IPM_SETADDRESS:
            return IPADDRESS_SetAddress (infoPtr, (DWORD)lParam);

        case IPM_GETADDRESS:
 	    return IPADDRESS_GetAddress (infoPtr, (LPDWORD)lParam);

	case IPM_SETRANGE:
	    return IPADDRESS_SetRange (infoPtr, (int)wParam, (WORD)lParam);

	case IPM_SETFOCUS:
	    IPADDRESS_SetFocusToField (infoPtr, (int)wParam);
	    break;

	case IPM_ISBLANK:
	    return IPADDRESS_IsBlank (infoPtr);

	default:
555
	    if ((uMsg >= WM_USER) && (uMsg < WM_APP))
556 557
		ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
	    return DefWindowProcW (hwnd, uMsg, wParam, lParam);
558 559 560 561 562
    }
    return 0;
}


563
void IPADDRESS_Register (void)
564
{
565 566 567
    WNDCLASSW wndClass;

    ZeroMemory (&wndClass, sizeof(WNDCLASSW));
568 569
    wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc   = IPADDRESS_WindowProc;
570 571
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(IPADDRESS_INFO *);
572
    wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_IBEAM);
573
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
574
    wndClass.lpszClassName = WC_IPADDRESSW;
575

576
    RegisterClassW (&wndClass);
577 578
}

579

580
void IPADDRESS_Unregister (void)
581
{
582
    UnregisterClassW (WC_IPADDRESSW, NULL);
583
}