aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/newview/llhoverview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra/newview/llhoverview.cpp')
-rw-r--r--linden/indra/newview/llhoverview.cpp801
1 files changed, 801 insertions, 0 deletions
diff --git a/linden/indra/newview/llhoverview.cpp b/linden/indra/newview/llhoverview.cpp
new file mode 100644
index 0000000..9c403f4
--- /dev/null
+++ b/linden/indra/newview/llhoverview.cpp
@@ -0,0 +1,801 @@
1/**
2 * @file llhoverview.cpp
3 * @brief LLHoverView class implementation
4 *
5 * Copyright (c) 2001-2007, Linden Research, Inc.
6 *
7 * The source code in this file ("Source Code") is provided by Linden Lab
8 * to you under the terms of the GNU General Public License, version 2.0
9 * ("GPL"), unless you have obtained a separate licensing agreement
10 * ("Other License"), formally executed by you and Linden Lab. Terms of
11 * the GPL can be found in doc/GPL-license.txt in this distribution, or
12 * online at http://secondlife.com/developers/opensource/gplv2
13 *
14 * There are special exceptions to the terms and conditions of the GPL as
15 * it is applied to this Source Code. View the full text of the exception
16 * in the file doc/FLOSS-exception.txt in this software distribution, or
17 * online at http://secondlife.com/developers/opensource/flossexception
18 *
19 * By copying, modifying or distributing this software, you acknowledge
20 * that you have read and understood your obligations described above,
21 * and agree to abide by those obligations.
22 *
23 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25 * COMPLETENESS OR PERFORMANCE.
26 */
27
28#include "llviewerprecompiledheaders.h"
29
30// self include
31#include "llhoverview.h"
32
33// Library includes
34#include "llfontgl.h"
35#include "message.h"
36#include "llgl.h"
37#include "llfontgl.h"
38#include "llparcel.h"
39#include "lldbstrings.h"
40#include "llclickaction.h"
41
42// Viewer includes
43#include "llagent.h"
44#include "llcachename.h"
45#include "llviewercontrol.h"
46#include "lldrawable.h"
47#include "llpermissions.h"
48#include "llresmgr.h"
49#include "llselectmgr.h"
50#include "lltoolmgr.h"
51#include "lltoolpie.h"
52#include "lltoolselectland.h"
53#include "llui.h"
54#include "llviewercamera.h"
55#include "llviewerobject.h"
56#include "llviewerobjectlist.h"
57#include "llviewerparcelmgr.h"
58#include "llviewerregion.h"
59#include "llviewerwindow.h"
60#include "llglheaders.h"
61#include "llviewerimagelist.h"
62//#include "lltoolobjpicker.h"
63#include "llhudmanager.h" // HACK for creating flex obj's
64
65#include "llhudmanager.h" // For testing effects
66#include "llhudeffect.h"
67
68//
69// Constants
70//
71const char* DEFAULT_DESC = "(No Description)";
72const F32 DELAY_BEFORE_SHOW_TIP = 0.35f;
73
74//
75// Local globals
76//
77
78LLHoverView *gHoverView = NULL;
79
80//
81// Static member functions
82//
83BOOL LLHoverView::sShowHoverTips = TRUE;
84
85//
86// Member functions
87//
88
89LLHoverView::LLHoverView(const std::string& name, const LLRect& rect)
90: LLView(name, rect, FALSE)
91{
92 mDoneHoverPick = FALSE;
93 mStartHoverPickTimer = FALSE;
94 mHoverActive = FALSE;
95 mUseHover = TRUE;
96 mTyping = FALSE;
97 mHoverOffset.clearVec();
98}
99
100LLHoverView::~LLHoverView()
101{
102 // children all deleted by LLView destructor
103 mText.deleteAllData();
104 mConnectors.reset();
105}
106
107EWidgetType LLHoverView::getWidgetType() const
108{
109 return WIDGET_TYPE_HOVER_VIEW;
110}
111
112LLString LLHoverView::getWidgetTag() const
113{
114 return LL_HOVER_VIEW_TAG;
115}
116
117void LLHoverView::updateHover(LLTool* current_tool)
118{
119 BOOL picking_tool = ( current_tool == gToolPie
120 || current_tool == gToolParcel );
121
122 mUseHover = !gAgent.cameraMouselook()
123 && picking_tool
124 && !mTyping;
125 if (mUseHover)
126 {
127 if ((gViewerWindow->getMouseVelocityStat()->getPrev(0) < 0.01f)
128 && (gCamera->getAngularVelocityStat()->getPrev(0) < 0.01f)
129 && (gCamera->getVelocityStat()->getPrev(0) < 0.01f))
130 {
131 if (!mStartHoverPickTimer)
132 {
133 mStartHoverTimer.reset();
134 mStartHoverPickTimer = TRUE;
135 // Delete the existing text so that we do not briefly show the wrong data.
136 mText.deleteAllData();
137 }
138
139 if (mDoneHoverPick)
140 {
141 // Just update the hover data
142 updateText();
143 }
144 else if (mStartHoverTimer.getElapsedTimeF32() > DELAY_BEFORE_SHOW_TIP)
145 {
146 gViewerWindow->hitObjectOrLandGlobalAsync(gViewerWindow->getCurrentMouseX(),
147 gViewerWindow->getCurrentMouseY(), 0, pickCallback, TRUE );
148 }
149 }
150 else
151 {
152 cancelHover();
153 }
154 }
155
156}
157
158void LLHoverView::pickCallback(S32 x, S32 y, MASK mask)
159{
160 LLViewerObject* hit_obj = gViewerWindow->lastObjectHit();
161
162 if (hit_obj)
163 {
164 gHoverView->setHoverActive(TRUE);
165 gSelectMgr->setHoverObject(hit_obj);
166 gHoverView->mLastHoverObject = hit_obj;
167 gHoverView->mHoverOffset = gViewerWindow->lastObjectHitOffset();
168 }
169 else
170 {
171 gHoverView->mLastHoverObject = NULL;
172 }
173
174 // We didn't hit an object, but we did hit land.
175 if (!hit_obj && gLastHitPosGlobal != LLVector3d::zero)
176 {
177 gHoverView->setHoverActive(TRUE);
178 gHoverView->mHoverLandGlobal = gLastHitPosGlobal;
179 gParcelMgr->requestHoverParcelProperties( gHoverView->mHoverLandGlobal );
180 }
181 else
182 {
183 gHoverView->mHoverLandGlobal = LLVector3d::zero;
184 }
185
186 gHoverView->mDoneHoverPick = TRUE;
187}
188
189void LLHoverView::setTyping(BOOL b)
190{
191 mTyping = b;
192}
193
194
195void LLHoverView::cancelHover()
196{
197 mStartHoverTimer.reset();
198 mDoneHoverPick = FALSE;
199 mStartHoverPickTimer = FALSE;
200
201 gSelectMgr->setHoverObject(NULL);
202 // Can't do this, some code relies on hover object still being
203 // set after the hover is cancelled! Dammit. JC
204 // mLastHoverObject = NULL;
205
206 setHoverActive(FALSE);
207 mConnectors.reset();
208}
209
210void LLHoverView::resetLastHoverObject()
211{
212 mLastHoverObject = NULL;
213}
214
215void LLHoverView::updateText()
216{
217 char first_name[DB_FIRST_NAME_BUF_SIZE];
218 char last_name[DB_LAST_NAME_BUF_SIZE];
219 char group_name[DB_GROUP_NAME_BUF_SIZE];
220
221 LLViewerObject* hit_object = getLastHoverObject();
222
223 mText.deleteAllData();
224 if ( hit_object )
225 {
226 if ( hit_object->isAttachment() )
227 {
228 // get root of attachment then parent, which is avatar
229 LLViewerObject* root_edit = hit_object->getRootEdit();
230 if (!root_edit)
231 {
232 // Strange parenting issue, don't show any text
233 return;
234 }
235 hit_object = (LLViewerObject*)root_edit->getParent();
236 if (!hit_object)
237 {
238 // another strange parenting issue, bail out
239 return;
240 }
241 }
242
243 if (hit_object->isAvatar())
244 {
245 LLString *line = new LLString("");
246 LLNameValue* title = hit_object->getNVPair("Title");
247 LLNameValue* firstname = hit_object->getNVPair("FirstName");
248 LLNameValue* lastname = hit_object->getNVPair("LastName");
249 if (firstname && lastname)
250 {
251 if (title)
252 {
253 line->append(title->getString());
254 line->append(1, ' ');
255 }
256 line->append(firstname->getString());
257 line->append(1, ' ');
258 line->append(lastname->getString());
259 }
260 else
261 {
262 line->append("Person");
263 }
264 mText.addDataAtEnd(line);
265 }
266 else
267 {
268 //
269 // We have hit a regular object (not an avatar or attachment)
270 //
271
272 //
273 // Default prefs will suppress display unless the object is interactive
274 //
275 BOOL suppressObjectHoverDisplay = !gSavedSettings.getBOOL("ShowAllObjectHoverTip");
276
277
278 LLSelectNodeList &nodes = gSelectMgr->getHoverObjects();
279 LLSelectNode *nodep = nodes.getFirstRootNode();
280 if (nodep)
281 {
282 char cstring[256];
283 LLString *temp_str = NULL;
284
285 temp_str = new LLString();
286 if (nodep->mName.empty())
287 {
288 temp_str->append("(no name)");
289 }
290 else
291 {
292 temp_str->append( nodep->mName );
293 }
294
295 mText.addDataAtEnd(temp_str);
296
297 if (!nodep->mDescription.empty()
298 && nodep->mDescription != DEFAULT_DESC)
299 {
300 temp_str = new LLString( nodep->mDescription );
301 mText.addDataAtEnd( temp_str );
302 }
303
304 // Line: "Owner: James Linden"
305 temp_str = new LLString();
306 temp_str->append("Owner: ");
307
308 if (nodep->mValid)
309 {
310 LLUUID owner;
311 if (!nodep->mPermissions->isGroupOwned())
312 {
313 owner = nodep->mPermissions->getOwner();
314 if (LLUUID::null == owner)
315 {
316 temp_str->append("Public");
317 }
318 else if(gCacheName->getName(
319 owner, first_name, last_name))
320 {
321 temp_str->append(first_name);
322 temp_str->append(" ");
323 temp_str->append(last_name);
324 }
325 else
326 {
327 temp_str->append("Retrieving...");
328 }
329 }else
330 {
331 owner = nodep->mPermissions->getGroup();
332 if (gCacheName->getGroupName(owner, group_name))
333 {
334 temp_str->append(group_name);
335 temp_str->append("(Group)");
336 }
337 else
338 {
339 temp_str->append("Retrieving...");
340 }
341 }
342 }
343 else
344 {
345 temp_str->append("Retrieving...");
346 }
347 mText.addDataAtEnd(temp_str);
348
349 // Build a line describing any special properties
350 // of this object.
351 LLViewerObject *object = hit_object;
352 LLViewerObject *parent = (LLViewerObject *)object->getParent();
353
354 if (object &&
355 (object->usePhysics() ||
356 object->flagScripted() ||
357 object->flagHandleTouch() || (parent && parent->flagHandleTouch()) ||
358 object->flagTakesMoney() || (parent && parent->flagTakesMoney()) ||
359 object->flagAllowInventoryAdd() ||
360 object->flagTemporary() ||
361 object->flagPhantom()) )
362 {
363 temp_str = new LLString();
364 if (object->flagScripted())
365 {
366 temp_str->append("Script ");
367 }
368
369 if (object->usePhysics())
370 {
371 temp_str->append("Physics ");
372 }
373
374 if (object->flagHandleTouch() || (parent && parent->flagHandleTouch()) )
375 {
376 temp_str->append("Touch ");
377 suppressObjectHoverDisplay = FALSE; // Show tip
378 }
379
380 if (object->flagTakesMoney() || (parent && parent->flagTakesMoney()) )
381 {
382 temp_str->append("Money ");
383 suppressObjectHoverDisplay = FALSE; // Show tip
384 }
385
386 if (object->flagAllowInventoryAdd())
387 {
388 temp_str->append("Drop Inventory ");
389 suppressObjectHoverDisplay = FALSE; // Show tip
390 }
391
392 if (object->flagPhantom())
393 {
394 temp_str->append("Phantom ");
395 }
396
397 if (object->flagTemporary())
398 {
399 temp_str->append("Temporary ");
400 }
401
402 if (object->usePhysics() ||
403 object->flagHandleTouch() ||
404 (parent && parent->flagHandleTouch()) )
405 {
406 temp_str->append("(Right-click for menu) ");
407 }
408 mText.addDataAtEnd(temp_str);
409 }
410
411 if (nodep->mValid)
412 {
413 BOOL for_copy = nodep->mPermissions->getMaskEveryone() & PERM_COPY && object->permCopy();
414 BOOL for_sale = nodep->mSaleInfo.isForSale() &&
415 nodep->mPermissions->getMaskOwner() & PERM_TRANSFER &&
416 (nodep->mPermissions->getMaskOwner() & PERM_COPY ||
417 nodep->mSaleInfo.getSaleType() != LLSaleInfo::FS_COPY);
418 if (for_copy)
419 {
420 temp_str = new LLString();
421 temp_str->append("Free to copy");
422 mText.addDataAtEnd(temp_str);
423 suppressObjectHoverDisplay = FALSE; // Show tip
424 }
425 else if (for_sale)
426 {
427 temp_str = new LLString();
428 temp_str->append("For Sale: ");
429 sprintf(cstring, "L$%d", nodep->mSaleInfo.getSalePrice());
430 temp_str->append(cstring);
431 mText.addDataAtEnd(temp_str);
432 suppressObjectHoverDisplay = FALSE; // Show tip
433 }
434 else
435 {
436 // Nothing if not for sale
437 // temp_str = new LLString();
438 // temp_str->append("Not for sale");
439 }
440 }
441 else
442 {
443 temp_str = new LLString();
444 temp_str->append("For Sale: Retrieving...");
445 mText.addDataAtEnd(temp_str);
446 }
447 }
448 // If the hover tip shouldn't be shown, delete all the object text
449 if (suppressObjectHoverDisplay)
450 {
451 mText.deleteAllData();
452 }
453 }
454 }
455 else if ( mHoverLandGlobal != LLVector3d::zero )
456 {
457
458 //
459 // Do not show hover for land unless prefs are set to allow it.
460 //
461
462 if (!gSavedSettings.getBOOL("ShowLandHoverTip")) return;
463
464 // Didn't hit an object, but since we have a land point we
465 // must be hovering over land.
466 LLString *line = NULL;
467
468 LLParcel* hover_parcel = gParcelMgr->getHoverParcel();
469 LLUUID owner;
470 S32 width = 0;
471 S32 height = 0;
472
473 if ( hover_parcel )
474 {
475 owner = hover_parcel->getOwnerID();
476 width = S32(gParcelMgr->getHoverParcelWidth());
477 height = S32(gParcelMgr->getHoverParcelHeight());
478 }
479
480 // Line: "Land"
481 line = new LLString();
482 mText.addDataAtEnd(line);
483
484 line->append("Land: ");
485 if (hover_parcel)
486 {
487 line->append(hover_parcel->getName());
488 }
489
490 // Line: "Owner: James Linden"
491 line = new LLString();
492 mText.addDataAtEnd(line);
493
494 line->append("Owner: ");
495
496 if ( hover_parcel )
497 {
498 if (LLUUID::null == owner)
499 {
500 line->append("Public");
501 }
502 else if (hover_parcel->getIsGroupOwned())
503 {
504 if (gCacheName->getGroupName(owner, group_name))
505 {
506 line->append(group_name);
507 line->append("(Group)");
508 }
509 else
510 {
511 line->append("Retrieving...");
512 }
513 }
514 else if(gCacheName->getName(owner, first_name, last_name))
515 {
516 line->append(first_name);
517 line->append(" ");
518 line->append(last_name);
519 }
520 else
521 {
522 line->append("Retrieving...");
523 }
524 }
525 else
526 {
527 line->append("Retrieving...");
528 }
529
530 // Line: "no fly, not safe, no build"
531
532 // Don't display properties for your land. This is just
533 // confusing, because you can do anything on your own land.
534 if ( hover_parcel && owner != gAgent.getID() )
535 {
536 S32 words = 0;
537 line = new LLString("");
538
539 // JC - Keep this in the same order as the checkboxes
540 // on the land info panel
541 if ( !hover_parcel->getAllowModify() )
542 {
543 if (words) line->append(", ");
544 if ( hover_parcel->getAllowGroupModify() )
545 {
546 line->append("Group Build");
547 }
548 else
549 {
550 line->append("No Build");
551 }
552
553 words++;
554 }
555
556 if ( !hover_parcel->getAllowTerraform() )
557 {
558 if (words) line->append(", ");
559 line->append("No Edit");
560 words++;
561 }
562
563 if ( hover_parcel->getAllowDamage() )
564 {
565 if (words) line->append(", ");
566 line->append("Not Safe");
567 words++;
568 }
569
570 // Maybe we should reflect the estate's block fly bit here as well? DK 12/1/04
571 if ( !hover_parcel->getAllowFly() )
572 {
573 if (words) line->append(", ");
574 line->append("No Fly");
575 words++;
576 }
577
578 if ( !hover_parcel->getAllowOtherScripts() )
579 {
580 if (words) line->append(", ");
581 if ( hover_parcel->getAllowGroupScripts() )
582 {
583 line->append("Group Scripts");
584 }
585 else
586 {
587 line->append("No Scripts");
588 }
589
590 words++;
591 }
592
593 if (words)
594 {
595 mText.addDataAtEnd(line);
596 }
597 else
598 {
599 delete line;
600 line = NULL;
601 }
602 }
603
604 // Line: "Size: 1x4"
605 // Only show for non-public land
606 /*
607 if ( hover_parcel && LLUUID::null != owner)
608 {
609 line = new LLString();
610 mText.addDataAtEnd(line);
611
612 char buffer[MAX_STRING];
613 sprintf(buffer, "Size: %dx%d", width, height );
614 line->append(buffer);
615 }
616 */
617 if (hover_parcel && hover_parcel->getParcelFlag(PF_FOR_SALE))
618 {
619 char buffer[MAX_STRING];
620 sprintf(buffer, "For Sale: L$%d", hover_parcel->getSalePrice() );
621
622 line = new LLString(buffer);
623 mText.addDataAtEnd(line);
624 }
625 }
626}
627
628
629void LLHoverView::draw()
630{
631 if( !getVisible() )
632 {
633 return;
634 }
635
636 if ( !isHovering() )
637 {
638 return;
639 }
640
641 // To toggle off hover tips, you have to just suppress the draw.
642 // The picking is still needed to do cursor changes over physical
643 // and scripted objects. JC
644 if (!sShowHoverTips) return;
645
646 const F32 MAX_HOVER_DISPLAY_SECS = 5.f;
647 if (mHoverTimer.getElapsedTimeF32() > MAX_HOVER_DISPLAY_SECS)
648 {
649 return;
650 }
651
652 const F32 MAX_ALPHA = 0.9f;
653 //const F32 STEADY_ALPHA = 0.3f;
654 F32 alpha;
655 if (mHoverActive)
656 {
657 alpha = 1.f;
658
659 if (isHoveringObject())
660 {
661 // look at object
662 LLViewerObject *hover_object = getLastHoverObject();
663 if (hover_object->isAvatar())
664 {
665 gAgent.setLookAt(LOOKAT_TARGET_HOVER, getLastHoverObject(), LLVector3::zero);
666 }
667 else
668 {
669 LLVector3 local_offset((F32)mHoverOffset.mdV[VX], (F32)mHoverOffset.mdV[VY], (F32)mHoverOffset.mdV[VZ]);
670 gAgent.setLookAt(LOOKAT_TARGET_HOVER, getLastHoverObject(), local_offset);
671 }
672 }
673 }
674 else
675 {
676 alpha = llmax(0.f, MAX_ALPHA - mHoverTimer.getElapsedTimeF32()*2.f);
677 }
678
679 // Bail out if no text to display
680 if (mText.isEmpty())
681 {
682 return;
683 }
684
685 // Don't draw if no alpha
686 if (alpha <= 0.f)
687 {
688 return;
689 }
690
691 LLViewerImage* box_imagep = gImageList.getImage(LLUUID(gViewerArt.getString("rounded_square.tga")), MIPMAP_FALSE, TRUE);
692 LLViewerImage* shadow_imagep = gImageList.getImage(LLUUID(gViewerArt.getString("rounded_square_soft.tga")), MIPMAP_FALSE, TRUE);
693
694 const LLFontGL* fontp = gResMgr->getRes(LLFONT_SANSSERIF_SMALL);
695
696 // Render text.
697 LLColor4 text_color = gColors.getColor("ToolTipTextColor");
698 // LLColor4 border_color = gColors.getColor("ToolTipBorderColor");
699 LLColor4 bg_color = gColors.getColor("ToolTipBgColor");
700 LLColor4 shadow_color = gColors.getColor("ColorDropShadow");
701
702 // Could decrease the alpha here. JC
703 //text_color.mV[VALPHA] = alpha;
704 //border_color.mV[VALPHA] = alpha;
705 //bg_color.mV[VALPHA] = alpha;
706
707 S32 max_width = 0;
708 S32 num_lines = mText.getLength();
709 LLString *cur_stringp;
710 for (cur_stringp = mText.getFirstData(); cur_stringp; cur_stringp = mText.getNextData())
711 {
712 max_width = llmax(max_width, (S32)fontp->getWidth(*cur_stringp));
713 }
714
715 S32 left = mHoverPos.mX + 10;
716 S32 top = mHoverPos.mY - 16;
717 S32 right = mHoverPos.mX + max_width + 30;
718 S32 bottom = mHoverPos.mY - 24 - llfloor(num_lines*fontp->getLineHeight());
719
720 // Push down if there's a one-click icon
721 if (mHoverActive
722 && isHoveringObject()
723 && mLastHoverObject->getClickAction() != CLICK_ACTION_NONE)
724 {
725 const S32 CLICK_OFFSET = 10;
726 top -= CLICK_OFFSET;
727 bottom -= CLICK_OFFSET;
728 }
729
730 // Make sure the rect is completely visible
731 LLRect old_rect = mRect;
732 mRect.set( left, top, right, bottom );
733 translateIntoRect( gViewerWindow->getVirtualWindowRect(), FALSE );
734 left = mRect.mLeft;
735 top = mRect.mTop;
736 right = mRect.mRight;
737 bottom = mRect.mBottom;
738 mRect = old_rect;
739
740 LLGLSUIDefault gls_ui;
741
742 shadow_color.mV[VALPHA] = 0.7f * alpha;
743 S32 shadow_offset = gSavedSettings.getS32("DropShadowTooltip");
744 glColor4fv(shadow_color.mV);
745 LLViewerImage::bindTexture(shadow_imagep);
746 gl_segmented_rect_2d_tex(left + shadow_offset, top - shadow_offset, right + shadow_offset, bottom - shadow_offset, shadow_imagep->getWidth(), shadow_imagep->getHeight(), 16);
747
748 bg_color.mV[VALPHA] = alpha;
749 glColor4fv(bg_color.mV);
750 LLViewerImage::bindTexture(box_imagep);
751 gl_segmented_rect_2d_tex(left, top, right, bottom, box_imagep->getWidth(), box_imagep->getHeight(), 16);
752
753 S32 cur_offset = top - 4;
754 for (cur_stringp = mText.getFirstData(); cur_stringp; cur_stringp = mText.getNextData())
755 {
756 fontp->renderUTF8(*cur_stringp, 0, left + 10, cur_offset, text_color, LLFontGL::LEFT, LLFontGL::TOP);
757 cur_offset -= llfloor(fontp->getLineHeight());
758 }
759}
760
761void LLHoverView::setHoverActive(const BOOL active)
762{
763 if (active != mHoverActive)
764 {
765 mHoverTimer.reset();
766 }
767
768 mHoverActive = active;
769
770 if (active)
771 {
772 mHoverPos = gViewerWindow->getCurrentMouse();
773 }
774}
775
776
777BOOL LLHoverView::isHoveringLand() const
778{
779 return !mHoverLandGlobal.isExactlyZero();
780}
781
782
783BOOL LLHoverView::isHoveringObject() const
784{
785 return !mLastHoverObject.isNull() && !mLastHoverObject->isDead();
786}
787
788
789LLViewerObject* LLHoverView::getLastHoverObject() const
790{
791 if (!mLastHoverObject.isNull() && !mLastHoverObject->isDead())
792 {
793 return mLastHoverObject;
794 }
795 else
796 {
797 return NULL;
798 }
799}
800
801// EOF