skyrope-mit-merge-hell-20040226
[openafs.git] / src / WINNT / afsapplib / ctl_spinner.cpp
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 extern "C" {
11 #include <afs/param.h>
12 #include <afs/stds.h>
13 }
14
15 #include <windows.h>
16 #include <windowsx.h>
17 #include <commctrl.h>
18 #include <WINNT/dialog.h>
19 #include <WINNT/subclass.h>
20 #include <WINNT/ctl_spinner.h>
21 #include <WINNT/TaLocale.h>
22
23 #ifndef cchRESOURCE
24 #define cchRESOURCE 256
25 #endif
26
27
28 /*
29  * MISCELLANEOUS ______________________________________________________________
30  *
31  */
32
33 #define ishexdigit(_ch) ( (((_ch) >= 'a') && ((_ch) <= 'f')) || \
34                           (((_ch) >= 'A') && ((_ch) <= 'F')) )
35
36 #define digitval(_ch) (isdigit(_ch) \
37                        ? ((DWORD)(_ch) - (DWORD)TEXT('0')) \
38                        : (((_ch) >= 'a') && ((_ch) <= 'f')) \
39                          ? ((DWORD)(_ch) - (DWORD)TEXT('a') + 10) \
40                          : (((_ch) >= 'A') && ((_ch) <= 'F')) \
41                            ? ((DWORD)(_ch) - (DWORD)TEXT('A') + 10) \
42                            : 0)
43
44 #ifndef REALLOC
45 #define REALLOC(_a,_c,_r,_i) SpinnerReallocFunction ((LPVOID*)&_a,sizeof(*_a),&_c,_r,_i)
46 BOOL SpinnerReallocFunction (LPVOID *ppTarget, size_t cbElement, size_t *pcTarget, size_t cReq, size_t cInc)
47 {
48    LPVOID pNew;
49    size_t cNew;
50
51    if (cReq <= *pcTarget)
52       return TRUE;
53
54    if ((cNew = cInc * ((cReq + cInc-1) / cInc)) <= 0)
55       return FALSE;
56
57    if ((pNew = (LPVOID)Allocate (cbElement * cNew)) == NULL)
58       return FALSE;
59    memset (pNew, 0x00, cbElement * cNew);
60
61    if (*pcTarget != 0)
62       {
63       memcpy (pNew, *ppTarget, cbElement * (*pcTarget));
64       Free (*ppTarget);
65       }
66
67    *ppTarget = pNew;
68    *pcTarget = cNew;
69    return TRUE;
70 }
71 #endif
72
73
74 /*
75  * SPINNERS ___________________________________________________________________
76  *
77  */
78
79 typedef struct
80    {
81    HWND hSpinner;
82    HWND hBuddy;
83    RECT rReq;
84
85    DWORD dwMin;
86    DWORD dwMax;
87    WORD  wBase;
88    BOOL  fSigned;
89    DWORD dwPos;
90
91    BOOL  fNewText;
92    BOOL  fCallingBack;
93    BOOL  fCanCallBack;
94    LPTSTR pszFormat;
95    } SpinnerInfo;
96
97 static CRITICAL_SECTION csSpinners;
98 static SpinnerInfo     *aSpinners = NULL;
99 static size_t           cSpinners = 0;
100 static LONG             oldSpinnerProc = 0;
101
102 #define cszSPINNERCLASS TEXT("Spinner")
103
104 BOOL CALLBACK SpinnerProc (HWND hSpin, UINT msg, WPARAM wp, LPARAM lp);
105 BOOL CALLBACK SpinnerDialogProc (HWND hSpin, UINT msg, WPARAM wp, LPARAM lp);
106 BOOL CALLBACK SpinnerBuddyProc (HWND hBuddy, UINT msg, WPARAM wp, LPARAM lp);
107
108 void SpinnerSendCallback (SpinnerInfo *psi, WORD spm, LPARAM lp);
109
110 void Spinner_OnCreate (SpinnerInfo *psi);
111 BOOL Spinner_OnGetRange (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
112 BOOL Spinner_OnSetRange (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
113 BOOL Spinner_OnGetPos (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
114 BOOL Spinner_OnSetPos (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
115 BOOL Spinner_OnGetBase (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
116 BOOL Spinner_OnSetBase (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
117 BOOL Spinner_OnReattach (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
118 BOOL Spinner_OnSetRect (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
119 BOOL Spinner_OnGetSpinner (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
120 BOOL Spinner_OnSetFormat (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
121 BOOL Spinner_OnSetBuddy (SpinnerInfo *psi, WPARAM wp, LPARAM lp);
122
123 void Spinner_GetNewText (SpinnerInfo *psi);
124 void Spinner_SetNewText (SpinnerInfo *psi, BOOL fCallback);
125
126
127 BOOL RegisterSpinnerClass (void)
128 {
129    static BOOL fRegistered = FALSE;
130
131    if (!fRegistered)
132       {
133       WNDCLASS wc;
134
135       InitializeCriticalSection (&csSpinners);
136
137       if (GetClassInfo (THIS_HINST, TEXT("scrollbar"), &wc))
138          {
139          oldSpinnerProc = (LONG)wc.lpfnWndProc;
140
141          wc.lpfnWndProc = (WNDPROC)SpinnerProc;
142          wc.style = CS_GLOBALCLASS;
143          wc.hInstance = THIS_HINST;
144          wc.lpszClassName = cszSPINNERCLASS;
145
146          if (RegisterClass (&wc))
147             fRegistered = TRUE;
148          }
149       }
150
151    return fRegistered;
152 }
153
154
155 SpinnerInfo *Spinner_FindSpinnerInfo (HWND hSpinner, HWND hBuddy)
156 {
157    SpinnerInfo *psi = NULL;
158
159    EnterCriticalSection (&csSpinners);
160
161    for (size_t ii = 0; ii < cSpinners; ++ii)
162       {
163       if ( (hSpinner && (aSpinners[ ii ].hSpinner == hSpinner)) ||
164            (hBuddy   && (aSpinners[ ii ].hBuddy == hBuddy)) )
165          {
166          psi = &aSpinners[ ii ];
167          break;
168          }
169       }
170
171    LeaveCriticalSection (&csSpinners);
172    return psi;
173 }
174
175
176 BOOL CreateSpinner (HWND hBuddy,
177                     WORD wBase, BOOL fSigned,
178                     DWORD dwMin, DWORD dwPos, DWORD dwMax,
179                     LPRECT prTarget)
180 {
181    if (!RegisterSpinnerClass())
182       return FALSE;
183
184    EnterCriticalSection (&csSpinners);
185
186    for (size_t ii = 0; ii < cSpinners; ++ii)
187       {
188       if (!aSpinners[ ii ].hSpinner)
189          break;
190       }
191    if (ii >= cSpinners)
192       {
193       if (!REALLOC (aSpinners, cSpinners, 1+ii, 4))
194          {
195          LeaveCriticalSection (&csSpinners);
196          return FALSE;
197          }
198       }
199
200    memset (&aSpinners[ ii ], 0x00, sizeof(SpinnerInfo));
201
202    aSpinners[ ii ].hBuddy = hBuddy;
203    aSpinners[ ii ].dwMin = dwMin;
204    aSpinners[ ii ].dwMax = dwMax;
205    aSpinners[ ii ].wBase = wBase;
206    aSpinners[ ii ].fSigned = fSigned;
207    aSpinners[ ii ].dwPos = dwPos;
208
209    if (prTarget != NULL)
210       aSpinners[ ii ].rReq = *prTarget;
211
212    aSpinners[ ii ].hSpinner = CreateWindowEx (
213                 0,      // extended window style
214                 cszSPINNERCLASS,        // pointer to registered class name
215                 TEXT(""),       // pointer to window name
216                 WS_CHILD | SBS_VERT,    // window style
217                 0, 0, 1, 1,     // spinner moves/resizes itself
218                 GetParent(hBuddy),      // handle to parent or owner window
219                 (HMENU)-1,      // handle to menu, or child-window identifier
220                 THIS_HINST,     // handle to application instance
221                 (LPVOID)ii);    // pointer to window-creation data
222
223    LeaveCriticalSection (&csSpinners);
224
225    if (aSpinners[ ii ].hSpinner == NULL)
226       return FALSE;
227
228    ShowWindow (aSpinners[ ii ].hSpinner, SW_SHOW);
229
230    if (!IsWindowEnabled (aSpinners[ ii ].hBuddy))
231       EnableWindow (aSpinners[ ii ].hSpinner, FALSE);
232
233    return TRUE;
234 }
235
236
237 BOOL fHasSpinner (HWND hBuddy)
238 {
239    if (!RegisterSpinnerClass())
240       return FALSE;
241
242    return (Spinner_FindSpinnerInfo (NULL, hBuddy) == NULL) ? FALSE : TRUE;
243 }
244
245
246 void Spinner_OnDestroy (SpinnerInfo *psi)
247 {
248    Subclass_RemoveHook (GetParent (psi->hSpinner), SpinnerDialogProc);
249    Subclass_RemoveHook (psi->hBuddy, SpinnerBuddyProc);
250
251    if (psi->pszFormat)
252       {
253       Free (psi->pszFormat);
254       psi->pszFormat = NULL;
255       }
256    psi->hSpinner = NULL;
257    psi->hBuddy = NULL;
258 }
259
260
261 BOOL CALLBACK SpinnerProc (HWND hSpinner, UINT msg, WPARAM wp, LPARAM lp)
262 {
263    EnterCriticalSection (&csSpinners);
264
265    if (msg == WM_CREATE)
266       {
267       aSpinners[ (int)((LPCREATESTRUCT)lp)->lpCreateParams ].hSpinner = hSpinner;
268       }
269
270    SpinnerInfo *psi = Spinner_FindSpinnerInfo (hSpinner, NULL);
271
272    LeaveCriticalSection (&csSpinners);
273
274    if (psi != NULL)
275       {
276       switch (msg)
277          {
278          case WM_CREATE:
279             Spinner_OnCreate (psi);
280             break;
281
282          case WM_SETFOCUS:
283             PostMessage (GetParent(psi->hSpinner), WM_NEXTDLGCTL, (WPARAM)psi->hBuddy, TRUE);
284             break;
285
286          case WM_DESTROY:
287             Spinner_OnDestroy (psi);
288             break;
289
290          case WM_SYSCHAR:
291          case WM_CHAR:
292             switch (wp)
293                {
294                case VK_UP:
295                   PostMessage (GetParent(psi->hSpinner), WM_VSCROLL, SB_LINEUP, (LPARAM)psi->hSpinner);
296                   break;
297
298                case VK_DOWN:
299                   PostMessage (GetParent(psi->hSpinner), WM_VSCROLL, SB_LINEDOWN, (LPARAM)psi->hSpinner);
300                   break;
301
302                case VK_PRIOR:
303                   PostMessage (GetParent(psi->hSpinner), WM_VSCROLL, SB_PAGEUP, (LPARAM)psi->hSpinner);
304                   break;
305
306                case VK_NEXT:
307                   PostMessage (GetParent(psi->hSpinner), WM_VSCROLL, SB_PAGEDOWN, (LPARAM)psi->hSpinner);
308                   break;
309
310                case VK_HOME:
311                   PostMessage (GetParent(psi->hSpinner), WM_VSCROLL, SB_TOP, (LPARAM)psi->hSpinner);
312                   break;
313
314                case VK_END:
315                   PostMessage (GetParent(psi->hSpinner), WM_VSCROLL, SB_BOTTOM, (LPARAM)psi->hSpinner);
316                   break;
317                }
318             break;
319          }
320       }
321
322    if (oldSpinnerProc == 0)
323       return DefWindowProc (hSpinner, msg, wp, lp);
324    else
325       return CallWindowProc ((WNDPROC)oldSpinnerProc, hSpinner, msg, wp, lp);
326 }
327
328
329 BOOL CALLBACK SpinnerDialogProc (HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
330 {
331    PVOID oldProc = Subclass_FindNextHook (hDlg, SpinnerDialogProc);
332    SpinnerInfo *psi;
333
334    switch (msg)
335       {
336       case WM_COMMAND:
337          if ((psi = Spinner_FindSpinnerInfo (NULL, (HWND)lp)) != NULL)
338             {
339             switch (HIWORD(wp))
340                {
341                // case CBN_SELCHANGE: --same value as LBN_SELCHANGE
342                case LBN_SELCHANGE:
343                case EN_UPDATE:
344                   Spinner_GetNewText (psi);
345
346                   if (psi->fCanCallBack == TRUE)
347                      SpinnerSendCallback (psi, SPN_UPDATE, (LPARAM)psi->dwPos);
348                   else
349                      oldProc = NULL; // don't forward this notification message
350                   break;
351
352                default:
353                   oldProc = NULL; // don't forward this notification message
354                   break;
355                }
356             }
357          break;
358
359       case WM_VSCROLL:
360          if ((psi = Spinner_FindSpinnerInfo ((HWND)lp, NULL)) != NULL)
361             {
362             if (psi->fNewText)
363                {
364                WORD wBaseOld = psi->wBase;
365                Spinner_GetNewText (psi);
366                if ((wBaseOld != psi->wBase) && !(psi->pszFormat))
367                   Spinner_OnSetBase (psi, psi->wBase, psi->fSigned);
368                }
369
370             switch (LOWORD(wp))
371                {
372                case SB_LINEUP:
373                   {
374                   DWORD dw = psi->dwPos;
375                   SpinnerSendCallback (psi, SPN_CHANGE_UP, (LPARAM)&dw);
376                   if (dw == psi->dwPos)
377                      psi->dwPos ++;
378                   else if (dw != SPVAL_UNCHANGED)
379                      psi->dwPos = dw;
380
381                   if (psi->wBase == 10 && psi->fSigned)
382                      psi->dwPos = (DWORD)limit ((signed long)psi->dwMin, (signed long)psi->dwPos, (signed long)psi->dwMax);
383                   else
384                      psi->dwPos = (DWORD)limit (psi->dwMin, psi->dwPos, psi->dwMax);
385
386                   Spinner_SetNewText (psi, TRUE);
387                   PostMessage (GetParent(psi->hSpinner), WM_NEXTDLGCTL, (WPARAM)psi->hBuddy, TRUE);
388                   break;
389                   }
390
391                case SB_LINEDOWN:
392                   {
393                   DWORD dw = psi->dwPos;
394                   SpinnerSendCallback (psi, SPN_CHANGE_DOWN, (LPARAM)&dw);
395                   if (dw == psi->dwPos)
396                      {
397                      if ((psi->dwPos > 0) || (psi->wBase == 10 && psi->fSigned))
398                         psi->dwPos --;
399                      }
400                   else if (dw != SPVAL_UNCHANGED)
401                      psi->dwPos = dw;
402
403                   if (psi->wBase == 10 && psi->fSigned)
404                      psi->dwPos = (DWORD)limit ((signed long)psi->dwMin, (signed long)psi->dwPos, (signed long)psi->dwMax);
405                   else
406                      psi->dwPos = (DWORD)limit (psi->dwMin, psi->dwPos, psi->dwMax);
407
408                   Spinner_SetNewText (psi, TRUE);
409                   PostMessage (GetParent(psi->hSpinner), WM_NEXTDLGCTL, (WPARAM)psi->hBuddy, TRUE);
410                   break;
411                   }
412                }
413             }
414          break;
415       }
416
417    if (oldProc == 0)
418       return DefWindowProc (hDlg, msg, wp, lp);
419    else
420       return CallWindowProc ((WNDPROC)oldProc, hDlg, msg, wp, lp);
421 }
422
423
424
425 BOOL CALLBACK SpinnerBuddyProc (HWND hBuddy, UINT msg, WPARAM wp, LPARAM lp)
426 {
427    PVOID oldProc = Subclass_FindNextHook (hBuddy, SpinnerBuddyProc);
428
429    SpinnerInfo *psi;
430    if ((psi = Spinner_FindSpinnerInfo (NULL, hBuddy)) != NULL)
431       {
432       switch (msg)
433          {
434          case WM_KEYDOWN:
435          case WM_KEYUP:
436             switch (wp)
437                {
438                case VK_HOME:
439                case VK_END:
440                case VK_PRIOR:
441                case VK_NEXT:
442                case VK_UP:
443                case VK_DOWN:
444                   SendMessage (psi->hSpinner, msg, wp, lp);
445                   return FALSE;
446                }
447             break;
448
449          case WM_CHAR:
450             psi->fNewText = TRUE;
451             break;
452
453          case WM_MOVE:
454          case WM_SIZE:
455             PostMessage (hBuddy, SPM_REATTACH, 0, 0);
456             break;
457
458          case WM_ENABLE:
459             EnableWindow (psi->hSpinner, wp);
460             break;
461
462          case WM_KILLFOCUS:
463             Spinner_GetNewText (psi);
464             Spinner_OnSetBase (psi, psi->wBase, psi->fSigned);
465             break;
466
467          case SPM_GETRANGE:
468             return Spinner_OnGetRange (psi, wp, lp);
469
470          case SPM_SETRANGE:
471             return Spinner_OnSetRange (psi, wp, lp);
472
473          case SPM_GETPOS:
474             return Spinner_OnGetPos (psi, wp, lp);
475
476          case SPM_SETPOS:
477             return Spinner_OnSetPos (psi, wp, lp);
478
479          case SPM_GETBASE:
480             return Spinner_OnGetBase (psi, wp, lp);
481
482          case SPM_SETBASE:
483             return Spinner_OnSetBase (psi, wp, lp);
484
485          case SPM_REATTACH:
486             return Spinner_OnReattach (psi, wp, lp);
487
488          case SPM_SETRECT:
489             return Spinner_OnSetRect (psi, wp, lp);
490
491          case SPM_GETSPINNER:
492             return Spinner_OnGetSpinner (psi, wp, lp);
493
494          case SPM_SETFORMAT:
495             return Spinner_OnSetFormat (psi, wp, lp);
496
497          case SPM_SETBUDDY:
498             return Spinner_OnSetBuddy (psi, wp, lp);
499          }
500       }
501
502    if (oldProc)
503       return CallWindowProc ((WNDPROC)oldProc, hBuddy, msg, wp, lp);
504    else
505       return DefWindowProc (hBuddy, msg, wp, lp);
506 }
507
508
509 void SpinnerSendCallback (SpinnerInfo *psi, WORD spm, LPARAM lp)
510 {
511    if ((psi->fCanCallBack == TRUE) && !psi->fCallingBack)
512       {
513       psi->fCallingBack = TRUE;
514
515       SendMessage (GetParent (psi->hSpinner),
516                    WM_COMMAND,
517                    MAKELONG ((WORD)GetWindowLong (psi->hBuddy, GWL_ID), spm),
518                    lp);
519
520       psi->fCallingBack = FALSE;
521       }
522 }
523
524
525 void Spinner_OnCreate (SpinnerInfo *psi)
526 {
527    Subclass_AddHook (GetParent(psi->hSpinner), SpinnerDialogProc);
528    Subclass_AddHook (psi->hBuddy, SpinnerBuddyProc);
529
530    Spinner_OnReattach (psi, 0, 0);
531    Spinner_SetNewText (psi, FALSE);
532
533    psi->fCanCallBack = TRUE;
534 }
535
536
537 BOOL Spinner_OnGetRange (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
538 {
539    if (wp != 0)
540       *(LPDWORD)wp = psi->dwMin;
541    if (lp != 0)
542       *(LPDWORD)lp = psi->dwMax;
543    return TRUE;
544 }
545
546 BOOL Spinner_OnSetRange (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
547 {
548    psi->dwMin = (DWORD)wp;
549    psi->dwMax = (DWORD)lp;
550    Spinner_SetNewText (psi, FALSE);
551    return TRUE;
552 }
553
554 BOOL Spinner_OnGetPos (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
555 {
556    if (psi->fNewText)
557       {
558       Spinner_GetNewText (psi);
559       Spinner_OnSetBase (psi, psi->wBase, psi->fSigned);
560       }
561    return (BOOL)psi->dwPos;
562 }
563
564 BOOL Spinner_OnSetPos (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
565 {
566    psi->dwPos = (DWORD)lp;
567    Spinner_SetNewText (psi, FALSE);
568    return TRUE;
569 }
570
571 BOOL Spinner_OnGetBase (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
572 {
573    if (psi->fNewText)
574       {
575       Spinner_GetNewText (psi);
576       Spinner_OnSetBase (psi, psi->wBase, psi->fSigned);
577       }
578
579    if (wp != 0)
580       *(WORD *)wp = psi->wBase;
581    if (lp != 0)
582       *(BOOL *)lp = psi->fSigned;
583    return TRUE;
584 }
585
586 BOOL Spinner_OnSetBase (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
587 {
588    if (psi->fNewText)
589       Spinner_GetNewText (psi);
590
591    switch ((WORD)wp)
592       {
593       case  2:
594       case  8:
595       case 10:
596       case 16:
597          psi->wBase = (WORD)wp;
598          break;
599
600       default:
601          psi->wBase = 10;
602          break;
603       }
604
605    if (psi->wBase != 10)
606       psi->fSigned = FALSE;
607    else
608       psi->fSigned = (BOOL)lp;
609
610    Spinner_SetNewText (psi, FALSE);
611    return TRUE;
612 }
613
614
615 BOOL Spinner_OnReattach (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
616 {
617    RECT  rSpinner;
618    if (psi->rReq.right != 0)
619       {
620       rSpinner = psi->rReq;
621       }
622    else
623       {
624       RECT  rBuddyInParent;
625       POINT pt = { 0, 0 };
626
627       ClientToScreen (GetParent (psi->hBuddy), &pt);
628
629       GetWindowRect (psi->hBuddy, &rBuddyInParent);
630       rBuddyInParent.left -= pt.x;
631       rBuddyInParent.right -= pt.x;
632       rBuddyInParent.top -= pt.y;
633       rBuddyInParent.bottom -= pt.y;
634
635       rSpinner.top = rBuddyInParent.top;
636       rSpinner.bottom = rBuddyInParent.bottom -2; // just like Win95 does
637       rSpinner.left = rBuddyInParent.right;
638       rSpinner.right = rBuddyInParent.right +GetSystemMetrics (SM_CXVSCROLL);
639       }
640
641    SetWindowPos (psi->hSpinner, NULL,
642                  rSpinner.left, rSpinner.top,
643                  rSpinner.right-rSpinner.left,
644                  rSpinner.bottom-rSpinner.top,
645                  SWP_NOACTIVATE | SWP_NOZORDER);
646    return TRUE;
647 }
648
649
650 BOOL Spinner_OnSetRect (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
651 {
652    LPRECT prTarget;
653    if ((prTarget = (LPRECT)lp) == NULL)
654       SetRectEmpty (&psi->rReq);
655    else
656       psi->rReq = *prTarget;
657
658    Spinner_OnReattach (psi, 0, 0);
659    return TRUE;
660 }
661
662
663 BOOL Spinner_OnGetSpinner (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
664 {
665    return (BOOL)psi->hSpinner;
666 }
667
668
669 BOOL Spinner_OnSetFormat (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
670 {
671    if (psi->pszFormat)
672       {
673       Free (psi->pszFormat);
674       psi->pszFormat = NULL;
675       }
676    if (lp != 0)
677       {
678       psi->pszFormat = (LPTSTR)Allocate (sizeof(TCHAR) * (1+lstrlen((LPTSTR)lp)));
679       lstrcpy (psi->pszFormat, (LPTSTR)lp);
680       }
681    Spinner_SetNewText (psi, FALSE);
682    return TRUE;
683 }
684
685
686 BOOL Spinner_OnSetBuddy (SpinnerInfo *psi, WPARAM wp, LPARAM lp)
687 {
688    HWND hBuddyNew = (HWND)wp;
689    BOOL fMove = (BOOL)lp;
690
691    // First un-subclass our buddy.
692    // Then subclass the new buddy.
693    //
694    Subclass_RemoveHook (psi->hBuddy, SpinnerBuddyProc);
695    psi->hBuddy = hBuddyNew;
696    Subclass_AddHook (psi->hBuddy, SpinnerBuddyProc);
697
698    // Update our SpinnerInfo structure, and move if requested.
699    //
700    Spinner_GetNewText (psi);
701
702    if (fMove)
703       {
704       SetRectEmpty (&psi->rReq);
705       Spinner_OnReattach (psi, 0, 0);
706       }
707
708    return TRUE;
709 }
710
711
712 void Spinner_GetNewText (SpinnerInfo *psi)
713 {
714    // First find out what kind of buddy we have.
715    // That will determine what we do here.
716    //
717    TCHAR szBuddyClass[256];
718    GetClassName (psi->hBuddy, szBuddyClass, 256);
719
720    // For comboboxes and listboxes, the dwPos value is actually
721    // the selected item's index.
722    //
723    if (!lstrcmpi (szBuddyClass, TEXT("listbox")))
724       {
725       psi->dwPos = (DWORD)LB_GetSelected (psi->hBuddy);
726       }
727
728    if (!lstrcmpi (szBuddyClass, TEXT("combobox")))
729       {
730       psi->dwPos = (DWORD)CB_GetSelected (psi->hBuddy);
731       }
732
733    // For edit controls, the dwPos value is actually
734    // the control's text's value.
735    //
736    if (!lstrcmpi (szBuddyClass, TEXT("edit")))
737       {
738       TCHAR szText[256];
739       LPTSTR pszText = szText;
740       BOOL fNegative = FALSE;
741
742       psi->fNewText = FALSE;
743       psi->dwPos = 0;
744       psi->wBase = 10;
745
746       GetWindowText (psi->hBuddy, szText, 256);
747
748       while (*pszText == TEXT(' ') || *pszText == TEXT('\t'))
749          ++pszText;
750
751       if (*pszText == TEXT('0'))
752          {
753          if ((*(1+pszText) == 'x') || (*(1+pszText) == 'X'))
754             {
755             psi->wBase = 16;
756             ++pszText;
757             ++pszText;
758             }
759          else if ((*(1+pszText) == 'b') || (*(1+pszText) == 'B') || (*(1+pszText) == '!'))
760             {
761             psi->wBase = 2;
762             ++pszText;
763             ++pszText;
764             }
765          else if (*(1+pszText) != '\0')
766             {
767             // psi->wBase = 8; // ignore octal--time controls use "4:08" etc
768             ++pszText;
769             }
770          }
771
772       for ( ; *pszText == TEXT('-'); ++pszText)
773          {
774          fNegative = !fNegative;
775          }
776
777       for ( ; *pszText; ++pszText)
778          {
779          if (!isdigit( *pszText ) &&
780              !(psi->wBase == 16 && ishexdigit( *pszText )))
781             {
782             break;
783             }
784
785          psi->dwPos *= psi->wBase;
786
787          if ((DWORD)digitval(*pszText) < (DWORD)psi->wBase)
788             psi->dwPos += digitval( *pszText );
789          }
790
791       if (fNegative && psi->wBase == 10 && psi->fSigned)
792          {
793          psi->dwPos = (DWORD)(0 - (signed long)psi->dwPos);
794          }
795       }
796
797    if (psi->wBase == 10 && psi->fSigned)
798       psi->dwPos = (DWORD)limit ((signed long)psi->dwMin, (signed long)psi->dwPos, (signed long)psi->dwMax);
799    else
800       psi->dwPos = (DWORD)limit (psi->dwMin, psi->dwPos, psi->dwMax);
801 }
802
803
804 void Spinner_SetNewText (SpinnerInfo *psi, BOOL fCallBack)
805 {
806    TCHAR szText[256];
807
808    // First find out what kind of buddy we have.
809    // That will determine what we do here.
810    //
811    TCHAR szBuddyClass[256];
812    GetClassName (psi->hBuddy, szBuddyClass, 256);
813
814    // Be sure to notify the parent window that the selection may be changing.
815    //
816    if (fCallBack)
817       {
818       DWORD dw = psi->dwPos;
819       SpinnerSendCallback (psi, SPN_CHANGE, (LPARAM)&dw);
820       if (dw != SPVAL_UNCHANGED)
821          psi->dwPos = dw;
822       }
823
824    if (psi->wBase == 10 && psi->fSigned)
825       psi->dwPos = (DWORD)limit ((signed long)psi->dwMin, (signed long)psi->dwPos, (signed long)psi->dwMax);
826    else
827       psi->dwPos = (DWORD)limit (psi->dwMin, psi->dwPos, psi->dwMax);
828
829    if (!fCallBack)
830       psi->fCanCallBack --;
831
832    // For comboboxes and listboxes, select the item specified by the
833    // given index.
834    //
835    if (!lstrcmpi (szBuddyClass, TEXT("listbox")))
836       {
837       LB_SetSelected (psi->hBuddy, psi->dwPos);
838       }
839
840    if (!lstrcmpi (szBuddyClass, TEXT("combobox")))
841       {
842       CB_SetSelected (psi->hBuddy, psi->dwPos);
843       }
844
845    // For edit controls, fill in the new value as text--expressed in the
846    // requested base, using the requested format.
847    //
848    if (!lstrcmpi (szBuddyClass, TEXT("edit")))
849       {
850       switch (psi->wBase)
851          {
852          case 10:
853             if (psi->pszFormat)
854                wsprintf (szText, psi->pszFormat, (unsigned long)psi->dwPos);
855             else if (psi->fSigned)
856                wsprintf (szText, TEXT("%ld"), (signed long)psi->dwPos);
857             else
858                wsprintf (szText, TEXT("%lu"), (unsigned long)psi->dwPos);
859             break;
860
861          case 16:
862             wsprintf (szText, TEXT("0x%lX"), (unsigned long)psi->dwPos);
863             break;
864
865          default:
866             TCHAR szTemp[256];
867             LPTSTR pszTemp = szTemp;
868             LPTSTR pszText = szText;
869
870             if (psi->dwPos == 0)
871                *pszTemp++ = TEXT('0');
872             else
873                {
874                for (DWORD dwRemainder = psi->dwPos; dwRemainder != 0; dwRemainder /= (DWORD)psi->wBase)
875                   {
876                   DWORD dw = (dwRemainder % (DWORD)psi->wBase);
877                   *pszTemp++ = TEXT('0') + (TCHAR)dw;
878                   }
879                }
880
881             if (psi->wBase == 8)
882                {
883                *pszText++ = TEXT('0');
884                }
885             else if (psi->wBase == 2)
886                {
887                *pszText++ = TEXT('0');
888                *pszText++ = TEXT('b');
889                }
890
891             for (--pszTemp; pszTemp >= szTemp; )
892                {
893                *pszText++ = *pszTemp--;
894                }
895             *pszText = TEXT('\0');
896             break;
897          }
898
899       SetWindowText (psi->hBuddy, szText);
900       }
901
902    if (!fCallBack)
903       psi->fCanCallBack ++;
904    else
905       SpinnerSendCallback (psi, SPN_UPDATE, (LPARAM)psi->dwPos);
906 }
907