source: trunk/src/comctl32/pager.c@ 6496

Last change on this file since 6496 was 6380, checked in by sandervl, 24 years ago

wine resync

File size: 33.9 KB
Line 
1/*
2 * Pager control
3 *
4 * Copyright 1998, 1999 Eric Kohl
5 *
6 * NOTES
7 * Tested primarily with the controlspy Pager application.
8 * Susan Farley (susan@codeweavers.com)
9 *
10 * TODO:
11 * Implement repetitive button press.
12 * Adjust arrow size relative to size of button.
13 * Allow border size changes.
14 * Implement drag and drop style.
15 */
16
17#include <string.h>
18#include "winbase.h"
19#include "commctrl.h"
20#include "debugtools.h"
21
22#ifdef __WIN32OS2__
23#include "ccbase.h"
24#define inline
25#endif
26
27DEFAULT_DEBUG_CHANNEL(pager);
28
29typedef struct
30{
31#ifdef __WIN32OS2__
32 COMCTL32_HEADER header;
33#endif
34 HWND hwndChild; /* handle of the contained wnd */
35 BOOL bNoResize; /* set when created with CCS_NORESIZE */
36 COLORREF clrBk; /* background color */
37 INT nBorder; /* border size for the control */
38 INT nButtonSize;/* size of the pager btns */
39 INT nPos; /* scroll position */
40 INT nWidth; /* from child wnd's response to PGN_CALCSIZE */
41 INT nHeight; /* from child wnd's response to PGN_CALCSIZE */
42 BOOL bForward; /* forward WM_MOUSEMOVE msgs to the contained wnd */
43 INT TLbtnState; /* state of top or left btn */
44 INT BRbtnState; /* state of bottom or right btn */
45 INT direction; /* direction of the scroll, (e.g. PGF_SCROLLUP) */
46} PAGER_INFO;
47
48#define PAGER_GetInfoPtr(hwnd) ((PAGER_INFO *)GetWindowLongA(hwnd, 0))
49#define PAGER_IsHorizontal(hwnd) ((GetWindowLongA (hwnd, GWL_STYLE) & PGS_HORZ))
50
51#define MIN_ARROW_WIDTH 8
52#define MIN_ARROW_HEIGHT 5
53
54#define TIMERID1 1
55#define TIMERID2 2
56#define INITIAL_DELAY 500
57#define REPEAT_DELAY 50
58
59/* the horizontal arrows are:
60 *
61 * 01234 01234
62 * 1 * *
63 * 2 ** **
64 * 3*** ***
65 * 4*** ***
66 * 5 ** **
67 * 6 * *
68 * 7
69 *
70 */
71static void
72PAGER_DrawHorzArrow (HDC hdc, RECT r, INT colorRef, BOOL left)
73{
74 INT x, y, w, h;
75 HPEN hOldPen;
76
77 w = r.right - r.left + 1;
78 h = r.bottom - r.top + 1;
79 if ((h < MIN_ARROW_WIDTH) || (w < MIN_ARROW_HEIGHT))
80 return; /* refuse to draw partial arrow */
81
82 hOldPen = SelectObject ( hdc, GetSysColorPen (colorRef));
83 if (left)
84 {
85 x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 3;
86 y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
87 MoveToEx (hdc, x, y, NULL);
88 LineTo (hdc, x--, y+5); y++;
89 MoveToEx (hdc, x, y, NULL);
90 LineTo (hdc, x--, y+3); y++;
91 MoveToEx (hdc, x, y, NULL);
92 LineTo (hdc, x, y+1);
93 }
94 else
95 {
96 x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
97 y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
98 MoveToEx (hdc, x, y, NULL);
99 LineTo (hdc, x++, y+5); y++;
100 MoveToEx (hdc, x, y, NULL);
101 LineTo (hdc, x++, y+3); y++;
102 MoveToEx (hdc, x, y, NULL);
103 LineTo (hdc, x, y+1);
104 }
105
106 SelectObject( hdc, hOldPen );
107}
108
109/* the vertical arrows are:
110 *
111 * 01234567 01234567
112 * 1****** **
113 * 2 **** ****
114 * 3 ** ******
115 * 4
116 *
117 */
118static void
119PAGER_DrawVertArrow (HDC hdc, RECT r, INT colorRef, BOOL up)
120{
121 INT x, y, w, h;
122 HPEN hOldPen;
123
124 w = r.right - r.left + 1;
125 h = r.bottom - r.top + 1;
126 if ((h < MIN_ARROW_WIDTH) || (w < MIN_ARROW_HEIGHT))
127 return; /* refuse to draw partial arrow */
128
129 hOldPen = SelectObject ( hdc, GetSysColorPen (colorRef));
130 if (up)
131 {
132 x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
133 y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 3;
134 MoveToEx (hdc, x, y, NULL);
135 LineTo (hdc, x+5, y--); x++;
136 MoveToEx (hdc, x, y, NULL);
137 LineTo (hdc, x+3, y--); x++;
138 MoveToEx (hdc, x, y, NULL);
139 LineTo (hdc, x+1, y);
140 }
141 else
142 {
143 x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
144 y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
145 MoveToEx (hdc, x, y, NULL);
146 LineTo (hdc, x+5, y++); x++;
147 MoveToEx (hdc, x, y, NULL);
148 LineTo (hdc, x+3, y++); x++;
149 MoveToEx (hdc, x, y, NULL);
150 LineTo (hdc, x+1, y);
151 }
152
153 SelectObject( hdc, hOldPen );
154}
155
156static void
157PAGER_DrawButton(HDC hdc, COLORREF clrBk, RECT arrowRect,
158 BOOL horz, BOOL topLeft, INT btnState)
159{
160 HBRUSH hBrush, hOldBrush;
161 RECT rc = arrowRect;
162
163 if (!btnState) /* PGF_INVISIBLE */
164 return;
165
166 if ((rc.right - rc.left <= 0) || (rc.bottom - rc.top <= 0))
167 return;
168
169 hBrush = CreateSolidBrush(clrBk);
170 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
171
172 FillRect(hdc, &rc, hBrush);
173
174 if (btnState == PGF_HOT)
175 {
176 DrawEdge( hdc, &rc, BDR_RAISEDINNER, BF_RECT);
177 if (horz)
178 PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
179 else
180 PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
181 }
182 else if (btnState == PGF_NORMAL)
183 {
184 DrawEdge (hdc, &rc, BDR_OUTER, BF_FLAT);
185 if (horz)
186 PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
187 else
188 PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
189 }
190 else if (btnState == PGF_DEPRESSED)
191 {
192 DrawEdge( hdc, &rc, BDR_SUNKENOUTER, BF_RECT);
193 if (horz)
194 PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
195 else
196 PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
197 }
198 else if (btnState == PGF_GRAYED)
199 {
200 DrawEdge (hdc, &rc, BDR_OUTER, BF_FLAT);
201 if (horz)
202 {
203 PAGER_DrawHorzArrow(hdc, rc, COLOR_3DHIGHLIGHT, topLeft);
204 rc.left++, rc.top++; rc.right++, rc.bottom++;
205 PAGER_DrawHorzArrow(hdc, rc, COLOR_3DSHADOW, topLeft);
206 }
207 else
208 {
209 PAGER_DrawVertArrow(hdc, rc, COLOR_3DHIGHLIGHT, topLeft);
210 rc.left++, rc.top++; rc.right++, rc.bottom++;
211 PAGER_DrawVertArrow(hdc, rc, COLOR_3DSHADOW, topLeft);
212 }
213 }
214
215 SelectObject( hdc, hOldBrush );
216 DeleteObject(hBrush);
217}
218
219/* << PAGER_GetDropTarget >> */
220
221static inline LRESULT
222PAGER_ForwardMouse (HWND hwnd, WPARAM wParam)
223{
224 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
225 TRACE("[%04x]\n", hwnd);
226
227 infoPtr->bForward = (BOOL)wParam;
228
229 return 0;
230}
231
232static inline LRESULT
233PAGER_GetButtonState (HWND hwnd, WPARAM wParam, LPARAM lParam)
234{
235 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
236 LRESULT btnState = PGF_INVISIBLE;
237 INT btn = (INT)lParam;
238 TRACE("[%04x]\n", hwnd);
239
240 if (btn == PGB_TOPORLEFT)
241 btnState = infoPtr->TLbtnState;
242 else if (btn == PGB_BOTTOMORRIGHT)
243 btnState = infoPtr->BRbtnState;
244
245 return btnState;
246}
247
248
249static inline LRESULT
250PAGER_GetPos(HWND hwnd)
251{
252 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
253 TRACE("[%04x] returns %d\n", hwnd, infoPtr->nPos);
254 return (LRESULT)infoPtr->nPos;
255}
256
257static inline LRESULT
258PAGER_GetButtonSize(HWND hwnd)
259{
260 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
261 TRACE("[%04x] returns %d\n", hwnd, infoPtr->nButtonSize);
262 return (LRESULT)infoPtr->nButtonSize;
263}
264
265static inline LRESULT
266PAGER_GetBorder(HWND hwnd)
267{
268 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
269 TRACE("[%04x] returns %d\n", hwnd, infoPtr->nBorder);
270 return (LRESULT)infoPtr->nBorder;
271}
272
273static inline LRESULT
274PAGER_GetBkColor(HWND hwnd)
275{
276 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
277 TRACE("[%04x] returns %06lx\n", hwnd, infoPtr->clrBk);
278 return (LRESULT)infoPtr->clrBk;
279}
280
281static void
282PAGER_CalcSize (HWND hwnd, INT* size, BOOL getWidth)
283{
284 NMPGCALCSIZE nmpgcs;
285 ZeroMemory (&nmpgcs, sizeof (NMPGCALCSIZE));
286 nmpgcs.hdr.hwndFrom = hwnd;
287 nmpgcs.hdr.idFrom = GetWindowLongA (hwnd, GWL_ID);
288 nmpgcs.hdr.code = PGN_CALCSIZE;
289 nmpgcs.dwFlag = getWidth ? PGF_CALCWIDTH : PGF_CALCHEIGHT;
290 nmpgcs.iWidth = getWidth ? *size : 0;
291 nmpgcs.iHeight = getWidth ? 0 : *size;
292 SendMessageA (hwnd, WM_NOTIFY,
293 (WPARAM)nmpgcs.hdr.idFrom, (LPARAM)&nmpgcs);
294
295 *size = getWidth ? nmpgcs.iWidth : nmpgcs.iHeight;
296
297 TRACE("[%04x] PGN_CALCSIZE returns %s=%d\n", hwnd,
298 getWidth ? "width" : "height", *size);
299}
300
301static void
302PAGER_PositionChildWnd(HWND hwnd, PAGER_INFO* infoPtr)
303{
304 if (infoPtr->hwndChild)
305 {
306 RECT rcClient;
307 int nPos = infoPtr->nPos;
308
309 /* compensate for a grayed btn, which will soon become invisible */
310 if (infoPtr->TLbtnState == PGF_GRAYED)
311 nPos += infoPtr->nButtonSize;
312
313 GetClientRect(hwnd, &rcClient);
314
315 if (PAGER_IsHorizontal(hwnd))
316 {
317 int wndSize = max(0, rcClient.right - rcClient.left);
318 if (infoPtr->nWidth < wndSize)
319 infoPtr->nWidth = wndSize;
320
321 TRACE("[%04x] SWP %dx%d at (%d,%d)\n", hwnd,
322 infoPtr->nWidth, infoPtr->nHeight,
323 -nPos, 0);
324 SetWindowPos(infoPtr->hwndChild, 0,
325 -nPos, 0,
326 infoPtr->nWidth, infoPtr->nHeight,
327 SWP_NOZORDER);
328 }
329 else
330 {
331 int wndSize = max(0, rcClient.bottom - rcClient.top);
332 if (infoPtr->nHeight < wndSize)
333 infoPtr->nHeight = wndSize;
334
335 TRACE("[%04x] SWP %dx%d at (%d,%d)\n", hwnd,
336 infoPtr->nWidth, infoPtr->nHeight,
337 0, -nPos);
338 SetWindowPos(infoPtr->hwndChild, 0,
339 0, -nPos,
340 infoPtr->nWidth, infoPtr->nHeight,
341 SWP_NOZORDER);
342 }
343
344 InvalidateRect(infoPtr->hwndChild, NULL, TRUE);
345 }
346}
347
348static INT
349PAGER_GetScrollRange(HWND hwnd, PAGER_INFO* infoPtr)
350{
351 INT scrollRange = 0;
352
353 if (infoPtr->hwndChild)
354 {
355 INT wndSize, childSize;
356 RECT wndRect;
357 GetWindowRect(hwnd, &wndRect);
358
359 if (PAGER_IsHorizontal(hwnd))
360 {
361 wndSize = wndRect.right - wndRect.left;
362 PAGER_CalcSize(hwnd, &infoPtr->nWidth, TRUE);
363 childSize = infoPtr->nWidth;
364 }
365 else
366 {
367 wndSize = wndRect.bottom - wndRect.top;
368 PAGER_CalcSize(hwnd, &infoPtr->nHeight, FALSE);
369 childSize = infoPtr->nHeight;
370 }
371
372 TRACE("childSize = %d, wndSize = %d\n", childSize, wndSize);
373 if (childSize > wndSize)
374 scrollRange = childSize - wndSize + infoPtr->nButtonSize;
375 }
376
377 TRACE("[%04x] returns %d\n", hwnd, scrollRange);
378 return scrollRange;
379}
380
381static void
382PAGER_GrayAndRestoreBtns(PAGER_INFO* infoPtr, INT scrollRange,
383 BOOL* needsResize, BOOL* needsRepaint)
384{
385 if (infoPtr->nPos > 0)
386 {
387 *needsResize |= !infoPtr->TLbtnState; /* PGF_INVISIBLE */
388 if (infoPtr->TLbtnState != PGF_DEPRESSED)
389 infoPtr->TLbtnState = PGF_NORMAL;
390 }
391 else
392 {
393 *needsRepaint |= (infoPtr->TLbtnState != PGF_GRAYED);
394 infoPtr->TLbtnState = PGF_GRAYED;
395 }
396
397 if (scrollRange <= 0)
398 {
399 *needsRepaint |= (infoPtr->TLbtnState != PGF_GRAYED);
400 infoPtr->TLbtnState = PGF_GRAYED;
401 *needsRepaint |= (infoPtr->BRbtnState != PGF_GRAYED);
402 infoPtr->BRbtnState = PGF_GRAYED;
403 }
404 else if (infoPtr->nPos < scrollRange)
405 {
406 *needsResize |= !infoPtr->BRbtnState; /* PGF_INVISIBLE */
407 if (infoPtr->BRbtnState != PGF_DEPRESSED)
408 infoPtr->BRbtnState = PGF_NORMAL;
409 }
410 else
411 {
412 *needsRepaint |= (infoPtr->BRbtnState != PGF_GRAYED);
413 infoPtr->BRbtnState = PGF_GRAYED;
414 }
415}
416
417
418static void
419PAGER_NormalizeBtns(PAGER_INFO* infoPtr, BOOL* needsRepaint)
420{
421 if (infoPtr->TLbtnState & (PGF_HOT | PGF_DEPRESSED))
422 {
423 infoPtr->TLbtnState = PGF_NORMAL;
424 *needsRepaint = TRUE;
425 }
426
427 if (infoPtr->BRbtnState & (PGF_HOT | PGF_DEPRESSED))
428 {
429 infoPtr->BRbtnState = PGF_NORMAL;
430 *needsRepaint = TRUE;
431 }
432}
433
434static void
435PAGER_HideGrayBtns(PAGER_INFO* infoPtr, BOOL* needsResize)
436{
437 if (infoPtr->TLbtnState == PGF_GRAYED)
438 {
439 infoPtr->TLbtnState = PGF_INVISIBLE;
440 *needsResize = TRUE;
441 }
442
443 if (infoPtr->BRbtnState == PGF_GRAYED)
444 {
445 infoPtr->BRbtnState = PGF_INVISIBLE;
446 *needsResize = TRUE;
447 }
448}
449
450static void
451PAGER_UpdateBtns(HWND hwnd, PAGER_INFO *infoPtr,
452 INT scrollRange, BOOL hideGrayBtns)
453{
454 BOOL resizeClient = FALSE;
455 BOOL repaintBtns = FALSE;
456
457 if (scrollRange < 0)
458 PAGER_NormalizeBtns(infoPtr, &repaintBtns);
459 else
460 PAGER_GrayAndRestoreBtns(infoPtr, scrollRange, &resizeClient, &repaintBtns);
461
462 if (hideGrayBtns)
463 PAGER_HideGrayBtns(infoPtr, &resizeClient);
464
465 if (resizeClient) /* initiate NCCalcSize to resize client wnd */
466 SetWindowPos(hwnd, 0,0,0,0,0,
467 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
468 SWP_NOZORDER | SWP_NOACTIVATE);
469
470 if (repaintBtns)
471 SendMessageA(hwnd, WM_NCPAINT, 0, 0);
472}
473
474static LRESULT
475PAGER_SetPos(HWND hwnd, INT newPos, BOOL fromBtnPress)
476{
477 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
478 INT scrollRange = PAGER_GetScrollRange(hwnd, infoPtr);
479 INT oldPos = infoPtr->nPos;
480
481 if ((scrollRange <= 0) || (newPos < 0))
482 infoPtr->nPos = 0;
483 else if (newPos > scrollRange)
484 infoPtr->nPos = scrollRange;
485 else
486 infoPtr->nPos = newPos;
487
488 TRACE("[%04x] pos=%d\n", hwnd, infoPtr->nPos);
489
490 if (infoPtr->nPos != oldPos)
491 {
492 /* gray and restore btns, and if from WM_SETPOS, hide the gray btns */
493 PAGER_UpdateBtns(hwnd, infoPtr, scrollRange, !fromBtnPress);
494 PAGER_PositionChildWnd(hwnd, infoPtr);
495 }
496
497 return 0;
498}
499
500static LRESULT
501PAGER_HandleWindowPosChanging(HWND hwnd, WINDOWPOS *winpos)
502{
503 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
504
505 if (infoPtr->bNoResize && !(winpos->flags & SWP_NOSIZE))
506 {
507 /* don't let the app resize the nonscrollable dimension of a control
508 * that was created with CCS_NORESIZE style
509 * (i.e. height for a horizontal pager, or width for a vertical one) */
510
511 if (PAGER_IsHorizontal(hwnd))
512 winpos->cy = infoPtr->nHeight;
513 else
514 winpos->cx = infoPtr->nWidth;
515 }
516
517 return 0;
518}
519
520static void
521PAGER_SetFixedWidth(HWND hwnd, PAGER_INFO* infoPtr)
522{
523 /* Must set the non-scrollable dimension to be less than the full height/width
524 * so that NCCalcSize is called. The Msoft docs mention 3/4 factor for button
525 * size, and experimentation shows that affect is almost right. */
526
527 RECT wndRect;
528 INT delta, h;
529 GetWindowRect(hwnd, &wndRect);
530
531 /* see what the app says for btn width */
532 PAGER_CalcSize(hwnd, &infoPtr->nWidth, TRUE);
533
534 if (infoPtr->bNoResize)
535 {
536 delta = wndRect.right - wndRect.left - infoPtr->nWidth;
537 if (delta > infoPtr->nButtonSize)
538 infoPtr->nWidth += 4 * infoPtr->nButtonSize / 3;
539 else if (delta > 0)
540 infoPtr->nWidth += infoPtr->nButtonSize / 3;
541 }
542
543 h = wndRect.bottom - wndRect.top + infoPtr->nButtonSize;
544
545 /* adjust non-scrollable dimension to fit the child */
546 SetWindowPos(hwnd, 0, 0,0, infoPtr->nWidth, h,
547 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER);
548
549
550 TRACE("[%04x] infoPtr->nWidth set to %d\n",
551 hwnd, infoPtr->nWidth);
552}
553
554static void
555PAGER_SetFixedHeight(HWND hwnd, PAGER_INFO* infoPtr)
556{
557 /* Must set the non-scrollable dimension to be less than the full height/width
558 * so that NCCalcSize is called. The Msoft docs mention 3/4 factor for button
559 * size, and experimentation shows that affect is almost right. */
560
561 RECT wndRect;
562 INT delta, w;
563 GetWindowRect(hwnd, &wndRect);
564
565 /* see what the app says for btn height */
566 PAGER_CalcSize(hwnd, &infoPtr->nHeight, FALSE);
567
568 if (infoPtr->bNoResize)
569 {
570 delta = wndRect.bottom - wndRect.top - infoPtr->nHeight;
571 if (delta > infoPtr->nButtonSize)
572 infoPtr->nHeight += infoPtr->nButtonSize;
573 else if (delta > 0)
574 infoPtr->nHeight += infoPtr->nButtonSize / 3;
575 }
576
577 w = wndRect.right - wndRect.left + infoPtr->nButtonSize;
578
579 /* adjust non-scrollable dimension to fit the child */
580 SetWindowPos(hwnd, 0, 0,0, w, infoPtr->nHeight,
581 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER);
582
583 TRACE("[%04x] infoPtr->nHeight set to %d\n",
584 hwnd, infoPtr->nHeight);
585}
586
587static LRESULT
588PAGER_RecalcSize(HWND hwnd)
589{
590 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
591
592 TRACE("[%04x]\n", hwnd);
593
594 if (infoPtr->hwndChild)
595 {
596 INT scrollRange = PAGER_GetScrollRange(hwnd, infoPtr);
597
598 if (scrollRange <= 0)
599 {
600 infoPtr->nPos = -1;
601 PAGER_SetPos(hwnd, 0, FALSE);
602 }
603 else
604 {
605 PAGER_UpdateBtns(hwnd, infoPtr, scrollRange, TRUE);
606 PAGER_PositionChildWnd(hwnd, infoPtr);
607 }
608 }
609
610 return 1;
611}
612
613
614static LRESULT
615PAGER_SetBkColor (HWND hwnd, WPARAM wParam, LPARAM lParam)
616{
617 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
618 COLORREF clrTemp = infoPtr->clrBk;
619
620 infoPtr->clrBk = (COLORREF)lParam;
621 TRACE("[%04x] %06lx\n", hwnd, infoPtr->clrBk);
622
623 PAGER_RecalcSize(hwnd);
624 SendMessageA(hwnd, WM_NCPAINT, (WPARAM)0, (LPARAM)0);
625
626 return (LRESULT)clrTemp;
627}
628
629
630static LRESULT
631PAGER_SetBorder (HWND hwnd, WPARAM wParam, LPARAM lParam)
632{
633 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
634 INT nTemp = infoPtr->nBorder;
635
636 infoPtr->nBorder = (INT)lParam;
637 TRACE("[%04x] %d\n", hwnd, infoPtr->nBorder);
638
639 PAGER_RecalcSize(hwnd);
640
641 return (LRESULT)nTemp;
642}
643
644
645static LRESULT
646PAGER_SetButtonSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
647{
648 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
649 INT nTemp = infoPtr->nButtonSize;
650
651 infoPtr->nButtonSize = (INT)lParam;
652 TRACE("[%04x] %d\n", hwnd, infoPtr->nButtonSize);
653
654 PAGER_RecalcSize(hwnd);
655
656 return (LRESULT)nTemp;
657}
658
659
660static LRESULT
661PAGER_SetChild (HWND hwnd, WPARAM wParam, LPARAM lParam)
662{
663 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
664
665 infoPtr->hwndChild = IsWindow ((HWND)lParam) ? (HWND)lParam : 0;
666
667 if (infoPtr->hwndChild)
668 {
669 TRACE("[%04x] hwndChild=%04x\n", hwnd, infoPtr->hwndChild);
670
671 if (PAGER_IsHorizontal(hwnd))
672 PAGER_SetFixedHeight(hwnd, infoPtr);
673 else
674 PAGER_SetFixedWidth(hwnd, infoPtr);
675
676 /* position child within the page scroller */
677 SetWindowPos(infoPtr->hwndChild, HWND_TOP,
678 0,0,0,0,
679 SWP_SHOWWINDOW | SWP_NOSIZE);
680
681 infoPtr->nPos = -1;
682 PAGER_SetPos(hwnd, 0, FALSE);
683 }
684
685 return 0;
686}
687
688static void
689PAGER_Scroll(HWND hwnd, INT dir)
690{
691 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
692 NMPGSCROLL nmpgScroll;
693 RECT rcWnd;
694
695 if (infoPtr->hwndChild)
696 {
697 ZeroMemory (&nmpgScroll, sizeof (NMPGSCROLL));
698 nmpgScroll.hdr.hwndFrom = hwnd;
699 nmpgScroll.hdr.idFrom = GetWindowLongA (hwnd, GWL_ID);
700 nmpgScroll.hdr.code = PGN_SCROLL;
701
702 GetWindowRect(hwnd, &rcWnd);
703 GetClientRect(hwnd, &nmpgScroll.rcParent);
704 nmpgScroll.iXpos = nmpgScroll.iYpos = 0;
705 nmpgScroll.iDir = dir;
706
707 if (PAGER_IsHorizontal(hwnd))
708 {
709 nmpgScroll.iScroll = rcWnd.right - rcWnd.left;
710 nmpgScroll.iXpos = infoPtr->nPos;
711 }
712 else
713 {
714 nmpgScroll.iScroll = rcWnd.bottom - rcWnd.top;
715 nmpgScroll.iYpos = infoPtr->nPos;
716 }
717 nmpgScroll.iScroll -= 2*infoPtr->nButtonSize;
718
719 SendMessageA (hwnd, WM_NOTIFY,
720 (WPARAM)nmpgScroll.hdr.idFrom, (LPARAM)&nmpgScroll);
721
722 TRACE("[%04x] PGN_SCROLL returns iScroll=%d\n", hwnd, nmpgScroll.iScroll);
723
724 if (nmpgScroll.iScroll > 0)
725 {
726 infoPtr->direction = dir;
727
728 if (dir == PGF_SCROLLLEFT || dir == PGF_SCROLLUP)
729 PAGER_SetPos(hwnd, infoPtr->nPos - nmpgScroll.iScroll, TRUE);
730 else
731 PAGER_SetPos(hwnd, infoPtr->nPos + nmpgScroll.iScroll, TRUE);
732 }
733 else
734 infoPtr->direction = -1;
735 }
736}
737
738static LRESULT
739PAGER_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
740{
741 PAGER_INFO *infoPtr;
742 DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
743
744 /* allocate memory for info structure */
745#ifdef __WIN32OS2__
746 infoPtr =(PAGER_INFO*)initControl(hwnd,sizeof(PAGER_INFO));
747#else
748 infoPtr = (PAGER_INFO *)COMCTL32_Alloc (sizeof(PAGER_INFO));
749#endif
750 SetWindowLongA (hwnd, 0, (DWORD)infoPtr);
751
752 /* set default settings */
753 infoPtr->hwndChild = (HWND)NULL;
754 infoPtr->bNoResize = dwStyle & CCS_NORESIZE;
755 infoPtr->clrBk = GetSysColor(COLOR_BTNFACE);
756 infoPtr->nBorder = 0;
757 infoPtr->nButtonSize = 12;
758 infoPtr->nPos = 0;
759 infoPtr->nWidth = 0;
760 infoPtr->nHeight = 0;
761 infoPtr->bForward = FALSE;
762 infoPtr->TLbtnState = PGF_INVISIBLE;
763 infoPtr->BRbtnState = PGF_INVISIBLE;
764 infoPtr->direction = -1;
765
766 if (dwStyle & PGS_AUTOSCROLL)
767 FIXME("[%04x] Autoscroll style is not implemented yet.\n", hwnd);
768 if (dwStyle & PGS_DRAGNDROP)
769 FIXME("[%04x] Drag and Drop style is not implemented yet.\n", hwnd);
770 /*
771 * If neither horizontal nor vertical style specified, default to vertical.
772 * This is probably not necessary, since the style may be set later on as
773 * the control is initialized, but just in case it isn't, set it here.
774 */
775 if (!(dwStyle & PGS_HORZ) && !(dwStyle & PGS_VERT))
776 {
777 dwStyle |= PGS_VERT;
778 SetWindowLongA(hwnd, GWL_STYLE, dwStyle);
779 }
780
781 return 0;
782}
783
784
785static LRESULT
786PAGER_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
787{
788 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
789 /* free pager info data */
790 COMCTL32_Free (infoPtr);
791 SetWindowLongA (hwnd, 0, 0);
792 return 0;
793}
794
795static LRESULT
796PAGER_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
797{
798 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
799 LPRECT lpRect = (LPRECT)lParam;
800 /*
801 * lParam points to a RECT struct. On entry, the struct
802 * contains the proposed wnd rectangle for the window.
803 * On exit, the struct should contain the screen
804 * coordinates of the corresponding window's client area.
805 */
806
807 if (PAGER_IsHorizontal(hwnd))
808 {
809 if (infoPtr->TLbtnState) /* != PGF_INVISIBLE */
810 lpRect->left += infoPtr->nButtonSize;
811 if (infoPtr->BRbtnState)
812 lpRect->right -= infoPtr->nButtonSize;
813 }
814 else
815 {
816 if (infoPtr->TLbtnState)
817 lpRect->top += infoPtr->nButtonSize;
818 if (infoPtr->BRbtnState)
819 lpRect->bottom -= infoPtr->nButtonSize;
820 }
821
822 TRACE("[%04x] client rect set to %dx%d at (%d,%d)\n", hwnd,
823 lpRect->right-lpRect->left,
824 lpRect->bottom-lpRect->top,
825 lpRect->left, lpRect->top);
826
827 return 0;
828}
829
830static LRESULT
831PAGER_NCPaint (HWND hwnd, WPARAM wParam, LPARAM lParam)
832{
833 PAGER_INFO* infoPtr = PAGER_GetInfoPtr(hwnd);
834 DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
835 RECT rcWindow, rcBottomRight, rcTopLeft;
836 HDC hdc;
837 BOOL bHorizontal = PAGER_IsHorizontal(hwnd);
838
839 if (dwStyle & WS_MINIMIZE)
840 return 0;
841
842 DefWindowProcA (hwnd, WM_NCPAINT, wParam, lParam);
843
844 if (!(hdc = GetDCEx (hwnd, 0, DCX_USESTYLE | DCX_WINDOW)))
845 return 0;
846
847 GetWindowRect (hwnd, &rcWindow);
848 OffsetRect (&rcWindow, -rcWindow.left, -rcWindow.top);
849
850 rcTopLeft = rcBottomRight = rcWindow;
851 if (bHorizontal)
852 {
853 rcTopLeft.right = rcTopLeft.left + infoPtr->nButtonSize;
854 rcBottomRight.left = rcBottomRight.right - infoPtr->nButtonSize;
855 }
856 else
857 {
858 rcTopLeft.bottom = rcTopLeft.top + infoPtr->nButtonSize;
859 rcBottomRight.top = rcBottomRight.bottom - infoPtr->nButtonSize;
860 }
861
862 PAGER_DrawButton(hdc, infoPtr->clrBk, rcTopLeft,
863 bHorizontal, TRUE, infoPtr->TLbtnState);
864 PAGER_DrawButton(hdc, infoPtr->clrBk, rcBottomRight,
865 bHorizontal, FALSE, infoPtr->BRbtnState);
866
867 ReleaseDC( hwnd, hdc );
868 return 0;
869}
870
871static INT
872PAGER_HitTest (HWND hwnd, LPPOINT pt)
873{
874 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
875 RECT clientRect;
876 BOOL bHorizontal = PAGER_IsHorizontal(hwnd);
877
878 GetClientRect (hwnd, &clientRect);
879
880 if (PtInRect(&clientRect, *pt))
881 {
882 /* TRACE("HTCLIENT\n"); */
883 return HTCLIENT;
884 }
885
886 if (infoPtr->TLbtnState && infoPtr->TLbtnState != PGF_GRAYED)
887 {
888 if (bHorizontal)
889 {
890 if (pt->x < clientRect.left)
891 {
892 /* TRACE("HTLEFT\n"); */
893 return HTLEFT;
894 }
895 }
896 else
897 {
898 if (pt->y < clientRect.top)
899 {
900 /* TRACE("HTTOP\n"); */
901 return HTTOP;
902 }
903 }
904 }
905
906 if (infoPtr->BRbtnState && infoPtr->BRbtnState != PGF_GRAYED)
907 {
908 if (bHorizontal)
909 {
910 if (pt->x > clientRect.right)
911 {
912 /* TRACE("HTRIGHT\n"); */
913 return HTRIGHT;
914 }
915 }
916 else
917 {
918 if (pt->y > clientRect.bottom)
919 {
920 /* TRACE("HTBOTTOM\n"); */
921 return HTBOTTOM;
922 }
923 }
924 }
925
926 /* TRACE("HTNOWHERE\n"); */
927 return HTNOWHERE;
928}
929
930static LRESULT
931PAGER_NCHitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
932{
933 POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
934 ScreenToClient (hwnd, &pt);
935 return PAGER_HitTest(hwnd, &pt);
936}
937
938static LRESULT
939PAGER_SetCursor( HWND hwnd, WPARAM wParam, LPARAM lParam )
940{
941 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
942 BOOL notCaptured = FALSE;
943
944 switch(LOWORD(lParam))
945 {
946 case HTLEFT:
947 case HTTOP:
948 if ((notCaptured = infoPtr->TLbtnState != PGF_HOT))
949 infoPtr->TLbtnState = PGF_HOT;
950 break;
951 case HTRIGHT:
952 case HTBOTTOM:
953 if ((notCaptured = infoPtr->BRbtnState != PGF_HOT))
954 infoPtr->BRbtnState = PGF_HOT;
955 break;
956 default:
957 return FALSE;
958 }
959
960 if (notCaptured)
961 {
962 TRACKMOUSEEVENT trackinfo;
963
964 TRACE("[%04x] SetCapture\n", hwnd);
965 SetCapture(hwnd);
966
967 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
968 trackinfo.dwFlags = TME_QUERY;
969 trackinfo.hwndTrack = hwnd;
970 trackinfo.dwHoverTime = HOVER_DEFAULT;
971
972 /* call _TrackMouseEvent to see if we are currently tracking for this hwnd */
973 _TrackMouseEvent(&trackinfo);
974
975 /* Make sure tracking is enabled so we receive a WM_MOUSELEAVE message */
976 if(!(trackinfo.dwFlags & TME_LEAVE)) {
977 trackinfo.dwFlags = TME_LEAVE; /* notify upon leaving */
978
979 /* call TRACKMOUSEEVENT so we receive a WM_MOUSELEAVE message */
980 /* and can properly deactivate the hot button */
981 _TrackMouseEvent(&trackinfo);
982 }
983
984 SendMessageA(hwnd, WM_NCPAINT, 0, 0);
985 }
986
987 return TRUE;
988}
989
990static LRESULT
991PAGER_MouseLeave (HWND hwnd, WPARAM wParam, LPARAM lParam)
992{
993 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
994
995 KillTimer (hwnd, TIMERID1);
996 KillTimer (hwnd, TIMERID2);
997
998 TRACE("[%04x] ReleaseCapture\n", hwnd);
999 ReleaseCapture();
1000
1001 /* Notify parent of released mouse capture */
1002 {
1003 NMHDR nmhdr;
1004 ZeroMemory (&nmhdr, sizeof (NMHDR));
1005 nmhdr.hwndFrom = hwnd;
1006 nmhdr.idFrom = GetWindowLongA (hwnd, GWL_ID);
1007 nmhdr.code = NM_RELEASEDCAPTURE;
1008 SendMessageA (GetParent(hwnd), WM_NOTIFY,
1009 (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
1010 }
1011
1012 /* make HOT btns NORMAL and hide gray btns */
1013 PAGER_UpdateBtns(hwnd, infoPtr, -1, TRUE);
1014
1015 return TRUE;
1016}
1017
1018static LRESULT
1019PAGER_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
1020{
1021 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1022 BOOL repaintBtns = FALSE;
1023 POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
1024 INT hit;
1025
1026 TRACE("[%04x]\n", hwnd);
1027
1028 hit = PAGER_HitTest(hwnd, &pt);
1029
1030 /* put btn in DEPRESSED state */
1031 if (hit == HTLEFT || hit == HTTOP)
1032 {
1033 repaintBtns = infoPtr->TLbtnState != PGF_DEPRESSED;
1034 infoPtr->TLbtnState = PGF_DEPRESSED;
1035 SetTimer(hwnd, TIMERID1, INITIAL_DELAY, 0);
1036 }
1037 else if (hit == HTRIGHT || hit == HTBOTTOM)
1038 {
1039 repaintBtns = infoPtr->BRbtnState != PGF_DEPRESSED;
1040 infoPtr->BRbtnState = PGF_DEPRESSED;
1041 SetTimer(hwnd, TIMERID1, INITIAL_DELAY, 0);
1042 }
1043
1044 if (repaintBtns)
1045 SendMessageA(hwnd, WM_NCPAINT, 0, 0);
1046
1047 switch(hit)
1048 {
1049 case HTLEFT:
1050 TRACE("[%04x] PGF_SCROLLLEFT\n", hwnd);
1051 PAGER_Scroll(hwnd, PGF_SCROLLLEFT);
1052 break;
1053 case HTTOP:
1054 TRACE("[%04x] PGF_SCROLLUP\n", hwnd);
1055 PAGER_Scroll(hwnd, PGF_SCROLLUP);
1056 break;
1057 case HTRIGHT:
1058 TRACE("[%04x] PGF_SCROLLRIGHT\n", hwnd);
1059 PAGER_Scroll(hwnd, PGF_SCROLLRIGHT);
1060 break;
1061 case HTBOTTOM:
1062 TRACE("[%04x] PGF_SCROLLDOWN\n", hwnd);
1063 PAGER_Scroll(hwnd, PGF_SCROLLDOWN);
1064 break;
1065 default:
1066 break;
1067 }
1068
1069 return TRUE;
1070}
1071
1072static LRESULT
1073PAGER_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
1074{
1075 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1076 TRACE("[%04x]\n", hwnd);
1077
1078 KillTimer (hwnd, TIMERID1);
1079 KillTimer (hwnd, TIMERID2);
1080
1081 /* make PRESSED btns NORMAL but don't hide gray btns */
1082 PAGER_UpdateBtns(hwnd, infoPtr, -1, FALSE);
1083
1084 return 0;
1085}
1086
1087static LRESULT
1088PAGER_EraseBackground (HWND hwnd, WPARAM wParam, LPARAM lParam)
1089{
1090 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1091 HBRUSH hBrush = CreateSolidBrush(infoPtr->clrBk);
1092 RECT rect;
1093
1094 GetClientRect (hwnd, &rect);
1095 FillRect ((HDC)wParam, &rect, hBrush);
1096
1097 /* background color of the child should be the same as the pager */
1098 if (infoPtr->hwndChild)
1099 {
1100 GetClientRect (infoPtr->hwndChild, &rect);
1101 FillRect ((HDC)wParam, &rect, hBrush);
1102 }
1103
1104 DeleteObject (hBrush);
1105 return TRUE;
1106}
1107
1108
1109static LRESULT
1110PAGER_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
1111{
1112 /* note that WM_SIZE is sent whenever NCCalcSize resizes the client wnd */
1113
1114 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1115 TRACE("[%04x] %dx%d\n", hwnd, LOWORD(lParam), HIWORD(lParam));
1116
1117 if (PAGER_IsHorizontal(hwnd))
1118 infoPtr->nHeight = HIWORD(lParam);
1119 else
1120 infoPtr->nWidth = LOWORD(lParam);
1121
1122 return PAGER_RecalcSize(hwnd);
1123}
1124
1125
1126static LRESULT WINAPI
1127PAGER_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1128{
1129 PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1130
1131 if (!infoPtr && (uMsg != WM_CREATE))
1132 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
1133
1134 switch (uMsg)
1135 {
1136 case PGM_FORWARDMOUSE:
1137 return PAGER_ForwardMouse (hwnd, wParam);
1138
1139 case PGM_GETBKCOLOR:
1140 return PAGER_GetBkColor(hwnd);
1141
1142 case PGM_GETBORDER:
1143 return PAGER_GetBorder(hwnd);
1144
1145 case PGM_GETBUTTONSIZE:
1146 return PAGER_GetButtonSize(hwnd);
1147
1148 case PGM_GETPOS:
1149 return PAGER_GetPos(hwnd);
1150
1151 case PGM_GETBUTTONSTATE:
1152 return PAGER_GetButtonState (hwnd, wParam, lParam);
1153
1154/* case PGM_GETDROPTARGET: */
1155
1156 case PGM_RECALCSIZE:
1157 return PAGER_RecalcSize(hwnd);
1158
1159 case PGM_SETBKCOLOR:
1160 return PAGER_SetBkColor (hwnd, wParam, lParam);
1161
1162 case PGM_SETBORDER:
1163 return PAGER_SetBorder (hwnd, wParam, lParam);
1164
1165 case PGM_SETBUTTONSIZE:
1166 return PAGER_SetButtonSize (hwnd, wParam, lParam);
1167
1168 case PGM_SETCHILD:
1169 return PAGER_SetChild (hwnd, wParam, lParam);
1170
1171 case PGM_SETPOS:
1172 return PAGER_SetPos(hwnd, (INT)lParam, FALSE);
1173
1174 case WM_CREATE:
1175 return PAGER_Create (hwnd, wParam, lParam);
1176
1177 case WM_DESTROY:
1178 return PAGER_Destroy (hwnd, wParam, lParam);
1179
1180 case WM_SIZE:
1181 return PAGER_Size (hwnd, wParam, lParam);
1182
1183 case WM_NCPAINT:
1184 return PAGER_NCPaint (hwnd, wParam, lParam);
1185
1186 case WM_WINDOWPOSCHANGING:
1187 return PAGER_HandleWindowPosChanging (hwnd, (WINDOWPOS*)lParam);
1188
1189 case WM_NCCALCSIZE:
1190 return PAGER_NCCalcSize (hwnd, wParam, lParam);
1191
1192 case WM_NCHITTEST:
1193 return PAGER_NCHitTest (hwnd, wParam, lParam);
1194
1195 case WM_SETCURSOR:
1196 {
1197 if (hwnd == (HWND)wParam)
1198 return PAGER_SetCursor(hwnd, wParam, lParam);
1199 else /* its for the child */
1200 return 0;
1201 }
1202
1203 case WM_MOUSEMOVE:
1204 if (infoPtr->bForward && infoPtr->hwndChild)
1205 PostMessageA(infoPtr->hwndChild, WM_MOUSEMOVE, wParam, lParam);
1206 return TRUE;
1207
1208 case WM_MOUSELEAVE:
1209 return PAGER_MouseLeave (hwnd, wParam, lParam);
1210
1211 case WM_LBUTTONDOWN:
1212 return PAGER_LButtonDown (hwnd, wParam, lParam);
1213
1214 case WM_LBUTTONUP:
1215 return PAGER_LButtonUp (hwnd, wParam, lParam);
1216
1217 case WM_ERASEBKGND:
1218 return PAGER_EraseBackground (hwnd, wParam, lParam);
1219/*
1220 case WM_PAINT:
1221 return PAGER_Paint (hwnd, wParam);
1222*/
1223 case WM_TIMER:
1224 /* if initial timer, kill it and start the repeat timer */
1225 if (wParam == TIMERID1)
1226 {
1227 KillTimer(hwnd, TIMERID1);
1228 SetTimer(hwnd, TIMERID2, REPEAT_DELAY, 0);
1229 }
1230
1231 KillTimer(hwnd, TIMERID2);
1232 if (infoPtr->direction > 0)
1233 {
1234 PAGER_Scroll(hwnd, infoPtr->direction);
1235 SetTimer(hwnd, TIMERID2, REPEAT_DELAY, 0);
1236 }
1237 break;
1238
1239 case WM_NOTIFY:
1240 case WM_COMMAND:
1241 return SendMessageA (GetParent (hwnd), uMsg, wParam, lParam);
1242
1243 default:
1244#ifdef __WIN32OS2__
1245 return defComCtl32ProcA(hwnd, uMsg, wParam, lParam);
1246#else
1247 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
1248#endif
1249 }
1250
1251 return 0;
1252}
1253
1254
1255VOID
1256PAGER_Register (void)
1257{
1258 WNDCLASSA wndClass;
1259
1260 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
1261 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS;
1262 wndClass.lpfnWndProc = (WNDPROC)PAGER_WindowProc;
1263 wndClass.cbClsExtra = 0;
1264 wndClass.cbWndExtra = sizeof(PAGER_INFO *);
1265 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
1266 wndClass.hbrBackground = 0;
1267 wndClass.lpszClassName = WC_PAGESCROLLERA;
1268
1269 RegisterClassA (&wndClass);
1270}
1271
1272
1273VOID
1274PAGER_Unregister (void)
1275{
1276 UnregisterClassA (WC_PAGESCROLLERA, (HINSTANCE)NULL);
1277}
1278
Note: See TracBrowser for help on using the repository browser.