source: trunk/src/ole32/oleobj.c@ 6666

Last change on this file since 6666 was 6648, checked in by bird, 24 years ago

Added $Id:$ keyword.

File size: 19.2 KB
Line 
1/* $Id: oleobj.c,v 1.2 2001-09-05 13:17:11 bird Exp $ */
2/*
3 * OLE2 COM objects
4 *
5 * Copyright 1998 Eric Kohl
6 * Copyright 1999 Francis Beaudet
7 */
8
9
10#include <string.h>
11#include "winbase.h"
12#include "winerror.h"
13#include "debugtools.h"
14#include "oleidl.h"
15
16DEFAULT_DEBUG_CHANNEL(ole);
17
18#define INITIAL_SINKS 10
19
20/**************************************************************************
21 * OleAdviseHolderImpl Implementation
22 */
23typedef struct OleAdviseHolderImpl
24{
25 ICOM_VFIELD(IOleAdviseHolder);
26
27 DWORD ref;
28
29 DWORD maxSinks;
30 IAdviseSink** arrayOfSinks;
31
32} OleAdviseHolderImpl;
33
34static LPOLEADVISEHOLDER OleAdviseHolderImpl_Constructor();
35static void OleAdviseHolderImpl_Destructor(OleAdviseHolderImpl* ptrToDestroy);
36static HRESULT WINAPI OleAdviseHolderImpl_QueryInterface(LPOLEADVISEHOLDER,REFIID,LPVOID*);
37static ULONG WINAPI OleAdviseHolderImpl_AddRef(LPOLEADVISEHOLDER);
38static ULONG WINAPI OleAdviseHolderImpl_Release(LPOLEADVISEHOLDER);
39static HRESULT WINAPI OleAdviseHolderImpl_Advise(LPOLEADVISEHOLDER, IAdviseSink*, DWORD*);
40static HRESULT WINAPI OleAdviseHolderImpl_Unadvise (LPOLEADVISEHOLDER, DWORD);
41static HRESULT WINAPI OleAdviseHolderImpl_EnumAdvise (LPOLEADVISEHOLDER, IEnumSTATDATA **);
42static HRESULT WINAPI OleAdviseHolderImpl_SendOnRename (LPOLEADVISEHOLDER, IMoniker *);
43static HRESULT WINAPI OleAdviseHolderImpl_SendOnSave (LPOLEADVISEHOLDER);
44static HRESULT WINAPI OleAdviseHolderImpl_SendOnClose (LPOLEADVISEHOLDER);
45
46
47/**************************************************************************
48 * OleAdviseHolderImpl_VTable
49 */
50static struct ICOM_VTABLE(IOleAdviseHolder) oahvt =
51{
52 ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
53 OleAdviseHolderImpl_QueryInterface,
54 OleAdviseHolderImpl_AddRef,
55 OleAdviseHolderImpl_Release,
56 OleAdviseHolderImpl_Advise,
57 OleAdviseHolderImpl_Unadvise,
58 OleAdviseHolderImpl_EnumAdvise,
59 OleAdviseHolderImpl_SendOnRename,
60 OleAdviseHolderImpl_SendOnSave,
61 OleAdviseHolderImpl_SendOnClose
62};
63
64/**************************************************************************
65 * OleAdviseHolderImpl_Constructor
66 */
67
68static LPOLEADVISEHOLDER OleAdviseHolderImpl_Constructor()
69{
70 OleAdviseHolderImpl* lpoah;
71 DWORD index;
72
73 lpoah= (OleAdviseHolderImpl*)HeapAlloc(GetProcessHeap(),
74 0,
75 sizeof(OleAdviseHolderImpl));
76
77 ICOM_VTBL(lpoah) = &oahvt;
78 lpoah->ref = 1;
79 lpoah->maxSinks = INITIAL_SINKS;
80 lpoah->arrayOfSinks = HeapAlloc(GetProcessHeap(),
81 0,
82 lpoah->maxSinks * sizeof(IAdviseSink*));
83
84 for (index = 0; index < lpoah->maxSinks; index++)
85 lpoah->arrayOfSinks[index]=0;
86
87 TRACE("returning %p\n", lpoah);
88 return (LPOLEADVISEHOLDER)lpoah;
89}
90
91/**************************************************************************
92 * OleAdviseHolderImpl_Destructor
93 */
94static void OleAdviseHolderImpl_Destructor(
95 OleAdviseHolderImpl* ptrToDestroy)
96{
97 DWORD index;
98 TRACE("%p\n", ptrToDestroy);
99
100 for (index = 0; index < ptrToDestroy->maxSinks; index++)
101 {
102 if (ptrToDestroy->arrayOfSinks[index]!=0)
103 {
104 IAdviseSink_Release(ptrToDestroy->arrayOfSinks[index]);
105 ptrToDestroy->arrayOfSinks[index] = NULL;
106 }
107 }
108
109 HeapFree(GetProcessHeap(),
110 0,
111 ptrToDestroy->arrayOfSinks);
112
113
114 HeapFree(GetProcessHeap(),
115 0,
116 ptrToDestroy);
117}
118
119/**************************************************************************
120 * OleAdviseHolderImpl_QueryInterface
121 */
122static HRESULT WINAPI OleAdviseHolderImpl_QueryInterface(
123 LPOLEADVISEHOLDER iface,
124 REFIID riid,
125 LPVOID* ppvObj)
126{
127 ICOM_THIS(OleAdviseHolderImpl, iface);
128 TRACE("(%p)->(%s,%p)\n",This,debugstr_guid(riid),ppvObj);
129 /*
130 * Sanity check
131 */
132 if (ppvObj==NULL)
133 return E_POINTER;
134
135 *ppvObj = NULL;
136
137 if (IsEqualIID(riid, &IID_IUnknown))
138 {
139 /* IUnknown */
140 *ppvObj = This;
141 }
142 else if(IsEqualIID(riid, &IID_IOleAdviseHolder))
143 {
144 /* IOleAdviseHolder */
145 *ppvObj = (IOleAdviseHolder*) This;
146 }
147
148 if(*ppvObj == NULL)
149 return E_NOINTERFACE;
150
151 /*
152 * A successful QI always increments the reference count.
153 */
154 IUnknown_AddRef((IUnknown*)*ppvObj);
155
156 return S_OK;
157}
158
159/******************************************************************************
160 * OleAdviseHolderImpl_AddRef
161 */
162static ULONG WINAPI OleAdviseHolderImpl_AddRef(
163 LPOLEADVISEHOLDER iface)
164{
165 ICOM_THIS(OleAdviseHolderImpl, iface);
166 TRACE("(%p)->(ref=%ld)\n", This, This->ref);
167 return ++(This->ref);
168}
169
170/******************************************************************************
171 * OleAdviseHolderImpl_Release
172 */
173static ULONG WINAPI OleAdviseHolderImpl_Release(
174 LPOLEADVISEHOLDER iface)
175{
176 ICOM_THIS(OleAdviseHolderImpl, iface);
177 TRACE("(%p)->(ref=%ld)\n", This, This->ref);
178 This->ref--;
179
180 if (This->ref == 0)
181 {
182 OleAdviseHolderImpl_Destructor(This);
183
184 return 0;
185 }
186
187 return This->ref;
188}
189
190/******************************************************************************
191 * OleAdviseHolderImpl_Advise
192 */
193static HRESULT WINAPI OleAdviseHolderImpl_Advise(
194 LPOLEADVISEHOLDER iface,
195 IAdviseSink* pAdvise,
196 DWORD* pdwConnection)
197{
198 DWORD index;
199
200 ICOM_THIS(OleAdviseHolderImpl, iface);
201
202 TRACE("(%p)->(%p, %p)\n", This, pAdvise, pdwConnection);
203
204 /*
205 * Sanity check
206 */
207 if (pdwConnection==NULL)
208 return E_POINTER;
209
210 *pdwConnection = 0;
211
212 /*
213 * Find a free spot in the array.
214 */
215 for (index = 0; index < This->maxSinks; index++)
216 {
217 if (This->arrayOfSinks[index]==NULL)
218 break;
219 }
220
221 /*
222 * If the array is full, we need to grow it.
223 */
224 if (index == This->maxSinks)
225 {
226 DWORD i;
227
228 This->maxSinks+=INITIAL_SINKS;
229
230 This->arrayOfSinks = HeapReAlloc(GetProcessHeap(),
231 0,
232 This->arrayOfSinks,
233 This->maxSinks*sizeof(IAdviseSink*));
234
235 for (i=index;i < This->maxSinks; i++)
236 This->arrayOfSinks[i]=0;
237 }
238
239 /*
240 * Store the new sink
241 */
242 This->arrayOfSinks[index] = pAdvise;
243
244 if (This->arrayOfSinks[index]!=NULL)
245 IAdviseSink_AddRef(This->arrayOfSinks[index]);
246
247 /*
248 * Return the index as the cookie.
249 * Since 0 is not a valid cookie, we will increment by
250 * 1 the index in the table.
251 */
252 *pdwConnection = index+1;
253
254 return S_OK;
255}
256
257/******************************************************************************
258 * OleAdviseHolderImpl_Unadvise
259 */
260static HRESULT WINAPI OleAdviseHolderImpl_Unadvise(
261 LPOLEADVISEHOLDER iface,
262 DWORD dwConnection)
263{
264 ICOM_THIS(OleAdviseHolderImpl, iface);
265
266 TRACE("(%p)->(%lu)\n", This, dwConnection);
267
268 /*
269 * So we don't return 0 as a cookie, the index was
270 * incremented by 1 in OleAdviseHolderImpl_Advise
271 * we have to compensate.
272 */
273 dwConnection--;
274
275 /*
276 * Check for invalid cookies.
277 */
278 if ( (dwConnection < 0) ||
279 (dwConnection >= This->maxSinks) )
280 return OLE_E_NOCONNECTION;
281
282 if (This->arrayOfSinks[dwConnection] == NULL)
283 return OLE_E_NOCONNECTION;
284
285 /*
286 * Release the sink and mark the spot in the list as free.
287 */
288 IAdviseSink_Release(This->arrayOfSinks[dwConnection]);
289 This->arrayOfSinks[dwConnection] = NULL;
290
291 return S_OK;
292}
293
294/******************************************************************************
295 * OleAdviseHolderImpl_EnumAdvise
296 */
297static HRESULT WINAPI
298OleAdviseHolderImpl_EnumAdvise (LPOLEADVISEHOLDER iface, IEnumSTATDATA **ppenumAdvise)
299{
300 ICOM_THIS(OleAdviseHolderImpl, iface);
301 FIXME("(%p)->(%p)\n", This, ppenumAdvise);
302
303 *ppenumAdvise = NULL;
304
305 return S_OK;
306}
307
308/******************************************************************************
309 * OleAdviseHolderImpl_SendOnRename
310 */
311static HRESULT WINAPI
312OleAdviseHolderImpl_SendOnRename (LPOLEADVISEHOLDER iface, IMoniker *pmk)
313{
314 ICOM_THIS(OleAdviseHolderImpl, iface);
315 FIXME("(%p)->(%p)\n", This, pmk);
316
317
318 return S_OK;
319}
320
321/******************************************************************************
322 * OleAdviseHolderImpl_SendOnSave
323 */
324static HRESULT WINAPI
325OleAdviseHolderImpl_SendOnSave (LPOLEADVISEHOLDER iface)
326{
327 ICOM_THIS(OleAdviseHolderImpl, iface);
328 FIXME("(%p)\n", This);
329
330 return S_OK;
331}
332
333/******************************************************************************
334 * OleAdviseHolderImpl_SendOnClose
335 */
336static HRESULT WINAPI
337OleAdviseHolderImpl_SendOnClose (LPOLEADVISEHOLDER iface)
338{
339 ICOM_THIS(OleAdviseHolderImpl, iface);
340 FIXME("(%p)\n", This);
341
342
343 return S_OK;
344}
345
346/**************************************************************************
347 * DataAdviseHolder Implementation
348 */
349typedef struct DataAdviseConnection {
350 IAdviseSink *sink;
351 FORMATETC fmat;
352 DWORD advf;
353} DataAdviseConnection;
354
355typedef struct DataAdviseHolder
356{
357 ICOM_VFIELD(IDataAdviseHolder);
358
359 DWORD ref;
360 DWORD maxCons;
361 DataAdviseConnection* Connections;
362} DataAdviseHolder;
363
364/**************************************************************************
365 * DataAdviseHolder method prototypes
366 */
367static IDataAdviseHolder* DataAdviseHolder_Constructor();
368static void DataAdviseHolder_Destructor(DataAdviseHolder* ptrToDestroy);
369static HRESULT WINAPI DataAdviseHolder_QueryInterface(
370 IDataAdviseHolder* iface,
371 REFIID riid,
372 void** ppvObject);
373static ULONG WINAPI DataAdviseHolder_AddRef(
374 IDataAdviseHolder* iface);
375static ULONG WINAPI DataAdviseHolder_Release(
376 IDataAdviseHolder* iface);
377static HRESULT WINAPI DataAdviseHolder_Advise(
378 IDataAdviseHolder* iface,
379 IDataObject* pDataObject,
380 FORMATETC* pFetc,
381 DWORD advf,
382 IAdviseSink* pAdvise,
383 DWORD* pdwConnection);
384static HRESULT WINAPI DataAdviseHolder_Unadvise(
385 IDataAdviseHolder* iface,
386 DWORD dwConnection);
387static HRESULT WINAPI DataAdviseHolder_EnumAdvise(
388 IDataAdviseHolder* iface,
389 IEnumSTATDATA** ppenumAdvise);
390static HRESULT WINAPI DataAdviseHolder_SendOnDataChange(
391 IDataAdviseHolder* iface,
392 IDataObject* pDataObject,
393 DWORD dwReserved,
394 DWORD advf);
395
396/**************************************************************************
397 * DataAdviseHolderImpl_VTable
398 */
399static struct ICOM_VTABLE(IDataAdviseHolder) DataAdviseHolderImpl_VTable =
400{
401 ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
402 DataAdviseHolder_QueryInterface,
403 DataAdviseHolder_AddRef,
404 DataAdviseHolder_Release,
405 DataAdviseHolder_Advise,
406 DataAdviseHolder_Unadvise,
407 DataAdviseHolder_EnumAdvise,
408 DataAdviseHolder_SendOnDataChange
409};
410
411/******************************************************************************
412 * DataAdviseHolder_Constructor
413 */
414static IDataAdviseHolder* DataAdviseHolder_Constructor()
415{
416 DataAdviseHolder* newHolder;
417
418 newHolder = (DataAdviseHolder*)HeapAlloc(GetProcessHeap(),
419 0,
420 sizeof(DataAdviseHolder));
421
422 ICOM_VTBL(newHolder) = &DataAdviseHolderImpl_VTable;
423 newHolder->ref = 1;
424 newHolder->maxCons = INITIAL_SINKS;
425 newHolder->Connections = HeapAlloc(GetProcessHeap(),
426 HEAP_ZERO_MEMORY,
427 newHolder->maxCons *
428 sizeof(DataAdviseConnection));
429
430 TRACE("returning %p\n", newHolder);
431 return (IDataAdviseHolder*)newHolder;
432}
433
434/******************************************************************************
435 * DataAdviseHolder_Destructor
436 */
437static void DataAdviseHolder_Destructor(DataAdviseHolder* ptrToDestroy)
438{
439 DWORD index;
440 TRACE("%p\n", ptrToDestroy);
441
442 for (index = 0; index < ptrToDestroy->maxCons; index++)
443 {
444 if (ptrToDestroy->Connections[index].sink != NULL)
445 {
446 IAdviseSink_Release(ptrToDestroy->Connections[index].sink);
447 ptrToDestroy->Connections[index].sink = NULL;
448 }
449 }
450
451 HeapFree(GetProcessHeap(), 0, ptrToDestroy->Connections);
452 HeapFree(GetProcessHeap(), 0, ptrToDestroy);
453}
454
455/************************************************************************
456 * DataAdviseHolder_QueryInterface (IUnknown)
457 *
458 * See Windows documentation for more details on IUnknown methods.
459 */
460static HRESULT WINAPI DataAdviseHolder_QueryInterface(
461 IDataAdviseHolder* iface,
462 REFIID riid,
463 void** ppvObject)
464{
465 ICOM_THIS(DataAdviseHolder, iface);
466 TRACE("(%p)->(%s,%p)\n",This,debugstr_guid(riid),ppvObject);
467 /*
468 * Perform a sanity check on the parameters.
469 */
470 if ( (This==0) || (ppvObject==0) )
471 return E_INVALIDARG;
472
473 /*
474 * Initialize the return parameter.
475 */
476 *ppvObject = 0;
477
478 /*
479 * Compare the riid with the interface IDs implemented by this object.
480 */
481 if ( (memcmp(&IID_IUnknown, riid, sizeof(IID_IUnknown)) == 0) ||
482 (memcmp(&IID_IDataAdviseHolder, riid, sizeof(IID_IDataAdviseHolder)) == 0) )
483 {
484 *ppvObject = iface;
485 }
486
487 /*
488 * Check that we obtained an interface.
489 */
490 if ((*ppvObject)==0)
491 {
492 return E_NOINTERFACE;
493 }
494
495 /*
496 * Query Interface always increases the reference count by one when it is
497 * successful.
498 */
499 IUnknown_AddRef((IUnknown*)*ppvObject);
500
501 return S_OK;;
502}
503
504/************************************************************************
505 * DataAdviseHolder_AddRef (IUnknown)
506 *
507 * See Windows documentation for more details on IUnknown methods.
508 */
509static ULONG WINAPI DataAdviseHolder_AddRef(
510 IDataAdviseHolder* iface)
511{
512 ICOM_THIS(DataAdviseHolder, iface);
513 TRACE("(%p) (ref=%ld)\n", This, This->ref);
514 This->ref++;
515
516 return This->ref;
517}
518
519/************************************************************************
520 * DataAdviseHolder_Release (IUnknown)
521 *
522 * See Windows documentation for more details on IUnknown methods.
523 */
524static ULONG WINAPI DataAdviseHolder_Release(
525 IDataAdviseHolder* iface)
526{
527 ICOM_THIS(DataAdviseHolder, iface);
528 TRACE("(%p) (ref=%ld)\n", This, This->ref);
529
530 /*
531 * Decrease the reference count on this object.
532 */
533 This->ref--;
534
535 /*
536 * If the reference count goes down to 0, perform suicide.
537 */
538 if (This->ref==0)
539 {
540 DataAdviseHolder_Destructor(This);
541
542 return 0;
543 }
544
545 return This->ref;
546}
547
548/************************************************************************
549 * DataAdviseHolder_Advise
550 *
551 */
552static HRESULT WINAPI DataAdviseHolder_Advise(
553 IDataAdviseHolder* iface,
554 IDataObject* pDataObject,
555 FORMATETC* pFetc,
556 DWORD advf,
557 IAdviseSink* pAdvise,
558 DWORD* pdwConnection)
559{
560 DWORD index;
561
562 ICOM_THIS(DataAdviseHolder, iface);
563
564 TRACE("(%p)->(%p, %p, %08lx, %p, %p)\n", This, pDataObject, pFetc, advf,
565 pAdvise, pdwConnection);
566 /*
567 * Sanity check
568 */
569 if (pdwConnection==NULL)
570 return E_POINTER;
571
572 *pdwConnection = 0;
573
574 /*
575 * Find a free spot in the array.
576 */
577 for (index = 0; index < This->maxCons; index++)
578 {
579 if (This->Connections[index].sink == NULL)
580 break;
581 }
582
583 /*
584 * If the array is full, we need to grow it.
585 */
586 if (index == This->maxCons)
587 {
588 This->maxCons+=INITIAL_SINKS;
589 This->Connections = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
590 This->Connections,
591 This->maxCons*sizeof(DataAdviseConnection));
592 }
593 /*
594 * Store the new sink
595 */
596 This->Connections[index].sink = pAdvise;
597 memcpy(&(This->Connections[index].fmat), pFetc, sizeof(FORMATETC));
598 This->Connections[index].advf = advf;
599
600 if (This->Connections[index].sink != NULL) {
601 IAdviseSink_AddRef(This->Connections[index].sink);
602 if(advf & ADVF_PRIMEFIRST) {
603 DataAdviseHolder_SendOnDataChange(iface, pDataObject, 0, advf);
604 }
605 }
606 /*
607 * Return the index as the cookie.
608 * Since 0 is not a valid cookie, we will increment by
609 * 1 the index in the table.
610 */
611 *pdwConnection = index+1;
612
613 return S_OK;
614}
615
616/******************************************************************************
617 * DataAdviseHolder_Unadvise
618 */
619static HRESULT WINAPI DataAdviseHolder_Unadvise(
620 IDataAdviseHolder* iface,
621 DWORD dwConnection)
622{
623 ICOM_THIS(DataAdviseHolder, iface);
624
625 TRACE("(%p)->(%lu)\n", This, dwConnection);
626
627 /*
628 * So we don't return 0 as a cookie, the index was
629 * incremented by 1 in OleAdviseHolderImpl_Advise
630 * we have to compensate.
631 */
632 dwConnection--;
633
634 /*
635 * Check for invalid cookies.
636 */
637 if ( (dwConnection < 0) ||
638 (dwConnection >= This->maxCons) )
639 return OLE_E_NOCONNECTION;
640
641 if (This->Connections[dwConnection].sink == NULL)
642 return OLE_E_NOCONNECTION;
643
644 /*
645 * Release the sink and mark the spot in the list as free.
646 */
647 IAdviseSink_Release(This->Connections[dwConnection].sink);
648 memset(&(This->Connections[dwConnection]), 0, sizeof(DataAdviseConnection));
649 return S_OK;
650}
651
652static HRESULT WINAPI DataAdviseHolder_EnumAdvise(
653 IDataAdviseHolder* iface,
654 IEnumSTATDATA** ppenumAdvise)
655{
656 ICOM_THIS(DataAdviseHolder, iface);
657
658 FIXME("(%p)->(%p)\n", This, ppenumAdvise);
659 return E_NOTIMPL;
660}
661
662/******************************************************************************
663 * DataAdviseHolder_SendOnDataChange
664 */
665static HRESULT WINAPI DataAdviseHolder_SendOnDataChange(
666 IDataAdviseHolder* iface,
667 IDataObject* pDataObject,
668 DWORD dwReserved,
669 DWORD advf)
670{
671 ICOM_THIS(DataAdviseHolder, iface);
672 DWORD index;
673 STGMEDIUM stg;
674 HRESULT res;
675
676 TRACE("(%p)->(%p,%08lx,%08lx)\n", This, pDataObject, dwReserved, advf);
677
678 for(index = 0; index < This->maxCons; index++) {
679 if(This->Connections[index].sink != NULL) {
680 if(!(This->Connections[index].advf & ADVF_NODATA)) {
681 TRACE("Calling IDataObject_GetData\n");
682 res = IDataObject_GetData(pDataObject,
683 &(This->Connections[index].fmat),
684 &stg);
685 TRACE("returns %08lx\n", res);
686 }
687 TRACE("Calling IAdviseSink_OnDataChange\n");
688 IAdviseSink_OnDataChange(This->Connections[index].sink,
689 &(This->Connections[index].fmat),
690 &stg);
691 TRACE("Done IAdviseSink_OnDataChange\n");
692 if(This->Connections[index].advf & ADVF_ONLYONCE) {
693 TRACE("Removing connection\n");
694 DataAdviseHolder_Unadvise(iface, index+1);
695 }
696 }
697 }
698 return S_OK;
699}
700
701/***********************************************************************
702 * API functions
703 */
704
705/***********************************************************************
706 * CreateOleAdviseHolder [OLE32.59]
707 */
708HRESULT WINAPI CreateOleAdviseHolder(
709 LPOLEADVISEHOLDER *ppOAHolder)
710{
711 TRACE("(%p)\n", ppOAHolder);
712
713 /*
714 * Sanity check,
715 */
716 if (ppOAHolder==NULL)
717 return E_POINTER;
718
719 *ppOAHolder = OleAdviseHolderImpl_Constructor ();
720
721 if (*ppOAHolder != NULL)
722 return S_OK;
723
724 return E_OUTOFMEMORY;
725}
726
727/******************************************************************************
728 * CreateDataAdviseHolder [OLE32.53]
729 */
730HRESULT WINAPI CreateDataAdviseHolder(
731 LPDATAADVISEHOLDER* ppDAHolder)
732{
733 TRACE("(%p)\n", ppDAHolder);
734
735 /*
736 * Sanity check,
737 */
738 if (ppDAHolder==NULL)
739 return E_POINTER;
740
741 *ppDAHolder = DataAdviseHolder_Constructor();
742
743 if (*ppDAHolder != NULL)
744 return S_OK;
745
746 return E_OUTOFMEMORY;
747}
748
Note: See TracBrowser for help on using the repository browser.