|
|
1.1 root 1: /*************************************************************************
2: **
3: ** OLE 2 Sample Code
4: **
5: ** dragdrop.c
6: **
7: ** This file contains the major interfaces, methods and related support
8: ** functions for implementing Drag/Drop. The code contained in this
9: ** file is used by BOTH the Container and Server (Object) versions
10: ** of the Outline sample code.
11: ** The Drag/Drop support includes the following implementation objects:
12: **
13: ** OleDoc Object
14: ** exposed interfaces:
15: ** IDropSource
16: ** IDropTarget
17: **
18: ** (c) Copyright Microsoft Corp. 1992 - 1993 All Rights Reserved
19: **
20: *************************************************************************/
21:
22: #include "outline.h"
23:
24: OLEDBGDATA
25:
26: extern LPOUTLINEAPP g_lpApp;
27:
28:
29: #if defined( USE_DRAGDROP )
30:
31: /* OleDoc_QueryDrag
32: * ----------------
33: * Check to see if Drag operation should be initiated. A Drag operation
34: * should be initiated when the mouse in either the top 10 pixels of the
35: * selected list box entry or in the bottom 10 pixels of the last selected
36: * item.
37: */
38:
39: BOOL OleDoc_QueryDrag(LPOLEDOC lpOleDoc, int y)
40: {
41: LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
42: LINERANGE LineRange;
43:
44: if ( LineList_GetSel( lpLL, (LPLINERANGE)&LineRange) ) {
45: RECT rect;
46:
47: if (!LineList_GetLineRect(lpLL,LineRange.m_nStartLine,(LPRECT)&rect))
48: return FALSE ;
49:
50: if ( rect.top <= y && y <= rect.top + DD_SEL_THRESH )
51: return TRUE;
52:
53: LineList_GetLineRect( lpLL, LineRange.m_nEndLine, (LPRECT)&rect );
54: if ( rect.bottom >= y && y >= rect.bottom - DD_SEL_THRESH )
55: return TRUE;
56:
57: }
58:
59: return FALSE;
60: }
61:
62: /* OleDoc_DoDragScroll
63: * -------------------
64: * Check to see if Drag scroll operation should be initiated. A Drag scroll
65: * operation should be initiated when the mouse has remained in the active
66: * scroll area (11 pixels frame around border of window) for a specified
67: * amount of time (50ms).
68: */
69:
70: BOOL OleDoc_DoDragScroll(LPOLEDOC lpOleDoc, POINTL pointl)
71: {
72: LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
73: LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
74: HWND hWndListBox = LineList_GetWindow(lpLL);
75: DWORD dwScrollDir = SCROLLDIR_NULL;
76: DWORD dwTime = GetCurrentTime();
77: POINT point;
78: RECT rect;
79:
80: if ( lpLL->m_nNumLines == 0 )
81: return FALSE;
82:
83: point.x = (int)pointl.x;
84: point.y = (int)pointl.y;
85:
86: ScreenToClient( hWndListBox, &point);
87: GetClientRect ( hWndListBox, (LPRECT) &rect );
88:
89: if (rect.top <= point.y && point.y<=(rect.top+lpOleApp->m_nScrollInset))
90: dwScrollDir = SCROLLDIR_UP;
91: else if ( (rect.bottom - lpOleApp->m_nScrollInset) <= point.y &&
92: point.y <= rect.bottom )
93: dwScrollDir = SCROLLDIR_DOWN;
94:
95: if (lpOleDoc->m_dwTimeEnterScrollArea) {
96:
97: /* cursor was already in Scroll Area */
98:
99: if (! dwScrollDir) {
100: /* cusor moved OUT of scroll area.
101: ** clear "EnterScrollArea" time.
102: */
103: lpOleDoc->m_dwTimeEnterScrollArea = 0;
104: lpOleDoc->m_lastdwScrollDir = SCROLLDIR_NULL;
105: } else if (dwScrollDir != lpOleDoc->m_lastdwScrollDir) {
106: /* cusor moved into a different direction scroll area.
107: ** reset "EnterScrollArea" time to start a new 50ms delay.
108: */
109: lpOleDoc->m_dwTimeEnterScrollArea = dwTime;
110: lpOleDoc->m_lastdwScrollDir = dwScrollDir;
111: } else if ((dwTime - lpOleDoc->m_dwTimeEnterScrollArea)
112: >= (DWORD)lpOleApp->m_nScrollDelay) {
113:
114: LineList_Scroll ( lpLL, dwScrollDir );
115: }
116: } else {
117: if (dwScrollDir) {
118: /* cusor moved INTO a scroll area.
119: ** reset "EnterScrollArea" time to start a new 50ms delay.
120: */
121: lpOleDoc->m_dwTimeEnterScrollArea = dwTime;
122: lpOleDoc->m_lastdwScrollDir = dwScrollDir;
123: }
124: }
125:
126: return (dwScrollDir ? TRUE : FALSE);
127: }
128:
129:
130: /* OleDoc_QueryDrop
131: ** ----------------
132: ** Check if the desired drop operation (identified by the given key
133: ** state) is possible at the current mouse position (pointl).
134: */
135: BOOL OleDoc_QueryDrop (
136: LPOLEDOC lpOleDoc,
137: DWORD grfKeyState,
138: POINTL pointl,
139: BOOL fDragScroll,
140: LPDWORD lpdwEffect
141: )
142: {
143: LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
144: LINERANGE linerange;
145: short nIndex = LineList_GetLineIndexFromPointl( lpLL, pointl );
146: DWORD dwScrollEffect = 0L;
147:
148: /* check if the cursor is in the active scroll area, if so need the
149: ** special scroll cursor.
150: */
151: if (fDragScroll)
152: dwScrollEffect = DROPEFFECT_SCROLL;
153:
154: /* if we have already determined that the source does NOT have any
155: ** acceptable data for us, the return NO-DROP
156: */
157: if (! lpOleDoc->m_fCanDropCopy && ! lpOleDoc->m_fCanDropLink)
158: goto dropeffect_none;
159:
160: /* if the Drag/Drop is local to our document, we can NOT accept a
161: ** drop in the middle of the current selection (which is the exact
162: ** data that is being dragged!).
163: */
164: if (lpOleDoc->m_fLocalDrag) {
165: LineList_GetSel( lpLL, (LPLINERANGE)&linerange );
166:
167: if (linerange.m_nStartLine <= nIndex && nIndex<linerange.m_nEndLine)
168: goto dropeffect_none;
169: }
170:
171: /* OLE2NOTE: determine what type of drop should be performed given
172: ** the current modifier key state. we rely on the standard
173: ** interpretation of the modifier keys:
174: ** no modifier -- DROPEFFECT_MOVE
175: ** CTRL -- DROPEFFECT_COPY
176: ** CTRL-SHIFT -- DROPEFFECT_LINK
177: */
178: *lpdwEffect = OleStdGetDropEffect(grfKeyState);
179: if (*lpdwEffect == 0)
180: *lpdwEffect = DROPEFFECT_MOVE; // no modifier given => MOVE
181:
182: if ( (*lpdwEffect == DROPEFFECT_COPY || *lpdwEffect == DROPEFFECT_MOVE)
183: && ! lpOleDoc->m_fCanDropCopy )
184: goto dropeffect_none;
185:
186: if ( *lpdwEffect == DROPEFFECT_LINK && ! lpOleDoc->m_fCanDropLink )
187: goto dropeffect_none;
188:
189: *lpdwEffect |= dwScrollEffect;
190: return TRUE;
191:
192: dropeffect_none:
193:
194: *lpdwEffect = DROPEFFECT_NONE;
195: return FALSE;
196: }
197:
198: /* OleDoc_DoDragDrop
199: * -----------------
200: * Actually perform a drag/drop operation with the current selection in
201: * the source document (lpSrcOleDoc).
202: *
203: * returns the result effect of the drag/drop operation:
204: * DROPEFFECT_NONE,
205: * DROPEFFECT_COPY,
206: * DROPEFFECT_MOVE, or
207: * DROPEFFECT_LINK
208: */
209:
210: DWORD OleDoc_DoDragDrop (LPOLEDOC lpSrcOleDoc)
211: {
212: LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
213: LPOUTLINEDOC lpSrcOutlineDoc = (LPOUTLINEDOC)lpSrcOleDoc;
214: LPOLEDOC lpDragDoc;
215: LPLINELIST lpSrcLL =
216: (LPLINELIST)&((LPOUTLINEDOC)lpSrcOleDoc)->m_LineList;
217: DWORD dwEffect = 0;
218: LPLINE lplineStart, lplineEnd;
219: LINERANGE linerange;
220:
221: OLEDBG_BEGIN3("OleDoc_DoDragDrop\r\n")
222:
223: /* squirrel away a copy of the current selection to the ClipboardDoc */
224: lpDragDoc = (LPOLEDOC)OutlineDoc_CreateDataTransferDoc(lpSrcOutlineDoc);
225: if ( ! lpDragDoc) {
226: dwEffect = DROPEFFECT_NONE;
227: goto error;
228: }
229:
230: /* OLE2NOTE: initially the Doc object is created with a 0 ref
231: ** count. in order to have a stable Doc object during the
232: ** process of initializing the Doc instance and performing the
233: ** drag operation, we intially AddRef the Doc ref cnt and later
234: ** Release it. This initial AddRef is artificial; it is simply
235: ** done to guarantee that a harmless QueryInterface followed by
236: ** a Release does not inadvertantly force our object to destroy
237: ** itself prematurely.
238: */
239: OleDoc_AddRef(lpDragDoc);
240:
241: //NOTE: we need to keep the LPLINE pointers
242: // rather than the indexes because the
243: // indexes will not be the same after the
244: // drop occurs -- the drop adds new
245: // entries to the list thereby shifting
246: // the whole list.
247: LineList_GetSel( lpSrcLL, (LPLINERANGE)&linerange );
248: lplineStart = LineList_GetLine ( lpSrcLL, linerange.m_nStartLine );
249: lplineEnd = LineList_GetLine ( lpSrcLL, linerange.m_nEndLine );
250:
251: lpSrcOleDoc->m_fLocalDrop = FALSE;
252: lpSrcOleDoc->m_fLocalDrag = TRUE;
253:
254: OLEDBG_BEGIN2("DoDragDrop called\r\n")
255: DoDragDrop ( (LPDATAOBJECT) &lpDragDoc->m_DataObject,
256: (LPDROPSOURCE) &lpSrcOleDoc->m_DropSource,
257: DROPEFFECT_MOVE | DROPEFFECT_COPY,
258: (LPDWORD) &dwEffect
259: );
260: OLEDBG_END2
261:
262: lpSrcOleDoc->m_fLocalDrag = FALSE;
263:
264: /* if after the Drag/Drop modal (mouse capture) loop is finished
265: ** and a drag MOVE operation was performed, then we must delete
266: ** the lines that were dragged.
267: */
268: if ( (dwEffect & DROPEFFECT_MOVE) != 0 ) {
269:
270: int i,j,iEnd;
271: LPLINE lplineFocusLine;
272:
273: /* disable repainting and sending data changed notifications
274: ** until after all lines have been deleted.
275: */
276: OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpSrcOleDoc, FALSE );
277:
278: /* if the drop was local to our document, then we must take
279: ** into account that the line indices of the original source
280: ** of the drag could have shifted because the dropped lines
281: ** have been inserted into our document. thus we will
282: ** re-determine the source line indices.
283: */
284: if (lpSrcOleDoc->m_fLocalDrop) {
285: i = LineList_GetFocusLineIndex ( lpSrcLL );
286: lplineFocusLine = LineList_GetLine ( lpSrcLL, i );
287: }
288:
289: for ( i = j = LineList_GetLineIndex(lpSrcLL,lplineStart ) ,
290: iEnd = LineList_GetLineIndex(lpSrcLL,lplineEnd ) ;
291: i <= iEnd ;
292: i++
293: ) OutlineDoc_DeleteLine ((LPOUTLINEDOC)lpSrcOleDoc, j );
294:
295: LineList_RecalcMaxLineWidthInHimetric(lpSrcLL, 0);
296:
297: if (lpSrcOleDoc->m_fLocalDrop) {
298: i = LineList_GetLineIndex ( lpSrcLL, lplineFocusLine );
299: LineList_SetFocusLine ( lpSrcLL, (WORD)i );
300: }
301:
302: OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpSrcOleDoc, TRUE );
303:
304: /* if it is a local Drag/Drop move, we need to balance the
305: ** SetRedraw(FALSE) call that was made in the implementation
306: ** of IDropTarget::Drop.
307: */
308: if (lpSrcOleDoc->m_fLocalDrop)
309: OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpSrcOleDoc, TRUE );
310:
311: LineList_ForceRedraw ( lpSrcLL, FALSE );
312: }
313:
314: OleDoc_Release(lpDragDoc); // rel artificial AddRef above
315:
316: OLEDBG_END3
317: return dwEffect;
318:
319: error:
320: OLEDBG_END3
321: return dwEffect;
322: }
323:
324:
325:
326: /*************************************************************************
327: ** OleDoc::IDropSource interface implementation
328: *************************************************************************/
329:
330: STDMETHODIMP OleDoc_DropSource_QueryInterface(
331: LPDROPSOURCE lpThis,
332: REFIID riid,
333: LPVOID FAR* lplpvObj
334: )
335: {
336: LPOLEDOC lpOleDoc = ((struct CDocDropSourceImpl FAR*)lpThis)->lpOleDoc;
337:
338: return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
339: }
340:
341:
342: STDMETHODIMP_(ULONG) OleDoc_DropSource_AddRef( LPDROPSOURCE lpThis )
343: {
344: LPOLEDOC lpOleDoc = ((struct CDocDropSourceImpl FAR*)lpThis)->lpOleDoc;
345:
346: OleDbgAddRefMethod(lpThis, "IDropSource");
347:
348: return OleDoc_AddRef(lpOleDoc);
349: }
350:
351:
352: STDMETHODIMP_(ULONG) OleDoc_DropSource_Release ( LPDROPSOURCE lpThis)
353: {
354: LPOLEDOC lpOleDoc = ((struct CDocDropSourceImpl FAR*)lpThis)->lpOleDoc;
355:
356: OleDbgReleaseMethod(lpThis, "IDropSource");
357:
358: return OleDoc_Release(lpOleDoc);
359: }
360:
361:
362: STDMETHODIMP OleDoc_DropSource_QueryContinueDrag (
363: LPDROPSOURCE lpThis,
364: BOOL fEscapePressed,
365: DWORD grfKeyState
366: ){
367: if (fEscapePressed)
368: return ResultFromScode(DRAGDROP_S_CANCEL);
369: else if (!(grfKeyState & MK_LBUTTON))
370: return ResultFromScode(DRAGDROP_S_DROP);
371: else
372: return NOERROR;
373: }
374:
375:
376: STDMETHODIMP OleDoc_DropSource_GiveFeedback (
377: LPDROPSOURCE lpThis,
378: DWORD dwEffect
379: )
380: {
381: // Tell OLE to use the standard drag/drop feedback cursors
382: return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
383:
384: #if defined( IF_SPECIAL_DD_CURSORS_NEEDED )
385: switch (dwEffect) {
386:
387: case DROPEFFECT_NONE:
388: SetCursor ( ((LPOLEAPP)g_lpApp)->m_hcursorDragNone );
389: break;
390:
391: case DROPEFFECT_COPY:
392: SetCursor ( ((LPOLEAPP)g_lpApp)->m_hcursorDragCopy );
393: break;
394:
395: case DROPEFFECT_MOVE:
396: SetCursor ( ((LPOLEAPP)g_lpApp)->m_hcursorDragMove );
397: break;
398:
399: case DROPEFFECT_LINK:
400: SetCursor ( ((LPOLEAPP)g_lpApp)->m_hcursorDragLink );
401: break;
402:
403: }
404:
405: return NOERROR;
406: #endif
407:
408: }
409:
410: /*************************************************************************
411: ** OleDoc::IDropTarget interface implementation
412: *************************************************************************/
413:
414: STDMETHODIMP OleDoc_DropTarget_QueryInterface(
415: LPDROPTARGET lpThis,
416: REFIID riid,
417: LPVOID FAR* lplpvObj
418: )
419: {
420: LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
421:
422: return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
423: }
424:
425:
426: STDMETHODIMP_(ULONG) OleDoc_DropTarget_AddRef(LPDROPTARGET lpThis)
427: {
428: LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
429:
430: OleDbgAddRefMethod(lpThis, "IDropTarget");
431:
432: return OleDoc_AddRef(lpOleDoc);
433: }
434:
435:
436: STDMETHODIMP_(ULONG) OleDoc_DropTarget_Release ( LPDROPTARGET lpThis)
437: {
438: LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
439:
440: OleDbgReleaseMethod(lpThis, "IDropTarget");
441:
442: return OleDoc_Release(lpOleDoc);
443: }
444:
445:
446: STDMETHODIMP OleDoc_DropTarget_DragEnter (
447: LPDROPTARGET lpThis,
448: LPDATAOBJECT lpDataObj,
449: DWORD grfKeyState,
450: POINTL pointl,
451: LPDWORD lpdwEffect
452: )
453: {
454: LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
455: LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
456: LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
457: BOOL fDragScroll;
458:
459: OLEDBG_BEGIN2("OleDoc_DropTarget_DragEnter\r\n")
460:
461: lpOleDoc->m_fDragLeave = FALSE;
462: lpOleDoc->m_dwTimeEnterScrollArea = 0;
463: lpOleDoc->m_lastdwScrollDir = SCROLLDIR_NULL;
464:
465:
466: /* Determine if the drag source data object offers a data format
467: ** that we can copy and/or link to.
468: */
469:
470: lpOleDoc->m_fCanDropCopy = OleDoc_QueryPasteFromData(
471: lpOleDoc,
472: lpDataObj,
473: FALSE /* fLink */
474: );
475:
476: lpOleDoc->m_fCanDropLink = OleDoc_QueryPasteFromData(
477: lpOleDoc,
478: lpDataObj,
479: TRUE /* fLink */
480: );
481:
482: fDragScroll = OleDoc_DoDragScroll ( lpOleDoc, pointl );
483:
484: if (OleDoc_QueryDrop(lpOleDoc,grfKeyState,pointl,fDragScroll,lpdwEffect))
485: LineList_SetDragOverLineFromPointl( lpLL, pointl );
486:
487: OLEDBG_END2
488: return NOERROR;
489:
490: }
491:
492: STDMETHODIMP OleDoc_DropTarget_DragOver (
493: LPDROPTARGET lpThis,
494: DWORD grfKeyState,
495: POINTL pointl,
496: LPDWORD lpdwEffect
497: )
498: {
499: LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
500: LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
501: LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
502: BOOL fDragScroll;
503:
504: fDragScroll = OleDoc_DoDragScroll ( lpOleDoc, pointl );
505:
506: if (OleDoc_QueryDrop(lpOleDoc,grfKeyState, pointl,fDragScroll,lpdwEffect))
507: LineList_SetDragOverLineFromPointl( lpLL, pointl );
508: else
509: LineList_RestoreDragFeedback( lpLL );
510:
511: return NOERROR;
512: }
513:
514:
515: STDMETHODIMP OleDoc_DropTarget_DragLeave ( LPDROPTARGET lpThis)
516: {
517: LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
518: LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
519:
520: OLEDBG_BEGIN2("OleDoc_DropTarget_DragLeave\r\n")
521:
522: lpOleDoc->m_fDragLeave = TRUE;
523:
524: LineList_RestoreDragFeedback( lpLL );
525:
526: OLEDBG_END2
527: return NOERROR;
528:
529: }
530:
531: STDMETHODIMP OleDoc_DropTarget_Drop (
532: LPDROPTARGET lpThis,
533: LPDATAOBJECT lpDataObj,
534: DWORD grfKeyState,
535: POINTL pointl,
536: LPDWORD lpdwEffect
537: )
538: {
539: LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
540: LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
541:
542: OLEDBG_BEGIN2("OleDoc_DropTarget_Drop\r\n")
543:
544: lpOleDoc->m_fDragLeave = TRUE;
545: lpOleDoc->m_fLocalDrop = TRUE;
546:
547: LineList_RestoreDragFeedback( lpLL );
548: SetFocus( LineList_GetWindow( lpLL) );
549:
550: if (OleDoc_QueryDrop(lpOleDoc, grfKeyState, pointl, FALSE, lpdwEffect)) {
551: BOOL fLink = (*lpdwEffect == DROPEFFECT_LINK);
552: int iFocusLine = LineList_GetFocusLineIndex( lpLL );
553: BOOL fStatus;
554:
555: OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpOleDoc, FALSE );
556: LineList_SetFocusLineFromPointl ( lpLL, pointl );
557:
558: fStatus = OleDoc_PasteFromData(
559: lpOleDoc,
560: lpDataObj,
561: lpOleDoc->m_fLocalDrag, /* data source is local to app */
562: fLink
563: );
564:
565: // if drop was unsuccessfull, restore the original focus line
566: if (! fStatus)
567: LineList_SetFocusLine( lpLL, (WORD)iFocusLine );
568:
569: #if defined( INPLACE_CNTR )
570: {
571: LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;
572:
573: /* OLE2NOTE: if there is currently a UIActive OLE object,
574: ** then we must tell it to UIDeactivate after
575: ** the drop has completed.
576: */
577: if (lpContainerDoc->m_lpLastUIActiveLine) {
578: ContainerLine_UIDeactivate(
579: lpContainerDoc->m_lpLastUIActiveLine);
580: }
581: }
582: #endif
583:
584: #if defined( INPLACE_SVR )
585: {
586: /* OLE2NOTE: if the drop was into a in-place visible
587: ** (in-place active but NOT UIActive object), then we
588: ** want to UIActivate the object after the drop is
589: ** complete.
590: */
591: ServerDoc_UIActivate((LPSERVERDOC) lpOleDoc);
592: }
593: #endif
594:
595:
596: /* if it is a local Drag/Drop move, don't enable redraw.
597: ** after the source is done deleting the moved lines, it
598: ** will re-enable redraw
599: */
600: if (! (lpOleDoc->m_fLocalDrag
601: && (*lpdwEffect & DROPEFFECT_MOVE) != 0 ))
602: OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpOleDoc, TRUE );
603: }
604:
605: OLEDBG_END2
606: return NOERROR;
607: }
608:
609:
610: #endif // USE_DRAGDROP
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.