|
|
1.1 root 1: /************************************************************************
2: *
1.1.1.2 ! root 3: * linefrac.c -- Main window procedure for LineFractal window class.
1.1 root 4: *
1.1.1.2 ! root 5: * Created by Microsoft Corporation, 1989
1.1 root 6: *
7: ************************************************************************/
8:
9: #define INCL_WIN
1.1.1.2 ! root 10: #define INCL_GPI
1.1 root 11: #define INCL_DOSSEMAPHORES
1.1.1.2 ! root 12: #define INCL_DOSMEMMGR
! 13: #define INCL_DOSPROCESS
1.1 root 14: #include <os2.h>
1.1.1.2 ! root 15: #include <mt\stdlib.h>
! 16:
! 17: #define INCL_GLOBALS
! 18: #define INCL_THREADS
1.1 root 19: #include "linefrac.h"
20:
1.1.1.2 ! root 21: #define INCL_LFMAIN
! 22: #define INCL_LFINIT
! 23: #define INCL_LFTHREAD
! 24: #define INCL_LFPS
! 25: #define INCL_LFCMD
! 26: #define INCL_LFDRAW
! 27: #include "lffuncs.h"
! 28:
! 29:
! 30:
1.1 root 31:
1.1.1.2 ! root 32: /************************************************************************
! 33: *
! 34: * Global Variables
! 35: *
! 36: ************************************************************************/
1.1 root 37:
1.1.1.2 ! root 38: GLOBALDATA global;
1.1 root 39:
40:
41:
42:
43: /************************************************************************
44: *
45: * main
46: *
47: * WinInitialize resizes our ring 2 stack, among other things, so
48: * we won't GP fault trying to do graphics. WinCreateMsgQueue defines
1.1.1.2 ! root 49: * us as a REAL PM app. (as does the WINDOWAPI in the .DEF file).
! 50: * Call a subroutine to register our window class and create a window.
! 51: * Loop over messages. Exit cleanly.
1.1 root 52: *
53: ************************************************************************/
54:
1.1.1.2 ! root 55: VOID cdecl
! 56: main( VOID )
1.1 root 57: {
1.1.1.2 ! root 58: QMSG qMsg;
! 59: int iRet = 0;
1.1 root 60:
1.1.1.2 ! root 61: global.hab = WinInitialize(NULL);
! 62: global.hMsgQ = WinCreateMsgQueue(global.hab, 0);
1.1 root 63:
1.1.1.2 ! root 64: if (LfInitApp())
! 65: while (WinGetMsg( global.hab, (PQMSG)&qMsg, (HWND)NULL, 0, 0 ))
! 66: WinDispatchMsg( global.hab, (PQMSG)&qMsg );
! 67: else
! 68: iRet = -1;
! 69:
! 70: WinDestroyWindow( global.hwndFrame );
! 71: WinDestroyMsgQueue( global.hMsgQ );
! 72: WinTerminate( global.hab );
! 73: DosExit(EXIT_PROCESS, iRet);
1.1 root 74: }
75:
76:
77:
78:
79: /************************************************************************
80: *
81: * LineFracWndProc
82: *
83: * Process messages for the LineFractal window class.
84: *
85: ************************************************************************/
86:
1.1.1.2 ! root 87: ULONG CALLBACK
! 88: LineFracWndProc( hwnd, usMsg, mp1, mp2 )
! 89: HWND hwnd;
! 90: USHORT usMsg;
1.1 root 91: MPARAM mp1;
92: MPARAM mp2;
93: {
1.1.1.2 ! root 94: HPS hps;
! 95: USHORT iNewTop;
! 96: int i;
! 97: PTHR pthr;
! 98: RECTL rcl;
! 99: BOOL fIsTimerUsed;
1.1 root 100:
1.1.1.2 ! root 101: switch (usMsg)
1.1 root 102: {
1.1.1.2 ! root 103: case WM_CREATE:
! 104: if ((global.hptr)[global.usCurPtr])
! 105: WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
! 106: break;
! 107:
1.1 root 108: case WM_CLOSE:
1.1.1.2 ! root 109: LfClose(hwnd);
1.1 root 110: break;
111:
112: case WM_COMMAND:
1.1.1.2 ! root 113: LfCommand(hwnd, LOUSHORT(mp1));
! 114: break;
! 115:
! 116: case WM_TIMER:
! 117: if (LOUSHORT(mp1) == IDT_AUTOSTARTREDRAW)
! 118: {
! 119: fIsTimerUsed = FALSE;
! 120: for (i = 0; i < global.cThr; ++i)
! 121: if (global.aThr[i]->fAutoStartRedraw)
! 122: {
! 123: DosSemClear(&(global.aThr[i])->lSemRedraw);
! 124: fIsTimerUsed = TRUE;
! 125: }
! 126: if (!fIsTimerUsed)
! 127: LfStopRedrawTimer();
! 128: else
! 129: {
! 130: LfStopRedrawTimer();
! 131: LfStartRedrawTimer();
! 132: }
! 133: }
! 134: else if (LOUSHORT(mp1) == IDT_AUTOSWITCH)
! 135: {
! 136: DosEnterCritSec();
! 137: iNewTop = (global.iThrTop + 1) % global.cThr;
! 138: DosExitCritSec();
! 139: LfBringThreadToTop(global.aThr[iNewTop]);
! 140: }
! 141: else
! 142: goto pass_it_on;
1.1 root 143: break;
144:
145: case WM_ERASEBACKGROUND:
1.1.1.2 ! root 146: LfEraseBackground(hwnd, NULL, NULL, NULL);
1.1 root 147: return FALSE;
148: break;
149:
150: case WM_PAINT:
1.1.1.2 ! root 151: if (!global.pThrTop)
! 152: WinSendMsg(hwnd, WM_COMMAND, (MPARAM)IDM_DCMEMORY, 0);
! 153:
! 154: hps = WinBeginPaint(global.hwnd, NULL, &rcl);
! 155: if (global.pThrTop) /* only try to paint if we have a drawing */
! 156: LfPaint(hps, &rcl);
! 157: WinEndPaint(hps);
! 158: break;
! 159:
! 160: case WM_CHAR:
! 161: if (LOUSHORT(mp1) & KC_CHAR)
! 162: LfChar(hwnd, (char)mp2);
! 163: break;
! 164:
! 165: case WM_BUTTON1DOWN:
! 166: case WM_BUTTON2DOWN:
! 167: LfButtonDown(hwnd, mp1);
! 168: WinSetFocus(HWND_DESKTOP, hwnd);
1.1 root 169: break;
170:
171: case WM_BUTTON1UP:
172: case WM_BUTTON2UP:
1.1.1.2 ! root 173: LfButtonUp(usMsg);
! 174: break;
1.1 root 175:
1.1.1.2 ! root 176: case WM_MOUSEMOVE:
! 177: LfMouseMove();
1.1 root 178: break;
179:
180: case WM_SIZE:
1.1.1.2 ! root 181: /* Resize each PS that has fAutoSizePS set to TRUE. */
! 182:
! 183: WinQueryWindowRect(global.hwnd, &global.rcl);
! 184: global.bm.cx = (USHORT) (global.rcl.xRight - global.rcl.xLeft);
! 185: global.bm.cy = (USHORT) (global.rcl.yTop - global.rcl.yBottom);
! 186:
! 187: for (i = 0; i < global.cThr; ++i)
! 188: if (pthr = global.aThr[i])
! 189: if (pthr->hps)
! 190: if (pthr->fAutoSizePS)
! 191: {
! 192: global.bm.cPlanes = pthr->cPlanes;
! 193: global.bm.cBitCount = pthr->cBitCount;
! 194:
! 195: if (LfResizePS(pthr))
! 196: DosSemClear(&pthr->lSemRedraw);
! 197: }
1.1 root 198:
199: /* fall through -- we might want to restart point accumulation,
200: but don't want to process the resize message */
201:
202: default:
1.1.1.2 ! root 203: pass_it_on:
! 204: return( (ULONG)WinDefWindowProc(hwnd, usMsg, mp1, mp2));
1.1 root 205: break;
206: }
207:
208: return FALSE;
209: }
1.1.1.2 ! root 210:
! 211:
! 212:
! 213:
! 214: /************************************************************************
! 215: *
! 216: * LfClose
! 217: *
! 218: * Kill all the threads still running and delete all our fancy pointers.
! 219: * In general, prepare to terminate the program.
! 220: *
! 221: ************************************************************************/
! 222:
! 223: VOID
! 224: LfClose(hwnd)
! 225: HWND hwnd;
! 226: {
! 227: int i;
! 228:
! 229: WinSendMsg(hwnd, WM_COMMAND, (MPARAM)IDM_KILLALLTHREADS, 0);
! 230: WinSetPointer(HWND_DESKTOP,
! 231: WinQuerySysPointer(HWND_DESKTOP,SPTR_ARROW,FALSE));
! 232: for (i = 0; i < NUM_PTR_SHAPES; ++i)
! 233: if ((global.hptr)[i])
! 234: WinDestroyPointer((global.hptr)[i]);
! 235: if (global.hptrTrack)
! 236: WinDestroyPointer(global.hptrTrack);
! 237: if (global.hptrPaste)
! 238: WinDestroyPointer(global.hptrPaste);
! 239: WinPostMsg(hwnd, WM_QUIT, 0L, 0L);
! 240: }
! 241:
! 242:
! 243:
! 244:
! 245: /************************************************************************
! 246: *
! 247: * LfEraseBackground
! 248: *
! 249: * Erase the window background to a hatch pattern. This enables the
! 250: * user to see where the bitmap's edges are in case it's smaller
! 251: * than the window.
! 252: *
! 253: ************************************************************************/
! 254:
! 255: VOID
! 256: LfEraseBackground(hwnd, hpsCaller, prclUpdate, prclX)
! 257: HWND hwnd;
! 258: HPS hpsCaller;
! 259: PRECTL prclUpdate;
! 260: PRECTL prclX; /* excluded rectangle */
! 261: {
! 262: HPS hps;
! 263: RECTL rcl;
! 264: RECTL rclT;
! 265: AREABUNDLE ab;
! 266: HRGN hrgnClipOld;
! 267: HRGN hrgn;
! 268: HRGN hrgnT;
! 269:
! 270:
! 271: if (hpsCaller)
! 272: hps = hpsCaller;
! 273: else
! 274: hps = WinGetPS(hwnd);
! 275:
! 276: if (prclUpdate)
! 277: rcl = *prclUpdate;
! 278: else
! 279: WinQueryUpdateRect(hwnd, (PRECTL)&rcl);
! 280:
! 281: ab.lColor = CLR_BLACK;
! 282: ab.lBackColor = CLR_WHITE;
! 283: ab.usSymbol = PATSYM_DIAG1;
! 284: GpiSetAttrs(hps, PRIM_AREA, ABB_COLOR|ABB_BACK_COLOR|ABB_SYMBOL,
! 285: 0L, (PBUNDLE)&ab);
! 286:
! 287:
! 288: /* Make a region out of the nearest rectangle, then copy the real
! 289: * region data into it. Set this as our clip rectangle.
! 290: */
! 291: if ((hrgn = GpiCreateRegion(hps, 1L, &rcl)) != HRGN_ERROR)
! 292: {
! 293: WinQueryUpdateRegion(hwnd, hrgn);
! 294: GpiSetClipRegion(hps, hrgn, &hrgnClipOld);
! 295: }
! 296:
! 297: if (!prclX)
! 298: GpiBitBlt(hps, NULL, 2L, (PPOINTL)&rcl, ROP_PATCOPY, NULL);
! 299: else
! 300: {
! 301: if (prclX->yTop < rcl.yTop)
! 302: {
! 303: rclT.xLeft = rcl.xLeft;
! 304: rclT.yBottom = prclX->yBottom;
! 305: rclT.xRight = rcl.xRight;
! 306: rclT.yTop = rcl.yTop;
! 307: GpiBitBlt(hps, NULL, 2L, (PPOINTL)&rclT, ROP_PATCOPY, NULL);
! 308: }
! 309:
! 310: if (prclX->xRight < rcl.xRight)
! 311: {
! 312: rclT.xLeft = prclX->xRight;
! 313: rclT.yBottom = rcl.yBottom;
! 314: rclT.xRight = rcl.xRight;
! 315: rclT.yTop = prclX->yTop;
! 316: GpiBitBlt(hps, NULL, 2L, (PPOINTL)&rclT, ROP_PATCOPY, NULL);
! 317: }
! 318: }
! 319:
! 320: if (hrgnClipOld != HRGN_ERROR)
! 321: GpiSetClipRegion(hps, hrgnClipOld, &hrgnT);
! 322: if (hrgn != HRGN_ERROR)
! 323: GpiDestroyRegion(hps, hrgn);
! 324:
! 325: if (!hpsCaller)
! 326: WinReleasePS(hps);
! 327: }
! 328:
! 329:
! 330:
! 331:
! 332: /************************************************************************
! 333: *
! 334: * LfPaint
! 335: *
! 336: * If we have a bitmap, blt it to the screen, no matter what state
! 337: * it's in. If the selection rectangle is still alive, then display
! 338: * it, too. Note that it goes directly to the screen, so we have to
! 339: * redraw it each time the bitmap is thrown back up.
! 340: *
! 341: ************************************************************************/
! 342:
! 343: VOID
! 344: LfPaint(hps, prcl)
! 345: HPS hps;
! 346: PRECTL prcl;
! 347: {
! 348: POINTL aptl[4];
! 349:
! 350: if (global.pThrTop)
! 351: {
! 352: switch (global.pThrTop->dcType)
! 353: {
! 354: case IDM_DCDIRECT: /* all drawing is already on the screen */
! 355: break;
! 356:
! 357: case IDM_DCPOSTSCRIPT:
! 358: case IDM_DCPROPRINTER:
! 359: break;
! 360:
! 361: case IDM_DCMEMORY:
! 362:
! 363: GpiSetAttrs(hps, PRIM_IMAGE, IBB_COLOR|IBB_BACK_COLOR, 0L, &global.pThrTop->ib);
! 364:
! 365: aptl[0].x = 0L;
! 366: aptl[0].y = 0L;
! 367: aptl[1].x = (global.pThrTop->rcl).xRight;
! 368: aptl[1].y = (global.pThrTop->rcl).yTop;
! 369: aptl[2].x = 0L;
! 370: aptl[2].y = 0L;
! 371:
! 372: GpiBitBlt(hps, global.pThrTop->hps, 3L, aptl, ROP_SRCCOPY, (LONG)NULL);
! 373: LfEraseBackground(global.hwnd, hps, prcl, &(global.pThrTop->rcl));
! 374: break;
! 375:
! 376: default:
! 377: break;
! 378: }
! 379: if (global.fShowSelectRc)
! 380: LfShowSelectRc(hps, global.fTempSelect ? &global.rclSelect : &global.rclCutCopy);
! 381: }
! 382: }
! 383:
! 384:
! 385:
! 386:
! 387: /************************************************************************
! 388: *
! 389: * LfShowSelectRc
! 390: *
! 391: * Draw the selection rectangle in the given presentation space. The
! 392: * rectangle is drawn in xor-mode so this can be called to remove it
! 393: * as well as show it.
! 394: *
! 395: ************************************************************************/
! 396:
! 397: VOID
! 398: LfShowSelectRc(hps, lprc)
! 399: HPS hps;
! 400: PRECTL lprc;
! 401: {
! 402: LINEBUNDLE lb;
! 403:
! 404:
! 405: lb.lColor = CLR_TRUE;
! 406: lb.usMixMode = FM_XOR;
! 407: lb.usType = LINETYPE_ALTERNATE;
! 408: GpiSetAttrs(hps, PRIM_LINE, LBB_COLOR|LBB_MIX_MODE|LBB_TYPE,
! 409: 0L, (PBUNDLE)&lb);
! 410: GpiSetCurrentPosition(hps, (POINTL *)&(lprc->xLeft));
! 411: GpiBox(hps, DRO_OUTLINE, (POINTL *)&(lprc->xRight), 0L, 0L);
! 412: }
! 413:
! 414:
! 415:
! 416:
! 417: /************************************************************************
! 418: *
! 419: * LfChar
! 420: *
! 421: * Handle LineFractal's keyboard interface. This consists of:
! 422: *
! 423: * <space> Cancels the selection rectangle.
! 424: * 0-9 Brings thread i to the top, if it exists.
! 425: *
! 426: ************************************************************************/
! 427:
! 428: VOID
! 429: LfChar(hwnd, ch)
! 430: HWND hwnd;
! 431: char ch;
! 432: {
! 433: HPS hps;
! 434: PRECTL lprc;
! 435: int i;
! 436:
! 437: if (ch == ' ')
! 438: {
! 439: if (global.fShowSelectRc)
! 440: {
! 441: global.fShowSelectRc = FALSE;
! 442: hps = WinGetPS(hwnd);
! 443: if (global.fTempSelect)
! 444: {
! 445: lprc = &global.rclSelect;
! 446: global.fTempSelect = FALSE;
! 447: }
! 448: else
! 449: lprc = &global.rclCutCopy;
! 450: LfShowSelectRc(hps, lprc);
! 451: WinReleasePS(hps);
! 452: }
! 453: }
! 454: else if (ch >= '0' && ch <= '9')
! 455: {
! 456: i = ch - '0';
! 457: if (i < global.cThr)
! 458: LfBringThreadToTop(global.aThr[i]);
! 459: }
! 460: }
! 461:
! 462:
! 463:
! 464:
! 465: /************************************************************************
! 466: *
! 467: * LfMouseMove
! 468: *
! 469: * Handle actions necessary upon each move of the mouse pointer.
! 470: * This consists of resetting the mouse pointer -- if we pass
! 471: * this on to WinDefWindowProc, it will reset it to the arrow.
! 472: * As long as we're setting the pointer so often, we can do a
! 473: * little animation.
! 474: *
! 475: ************************************************************************/
! 476:
! 477: VOID
! 478: LfMouseMove()
! 479: {
! 480: /* Do this so that if in tracking mode, the correct pointer
! 481: shape appears immediately. */
! 482: if (global.fTracking || global.fSelecting)
! 483: {
! 484: if (global.hptrTrack)
! 485: WinSetPointer(HWND_DESKTOP,global.hptrTrack);
! 486: return;
! 487: }
! 488: if (global.fPasting)
! 489: {
! 490: if (global.hptrPaste)
! 491: WinSetPointer(HWND_DESKTOP,global.hptrPaste);
! 492: return;
! 493: }
! 494: if (global.pThrTop)
! 495: {
! 496: if (global.pThrTop->fBusy)
! 497: {
! 498: if (global.hptrWait)
! 499: WinSetPointer(HWND_DESKTOP,global.hptrWait);
! 500: return;
! 501: }
! 502: }
! 503: global.usPtrCounter = (global.usPtrCounter+1) % global.usPtrThreshold;
! 504: if (global.usPtrCounter == 0)
! 505: {
! 506: global.usCurPtr += global.usPtrIncr;
! 507: if (global.usCurPtr <= 0)
! 508: global.usCurPtr = 0;
! 509: else
! 510: global.usCurPtr %= NUM_PTR_SHAPES;
! 511: if ((global.usCurPtr == NUM_PTR_SHAPES - 1) ||
! 512: (global.usCurPtr == 0))
! 513: global.usPtrIncr *= -1;
! 514: }
! 515: if ((global.hptr)[global.usCurPtr])
! 516: WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
! 517: }
! 518:
! 519:
! 520:
! 521:
! 522: /************************************************************************
! 523: *
! 524: * LfButtonUp
! 525: *
! 526: * Handle up clicks of the mouse buttons. This consists of:
! 527: *
! 528: * left button up increase the depth of recursion
! 529: * right button up decrease the depth of recursion
! 530: *
! 531: * In both cases, clear the semaphore so the
! 532: * drawing can restart at the new level of
! 533: * recursion.
! 534: *
! 535: ************************************************************************/
! 536:
! 537: VOID
! 538: LfButtonUp(usMsg)
! 539: USHORT usMsg;
! 540: {
! 541: if (global.fMouseChangesRecursion)
! 542: {
! 543: if (usMsg == WM_BUTTON1UP)
! 544: global.pThrTop->usRecursion = ++global.pThrTop->usRecursion;
! 545: else if (global.pThrTop->usRecursion > 0)
! 546: global.pThrTop->usRecursion = --global.pThrTop->usRecursion;
! 547:
! 548: global.pThrTop->flMiscAttrs |= LFA_RECURSION;
! 549: global.pThrTop->fUpdateAttrs = TRUE;
! 550: global.fUpdateAttrs = TRUE;
! 551:
! 552: if (global.pThrTop->hps)
! 553: {
! 554: global.pThrTop->fInterrupted = TRUE;
! 555: DosSemClear(&(global.pThrTop)->lSemRedraw);
! 556: }
! 557: }
! 558: }
! 559:
! 560:
! 561:
! 562:
! 563: /************************************************************************
! 564: *
! 565: * LfButtonDown
! 566: *
! 567: * Handle down clicks of the mouse buttons. This consists of
! 568: * changing the mouse pointer depending upon which mode we're in
! 569: * and calling off to the subroutine to do the real work given
! 570: * the position at which the mouse button was clicked. The modes
! 571: * are "tracking", "selecting", and "pasting". Tracking means the
! 572: * user is defining the new dimensions of the fractal. Selecting
! 573: * means the user is dragging a rectangle to cut or copy. Pasting
! 574: * means the user is positioning the cut or copied rectangle in
! 575: * the window.
! 576: *
! 577: ************************************************************************/
! 578:
! 579: VOID
! 580: LfButtonDown(hwnd, mp1)
! 581: HWND hwnd;
! 582: MPARAM mp1;
! 583: {
! 584: POINTS pt;
! 585:
! 586: if (global.fTracking)
! 587: {
! 588: if (global.hptrTrack)
! 589: WinSetPointer(HWND_DESKTOP,global.hptrTrack);
! 590: pt.x = LOUSHORT(mp1);
! 591: pt.y = HIUSHORT(mp1);
! 592: LfSelectDimension(hwnd, pt);
! 593: global.fTracking = FALSE;
! 594: if ((global.hptr)[global.usCurPtr])
! 595: WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
! 596: if (global.pThrTop->fAttrRedraw)
! 597: {
! 598: global.pThrTop->fInterrupted = TRUE;
! 599: DosSemClear(&(global.pThrTop)->lSemRedraw);
! 600: }
! 601: }
! 602: else if (global.fSelecting)
! 603: {
! 604: if (global.hptrTrack)
! 605: WinSetPointer(HWND_DESKTOP,global.hptrTrack);
! 606: pt.x = LOUSHORT(mp1);
! 607: pt.y = HIUSHORT(mp1);
! 608: LfSelect(hwnd, pt);
! 609: global.fSelecting = FALSE;
! 610: if ((global.hptr)[global.usCurPtr])
! 611: WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
! 612: }
! 613: else if (global.fPasting)
! 614: {
! 615: if (global.hptrPaste)
! 616: WinSetPointer(HWND_DESKTOP,global.hptrPaste);
! 617: pt.x = LOUSHORT(mp1);
! 618: pt.y = HIUSHORT(mp1);
! 619: LfPaste(hwnd);
! 620: global.fPasting = FALSE;
! 621: if ((global.hptr)[global.usCurPtr])
! 622: WinSetPointer(HWND_DESKTOP,(global.hptr)[global.usCurPtr]);
! 623: }
! 624: }
! 625:
! 626:
! 627:
! 628:
! 629: /************************************************************************
! 630: *
! 631: * LfStartRedrawTimer
! 632: *
! 633: ************************************************************************/
! 634:
! 635: VOID
! 636: LfStartRedrawTimer()
! 637: {
! 638: USHORT timeout;
! 639:
! 640: if (!global.fTimerOn)
! 641: {
! 642: timeout = (USHORT) ((rand()/32767.0) *
! 643: (global.usMaxTimerDelay - global.usMinTimerDelay) +
! 644: global.usMinTimerDelay);
! 645: WinStartTimer(global.hab, global.hwnd, IDT_AUTOSTARTREDRAW, timeout);
! 646: global.fTimerOn = TRUE;
! 647: }
! 648: }
! 649:
! 650:
! 651:
! 652:
! 653: /************************************************************************
! 654: *
! 655: * LfStopRedrawTimer
! 656: *
! 657: ************************************************************************/
! 658:
! 659: VOID
! 660: LfStopRedrawTimer()
! 661: {
! 662: WinStopTimer(global.hab, global.hwnd, IDT_AUTOSTARTREDRAW);
! 663: global.fTimerOn = FALSE;
! 664: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.