]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/ogl/divided.cpp
compilation fix for gcc (closes bug 996701)
[wxWidgets.git] / contrib / src / ogl / divided.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: divided.cpp
3 // Purpose: wxDividedShape class
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 12/07/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #ifdef __GNUG__
13 #pragma implementation "divided.h"
14 #endif
15
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 #pragma hdrstop
21 #endif
22
23 #ifndef WX_PRECOMP
24 #include <wx/wx.h>
25 #endif
26
27 #if wxUSE_PROLOGIO
28 #include <wx/deprecated/wxexpr.h>
29 #endif
30
31 #include "wx/ogl/ogl.h"
32
33
34 class wxDividedShapeControlPoint: public wxControlPoint
35 {
36 DECLARE_DYNAMIC_CLASS(wxDividedShapeControlPoint)
37 private:
38 int regionId;
39 public:
40 wxDividedShapeControlPoint() { regionId = 0; }
41 wxDividedShapeControlPoint(wxShapeCanvas *the_canvas, wxShape *object, int region,
42 double size, double the_xoffset, double the_yoffset, int the_type);
43 ~wxDividedShapeControlPoint();
44
45 void OnDragLeft(bool draw, double x, double y, int keys=0, int attachment = 0);
46 void OnBeginDragLeft(double x, double y, int keys=0, int attachment = 0);
47 void OnEndDragLeft(double x, double y, int keys=0, int attachment = 0);
48 };
49
50 IMPLEMENT_DYNAMIC_CLASS(wxDividedShapeControlPoint, wxControlPoint)
51
52 /*
53 * Divided object
54 *
55 */
56
57 IMPLEMENT_DYNAMIC_CLASS(wxDividedShape, wxRectangleShape)
58
59 wxDividedShape::wxDividedShape(double w, double h): wxRectangleShape(w, h)
60 {
61 ClearRegions();
62 }
63
64 wxDividedShape::~wxDividedShape()
65 {
66 }
67
68 void wxDividedShape::OnDraw(wxDC& dc)
69 {
70 wxRectangleShape::OnDraw(dc);
71 }
72
73 void wxDividedShape::OnDrawContents(wxDC& dc)
74 {
75 double defaultProportion = (double)(GetRegions().GetCount() > 0 ? (1.0/((double)(GetRegions().GetCount()))) : 0.0);
76 double currentY = (double)(m_ypos - (m_height / 2.0));
77 double maxY = (double)(m_ypos + (m_height / 2.0));
78
79 double leftX = (double)(m_xpos - (m_width / 2.0));
80 double rightX = (double)(m_xpos + (m_width / 2.0));
81
82 if (m_pen) dc.SetPen(* m_pen);
83
84 dc.SetTextForeground(m_textColour);
85
86 #ifdef __WXMSW__
87 // For efficiency, don't do this under X - doesn't make
88 // any visible difference for our purposes.
89 if (m_brush)
90 dc.SetTextBackground(m_brush->GetColour());
91 #endif
92 /*
93 if (!formatted)
94 {
95 FormatRegionText();
96 formatted = true;
97 }
98 */
99 if (GetDisableLabel()) return;
100
101 double xMargin = 2;
102 double yMargin = 2;
103 dc.SetBackgroundMode(wxTRANSPARENT);
104
105 wxNode *node = GetRegions().GetFirst();
106 while (node)
107 {
108 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
109 dc.SetFont(* region->GetFont());
110 dc.SetTextForeground(region->GetActualColourObject());
111
112 double proportion =
113 region->m_regionProportionY < 0.0 ? defaultProportion : region->m_regionProportionY;
114
115 double y = currentY + m_height*proportion;
116 double actualY = maxY < y ? maxY : y;
117
118 double centreX = m_xpos;
119 double centreY = (double)(currentY + (actualY - currentY)/2.0);
120
121 oglDrawFormattedText(dc, &region->m_formattedText,
122 (double)(centreX), (double)(centreY), (double)(m_width-2*xMargin), (double)(actualY - currentY - 2*yMargin),
123 region->m_formatMode);
124 if ((y <= maxY) && (node->GetNext()))
125 {
126 wxPen *regionPen = region->GetActualPen();
127 if (regionPen)
128 {
129 dc.SetPen(* regionPen);
130 dc.DrawLine(WXROUND(leftX), WXROUND(y), WXROUND(rightX), WXROUND(y));
131 }
132 }
133
134 currentY = actualY;
135
136 node = node->GetNext();
137 }
138 }
139
140 void wxDividedShape::SetSize(double w, double h, bool WXUNUSED(recursive))
141 {
142 SetAttachmentSize(w, h);
143 m_width = w;
144 m_height = h;
145 SetRegionSizes();
146 }
147
148 void wxDividedShape::SetRegionSizes()
149 {
150 if (GetRegions().GetCount() == 0)
151 return;
152
153 double defaultProportion = (double)(GetRegions().GetCount() > 0 ? (1.0/((double)(GetRegions().GetCount()))) : 0.0);
154 double currentY = (double)(m_ypos - (m_height / 2.0));
155 double maxY = (double)(m_ypos + (m_height / 2.0));
156
157 // double leftX = (double)(m_xpos - (m_width / 2.0));
158 // double rightX = (double)(m_xpos + (m_width / 2.0));
159
160 wxNode *node = GetRegions().GetFirst();
161 while (node)
162 {
163 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
164 double proportion =
165 region->m_regionProportionY <= 0.0 ? defaultProportion : region->m_regionProportionY;
166
167 double sizeY = (double)proportion*m_height;
168 double y = currentY + sizeY;
169 double actualY = maxY < y ? maxY : y;
170
171 double centreY = (double)(currentY + (actualY - currentY)/2.0);
172
173 region->SetSize(m_width, sizeY);
174 region->SetPosition(0.0, (double)(centreY - m_ypos));
175 currentY = actualY;
176 node = node->GetNext();
177 }
178 }
179
180 // Attachment points correspond to regions in the divided box
181 bool wxDividedShape::GetAttachmentPosition(int attachment, double *x, double *y, int nth, int no_arcs,
182 wxLineShape *line)
183 {
184 int totalNumberAttachments = (GetRegions().GetCount() * 2) + 2;
185 if ((GetAttachmentMode() == ATTACHMENT_MODE_NONE) || (attachment >= totalNumberAttachments))
186 {
187 return wxShape::GetAttachmentPosition(attachment, x, y, nth, no_arcs);
188 }
189
190 int n = GetRegions().GetCount();
191 bool isEnd = (line && line->IsEnd(this));
192
193 double left = (double)(m_xpos - m_width/2.0);
194 double right = (double)(m_xpos + m_width/2.0);
195 double top = (double)(m_ypos - m_height/2.0);
196 double bottom = (double)(m_ypos + m_height/2.0);
197
198 // Zero is top, n+1 is bottom.
199 if (attachment == 0)
200 {
201 *y = top;
202 if (m_spaceAttachments)
203 {
204 if (line && (line->GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE))
205 {
206 // Align line according to the next handle along
207 wxRealPoint *point = line->GetNextControlPoint(this);
208 if (point->x < left)
209 *x = left;
210 else if (point->x > right)
211 *x = right;
212 else
213 *x = point->x;
214 }
215 else
216 *x = left + (nth + 1)*m_width/(no_arcs + 1);
217 }
218 else
219 *x = m_xpos;
220 }
221 else if (attachment == (n+1))
222 {
223 *y = bottom;
224 if (m_spaceAttachments)
225 {
226 if (line && (line->GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE))
227 {
228 // Align line according to the next handle along
229 wxRealPoint *point = line->GetNextControlPoint(this);
230 if (point->x < left)
231 *x = left;
232 else if (point->x > right)
233 *x = right;
234 else
235 *x = point->x;
236 }
237 else
238 *x = left + (nth + 1)*m_width/(no_arcs + 1);
239 }
240 else
241 *x = m_xpos;
242 }
243 // Left or right.
244 else
245 {
246 bool isLeft = !(attachment < (n+1));
247 int i = (isLeft) ? (totalNumberAttachments - attachment - 1) : (attachment-1);
248 wxNode *node = GetRegions().Item(i);
249 if (node)
250 {
251 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
252
253 if (isLeft)
254 *x = left;
255 else
256 *x = right;
257
258 // Calculate top and bottom of region
259 top = (double)((m_ypos + region->m_y) - (region->m_height/2.0));
260 bottom = (double)((m_ypos + region->m_y) + (region->m_height/2.0));
261
262 // Assuming we can trust the absolute size and
263 // position of these regions...
264 if (m_spaceAttachments)
265 {
266 if (line && (line->GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE))
267 {
268 // Align line according to the next handle along
269 wxRealPoint *point = line->GetNextControlPoint(this);
270 if (point->y < bottom)
271 *y = bottom;
272 else if (point->y > top)
273 *y = top;
274 else
275 *y = point->y;
276 }
277 else
278 // *y = (double)(((m_ypos + region->m_y) - (region->m_height/2.0)) + (nth + 1)*region->m_height/(no_arcs+1));
279 *y = (double)(top + (nth + 1)*region->m_height/(no_arcs+1));
280 }
281 else
282 *y = (double)(m_ypos + region->m_y);
283 }
284 else
285 {
286 *x = m_xpos;
287 *y = m_ypos;
288 return false;
289 }
290 }
291 return true;
292 }
293
294 int wxDividedShape::GetNumberOfAttachments() const
295 {
296 // There are two attachments for each region (left and right),
297 // plus one on the top and one on the bottom.
298 int n = (GetRegions().GetCount() * 2) + 2;
299
300 int maxN = n - 1;
301 wxNode *node = m_attachmentPoints.GetFirst();
302 while (node)
303 {
304 wxAttachmentPoint *point = (wxAttachmentPoint *)node->GetData();
305 if (point->m_id > maxN)
306 maxN = point->m_id;
307 node = node->GetNext();
308 }
309 return maxN + 1;
310 }
311
312 bool wxDividedShape::AttachmentIsValid(int attachment) const
313 {
314 int totalNumberAttachments = (GetRegions().GetCount() * 2) + 2;
315 if (attachment >= totalNumberAttachments)
316 {
317 return wxShape::AttachmentIsValid(attachment);
318 }
319 else if (attachment >= 0)
320 return true;
321 else
322 return false;
323 }
324
325 void wxDividedShape::Copy(wxShape& copy)
326 {
327 wxRectangleShape::Copy(copy);
328 }
329
330 // Region operations
331
332 void wxDividedShape::MakeControlPoints()
333 {
334 wxRectangleShape::MakeControlPoints();
335
336 MakeMandatoryControlPoints();
337 }
338
339 void wxDividedShape::MakeMandatoryControlPoints()
340 {
341 double currentY = (double)(GetY() - (m_height / 2.0));
342 double maxY = (double)(GetY() + (m_height / 2.0));
343
344 wxNode *node = GetRegions().GetFirst();
345 int i = 0;
346 while (node)
347 {
348 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
349
350 double proportion = region->m_regionProportionY;
351
352 double y = currentY + m_height*proportion;
353 double actualY = (double)(maxY < y ? maxY : y);
354
355 if (node->GetNext())
356 {
357 wxDividedShapeControlPoint *controlPoint =
358 new wxDividedShapeControlPoint(m_canvas, this, i, CONTROL_POINT_SIZE, 0.0, (double)(actualY - GetY()), 0);
359 m_canvas->AddShape(controlPoint);
360 m_controlPoints.Append(controlPoint);
361 }
362 currentY = actualY;
363 i ++;
364 node = node->GetNext();
365 }
366 }
367
368 void wxDividedShape::ResetControlPoints()
369 {
370 // May only have the region handles, (n - 1) of them.
371 if (m_controlPoints.GetCount() > (GetRegions().GetCount() - 1))
372 wxRectangleShape::ResetControlPoints();
373
374 ResetMandatoryControlPoints();
375 }
376
377 void wxDividedShape::ResetMandatoryControlPoints()
378 {
379 double currentY = (double)(GetY() - (m_height / 2.0));
380 double maxY = (double)(GetY() + (m_height / 2.0));
381
382 wxNode *node = m_controlPoints.GetFirst();
383 int i = 0;
384 while (node)
385 {
386 wxControlPoint *controlPoint = (wxControlPoint *)node->GetData();
387 if (controlPoint->IsKindOf(CLASSINFO(wxDividedShapeControlPoint)))
388 {
389 wxNode *node1 = GetRegions().Item(i);
390 wxShapeRegion *region = (wxShapeRegion *)node1->GetData();
391
392 double proportion = region->m_regionProportionY;
393
394 double y = currentY + m_height*proportion;
395 double actualY = (double)(maxY < y ? maxY : y);
396
397 controlPoint->m_xoffset = 0.0;
398 controlPoint->m_yoffset = (double)(actualY - GetY());
399 currentY = actualY;
400 i ++;
401 }
402 node = node->GetNext();
403 }
404 }
405
406 #if wxUSE_PROLOGIO
407 void wxDividedShape::WriteAttributes(wxExpr *clause)
408 {
409 wxRectangleShape::WriteAttributes(clause);
410 }
411
412 void wxDividedShape::ReadAttributes(wxExpr *clause)
413 {
414 wxRectangleShape::ReadAttributes(clause);
415 }
416 #endif
417
418 /*
419 * Edit the division colour/style
420 *
421 */
422
423 void wxDividedShape::EditRegions()
424 {
425 wxMessageBox(wxT("EditRegions() is unimplemented."), wxT("OGL"), wxOK);
426
427 // TODO
428 #if 0
429 if (GetRegions().GetCount() < 2)
430 return;
431
432 wxBeginBusyCursor();
433
434 GraphicsForm *form = new GraphicsForm("Divided nodes");
435 // Need an array to store all the style strings,
436 // since they need to be converted to integers
437 char **styleStrings = new char *[GetRegions().GetCount()];
438 for (int j = 0; j < GetRegions().GetCount(); j++)
439 styleStrings[j] = NULL;
440
441 int i = 0;
442 wxNode *node = GetRegions().GetFirst();
443 while (node && node->GetNext())
444 {
445 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
446 char buf[50];
447 sprintf(buf, "Region %d", (i+1));
448 form->Add(wxMakeFormMessage(buf));
449 form->Add(wxMakeFormNewLine());
450
451 form->Add(wxMakeFormString("Colour", &region->penColour, wxFORM_CHOICE,
452 new wxList(wxMakeConstraintStrings(
453 "Invisible" ,
454 "BLACK" ,
455 "BLUE" ,
456 "BROWN" ,
457 "CORAL" ,
458 "CYAN" ,
459 "DARK GREY" ,
460 "DARK GREEN" ,
461 "DIM GREY" ,
462 "GREY" ,
463 "GREEN" ,
464 "LIGHT BLUE" ,
465 "LIGHT GREY" ,
466 "MAGENTA" ,
467 "MAROON" ,
468 "NAVY" ,
469 "ORANGE" ,
470 "PURPLE" ,
471 "RED" ,
472 "TURQUOISE" ,
473 "VIOLET" ,
474 "WHITE" ,
475 "YELLOW" ,
476 NULL),
477 NULL), NULL, wxVERTICAL, 150));
478
479 char *styleString = NULL;
480 switch (region->penStyle)
481 {
482 case wxSHORT_DASH:
483 styleString = "Short Dash";
484 break;
485 case wxLONG_DASH:
486 styleString = "Long Dash";
487 break;
488 case wxDOT:
489 styleString = "Dot";
490 break;
491 case wxDOT_DASH:
492 styleString = "Dot Dash";
493 break;
494 case wxSOLID:
495 default:
496 styleString = "Solid";
497 break;
498 }
499 styleStrings[i] = copystring(styleString);
500 form->Add(wxMakeFormString("Style", &(styleStrings[i]), wxFORM_CHOICE,
501 new wxList(wxMakeConstraintStrings(
502 "Solid" ,
503 "Short Dash" ,
504 "Long Dash" ,
505 "Dot" ,
506 "Dot Dash" ,
507 NULL),
508 NULL), NULL, wxVERTICAL, 100));
509 node = node->GetNext();
510 i ++;
511 if (node && node->GetNext())
512 form->Add(wxMakeFormNewLine());
513 }
514 wxDialogBox *dialog = new wxDialogBox(m_canvas->GetParent(), "Divided object properties", 10, 10, 500, 500);
515 if (GraphicsLabelFont)
516 dialog->SetLabelFont(GraphicsLabelFont);
517 if (GraphicsButtonFont)
518 dialog->SetButtonFont(GraphicsButtonFont);
519 form->AssociatePanel(dialog);
520 form->dialog = dialog;
521
522 dialog->Fit();
523 dialog->Centre(wxBOTH);
524
525 wxEndBusyCursor();
526
527 dialog->Show(true);
528
529 node = GetRegions().GetFirst();
530 i = 0;
531 while (node)
532 {
533 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
534
535 if (styleStrings[i])
536 {
537 if (strcmp(styleStrings[i], "Solid") == 0)
538 region->penStyle = wxSOLID;
539 else if (strcmp(styleStrings[i], "Dot") == 0)
540 region->penStyle = wxDOT;
541 else if (strcmp(styleStrings[i], "Short Dash") == 0)
542 region->penStyle = wxSHORT_DASH;
543 else if (strcmp(styleStrings[i], "Long Dash") == 0)
544 region->penStyle = wxLONG_DASH;
545 else if (strcmp(styleStrings[i], "Dot Dash") == 0)
546 region->penStyle = wxDOT_DASH;
547 delete[] styleStrings[i];
548 }
549 region->m_actualPenObject = NULL;
550 node = node->GetNext();
551 i ++;
552 }
553 delete[] styleStrings;
554 Draw(dc);
555 #endif
556 }
557
558 void wxDividedShape::OnRightClick(double x, double y, int keys, int attachment)
559 {
560 if (keys & KEY_CTRL)
561 {
562 EditRegions();
563 }
564 else
565 {
566 wxRectangleShape::OnRightClick(x, y, keys, attachment);
567 }
568 }
569
570 wxDividedShapeControlPoint::wxDividedShapeControlPoint(wxShapeCanvas *the_canvas, wxShape *object,
571 int region, double size, double the_m_xoffset, double the_m_yoffset, int the_type):
572 wxControlPoint(the_canvas, object, size, the_m_xoffset, the_m_yoffset, the_type)
573 {
574 regionId = region;
575 }
576
577 wxDividedShapeControlPoint::~wxDividedShapeControlPoint()
578 {
579 }
580
581 // Implement resizing of divided object division
582 void wxDividedShapeControlPoint::OnDragLeft(bool WXUNUSED(draw), double WXUNUSED(x), double y, int WXUNUSED(keys), int WXUNUSED(attachment))
583 {
584 wxClientDC dc(GetCanvas());
585 GetCanvas()->PrepareDC(dc);
586
587 dc.SetLogicalFunction(OGLRBLF);
588 wxPen dottedPen(wxColour(0, 0, 0), 1, wxDOT);
589 dc.SetPen(dottedPen);
590 dc.SetBrush((* wxTRANSPARENT_BRUSH));
591
592 wxDividedShape *dividedObject = (wxDividedShape *)m_shape;
593 double x1 = (double)(dividedObject->GetX() - (dividedObject->GetWidth()/2.0));
594 double y1 = y;
595 double x2 = (double)(dividedObject->GetX() + (dividedObject->GetWidth()/2.0));
596 double y2 = y;
597 dc.DrawLine(WXROUND(x1), WXROUND(y1), WXROUND(x2), WXROUND(y2));
598 }
599
600 void wxDividedShapeControlPoint::OnBeginDragLeft(double WXUNUSED(x), double y, int WXUNUSED(keys), int WXUNUSED(attachment))
601 {
602 wxClientDC dc(GetCanvas());
603 GetCanvas()->PrepareDC(dc);
604
605 wxDividedShape *dividedObject = (wxDividedShape *)m_shape;
606 dc.SetLogicalFunction(OGLRBLF);
607 wxPen dottedPen(wxColour(0, 0, 0), 1, wxDOT);
608 dc.SetPen(dottedPen);
609 dc.SetBrush((* wxTRANSPARENT_BRUSH));
610
611 double x1 = (double)(dividedObject->GetX() - (dividedObject->GetWidth()/2.0));
612 double y1 = y;
613 double x2 = (double)(dividedObject->GetX() + (dividedObject->GetWidth()/2.0));
614 double y2 = y;
615 dc.DrawLine(WXROUND(x1), WXROUND(y1), WXROUND(x2), WXROUND(y2));
616 m_canvas->CaptureMouse();
617 }
618
619 void wxDividedShapeControlPoint::OnEndDragLeft(double WXUNUSED(x), double y, int WXUNUSED(keys), int WXUNUSED(attachment))
620 {
621 wxClientDC dc(GetCanvas());
622 GetCanvas()->PrepareDC(dc);
623
624 wxDividedShape *dividedObject = (wxDividedShape *)m_shape;
625 wxNode *node = dividedObject->GetRegions().Item(regionId);
626 if (!node)
627 return;
628
629 wxShapeRegion *thisRegion = (wxShapeRegion *)node->GetData();
630 wxShapeRegion *nextRegion = NULL; // Region below this one
631
632 dc.SetLogicalFunction(wxCOPY);
633
634 m_canvas->ReleaseMouse();
635
636 // Find the old top and bottom of this region,
637 // and calculate the new proportion for this region
638 // if legal.
639
640 double currentY = (double)(dividedObject->GetY() - (dividedObject->GetHeight() / 2.0));
641 double maxY = (double)(dividedObject->GetY() + (dividedObject->GetHeight() / 2.0));
642
643 // Save values
644 double thisRegionTop = 0.0;
645 #if 0
646 // this variable is not readed later
647 double thisRegionBottom = 0.0;
648 #endif
649 double nextRegionBottom = 0.0;
650
651 node = dividedObject->GetRegions().GetFirst();
652 while (node)
653 {
654 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
655
656 double proportion = region->m_regionProportionY;
657 double yy = currentY + (dividedObject->GetHeight()*proportion);
658 double actualY = (double)(maxY < yy ? maxY : yy);
659
660 if (region == thisRegion)
661 {
662 thisRegionTop = currentY;
663 #if 0
664 // no need for assignment if value is not used later
665 thisRegionBottom = actualY;
666 #endif
667 if (node->GetNext())
668 nextRegion = (wxShapeRegion *)node->GetNext()->GetData();
669 }
670 if (region == nextRegion)
671 {
672 nextRegionBottom = actualY;
673 }
674
675 currentY = actualY;
676 node = node->GetNext();
677 }
678 if (!nextRegion)
679 return;
680
681 // Check that we haven't gone above this region or below
682 // next region.
683 if ((y <= thisRegionTop) || (y >= nextRegionBottom))
684 return;
685
686 dividedObject->EraseLinks(dc);
687
688 // Now calculate the new proportions of this region and the next region.
689 double thisProportion = (double)((y - thisRegionTop)/dividedObject->GetHeight());
690 double nextProportion = (double)((nextRegionBottom - y)/dividedObject->GetHeight());
691 thisRegion->SetProportions(0.0, thisProportion);
692 nextRegion->SetProportions(0.0, nextProportion);
693 m_yoffset = (double)(y - dividedObject->GetY());
694
695 // Now reformat text
696 int i = 0;
697 node = dividedObject->GetRegions().GetFirst();
698 while (node)
699 {
700 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
701 if (region->GetText())
702 {
703 wxString s(region->GetText());
704 dividedObject->FormatText(dc, s.c_str(), i);
705 }
706 node = node->GetNext();
707 i++;
708 }
709 dividedObject->SetRegionSizes();
710 dividedObject->Draw(dc);
711 dividedObject->GetEventHandler()->OnMoveLinks(dc);
712 }
713