]> git.saurik.com Git - wxWidgets.git/blame - contrib/src/ogl/lines.cpp
added new files (after library split)
[wxWidgets.git] / contrib / src / ogl / lines.cpp
CommitLineData
1fc25a89
JS
1/////////////////////////////////////////////////////////////////////////////
2// Name: lines.cpp
3// Purpose: wxLineShape
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 "lines.h"
14#pragma implementation "linesp.h"
15#endif
16
17// For compilers that support precompilation, includes "wx.h".
92a19c2e 18#include "wx/wxprec.h"
1fc25a89
JS
19
20#ifdef __BORLANDC__
21#pragma hdrstop
22#endif
23
24#ifndef WX_PRECOMP
25#include <wx/wx.h>
26#endif
27
7c9955d1 28#include <wx/deprecated/wxexpr.h>
1fc25a89 29
3f1802b5
JS
30#ifdef new
31#undef new
32#endif
33
1fc25a89
JS
34#include <ctype.h>
35#include <math.h>
36
37#include <wx/ogl/basic.h>
38#include <wx/ogl/basicp.h>
39#include <wx/ogl/lines.h>
40#include <wx/ogl/linesp.h>
41#include <wx/ogl/drawn.h>
42#include <wx/ogl/misc.h>
43#include <wx/ogl/canvas.h>
44
45// Line shape
46IMPLEMENT_DYNAMIC_CLASS(wxLineShape, wxShape)
47
48wxLineShape::wxLineShape()
49{
50 m_sensitivity = OP_CLICK_LEFT | OP_CLICK_RIGHT;
51 m_draggable = FALSE;
52 m_attachmentTo = 0;
53 m_attachmentFrom = 0;
54/*
55 m_actualTextWidth = 0.0;
56 m_actualTextHeight = 0.0;
57*/
58 m_from = NULL;
59 m_to = NULL;
60 m_erasing = FALSE;
61 m_arrowSpacing = 5.0; // For the moment, don't bother saving this to file.
62 m_ignoreArrowOffsets = FALSE;
63 m_isSpline = FALSE;
64 m_maintainStraightLines = FALSE;
65 m_alignmentStart = 0;
66 m_alignmentEnd = 0;
67
68 m_lineControlPoints = NULL;
69
70 // Clear any existing regions (created in an earlier constructor)
71 // and make the three line regions.
72 ClearRegions();
73 wxShapeRegion *newRegion = new wxShapeRegion;
9e053640 74 newRegion->SetName(wxT("Middle"));
1fc25a89
JS
75 newRegion->SetSize(150, 50);
76 m_regions.Append((wxObject *)newRegion);
77
78 newRegion = new wxShapeRegion;
9e053640 79 newRegion->SetName(wxT("Start"));
1fc25a89
JS
80 newRegion->SetSize(150, 50);
81 m_regions.Append((wxObject *)newRegion);
82
83 newRegion = new wxShapeRegion;
9e053640 84 newRegion->SetName(wxT("End"));
1fc25a89
JS
85 newRegion->SetSize(150, 50);
86 m_regions.Append((wxObject *)newRegion);
87
88 for (int i = 0; i < 3; i++)
89 m_labelObjects[i] = NULL;
90}
91
92wxLineShape::~wxLineShape()
93{
94 if (m_lineControlPoints)
95 {
96 ClearPointList(*m_lineControlPoints);
97 delete m_lineControlPoints;
98 }
99 for (int i = 0; i < 3; i++)
100 {
101 if (m_labelObjects[i])
102 {
103 m_labelObjects[i]->Select(FALSE);
104 m_labelObjects[i]->RemoveFromCanvas(m_canvas);
105 delete m_labelObjects[i];
106 m_labelObjects[i] = NULL;
107 }
108 }
109 ClearArrowsAtPosition(-1);
110}
111
112void wxLineShape::MakeLineControlPoints(int n)
113{
114 if (m_lineControlPoints)
115 {
116 ClearPointList(*m_lineControlPoints);
117 delete m_lineControlPoints;
118 }
119 m_lineControlPoints = new wxList;
120
121 int i = 0;
122 for (i = 0; i < n; i++)
123 {
124 wxRealPoint *point = new wxRealPoint(-999, -999);
125 m_lineControlPoints->Append((wxObject*) point);
126 }
127}
128
129wxNode *wxLineShape::InsertLineControlPoint(wxDC* dc)
130{
131 if (dc)
132 Erase(*dc);
133
b9ac87bc
RD
134 wxNode *last = m_lineControlPoints->GetLast();
135 wxNode *second_last = last->GetPrevious();
136 wxRealPoint *last_point = (wxRealPoint *)last->GetData();
137 wxRealPoint *second_last_point = (wxRealPoint *)second_last->GetData();
1fc25a89
JS
138
139 // Choose a point half way between the last and penultimate points
140 double line_x = ((last_point->x + second_last_point->x)/2);
141 double line_y = ((last_point->y + second_last_point->y)/2);
142
143 wxRealPoint *point = new wxRealPoint(line_x, line_y);
144 wxNode *node = m_lineControlPoints->Insert(last, (wxObject*) point);
145 return node;
146}
147
148bool wxLineShape::DeleteLineControlPoint()
149{
b9ac87bc 150 if (m_lineControlPoints->GetCount() < 3)
1fc25a89
JS
151 return FALSE;
152
b9ac87bc
RD
153 wxNode *last = m_lineControlPoints->GetLast();
154 wxNode *second_last = last->GetPrevious();
1fc25a89 155
b9ac87bc 156 wxRealPoint *second_last_point = (wxRealPoint *)second_last->GetData();
1fc25a89
JS
157 delete second_last_point;
158 delete second_last;
159
160 return TRUE;
161}
162
163void wxLineShape::Initialise()
164{
165 if (m_lineControlPoints)
166 {
167 // Just move the first and last control points
b9ac87bc
RD
168 wxNode *first = m_lineControlPoints->GetFirst();
169 wxRealPoint *first_point = (wxRealPoint *)first->GetData();
1fc25a89 170
b9ac87bc
RD
171 wxNode *last = m_lineControlPoints->GetLast();
172 wxRealPoint *last_point = (wxRealPoint *)last->GetData();
1fc25a89
JS
173
174 // If any of the line points are at -999, we must
175 // initialize them by placing them half way between the first
176 // and the last.
b9ac87bc 177 wxNode *node = first->GetNext();
1fc25a89
JS
178 while (node)
179 {
b9ac87bc 180 wxRealPoint *point = (wxRealPoint *)node->GetData();
1fc25a89
JS
181 if (point->x == -999)
182 {
183 double x1, y1, x2, y2;
184 if (first_point->x < last_point->x)
185 { x1 = first_point->x; x2 = last_point->x; }
186 else
187 { x2 = first_point->x; x1 = last_point->x; }
188
189 if (first_point->y < last_point->y)
190 { y1 = first_point->y; y2 = last_point->y; }
191 else
192 { y2 = first_point->y; y1 = last_point->y; }
193
194 point->x = ((x2 - x1)/2 + x1);
195 point->y = ((y2 - y1)/2 + y1);
196 }
b9ac87bc 197 node = node->GetNext();
1fc25a89
JS
198 }
199 }
200}
201
202// Format a text string according to the region size, adding
203// strings with positions to region text list
204void wxLineShape::FormatText(wxDC& dc, const wxString& s, int i)
205{
206 double w, h;
207 ClearText(i);
208
b9ac87bc 209 if (m_regions.GetCount() < 1)
1fc25a89 210 return;
b9ac87bc 211 wxNode *node = m_regions.Item(i);
1fc25a89
JS
212 if (!node)
213 return;
214
b9ac87bc 215 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
1fc25a89
JS
216 region->SetText(s);
217 dc.SetFont(* region->GetFont());
218
219 region->GetSize(&w, &h);
220 // Initialize the size if zero
c1fa2fda 221 if (((w == 0) || (h == 0)) && (s.Length() > 0))
1fc25a89
JS
222 {
223 w = 100; h = 50;
224 region->SetSize(w, h);
225 }
226
227 wxStringList *string_list = oglFormatText(dc, s, (w-5), (h-5), region->GetFormatMode());
b9ac87bc 228 node = (wxNode*)string_list->GetFirst();
1fc25a89
JS
229 while (node)
230 {
b9ac87bc 231 wxChar *s = (wxChar *)node->GetData();
1fc25a89
JS
232 wxShapeTextLine *line = new wxShapeTextLine(0.0, 0.0, s);
233 region->GetFormattedText().Append((wxObject *)line);
b9ac87bc 234 node = node->GetNext();
1fc25a89
JS
235 }
236 delete string_list;
237 double actualW = w;
238 double actualH = h;
239 if (region->GetFormatMode() & FORMAT_SIZE_TO_CONTENTS)
240 {
241 oglGetCentredTextExtent(dc, &(region->GetFormattedText()), m_xpos, m_ypos, w, h, &actualW, &actualH);
242 if ((actualW != w ) || (actualH != h))
243 {
244 double xx, yy;
245 GetLabelPosition(i, &xx, &yy);
246 EraseRegion(dc, region, xx, yy);
247 if (m_labelObjects[i])
248 {
249 m_labelObjects[i]->Select(FALSE, &dc);
250 m_labelObjects[i]->Erase(dc);
251 m_labelObjects[i]->SetSize(actualW, actualH);
252 }
253
254 region->SetSize(actualW, actualH);
255
256 if (m_labelObjects[i])
257 {
258 m_labelObjects[i]->Select(TRUE, & dc);
259 m_labelObjects[i]->Draw(dc);
260 }
261 }
262 }
263 oglCentreText(dc, &(region->GetFormattedText()), m_xpos, m_ypos, actualW, actualH, region->GetFormatMode());
264 m_formatted = TRUE;
265}
266
267void wxLineShape::DrawRegion(wxDC& dc, wxShapeRegion *region, double x, double y)
268{
269 if (GetDisableLabel())
270 return;
271
272 double w, h;
273 double xx, yy;
274 region->GetSize(&w, &h);
275
276 // Get offset from x, y
277 region->GetPosition(&xx, &yy);
278
279 double xp = xx + x;
280 double yp = yy + y;
281
282 // First, clear a rectangle for the text IF there is any
b9ac87bc 283 if (region->GetFormattedText().GetCount() > 0)
1fc25a89 284 {
67d54b58
RD
285 dc.SetPen(GetBackgroundPen());
286 dc.SetBrush(GetBackgroundBrush());
1fc25a89
JS
287
288 // Now draw the text
289 if (region->GetFont()) dc.SetFont(* region->GetFont());
290
291 dc.DrawRectangle((long)(xp - w/2.0), (long)(yp - h/2.0), (long)w, (long)h);
292
293 if (m_pen) dc.SetPen(* m_pen);
294 dc.SetTextForeground(* region->GetActualColourObject());
295
296#ifdef __WXMSW__
67d54b58 297 dc.SetTextBackground(GetBackgroundBrush().GetColour());
1fc25a89
JS
298#endif
299
300 oglDrawFormattedText(dc, &(region->GetFormattedText()), xp, yp, w, h, region->GetFormatMode());
301 }
302}
303
304void wxLineShape::EraseRegion(wxDC& dc, wxShapeRegion *region, double x, double y)
305{
306 if (GetDisableLabel())
307 return;
308
309 double w, h;
310 double xx, yy;
311 region->GetSize(&w, &h);
312
313 // Get offset from x, y
314 region->GetPosition(&xx, &yy);
315
316 double xp = xx + x;
317 double yp = yy + y;
318
b9ac87bc 319 if (region->GetFormattedText().GetCount() > 0)
1fc25a89 320 {
67d54b58
RD
321 dc.SetPen(GetBackgroundPen());
322 dc.SetBrush(GetBackgroundBrush());
1fc25a89
JS
323
324 dc.DrawRectangle((long)(xp - w/2.0), (long)(yp - h/2.0), (long)w, (long)h);
325 }
326}
327
328// Get the reference point for a label. Region x and y
329// are offsets from this.
330// position is 0, 1, 2
331void wxLineShape::GetLabelPosition(int position, double *x, double *y)
332{
333 switch (position)
334 {
335 case 0:
336 {
337 // Want to take the middle section for the label
b9ac87bc 338 int n = m_lineControlPoints->GetCount();
1fc25a89
JS
339 int half_way = (int)(n/2);
340
341 // Find middle of this line
b9ac87bc
RD
342 wxNode *node = m_lineControlPoints->Item(half_way - 1);
343 wxRealPoint *point = (wxRealPoint *)node->GetData();
344 wxNode *next_node = node->GetNext();
345 wxRealPoint *next_point = (wxRealPoint *)next_node->GetData();
1fc25a89
JS
346
347 double dx = (next_point->x - point->x);
348 double dy = (next_point->y - point->y);
349 *x = (double)(point->x + dx/2.0);
350 *y = (double)(point->y + dy/2.0);
351 break;
352 }
353 case 1:
354 {
b9ac87bc
RD
355 wxNode *node = m_lineControlPoints->GetFirst();
356 *x = ((wxRealPoint *)node->GetData())->x;
357 *y = ((wxRealPoint *)node->GetData())->y;
1fc25a89
JS
358 break;
359 }
360 case 2:
361 {
b9ac87bc
RD
362 wxNode *node = m_lineControlPoints->GetLast();
363 *x = ((wxRealPoint *)node->GetData())->x;
364 *y = ((wxRealPoint *)node->GetData())->y;
1fc25a89
JS
365 break;
366 }
367 default:
368 break;
369 }
370}
371
372/*
373 * Find whether line is supposed to be vertical or horizontal and
374 * make it so.
375 *
376 */
377void GraphicsStraightenLine(wxRealPoint *point1, wxRealPoint *point2)
378{
379 double dx = point2->x - point1->x;
380 double dy = point2->y - point1->y;
381
382 if (dx == 0.0)
383 return;
384 else if (fabs(dy/dx) > 1.0)
385 {
386 point2->x = point1->x;
387 }
388 else point2->y = point1->y;
389}
390
391void wxLineShape::Straighten(wxDC *dc)
392{
b9ac87bc 393 if (!m_lineControlPoints || m_lineControlPoints->GetCount() < 3)
1fc25a89
JS
394 return;
395
396 if (dc)
397 Erase(* dc);
398
b9ac87bc
RD
399 wxNode *first_point_node = m_lineControlPoints->GetFirst();
400 wxNode *last_point_node = m_lineControlPoints->GetLast();
401 wxNode *second_last_point_node = last_point_node->GetPrevious();
1fc25a89 402
b9ac87bc
RD
403 wxRealPoint *last_point = (wxRealPoint *)last_point_node->GetData();
404 wxRealPoint *second_last_point = (wxRealPoint *)second_last_point_node->GetData();
1fc25a89
JS
405
406 GraphicsStraightenLine(last_point, second_last_point);
407
408 wxNode *node = first_point_node;
409 while (node && (node != second_last_point_node))
410 {
b9ac87bc
RD
411 wxRealPoint *point = (wxRealPoint *)node->GetData();
412 wxRealPoint *next_point = (wxRealPoint *)(node->GetNext()->GetData());
1fc25a89
JS
413
414 GraphicsStraightenLine(point, next_point);
b9ac87bc 415 node = node->GetNext();
1fc25a89
JS
416 }
417
418 if (dc)
419 Draw(* dc);
420}
421
422
423void wxLineShape::Unlink()
424{
425 if (m_to)
426 m_to->GetLines().DeleteObject(this);
427 if (m_from)
428 m_from->GetLines().DeleteObject(this);
429 m_to = NULL;
430 m_from = NULL;
431}
432
433void wxLineShape::SetEnds(double x1, double y1, double x2, double y2)
434{
435 // Find centre point
b9ac87bc
RD
436 wxNode *first_point_node = m_lineControlPoints->GetFirst();
437 wxNode *last_point_node = m_lineControlPoints->GetLast();
438 wxRealPoint *first_point = (wxRealPoint *)first_point_node->GetData();
439 wxRealPoint *last_point = (wxRealPoint *)last_point_node->GetData();
1fc25a89
JS
440
441 first_point->x = x1;
442 first_point->y = y1;
443 last_point->x = x2;
444 last_point->y = y2;
445
446 m_xpos = (double)((x1 + x2)/2.0);
447 m_ypos = (double)((y1 + y2)/2.0);
448}
449
450// Get absolute positions of ends
451void wxLineShape::GetEnds(double *x1, double *y1, double *x2, double *y2)
452{
b9ac87bc
RD
453 wxNode *first_point_node = m_lineControlPoints->GetFirst();
454 wxNode *last_point_node = m_lineControlPoints->GetLast();
455 wxRealPoint *first_point = (wxRealPoint *)first_point_node->GetData();
456 wxRealPoint *last_point = (wxRealPoint *)last_point_node->GetData();
1fc25a89
JS
457
458 *x1 = first_point->x; *y1 = first_point->y;
459 *x2 = last_point->x; *y2 = last_point->y;
460}
461
462void wxLineShape::SetAttachments(int from_attach, int to_attach)
463{
464 m_attachmentFrom = from_attach;
465 m_attachmentTo = to_attach;
466}
467
468bool wxLineShape::HitTest(double x, double y, int *attachment, double *distance)
469{
470 if (!m_lineControlPoints)
471 return FALSE;
472
473 // Look at label regions in case mouse is over a label
474 bool inLabelRegion = FALSE;
475 for (int i = 0; i < 3; i ++)
476 {
b9ac87bc 477 wxNode *regionNode = m_regions.Item(i);
1fc25a89
JS
478 if (regionNode)
479 {
b9ac87bc
RD
480 wxShapeRegion *region = (wxShapeRegion *)regionNode->GetData();
481 if (region->m_formattedText.GetCount() > 0)
1fc25a89
JS
482 {
483 double xp, yp, cx, cy, cw, ch;
484 GetLabelPosition(i, &xp, &yp);
485 // Offset region from default label position
486 region->GetPosition(&cx, &cy);
487 region->GetSize(&cw, &ch);
488 cx += xp;
489 cy += yp;
490 double rLeft = (double)(cx - (cw/2.0));
491 double rTop = (double)(cy - (ch/2.0));
492 double rRight = (double)(cx + (cw/2.0));
493 double rBottom = (double)(cy + (ch/2.0));
494 if (x > rLeft && x < rRight && y > rTop && y < rBottom)
495 {
496 inLabelRegion = TRUE;
497 i = 3;
498 }
499 }
500 }
501 }
502
b9ac87bc 503 wxNode *node = m_lineControlPoints->GetFirst();
1fc25a89 504
b9ac87bc 505 while (node && node->GetNext())
1fc25a89 506 {
b9ac87bc
RD
507 wxRealPoint *point1 = (wxRealPoint *)node->GetData();
508 wxRealPoint *point2 = (wxRealPoint *)node->GetNext()->GetData();
1fc25a89
JS
509
510 // Allow for inaccurate mousing or vert/horiz lines
511 int extra = 4;
512 double left = wxMin(point1->x, point2->x) - extra;
513 double right = wxMax(point1->x, point2->x) + extra;
514
515 double bottom = wxMin(point1->y, point2->y) - extra;
516 double top = wxMax(point1->y, point2->y) + extra;
517
518 if ((x > left && x < right && y > bottom && y < top) || inLabelRegion)
519 {
520 // Work out distance from centre of line
521 double centre_x = (double)(left + (right - left)/2.0);
522 double centre_y = (double)(bottom + (top - bottom)/2.0);
523
524 *attachment = 0;
525 *distance = (double)sqrt((centre_x - x)*(centre_x - x) + (centre_y - y)*(centre_y - y));
526 return TRUE;
527 }
528
b9ac87bc 529 node = node->GetNext();
1fc25a89
JS
530 }
531 return FALSE;
532}
533
534void wxLineShape::DrawArrows(wxDC& dc)
535{
536 // Distance along line of each arrow: space them out evenly.
537 double startArrowPos = 0.0;
538 double endArrowPos = 0.0;
539 double middleArrowPos = 0.0;
540
b9ac87bc 541 wxNode *node = m_arcArrows.GetFirst();
1fc25a89
JS
542 while (node)
543 {
b9ac87bc 544 wxArrowHead *arrow = (wxArrowHead *)node->GetData();
1fc25a89
JS
545 switch (arrow->GetArrowEnd())
546 {
547 case ARROW_POSITION_START:
548 {
549 if ((arrow->GetXOffset() != 0.0) && !m_ignoreArrowOffsets)
550 // If specified, x offset is proportional to line length
551 DrawArrow(dc, arrow, arrow->GetXOffset(), TRUE);
552 else
553 {
554 DrawArrow(dc, arrow, startArrowPos, FALSE); // Absolute distance
555 startArrowPos += arrow->GetSize() + arrow->GetSpacing();
556 }
557 break;
558 }
559 case ARROW_POSITION_END:
560 {
561 if ((arrow->GetXOffset() != 0.0) && !m_ignoreArrowOffsets)
562 DrawArrow(dc, arrow, arrow->GetXOffset(), TRUE);
563 else
564 {
565 DrawArrow(dc, arrow, endArrowPos, FALSE);
566 endArrowPos += arrow->GetSize() + arrow->GetSpacing();
567 }
568 break;
569 }
570 case ARROW_POSITION_MIDDLE:
571 {
572 arrow->SetXOffset(middleArrowPos);
573 if ((arrow->GetXOffset() != 0.0) && !m_ignoreArrowOffsets)
574 DrawArrow(dc, arrow, arrow->GetXOffset(), TRUE);
575 else
576 {
577 DrawArrow(dc, arrow, middleArrowPos, FALSE);
578 middleArrowPos += arrow->GetSize() + arrow->GetSpacing();
579 }
580 break;
581 }
582 }
b9ac87bc 583 node = node->GetNext();
1fc25a89
JS
584 }
585}
586
587void wxLineShape::DrawArrow(wxDC& dc, wxArrowHead *arrow, double xOffset, bool proportionalOffset)
588{
b9ac87bc
RD
589 wxNode *first_line_node = m_lineControlPoints->GetFirst();
590 wxRealPoint *first_line_point = (wxRealPoint *)first_line_node->GetData();
591 wxNode *second_line_node = first_line_node->GetNext();
592 wxRealPoint *second_line_point = (wxRealPoint *)second_line_node->GetData();
1fc25a89 593
b9ac87bc
RD
594 wxNode *last_line_node = m_lineControlPoints->GetLast();
595 wxRealPoint *last_line_point = (wxRealPoint *)last_line_node->GetData();
596 wxNode *second_last_line_node = last_line_node->GetPrevious();
597 wxRealPoint *second_last_line_point = (wxRealPoint *)second_last_line_node->GetData();
1fc25a89
JS
598
599 // Position where we want to start drawing
600 double positionOnLineX, positionOnLineY;
601
602 // Position of start point of line, at the end of which we draw the arrow.
603 double startPositionX, startPositionY;
604
605 switch (arrow->GetPosition())
606 {
607 case ARROW_POSITION_START:
608 {
609 // If we're using a proportional offset, calculate just where this will
610 // be on the line.
611 double realOffset = xOffset;
612 if (proportionalOffset)
613 {
614 double totalLength =
615 (double)sqrt((second_line_point->x - first_line_point->x)*(second_line_point->x - first_line_point->x) +
616 (second_line_point->y - first_line_point->y)*(second_line_point->y - first_line_point->y));
617 realOffset = (double)(xOffset * totalLength);
618 }
619 GetPointOnLine(second_line_point->x, second_line_point->y,
620 first_line_point->x, first_line_point->y,
621 realOffset, &positionOnLineX, &positionOnLineY);
622 startPositionX = second_line_point->x;
623 startPositionY = second_line_point->y;
624 break;
625 }
626 case ARROW_POSITION_END:
627 {
628 // If we're using a proportional offset, calculate just where this will
629 // be on the line.
630 double realOffset = xOffset;
631 if (proportionalOffset)
632 {
633 double totalLength =
634 (double)sqrt((second_last_line_point->x - last_line_point->x)*(second_last_line_point->x - last_line_point->x) +
635 (second_last_line_point->y - last_line_point->y)*(second_last_line_point->y - last_line_point->y));
636 realOffset = (double)(xOffset * totalLength);
637 }
638 GetPointOnLine(second_last_line_point->x, second_last_line_point->y,
639 last_line_point->x, last_line_point->y,
640 realOffset, &positionOnLineX, &positionOnLineY);
641 startPositionX = second_last_line_point->x;
642 startPositionY = second_last_line_point->y;
643 break;
644 }
645 case ARROW_POSITION_MIDDLE:
646 {
647 // Choose a point half way between the last and penultimate points
648 double x = ((last_line_point->x + second_last_line_point->x)/2);
649 double y = ((last_line_point->y + second_last_line_point->y)/2);
650
651 // If we're using a proportional offset, calculate just where this will
652 // be on the line.
653 double realOffset = xOffset;
654 if (proportionalOffset)
655 {
656 double totalLength =
657 (double)sqrt((second_last_line_point->x - x)*(second_last_line_point->x - x) +
658 (second_last_line_point->y - y)*(second_last_line_point->y - y));
659 realOffset = (double)(xOffset * totalLength);
660 }
661
662 GetPointOnLine(second_last_line_point->x, second_last_line_point->y,
663 x, y, realOffset, &positionOnLineX, &positionOnLineY);
664 startPositionX = second_last_line_point->x;
665 startPositionY = second_last_line_point->y;
666 break;
667 }
668 }
669
670 /*
671 * Add yOffset to arrow, if any
672 */
673
674 const double myPi = (double) 3.14159265;
675 // The translation that the y offset may give
676 double deltaX = 0.0;
677 double deltaY = 0.0;
678 if ((arrow->GetYOffset() != 0.0) && !m_ignoreArrowOffsets)
679 {
680 /*
681 |(x4, y4)
682 |d
683 |
684 (x1, y1)--------------(x3, y3)------------------(x2, y2)
685 x4 = x3 - d * sin(theta)
686 y4 = y3 + d * cos(theta)
687
688 Where theta = tan(-1) of (y3-y1)/(x3-x1)
689 */
690 double x1 = startPositionX;
691 double y1 = startPositionY;
692 double x3 = positionOnLineX;
693 double y3 = positionOnLineY;
694 double d = -arrow->GetYOffset(); // Negate so +offset is above line
695
696 double theta = 0.0;
697 if (x3 == x1)
698 theta = (double)(myPi/2.0);
699 else
700 theta = (double)atan((y3-y1)/(x3-x1));
701
702 double x4 = (double)(x3 - (d*sin(theta)));
703 double y4 = (double)(y3 + (d*cos(theta)));
704
705 deltaX = x4 - positionOnLineX;
706 deltaY = y4 - positionOnLineY;
707 }
708
709 switch (arrow->_GetType())
710 {
711 case ARROW_ARROW:
712 {
713 double arrowLength = arrow->GetSize();
714 double arrowWidth = (double)(arrowLength/3.0);
715
716 double tip_x, tip_y, side1_x, side1_y, side2_x, side2_y;
717 oglGetArrowPoints(startPositionX+deltaX, startPositionY+deltaY,
718 positionOnLineX+deltaX, positionOnLineY+deltaY,
719 arrowLength, arrowWidth, &tip_x, &tip_y,
720 &side1_x, &side1_y, &side2_x, &side2_y);
721
722 wxPoint points[4];
723 points[0].x = (int) tip_x; points[0].y = (int) tip_y;
724 points[1].x = (int) side1_x; points[1].y = (int) side1_y;
725 points[2].x = (int) side2_x; points[2].y = (int) side2_y;
726 points[3].x = (int) tip_x; points[3].y = (int) tip_y;
727
728 dc.SetPen(* m_pen);
729 dc.SetBrush(* m_brush);
730 dc.DrawPolygon(4, points);
731 break;
732 }
733 case ARROW_HOLLOW_CIRCLE:
734 case ARROW_FILLED_CIRCLE:
735 {
736 // Find point on line of centre of circle, which is a radius away
737 // from the end position
738 double diameter = (double)(arrow->GetSize());
739 double x, y;
740 GetPointOnLine(startPositionX+deltaX, startPositionY+deltaY,
741 positionOnLineX+deltaX, positionOnLineY+deltaY,
742 (double)(diameter/2.0),
743 &x, &y);
744
745 // Convert ellipse centre to top-left coordinates
746 double x1 = (double)(x - (diameter/2.0));
747 double y1 = (double)(y - (diameter/2.0));
748
749 dc.SetPen(* m_pen);
750 if (arrow->_GetType() == ARROW_HOLLOW_CIRCLE)
751 dc.SetBrush(* g_oglWhiteBackgroundBrush);
752 else
753 dc.SetBrush(* m_brush);
754
755 dc.DrawEllipse((long) x1, (long) y1, (long) diameter, (long) diameter);
756 break;
757 }
758 case ARROW_SINGLE_OBLIQUE:
759 {
760 break;
761 }
762 case ARROW_METAFILE:
763 {
764 if (arrow->GetMetaFile())
765 {
766 // Find point on line of centre of object, which is a half-width away
767 // from the end position
768 /*
769 * width
770 * <-- start pos <-----><-- positionOnLineX
771 * _____
772 * --------------| x | <-- e.g. rectangular arrowhead
773 * -----
774 */
775 double x, y;
776 GetPointOnLine(startPositionX, startPositionY,
777 positionOnLineX, positionOnLineY,
778 (double)(arrow->GetMetaFile()->m_width/2.0),
779 &x, &y);
780
781 // Calculate theta for rotating the metafile.
782 /*
783 |
784 | o(x2, y2) 'o' represents the arrowhead.
785 | /
786 | /
787 | /theta
788 | /(x1, y1)
789 |______________________
790 */
791 double theta = 0.0;
792 double x1 = startPositionX;
793 double y1 = startPositionY;
794 double x2 = positionOnLineX;
795 double y2 = positionOnLineY;
796
797 if ((x1 == x2) && (y1 == y2))
798 theta = 0.0;
799
800 else if ((x1 == x2) && (y1 > y2))
801 theta = (double)(3.0*myPi/2.0);
802
803 else if ((x1 == x2) && (y2 > y1))
804 theta = (double)(myPi/2.0);
805
806 else if ((x2 > x1) && (y2 >= y1))
807 theta = (double)atan((y2 - y1)/(x2 - x1));
808
809 else if (x2 < x1)
810 theta = (double)(myPi + atan((y2 - y1)/(x2 - x1)));
811
812 else if ((x2 > x1) && (y2 < y1))
813 theta = (double)(2*myPi + atan((y2 - y1)/(x2 - x1)));
814
815 else
816 {
c1fa2fda 817 wxLogFatalError(wxT("Unknown arrowhead rotation case in lines.cc"));
1fc25a89
JS
818 }
819
820 // Rotate about the centre of the object, then place
821 // the object on the line.
822 if (arrow->GetMetaFile()->GetRotateable())
823 arrow->GetMetaFile()->Rotate(0.0, 0.0, theta);
824
825 if (m_erasing)
826 {
827 // If erasing, just draw a rectangle.
828 double minX, minY, maxX, maxY;
829 arrow->GetMetaFile()->GetBounds(&minX, &minY, &maxX, &maxY);
830 // Make erasing rectangle slightly bigger or you get droppings.
831 int extraPixels = 4;
832 dc.DrawRectangle((long)(deltaX + x + minX - (extraPixels/2.0)), (long)(deltaY + y + minY - (extraPixels/2.0)),
833 (long)(maxX - minX + extraPixels), (long)(maxY - minY + extraPixels));
834 }
835 else
836 arrow->GetMetaFile()->Draw(dc, x+deltaX, y+deltaY);
837 }
838 break;
839 }
840 default:
841 {
842 }
843 }
844}
845
846void wxLineShape::OnErase(wxDC& dc)
847{
848 wxPen *old_pen = m_pen;
849 wxBrush *old_brush = m_brush;
67d54b58
RD
850 wxPen bg_pen = GetBackgroundPen();
851 wxBrush bg_brush = GetBackgroundBrush();
852 SetPen(&bg_pen);
853 SetBrush(&bg_brush);
1fc25a89
JS
854
855 double bound_x, bound_y;
856 GetBoundingBoxMax(&bound_x, &bound_y);
857 if (m_font) dc.SetFont(* m_font);
858
859 // Undraw text regions
860 for (int i = 0; i < 3; i++)
861 {
b9ac87bc 862 wxNode *node = m_regions.Item(i);
1fc25a89
JS
863 if (node)
864 {
865 double x, y;
b9ac87bc 866 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
1fc25a89
JS
867 GetLabelPosition(i, &x, &y);
868 EraseRegion(dc, region, x, y);
869 }
870 }
871
872 // Undraw line
67d54b58
RD
873 dc.SetPen(GetBackgroundPen());
874 dc.SetBrush(GetBackgroundBrush());
1fc25a89
JS
875
876 // Drawing over the line only seems to work if the line has a thickness
877 // of 1.
878 if (old_pen && (old_pen->GetWidth() > 1))
879 {
880 dc.DrawRectangle((long)(m_xpos - (bound_x/2.0) - 2.0), (long)(m_ypos - (bound_y/2.0) - 2.0),
881 (long)(bound_x+4.0), (long)(bound_y+4.0));
882 }
883 else
884 {
885 m_erasing = TRUE;
886 GetEventHandler()->OnDraw(dc);
887 GetEventHandler()->OnEraseControlPoints(dc);
888 m_erasing = FALSE;
889 }
890
891 if (old_pen) SetPen(old_pen);
892 if (old_brush) SetBrush(old_brush);
893}
894
895void wxLineShape::GetBoundingBoxMin(double *w, double *h)
896{
897 double x1 = 10000;
898 double y1 = 10000;
899 double x2 = -10000;
900 double y2 = -10000;
901
b9ac87bc 902 wxNode *node = m_lineControlPoints->GetFirst();
1fc25a89
JS
903 while (node)
904 {
b9ac87bc 905 wxRealPoint *point = (wxRealPoint *)node->GetData();
1fc25a89
JS
906
907 if (point->x < x1) x1 = point->x;
908 if (point->y < y1) y1 = point->y;
909 if (point->x > x2) x2 = point->x;
910 if (point->y > y2) y2 = point->y;
911
b9ac87bc 912 node = node->GetNext();
1fc25a89
JS
913 }
914 *w = (double)(x2 - x1);
915 *h = (double)(y2 - y1);
916}
917
918/*
919 * For a node image of interest, finds the position of this arc
920 * amongst all the arcs which are attached to THIS SIDE of the node image,
921 * and the number of same.
922 */
923void wxLineShape::FindNth(wxShape *image, int *nth, int *no_arcs, bool incoming)
924{
925 int n = -1;
926 int num = 0;
b9ac87bc 927 wxNode *node = image->GetLines().GetFirst();
1fc25a89
JS
928 int this_attachment;
929 if (image == m_to)
930 this_attachment = m_attachmentTo;
931 else
932 this_attachment = m_attachmentFrom;
933
934 // Find number of lines going into/out of this particular attachment point
935 while (node)
936 {
b9ac87bc 937 wxLineShape *line = (wxLineShape *)node->GetData();
1fc25a89
JS
938
939 if (line->m_from == image)
940 {
941 // This is the nth line attached to 'image'
942 if ((line == this) && !incoming)
943 n = num;
944
945 // Increment num count if this is the same side (attachment number)
946 if (line->m_attachmentFrom == this_attachment)
947 num ++;
948 }
949
950 if (line->m_to == image)
951 {
952 // This is the nth line attached to 'image'
953 if ((line == this) && incoming)
954 n = num;
955
956 // Increment num count if this is the same side (attachment number)
957 if (line->m_attachmentTo == this_attachment)
958 num ++;
959 }
960
b9ac87bc 961 node = node->GetNext();
1fc25a89
JS
962 }
963 *nth = n;
964 *no_arcs = num;
965}
966
967void wxLineShape::OnDrawOutline(wxDC& dc, double x, double y, double w, double h)
968{
969 wxPen *old_pen = m_pen;
970 wxBrush *old_brush = m_brush;
971
972 wxPen dottedPen(wxColour(0, 0, 0), 1, wxDOT);
973 SetPen(& dottedPen);
974 SetBrush( wxTRANSPARENT_BRUSH );
975
976 GetEventHandler()->OnDraw(dc);
977
978 if (old_pen) SetPen(old_pen);
979 else SetPen(NULL);
980 if (old_brush) SetBrush(old_brush);
981 else SetBrush(NULL);
982}
983
984bool wxLineShape::OnMovePre(wxDC& dc, double x, double y, double old_x, double old_y, bool display)
985{
986 double x_offset = x - old_x;
987 double y_offset = y - old_y;
988
989 if (m_lineControlPoints && !(x_offset == 0.0 && y_offset == 0.0))
990 {
b9ac87bc 991 wxNode *node = m_lineControlPoints->GetFirst();
1fc25a89
JS
992 while (node)
993 {
b9ac87bc 994 wxRealPoint *point = (wxRealPoint *)node->GetData();
1fc25a89
JS
995 point->x += x_offset;
996 point->y += y_offset;
b9ac87bc 997 node = node->GetNext();
1fc25a89
JS
998 }
999
1000 }
1001
1002 // Move temporary label rectangles if necessary
1003 for (int i = 0; i < 3; i++)
1004 {
1005 if (m_labelObjects[i])
1006 {
1007 m_labelObjects[i]->Erase(dc);
1008 double xp, yp, xr, yr;
1009 GetLabelPosition(i, &xp, &yp);
b9ac87bc 1010 wxNode *node = m_regions.Item(i);
1fc25a89
JS
1011 if (node)
1012 {
b9ac87bc 1013 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
1fc25a89
JS
1014 region->GetPosition(&xr, &yr);
1015 }
1016 else
1017 {
1018 xr = 0.0; yr = 0.0;
1019 }
1020
1021 m_labelObjects[i]->Move(dc, xp+xr, yp+yr);
1022 }
1023 }
1024 return TRUE;
1025}
1026
1027void wxLineShape::OnMoveLink(wxDC& dc, bool moveControlPoints)
1028{
1029 if (!m_from || !m_to)
1030 return;
1031
b9ac87bc 1032 if (m_lineControlPoints->GetCount() > 2)
1fc25a89
JS
1033 Initialise();
1034
1035 // Do each end - nothing in the middle. User has to move other points
1036 // manually if necessary.
1037 double end_x, end_y;
1038 double other_end_x, other_end_y;
1039
1040 FindLineEndPoints(&end_x, &end_y, &other_end_x, &other_end_y);
1041
b9ac87bc
RD
1042 wxNode *first = m_lineControlPoints->GetFirst();
1043 wxRealPoint *first_point = (wxRealPoint *)first->GetData();
1044 wxNode *last = m_lineControlPoints->GetLast();
1045 wxRealPoint *last_point = (wxRealPoint *)last->GetData();
1fc25a89
JS
1046
1047/* This is redundant, surely? Done by SetEnds.
1048 first_point->x = end_x; first_point->y = end_y;
1049 last_point->x = other_end_x; last_point->y = other_end_y;
1050*/
1051
1052 double oldX = m_xpos;
1053 double oldY = m_ypos;
1054
1055 SetEnds(end_x, end_y, other_end_x, other_end_y);
1056
1057 // Do a second time, because one may depend on the other.
1058 FindLineEndPoints(&end_x, &end_y, &other_end_x, &other_end_y);
1059 SetEnds(end_x, end_y, other_end_x, other_end_y);
1060
1061 // Try to move control points with the arc
1062 double x_offset = m_xpos - oldX;
1063 double y_offset = m_ypos - oldY;
1064
1065// if (moveControlPoints && m_lineControlPoints && !(x_offset == 0.0 && y_offset == 0.0))
1066 // Only move control points if it's a self link. And only works if attachment mode is ON.
1067 if ((m_from == m_to) && (m_from->GetAttachmentMode() != ATTACHMENT_MODE_NONE) && moveControlPoints && m_lineControlPoints && !(x_offset == 0.0 && y_offset == 0.0))
1068 {
b9ac87bc 1069 wxNode *node = m_lineControlPoints->GetFirst();
1fc25a89
JS
1070 while (node)
1071 {
b9ac87bc 1072 if ((node != m_lineControlPoints->GetFirst()) && (node != m_lineControlPoints->GetLast()))
1fc25a89 1073 {
b9ac87bc 1074 wxRealPoint *point = (wxRealPoint *)node->GetData();
1fc25a89
JS
1075 point->x += x_offset;
1076 point->y += y_offset;
1077 }
b9ac87bc 1078 node = node->GetNext();
1fc25a89
JS
1079 }
1080 }
1081
1082 Move(dc, m_xpos, m_ypos);
1083}
1084
1085// Finds the x, y points at the two ends of the line.
1086// This function can be used by e.g. line-routing routines to
1087// get the actual points on the two node images where the lines will be drawn
1088// to/from.
1089void wxLineShape::FindLineEndPoints(double *fromX, double *fromY, double *toX, double *toY)
1090{
1091 if (!m_from || !m_to)
1092 return;
1093
1094 // Do each end - nothing in the middle. User has to move other points
1095 // manually if necessary.
1096 double end_x, end_y;
1097 double other_end_x, other_end_y;
1098
b9ac87bc
RD
1099 wxNode *first = m_lineControlPoints->GetFirst();
1100 wxRealPoint *first_point = (wxRealPoint *)first->GetData();
1101 wxNode *last = m_lineControlPoints->GetLast();
1102 wxRealPoint *last_point = (wxRealPoint *)last->GetData();
1fc25a89 1103
b9ac87bc
RD
1104 wxNode *second = first->GetNext();
1105 wxRealPoint *second_point = (wxRealPoint *)second->GetData();
1fc25a89 1106
b9ac87bc
RD
1107 wxNode *second_last = last->GetPrevious();
1108 wxRealPoint *second_last_point = (wxRealPoint *)second_last->GetData();
1fc25a89 1109
b9ac87bc 1110 if (m_lineControlPoints->GetCount() > 2)
1fc25a89
JS
1111 {
1112 if (m_from->GetAttachmentMode() != ATTACHMENT_MODE_NONE)
1113 {
1114 int nth, no_arcs;
1115 FindNth(m_from, &nth, &no_arcs, FALSE); // Not incoming
1116 m_from->GetAttachmentPosition(m_attachmentFrom, &end_x, &end_y, nth, no_arcs, this);
1117 }
1118 else
1119 (void) m_from->GetPerimeterPoint(m_from->GetX(), m_from->GetY(),
1120 (double)second_point->x, (double)second_point->y,
1121 &end_x, &end_y);
1122
1123 if (m_to->GetAttachmentMode() != ATTACHMENT_MODE_NONE)
1124 {
1125 int nth, no_arcs;
1126 FindNth(m_to, &nth, &no_arcs, TRUE); // Incoming
1127 m_to->GetAttachmentPosition(m_attachmentTo, &other_end_x, &other_end_y, nth, no_arcs, this);
1128 }
1129 else
1130 (void) m_to->GetPerimeterPoint(m_to->GetX(), m_to->GetY(),
1131 (double)second_last_point->x, (double)second_last_point->y,
1132 &other_end_x, &other_end_y);
1133 }
1134 else
1135 {
1136 double fromX = m_from->GetX();
1137 double fromY = m_from->GetY();
1138 double toX = m_to->GetX();
1139 double toY = m_to->GetY();
1140
1141 if (m_from->GetAttachmentMode() != ATTACHMENT_MODE_NONE)
1142 {
1143 int nth, no_arcs;
1144 FindNth(m_from, &nth, &no_arcs, FALSE);
1145 m_from->GetAttachmentPosition(m_attachmentFrom, &end_x, &end_y, nth, no_arcs, this);
1146 fromX = end_x;
1147 fromY = end_y;
1148 }
1149
1150 if (m_to->GetAttachmentMode() != ATTACHMENT_MODE_NONE)
1151 {
1152 int nth, no_arcs;
1153 FindNth(m_to, &nth, &no_arcs, TRUE);
1154 m_to->GetAttachmentPosition(m_attachmentTo, &other_end_x, &other_end_y, nth, no_arcs, this);
1155 toX = other_end_x;
1156 toY = other_end_y;
1157 }
1158
1159 if (m_from->GetAttachmentMode() == ATTACHMENT_MODE_NONE)
1160 (void) m_from->GetPerimeterPoint(m_from->GetX(), m_from->GetY(),
1161 toX, toY,
1162 &end_x, &end_y);
1163
1164 if (m_to->GetAttachmentMode() == ATTACHMENT_MODE_NONE)
1165 (void) m_to->GetPerimeterPoint(m_to->GetX(), m_to->GetY(),
1166 fromX, fromY,
1167 &other_end_x, &other_end_y);
1168 }
1169 *fromX = end_x;
1170 *fromY = end_y;
1171 *toX = other_end_x;
1172 *toY = other_end_y;
1173}
1174
1175void wxLineShape::OnDraw(wxDC& dc)
1176{
1177 if (m_lineControlPoints)
1178 {
1179 if (m_pen)
1180 dc.SetPen(* m_pen);
1181 if (m_brush)
1182 dc.SetBrush(* m_brush);
1183
b9ac87bc 1184 int n = m_lineControlPoints->GetCount();
1fc25a89
JS
1185 wxPoint *points = new wxPoint[n];
1186 int i;
1187 for (i = 0; i < n; i++)
1188 {
b9ac87bc 1189 wxRealPoint* point = (wxRealPoint*) m_lineControlPoints->Item(i)->GetData();
1fc25a89
JS
1190 points[i].x = WXROUND(point->x);
1191 points[i].y = WXROUND(point->y);
1192 }
1193
1194 if (m_isSpline)
1195 dc.DrawSpline(n, points);
1196 else
1197 dc.DrawLines(n, points);
1198
1199#ifdef __WXMSW__
1200 // For some reason, last point isn't drawn under Windows.
1201 dc.DrawPoint(points[n-1]);
1202#endif
1203
1204 delete[] points;
1205
1206
1207 // Problem with pen - if not a solid pen, does strange things
1208 // to the arrowhead. So make (get) a new pen that's solid.
1209 if (m_pen && (m_pen->GetStyle() != wxSOLID))
1210 {
1211 wxPen *solid_pen =
1212 wxThePenList->FindOrCreatePen(m_pen->GetColour(), 1, wxSOLID);
1213 if (solid_pen)
1214 dc.SetPen(* solid_pen);
1215 }
1216 DrawArrows(dc);
1217 }
1218}
1219
1220void wxLineShape::OnDrawControlPoints(wxDC& dc)
1221{
1222 if (!m_drawHandles)
1223 return;
1224
1225 // Draw temporary label rectangles if necessary
1226 for (int i = 0; i < 3; i++)
1227 {
1228 if (m_labelObjects[i])
1229 m_labelObjects[i]->Draw(dc);
1230 }
1231 wxShape::OnDrawControlPoints(dc);
1232}
1233
1234void wxLineShape::OnEraseControlPoints(wxDC& dc)
1235{
1236 // Erase temporary label rectangles if necessary
1237 for (int i = 0; i < 3; i++)
1238 {
1239 if (m_labelObjects[i])
1240 m_labelObjects[i]->Erase(dc);
1241 }
1242 wxShape::OnEraseControlPoints(dc);
1243}
1244
1245void wxLineShape::OnDragLeft(bool draw, double x, double y, int keys, int attachment)
1246{
1247}
1248
1249void wxLineShape::OnBeginDragLeft(double x, double y, int keys, int attachment)
1250{
1251}
1252
1253void wxLineShape::OnEndDragLeft(double x, double y, int keys, int attachment)
1254{
1255}
1256
1257/*
1258void wxLineShape::SetArrowSize(double length, double width)
1259{
1260 arrow_length = length;
1261 arrow_width = width;
1262}
1263
1264void wxLineShape::SetStartArrow(int style)
1265{
1266 start_style = style;
1267}
1268
1269void wxLineShape::SetMiddleArrow(int style)
1270{
1271 middle_style = style;
1272}
1273
1274void wxLineShape::SetEndArrow(int style)
1275{
1276 end_style = style;
1277}
1278*/
1279
1280void wxLineShape::OnDrawContents(wxDC& dc)
1281{
1282 if (GetDisableLabel())
1283 return;
1284
1285 for (int i = 0; i < 3; i++)
1286 {
b9ac87bc 1287 wxNode *node = m_regions.Item(i);
1fc25a89
JS
1288 if (node)
1289 {
b9ac87bc 1290 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
1fc25a89
JS
1291 double x, y;
1292 GetLabelPosition(i, &x, &y);
1293 DrawRegion(dc, region, x, y);
1294 }
1295 }
1296}
1297
1298void wxLineShape::SetTo(wxShape *object)
1299{
1300 m_to = object;
1301}
1302
1303void wxLineShape::SetFrom(wxShape *object)
1304{
1305 m_from = object;
1306}
1307
1308void wxLineShape::MakeControlPoints()
1309{
1310 if (m_canvas && m_lineControlPoints)
1311 {
b9ac87bc
RD
1312 wxNode *first = m_lineControlPoints->GetFirst();
1313 wxNode *last = m_lineControlPoints->GetLast();
1314 wxRealPoint *first_point = (wxRealPoint *)first->GetData();
1315 wxRealPoint *last_point = (wxRealPoint *)last->GetData();
1fc25a89
JS
1316
1317 wxLineControlPoint *control = new wxLineControlPoint(m_canvas, this, CONTROL_POINT_SIZE,
1318 first_point->x, first_point->y,
1319 CONTROL_POINT_ENDPOINT_FROM);
1320 control->m_point = first_point;
1321 m_canvas->AddShape(control);
1322 m_controlPoints.Append(control);
1323
1324
b9ac87bc 1325 wxNode *node = first->GetNext();
1fc25a89
JS
1326 while (node != last)
1327 {
b9ac87bc 1328 wxRealPoint *point = (wxRealPoint *)node->GetData();
1fc25a89
JS
1329
1330 control = new wxLineControlPoint(m_canvas, this, CONTROL_POINT_SIZE,
1331 point->x, point->y,
1332 CONTROL_POINT_LINE);
1333 control->m_point = point;
1334
1335 m_canvas->AddShape(control);
1336 m_controlPoints.Append(control);
1337
b9ac87bc 1338 node = node->GetNext();
1fc25a89
JS
1339 }
1340 control = new wxLineControlPoint(m_canvas, this, CONTROL_POINT_SIZE,
1341 last_point->x, last_point->y,
1342 CONTROL_POINT_ENDPOINT_TO);
1343 control->m_point = last_point;
1344 m_canvas->AddShape(control);
1345 m_controlPoints.Append(control);
1346
1347 }
1348
1349}
1350
1351void wxLineShape::ResetControlPoints()
1352{
b9ac87bc 1353 if (m_canvas && m_lineControlPoints && m_controlPoints.GetCount() > 0)
1fc25a89 1354 {
b9ac87bc
RD
1355 wxNode *node = m_controlPoints.GetFirst();
1356 wxNode *control_node = m_lineControlPoints->GetFirst();
1fc25a89
JS
1357 while (node && control_node)
1358 {
b9ac87bc
RD
1359 wxRealPoint *point = (wxRealPoint *)control_node->GetData();
1360 wxLineControlPoint *control = (wxLineControlPoint *)node->GetData();
1fc25a89
JS
1361 control->SetX(point->x);
1362 control->SetY(point->y);
1363
b9ac87bc
RD
1364 node = node->GetNext();
1365 control_node = control_node->GetNext();
1fc25a89
JS
1366 }
1367 }
1368}
1369
2b5f62a0 1370#if wxUSE_PROLOGIO
1fc25a89
JS
1371void wxLineShape::WriteAttributes(wxExpr *clause)
1372{
1373 wxShape::WriteAttributes(clause);
1374
1375 if (m_from)
1376 clause->AddAttributeValue("from", m_from->GetId());
1377 if (m_to)
1378 clause->AddAttributeValue("to", m_to->GetId());
1379
1380 if (m_attachmentTo != 0)
1381 clause->AddAttributeValue("attachment_to", (long)m_attachmentTo);
1382 if (m_attachmentFrom != 0)
1383 clause->AddAttributeValue("attachment_from", (long)m_attachmentFrom);
1384
1385 if (m_alignmentStart != 0)
1386 clause->AddAttributeValue("align_start", (long)m_alignmentStart);
1387 if (m_alignmentEnd != 0)
1388 clause->AddAttributeValue("align_end", (long)m_alignmentEnd);
1389
1390 clause->AddAttributeValue("is_spline", (long)m_isSpline);
1391 if (m_maintainStraightLines)
1392 clause->AddAttributeValue("keep_lines_straight", (long)m_maintainStraightLines);
1393
1394 // Make a list of lists for the (sp)line controls
1395 wxExpr *list = new wxExpr(wxExprList);
b9ac87bc 1396 wxNode *node = m_lineControlPoints->GetFirst();
1fc25a89
JS
1397 while (node)
1398 {
b9ac87bc 1399 wxRealPoint *point = (wxRealPoint *)node->GetData();
1fc25a89
JS
1400 wxExpr *point_list = new wxExpr(wxExprList);
1401 wxExpr *x_expr = new wxExpr((double) point->x);
1402 wxExpr *y_expr = new wxExpr((double) point->y);
1403 point_list->Append(x_expr);
1404 point_list->Append(y_expr);
1405 list->Append(point_list);
1406
b9ac87bc 1407 node = node->GetNext();
1fc25a89
JS
1408 }
1409 clause->AddAttributeValue("controls", list);
1410
1411 // Write arc arrows in new OGL format, if there are any.
1412 // This is a list of lists. Each sublist comprises:
1413 // (arrowType arrowEnd xOffset arrowSize)
b9ac87bc 1414 if (m_arcArrows.GetCount() > 0)
1fc25a89
JS
1415 {
1416 wxExpr *arrow_list = new wxExpr(wxExprList);
b9ac87bc 1417 node = m_arcArrows.GetFirst();
1fc25a89
JS
1418 while (node)
1419 {
b9ac87bc 1420 wxArrowHead *head = (wxArrowHead *)node->GetData();
1fc25a89
JS
1421 wxExpr *head_list = new wxExpr(wxExprList);
1422 head_list->Append(new wxExpr((long)head->_GetType()));
1423 head_list->Append(new wxExpr((long)head->GetArrowEnd()));
1424 head_list->Append(new wxExpr(head->GetXOffset()));
1425 head_list->Append(new wxExpr(head->GetArrowSize()));
1426 head_list->Append(new wxExpr(wxExprString, head->GetName()));
1427 head_list->Append(new wxExpr(head->GetId()));
1428
1429 // New members of wxArrowHead
1430 head_list->Append(new wxExpr(head->GetYOffset()));
1431 head_list->Append(new wxExpr(head->GetSpacing()));
1432
1433 arrow_list->Append(head_list);
1434
b9ac87bc 1435 node = node->GetNext();
1fc25a89
JS
1436 }
1437 clause->AddAttributeValue("arrows", arrow_list);
1438 }
1439}
1440
1441void wxLineShape::ReadAttributes(wxExpr *clause)
1442{
1443 wxShape::ReadAttributes(clause);
1444
1445 int iVal = (int) m_isSpline;
c1fa2fda 1446 clause->AssignAttributeValue(wxT("is_spline"), &iVal);
1fc25a89
JS
1447 m_isSpline = (iVal != 0);
1448
1449 iVal = (int) m_maintainStraightLines;
c1fa2fda 1450 clause->AssignAttributeValue(wxT("keep_lines_straight"), &iVal);
1fc25a89
JS
1451 m_maintainStraightLines = (iVal != 0);
1452
c1fa2fda
RD
1453 clause->AssignAttributeValue(wxT("align_start"), &m_alignmentStart);
1454 clause->AssignAttributeValue(wxT("align_end"), &m_alignmentEnd);
1fc25a89
JS
1455
1456 // Compatibility: check for no regions.
b9ac87bc 1457 if (m_regions.GetCount() == 0)
1fc25a89
JS
1458 {
1459 wxShapeRegion *newRegion = new wxShapeRegion;
1460 newRegion->SetName("Middle");
1461 newRegion->SetSize(150, 50);
1462 m_regions.Append((wxObject *)newRegion);
b9ac87bc 1463 if (m_text.GetCount() > 0)
1fc25a89
JS
1464 {
1465 newRegion->ClearText();
b9ac87bc 1466 wxNode *node = m_text.GetFirst();
1fc25a89
JS
1467 while (node)
1468 {
b9ac87bc
RD
1469 wxShapeTextLine *textLine = (wxShapeTextLine *)node->GetData();
1470 wxNode *next = node->GetNext();
1fc25a89
JS
1471 newRegion->GetFormattedText().Append((wxObject *)textLine);
1472 delete node;
1473 node = next;
1474 }
1475 }
1476
1477 newRegion = new wxShapeRegion;
c1fa2fda 1478 newRegion->SetName(wxT("Start"));
1fc25a89
JS
1479 newRegion->SetSize(150, 50);
1480 m_regions.Append((wxObject *)newRegion);
1481
1482 newRegion = new wxShapeRegion;
c1fa2fda 1483 newRegion->SetName(wxT("End"));
1fc25a89
JS
1484 newRegion->SetSize(150, 50);
1485 m_regions.Append((wxObject *)newRegion);
1486 }
1487
1488 m_attachmentTo = 0;
1489 m_attachmentFrom = 0;
1490
c1fa2fda
RD
1491 clause->AssignAttributeValue(wxT("attachment_to"), &m_attachmentTo);
1492 clause->AssignAttributeValue(wxT("attachment_from"), &m_attachmentFrom);
1fc25a89
JS
1493
1494 wxExpr *line_list = NULL;
1495
1496 // When image is created, there are default control points. Override
1497 // them if there are some in the file.
c1fa2fda 1498 clause->AssignAttributeValue(wxT("controls"), &line_list);
1fc25a89
JS
1499
1500 if (line_list)
1501 {
1502 // Read a list of lists for the spline controls
1503 if (m_lineControlPoints)
1504 {
1505 ClearPointList(*m_lineControlPoints);
1506 }
1507 else
1508 m_lineControlPoints = new wxList;
1509
1510 wxExpr *node = line_list->value.first;
1511
1512 while (node)
1513 {
1514 wxExpr *xexpr = node->value.first;
1515 double x = xexpr->RealValue();
1516
1517 wxExpr *yexpr = xexpr->next;
1518 double y = yexpr->RealValue();
1519
1520 wxRealPoint *point = new wxRealPoint(x, y);
1521 m_lineControlPoints->Append((wxObject*) point);
1522
1523 node = node->next;
1524 }
1525 }
1526
1527 // Read arrow list, for new OGL code
1528 wxExpr *arrow_list = NULL;
1529
c1fa2fda 1530 clause->AssignAttributeValue(wxT("arrows"), &arrow_list);
1fc25a89
JS
1531 if (arrow_list)
1532 {
1533 wxExpr *node = arrow_list->value.first;
1534
1535 while (node)
1536 {
1537 WXTYPE arrowType = ARROW_ARROW;
1538 int arrowEnd = 0;
1539 double xOffset = 0.0;
1540 double arrowSize = 0.0;
c1fa2fda 1541 wxString arrowName;
1fc25a89
JS
1542 long arrowId = -1;
1543
7c9955d1
JS
1544 wxExpr *type_expr = node->Nth(0);
1545 wxExpr *end_expr = node->Nth(1);
1546 wxExpr *dist_expr = node->Nth(2);
1547 wxExpr *size_expr = node->Nth(3);
1548 wxExpr *name_expr = node->Nth(4);
1549 wxExpr *id_expr = node->Nth(5);
1fc25a89
JS
1550
1551 // New members of wxArrowHead
7c9955d1
JS
1552 wxExpr *yOffsetExpr = node->Nth(6);
1553 wxExpr *spacingExpr = node->Nth(7);
1fc25a89
JS
1554
1555 if (type_expr)
1556 arrowType = (int)type_expr->IntegerValue();
1557 if (end_expr)
1558 arrowEnd = (int)end_expr->IntegerValue();
1559 if (dist_expr)
1560 xOffset = dist_expr->RealValue();
1561 if (size_expr)
1562 arrowSize = size_expr->RealValue();
1563 if (name_expr)
1564 arrowName = name_expr->StringValue();
1565 if (id_expr)
1566 arrowId = id_expr->IntegerValue();
1567
1568 if (arrowId == -1)
1569 arrowId = wxNewId();
1570 else
1571 wxRegisterId(arrowId);
1572
c1fa2fda 1573 wxArrowHead *arrowHead = AddArrow(arrowType, arrowEnd, arrowSize, xOffset, arrowName, NULL, arrowId);
1fc25a89
JS
1574 if (yOffsetExpr)
1575 arrowHead->SetYOffset(yOffsetExpr->RealValue());
1576 if (spacingExpr)
1577 arrowHead->SetSpacing(spacingExpr->RealValue());
1578
1579 node = node->next;
1580 }
1581 }
1582}
1583#endif
1584
1585void wxLineShape::Copy(wxShape& copy)
1586{
1587 wxShape::Copy(copy);
1588
1589 wxASSERT( copy.IsKindOf(CLASSINFO(wxLineShape)) );
1590
1591 wxLineShape& lineCopy = (wxLineShape&) copy;
1592
1593 lineCopy.m_to = m_to;
1594 lineCopy.m_from = m_from;
1595 lineCopy.m_attachmentTo = m_attachmentTo;
1596 lineCopy.m_attachmentFrom = m_attachmentFrom;
1597 lineCopy.m_isSpline = m_isSpline;
1598 lineCopy.m_alignmentStart = m_alignmentStart;
1599 lineCopy.m_alignmentEnd = m_alignmentEnd;
1600 lineCopy.m_maintainStraightLines = m_maintainStraightLines;
1601 lineCopy.m_lineOrientations.Clear();
1602
b9ac87bc 1603 wxNode *node = m_lineOrientations.GetFirst();
1fc25a89
JS
1604 while (node)
1605 {
b9ac87bc
RD
1606 lineCopy.m_lineOrientations.Append(node->GetData());
1607 node = node->GetNext();
1fc25a89
JS
1608 }
1609
1610 if (lineCopy.m_lineControlPoints)
1611 {
1612 ClearPointList(*lineCopy.m_lineControlPoints);
1613 delete lineCopy.m_lineControlPoints;
1614 }
1615
1616 lineCopy.m_lineControlPoints = new wxList;
1617
b9ac87bc 1618 node = m_lineControlPoints->GetFirst();
1fc25a89
JS
1619 while (node)
1620 {
b9ac87bc 1621 wxRealPoint *point = (wxRealPoint *)node->GetData();
1fc25a89
JS
1622 wxRealPoint *new_point = new wxRealPoint(point->x, point->y);
1623 lineCopy.m_lineControlPoints->Append((wxObject*) new_point);
b9ac87bc 1624 node = node->GetNext();
1fc25a89
JS
1625 }
1626
1627 // Copy arrows
1628 lineCopy.ClearArrowsAtPosition(-1);
b9ac87bc 1629 node = m_arcArrows.GetFirst();
1fc25a89
JS
1630 while (node)
1631 {
b9ac87bc 1632 wxArrowHead *arrow = (wxArrowHead *)node->GetData();
1fc25a89 1633 lineCopy.m_arcArrows.Append(new wxArrowHead(*arrow));
b9ac87bc 1634 node = node->GetNext();
1fc25a89
JS
1635 }
1636}
1637
1638// Override select, to create/delete temporary label-moving objects
1639void wxLineShape::Select(bool select, wxDC* dc)
1640{
1641 wxShape::Select(select, dc);
1642 if (select)
1643 {
1644 for (int i = 0; i < 3; i++)
1645 {
b9ac87bc 1646 wxNode *node = m_regions.Item(i);
1fc25a89
JS
1647 if (node)
1648 {
b9ac87bc
RD
1649 wxShapeRegion *region = (wxShapeRegion *)node->GetData();
1650 if (region->m_formattedText.GetCount() > 0)
1fc25a89
JS
1651 {
1652 double w, h, x, y, xx, yy;
1653 region->GetSize(&w, &h);
1654 region->GetPosition(&x, &y);
1655 GetLabelPosition(i, &xx, &yy);
1656 if (m_labelObjects[i])
1657 {
1658 m_labelObjects[i]->Select(FALSE);
1659 m_labelObjects[i]->RemoveFromCanvas(m_canvas);
1660 delete m_labelObjects[i];
1661 }
1662 m_labelObjects[i] = OnCreateLabelShape(this, region, w, h);
1663 m_labelObjects[i]->AddToCanvas(m_canvas);
1664 m_labelObjects[i]->Show(TRUE);
1665 if (dc)
1666 m_labelObjects[i]->Move(*dc, (double)(x + xx), (double)(y + yy));
1667 m_labelObjects[i]->Select(TRUE, dc);
1668 }
1669 }
1670 }
1671 }
1672 else
1673 {
1674 for (int i = 0; i < 3; i++)
1675 {
1676 if (m_labelObjects[i])
1677 {
1678 m_labelObjects[i]->Select(FALSE, dc);
1679 m_labelObjects[i]->Erase(*dc);
1680 m_labelObjects[i]->RemoveFromCanvas(m_canvas);
1681 delete m_labelObjects[i];
1682 m_labelObjects[i] = NULL;
1683 }
1684 }
1685 }
1686}
1687
1688/*
1689 * Line control point
1690 *
1691 */
1692
1693IMPLEMENT_DYNAMIC_CLASS(wxLineControlPoint, wxControlPoint)
1694
1695wxLineControlPoint::wxLineControlPoint(wxShapeCanvas *theCanvas, wxShape *object, double size, double x, double y, int the_type):
1696 wxControlPoint(theCanvas, object, size, x, y, the_type)
1697{
1698 m_xpos = x;
1699 m_ypos = y;
1700 m_type = the_type;
1701 m_point = NULL;
1702}
1703
1704wxLineControlPoint::~wxLineControlPoint()
1705{
1706}
1707
1708void wxLineControlPoint::OnDraw(wxDC& dc)
1709{
1710 wxRectangleShape::OnDraw(dc);
1711}
1712
1713// Implement movement of Line point
1714void wxLineControlPoint::OnDragLeft(bool draw, double x, double y, int keys, int attachment)
1715{
1716 m_shape->GetEventHandler()->OnSizingDragLeft(this, draw, x, y, keys, attachment);
1717}
1718
1719void wxLineControlPoint::OnBeginDragLeft(double x, double y, int keys, int attachment)
1720{
1721 m_shape->GetEventHandler()->OnSizingBeginDragLeft(this, x, y, keys, attachment);
1722}
1723
1724void wxLineControlPoint::OnEndDragLeft(double x, double y, int keys, int attachment)
1725{
1726 m_shape->GetEventHandler()->OnSizingEndDragLeft(this, x, y, keys, attachment);
1727}
1728
1729// Control points ('handles') redirect control to the actual shape, to make it easier
1730// to override sizing behaviour.
1731void wxLineShape::OnSizingDragLeft(wxControlPoint* pt, bool draw, double x, double y, int keys, int attachment)
1732{
1733 wxLineControlPoint* lpt = (wxLineControlPoint*) pt;
1734
1735 wxClientDC dc(GetCanvas());
1736 GetCanvas()->PrepareDC(dc);
1737
1738 dc.SetLogicalFunction(OGLRBLF);
1739
1740 wxPen dottedPen(wxColour(0, 0, 0), 1, wxDOT);
1741 dc.SetPen(dottedPen);
1742 dc.SetBrush((* wxTRANSPARENT_BRUSH));
1743
1744 if (lpt->m_type == CONTROL_POINT_LINE)
1745 {
1746 m_canvas->Snap(&x, &y);
1747
1748 lpt->SetX(x); lpt->SetY(y);
1749 lpt->m_point->x = x; lpt->m_point->y = y;
1750
1751 wxLineShape *lineShape = (wxLineShape *)this;
1752
1753 wxPen *old_pen = lineShape->GetPen();
1754 wxBrush *old_brush = lineShape->GetBrush();
1755
1756 wxPen dottedPen(wxColour(0, 0, 0), 1, wxDOT);
1757 lineShape->SetPen(& dottedPen);
1758 lineShape->SetBrush(wxTRANSPARENT_BRUSH);
1759
1760 lineShape->GetEventHandler()->OnMoveLink(dc, FALSE);
1761
1762 lineShape->SetPen(old_pen);
1763 lineShape->SetBrush(old_brush);
1764 }
1765
1766 if (lpt->m_type == CONTROL_POINT_ENDPOINT_FROM || lpt->m_type == CONTROL_POINT_ENDPOINT_TO)
1767 {
1768// lpt->SetX(x); lpt->SetY(y);
1769 }
1770
1771}
1772
1773void wxLineShape::OnSizingBeginDragLeft(wxControlPoint* pt, double x, double y, int keys, int attachment)
1774{
1775 wxLineControlPoint* lpt = (wxLineControlPoint*) pt;
1776
1777 wxClientDC dc(GetCanvas());
1778 GetCanvas()->PrepareDC(dc);
1779
1780 wxLineShape *lineShape = (wxLineShape *)this;
1781 if (lpt->m_type == CONTROL_POINT_LINE)
1782 {
1783 lpt->m_originalPos = * (lpt->m_point);
1784 m_canvas->Snap(&x, &y);
1785
1786 this->Erase(dc);
1787
1788 // Redraw start and end objects because we've left holes
1789 // when erasing the line
1790 lineShape->GetFrom()->OnDraw(dc);
1791 lineShape->GetFrom()->OnDrawContents(dc);
1792 lineShape->GetTo()->OnDraw(dc);
1793 lineShape->GetTo()->OnDrawContents(dc);
1794
1795 this->SetDisableLabel(TRUE);
1796 dc.SetLogicalFunction(OGLRBLF);
1797
1798 lpt->m_xpos = x; lpt->m_ypos = y;
1799 lpt->m_point->x = x; lpt->m_point->y = y;
1800
1801 wxPen *old_pen = lineShape->GetPen();
1802 wxBrush *old_brush = lineShape->GetBrush();
1803
1804 wxPen dottedPen(wxColour(0, 0, 0), 1, wxDOT);
1805 lineShape->SetPen(& dottedPen);
1806 lineShape->SetBrush(wxTRANSPARENT_BRUSH);
1807
1808 lineShape->GetEventHandler()->OnMoveLink(dc, FALSE);
1809
1810 lineShape->SetPen(old_pen);
1811 lineShape->SetBrush(old_brush);
1812 }
1813
1814 if (lpt->m_type == CONTROL_POINT_ENDPOINT_FROM || lpt->m_type == CONTROL_POINT_ENDPOINT_TO)
1815 {
67d54b58 1816 m_canvas->SetCursor(wxCursor(wxCURSOR_BULLSEYE));
1fc25a89
JS
1817 lpt->m_oldCursor = wxSTANDARD_CURSOR;
1818 }
1819}
1820
1821void wxLineShape::OnSizingEndDragLeft(wxControlPoint* pt, double x, double y, int keys, int attachment)
1822{
1823 wxLineControlPoint* lpt = (wxLineControlPoint*) pt;
1824
1825 wxClientDC dc(GetCanvas());
1826 GetCanvas()->PrepareDC(dc);
1827
1828 this->SetDisableLabel(FALSE);
1829 wxLineShape *lineShape = (wxLineShape *)this;
1830
1831 if (lpt->m_type == CONTROL_POINT_LINE)
1832 {
1833 m_canvas->Snap(&x, &y);
1834
1835 wxRealPoint pt = wxRealPoint(x, y);
1836
1837 // Move the control point back to where it was;
1838 // MoveControlPoint will move it to the new position
1839 // if it decides it wants. We only moved the position
1840 // during user feedback so we could redraw the line
1841 // as it changed shape.
1842 lpt->m_xpos = lpt->m_originalPos.x; lpt->m_ypos = lpt->m_originalPos.y;
1843 lpt->m_point->x = lpt->m_originalPos.x; lpt->m_point->y = lpt->m_originalPos.y;
1844
1845 OnMoveMiddleControlPoint(dc, lpt, pt);
1846 }
1847 if (lpt->m_type == CONTROL_POINT_ENDPOINT_FROM)
1848 {
1849 if (lpt->m_oldCursor)
1850 m_canvas->SetCursor(* lpt->m_oldCursor);
1851
1852// this->Erase(dc);
1853
1854// lpt->m_xpos = x; lpt->m_ypos = y;
1855
1856 if (lineShape->GetFrom())
1857 {
1858 lineShape->GetFrom()->MoveLineToNewAttachment(dc, lineShape, x, y);
1859 }
1860 }
1861 if (lpt->m_type == CONTROL_POINT_ENDPOINT_TO)
1862 {
1863 if (lpt->m_oldCursor)
1864 m_canvas->SetCursor(* lpt->m_oldCursor);
1865
1866// lpt->m_xpos = x; lpt->m_ypos = y;
1867
1868 if (lineShape->GetTo())
1869 {
1870 lineShape->GetTo()->MoveLineToNewAttachment(dc, lineShape, x, y);
1871 }
1872 }
1873
1874 // Needed?
1875#if 0
1876 int i = 0;
b9ac87bc
RD
1877 for (i = 0; i < lineShape->GetLineControlPoints()->GetCount(); i++)
1878 if (((wxRealPoint *)(lineShape->GetLineControlPoints()->Item(i)->GetData())) == lpt->m_point)
1fc25a89
JS
1879 break;
1880
1881 // N.B. in OnMoveControlPoint, an event handler in Hardy could have deselected
1882 // the line and therefore deleted 'this'. -> GPF, intermittently.
1883 // So assume at this point that we've been blown away.
1884
1885 lineShape->OnMoveControlPoint(i+1, x, y);
1886#endif
1887}
1888
1889// This is called only when a non-end control point is moved.
1890bool wxLineShape::OnMoveMiddleControlPoint(wxDC& dc, wxLineControlPoint* lpt, const wxRealPoint& pt)
1891{
1892 lpt->m_xpos = pt.x; lpt->m_ypos = pt.y;
1893 lpt->m_point->x = pt.x; lpt->m_point->y = pt.y;
1894
1895 GetEventHandler()->OnMoveLink(dc);
1896
1897 return TRUE;
1898}
1899
1900// Implement movement of endpoint to a new attachment
1901// OBSOLETE: done by dragging with the left button.
1902
1903#if 0
1904void wxLineControlPoint::OnDragRight(bool draw, double x, double y, int keys, int attachment)
1905{
1906 if (m_type == CONTROL_POINT_ENDPOINT_FROM || m_type == CONTROL_POINT_ENDPOINT_TO)
1907 {
1908 m_xpos = x; m_ypos = y;
1909 }
1910}
1911
1912void wxLineControlPoint::OnBeginDragRight(double x, double y, int keys, int attachment)
1913{
1914 wxClientDC dc(GetCanvas());
1915 GetCanvas()->PrepareDC(dc);
1916
1917 wxLineShape *lineShape = (wxLineShape *)m_shape;
1918 if (m_type == CONTROL_POINT_ENDPOINT_FROM || m_type == CONTROL_POINT_ENDPOINT_TO)
1919 {
1920 Erase(dc);
1921 lineShape->GetEventHandler()->OnDraw(dc);
1922 if (m_type == CONTROL_POINT_ENDPOINT_FROM)
1923 {
1924 lineShape->GetFrom()->GetEventHandler()->OnDraw(dc);
1925 lineShape->GetFrom()->GetEventHandler()->OnDrawContents(dc);
1926 }
1927 else
1928 {
1929 lineShape->GetTo()->GetEventHandler()->OnDraw(dc);
1930 lineShape->GetTo()->GetEventHandler()->OnDrawContents(dc);
1931 }
67d54b58 1932 m_canvas->SetCursor(wxCursor(wxCURSOR_BULLSEYE));
1fc25a89
JS
1933 m_oldCursor = wxSTANDARD_CURSOR;
1934 }
1935}
1936
1937void wxLineControlPoint::OnEndDragRight(double x, double y, int keys, int attachment)
1938{
1939 wxClientDC dc(GetCanvas());
1940 GetCanvas()->PrepareDC(dc);
1941
1942 wxLineShape *lineShape = (wxLineShape *)m_shape;
1943 if (m_type == CONTROL_POINT_ENDPOINT_FROM)
1944 {
1945 if (m_oldCursor)
1946 m_canvas->SetCursor(m_oldCursor);
1947
1948 m_xpos = x; m_ypos = y;
1949
1950 if (lineShape->GetFrom())
1951 {
1952 lineShape->GetFrom()->EraseLinks(dc);
1953
1954 int new_attachment;
1955 double distance;
1956
1957 if (lineShape->GetFrom()->HitTest(x, y, &new_attachment, &distance))
1958 lineShape->SetAttachments(new_attachment, lineShape->GetAttachmentTo());
1959
1960 lineShape->GetFrom()->MoveLinks(dc);
1961 }
1962 }
1963 if (m_type == CONTROL_POINT_ENDPOINT_TO)
1964 {
1965 if (m_oldCursor)
1966 m_canvas->SetCursor(m_oldCursor);
1967 m_shape->Erase(dc);
1968
1969 m_xpos = x; m_ypos = y;
1970
1971 if (lineShape->GetTo())
1972 {
1973 lineShape->GetTo()->EraseLinks(dc);
1974
1975 int new_attachment;
1976 double distance;
1977 if (lineShape->GetTo()->HitTest(x, y, &new_attachment, &distance))
1978 lineShape->SetAttachments(lineShape->GetAttachmentFrom(), new_attachment);
1979
1980 lineShape->GetTo()->MoveLinks(dc);
1981 }
1982 }
1983 int i = 0;
b9ac87bc
RD
1984 for (i = 0; i < lineShape->GetLineControlPoints()->GetCount(); i++)
1985 if (((wxRealPoint *)(lineShape->GetLineControlPoints()->Item(i)->GetData())) == m_point)
1fc25a89
JS
1986 break;
1987 lineShape->OnMoveControlPoint(i+1, x, y);
1988 if (!m_canvas->GetQuickEditMode()) m_canvas->Redraw(dc);
1989}
1990#endif
1991
1992/*
1993 * Get the point on the given line (x1, y1) (x2, y2)
1994 * distance 'length' along from the end,
1995 * returned values in x and y
1996 */
1997
1998void GetPointOnLine(double x1, double y1, double x2, double y2,
1999 double length, double *x, double *y)
2000{
2001 double l = (double)sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
2002
2003 if (l < 0.01)
2004 l = (double) 0.01;
2005
2006 double i_bar = (x2 - x1)/l;
2007 double j_bar = (y2 - y1)/l;
2008
2009 *x = (- length*i_bar) + x2;
2010 *y = (- length*j_bar) + y2;
2011}
2012
2013wxArrowHead *wxLineShape::AddArrow(WXTYPE type, int end, double size, double xOffset,
2014 const wxString& name, wxPseudoMetaFile *mf, long arrowId)
2015{
2016 wxArrowHead *arrow = new wxArrowHead(type, end, size, xOffset, name, mf, arrowId);
2017 m_arcArrows.Append(arrow);
2018 return arrow;
2019}
2020
2021/*
2022 * Add arrowhead at a particular position in the arrowhead list.
2023 */
2024bool wxLineShape::AddArrowOrdered(wxArrowHead *arrow, wxList& referenceList, int end)
2025{
b9ac87bc
RD
2026 wxNode *refNode = referenceList.GetFirst();
2027 wxNode *currNode = m_arcArrows.GetFirst();
1fc25a89
JS
2028 wxString targetName(arrow->GetName());
2029 if (!refNode) return FALSE;
2030
2031 // First check whether we need to insert in front of list,
2032 // because this arrowhead is the first in the reference
2033 // list and should therefore be first in the current list.
b9ac87bc 2034 wxArrowHead *refArrow = (wxArrowHead *)refNode->GetData();
1fc25a89
JS
2035 if (refArrow->GetName() == targetName)
2036 {
2037 m_arcArrows.Insert(arrow);
2038 return TRUE;
2039 }
2040
2041 while (refNode && currNode)
2042 {
b9ac87bc
RD
2043 wxArrowHead *currArrow = (wxArrowHead *)currNode->GetData();
2044 refArrow = (wxArrowHead *)refNode->GetData();
1fc25a89
JS
2045
2046 // Matching: advance current arrow pointer
2047 if ((currArrow->GetArrowEnd() == end) &&
2048 (currArrow->GetName() == refArrow->GetName()))
2049 {
b9ac87bc 2050 currNode = currNode->GetNext(); // Could be NULL now
1fc25a89 2051 if (currNode)
b9ac87bc 2052 currArrow = (wxArrowHead *)currNode->GetData();
1fc25a89
JS
2053 }
2054
2055 // Check if we're at the correct position in the
2056 // reference list
2057 if (targetName == refArrow->GetName())
2058 {
2059 if (currNode)
2060 m_arcArrows.Insert(currNode, arrow);
2061 else
2062 m_arcArrows.Append(arrow);
2063 return TRUE;
2064 }
b9ac87bc 2065 refNode = refNode->GetNext();
1fc25a89
JS
2066 }
2067 m_arcArrows.Append(arrow);
2068 return TRUE;
2069}
2070
2071void wxLineShape::ClearArrowsAtPosition(int end)
2072{
b9ac87bc 2073 wxNode *node = m_arcArrows.GetFirst();
1fc25a89
JS
2074 while (node)
2075 {
b9ac87bc
RD
2076 wxArrowHead *arrow = (wxArrowHead *)node->GetData();
2077 wxNode *next = node->GetNext();
1fc25a89
JS
2078 switch (end)
2079 {
2080 case -1:
2081 {
2082 delete arrow;
2083 delete node;
2084 break;
2085 }
2086 case ARROW_POSITION_START:
2087 {
2088 if (arrow->GetArrowEnd() == ARROW_POSITION_START)
2089 {
2090 delete arrow;
2091 delete node;
2092 }
2093 break;
2094 }
2095 case ARROW_POSITION_END:
2096 {
2097 if (arrow->GetArrowEnd() == ARROW_POSITION_END)
2098 {
2099 delete arrow;
2100 delete node;
2101 }
2102 break;
2103 }
2104 case ARROW_POSITION_MIDDLE:
2105 {
2106 if (arrow->GetArrowEnd() == ARROW_POSITION_MIDDLE)
2107 {
2108 delete arrow;
2109 delete node;
2110 }
2111 break;
2112 }
2113 }
2114 node = next;
2115 }
2116}
2117
2118bool wxLineShape::ClearArrow(const wxString& name)
2119{
b9ac87bc 2120 wxNode *node = m_arcArrows.GetFirst();
1fc25a89
JS
2121 while (node)
2122 {
b9ac87bc 2123 wxArrowHead *arrow = (wxArrowHead *)node->GetData();
1fc25a89
JS
2124 if (arrow->GetName() == name)
2125 {
2126 delete arrow;
2127 delete node;
2128 return TRUE;
2129 }
b9ac87bc 2130 node = node->GetNext();
1fc25a89
JS
2131 }
2132 return FALSE;
2133}
2134
2135/*
2136 * Finds an arrowhead at the given position (if -1, any position)
2137 *
2138 */
2139
2140wxArrowHead *wxLineShape::FindArrowHead(int position, const wxString& name)
2141{
b9ac87bc 2142 wxNode *node = m_arcArrows.GetFirst();
1fc25a89
JS
2143 while (node)
2144 {
b9ac87bc 2145 wxArrowHead *arrow = (wxArrowHead *)node->GetData();
1fc25a89
JS
2146 if (((position == -1) || (position == arrow->GetArrowEnd())) &&
2147 (arrow->GetName() == name))
2148 return arrow;
b9ac87bc 2149 node = node->GetNext();
1fc25a89
JS
2150 }
2151 return NULL;
2152}
2153
2154wxArrowHead *wxLineShape::FindArrowHead(long arrowId)
2155{
b9ac87bc 2156 wxNode *node = m_arcArrows.GetFirst();
1fc25a89
JS
2157 while (node)
2158 {
b9ac87bc 2159 wxArrowHead *arrow = (wxArrowHead *)node->GetData();
1fc25a89
JS
2160 if (arrowId == arrow->GetId())
2161 return arrow;
b9ac87bc 2162 node = node->GetNext();
1fc25a89
JS
2163 }
2164 return NULL;
2165}
2166
2167/*
2168 * Deletes an arrowhead at the given position (if -1, any position)
2169 *
2170 */
2171
2172bool wxLineShape::DeleteArrowHead(int position, const wxString& name)
2173{
b9ac87bc 2174 wxNode *node = m_arcArrows.GetFirst();
1fc25a89
JS
2175 while (node)
2176 {
b9ac87bc 2177 wxArrowHead *arrow = (wxArrowHead *)node->GetData();
1fc25a89
JS
2178 if (((position == -1) || (position == arrow->GetArrowEnd())) &&
2179 (arrow->GetName() == name))
2180 {
2181 delete arrow;
2182 delete node;
2183 return TRUE;
2184 }
b9ac87bc 2185 node = node->GetNext();
1fc25a89
JS
2186 }
2187 return FALSE;
2188}
2189
2190// Overloaded DeleteArrowHead: pass arrowhead id.
2191bool wxLineShape::DeleteArrowHead(long id)
2192{
b9ac87bc 2193 wxNode *node = m_arcArrows.GetFirst();
1fc25a89
JS
2194 while (node)
2195 {
b9ac87bc 2196 wxArrowHead *arrow = (wxArrowHead *)node->GetData();
1fc25a89
JS
2197 if (arrow->GetId() == id)
2198 {
2199 delete arrow;
2200 delete node;
2201 return TRUE;
2202 }
b9ac87bc 2203 node = node->GetNext();
1fc25a89
JS
2204 }
2205 return FALSE;
2206}
2207
2208/*
2209 * Calculate the minimum width a line
2210 * occupies, for the purposes of drawing lines in tools.
2211 *
2212 */
2213
2214double wxLineShape::FindMinimumWidth()
2215{
2216 double minWidth = 0.0;
b9ac87bc 2217 wxNode *node = m_arcArrows.GetFirst();
1fc25a89
JS
2218 while (node)
2219 {
b9ac87bc 2220 wxArrowHead *arrowHead = (wxArrowHead *)node->GetData();
1fc25a89 2221 minWidth += arrowHead->GetSize();
b9ac87bc 2222 if (node->GetNext())
1fc25a89
JS
2223 minWidth += arrowHead->GetSpacing();
2224
b9ac87bc 2225 node = node->GetNext();
1fc25a89
JS
2226 }
2227 // We have ABSOLUTE minimum now. So
2228 // scale it to give it reasonable aesthetics
2229 // when drawing with line.
2230 if (minWidth > 0.0)
2231 minWidth = (double)(minWidth * 1.4);
2232 else
2233 minWidth = 20.0;
2234
2235 SetEnds(0.0, 0.0, minWidth, 0.0);
2236 Initialise();
2237
2238 return minWidth;
2239}
2240
2241// Find which position we're talking about at this (x, y).
2242// Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END
2243int wxLineShape::FindLinePosition(double x, double y)
2244{
2245 double startX, startY, endX, endY;
2246 GetEnds(&startX, &startY, &endX, &endY);
2247
2248 // Find distances from centre, start and end. The smallest wins.
2249 double centreDistance = (double)(sqrt((x - m_xpos)*(x - m_xpos) + (y - m_ypos)*(y - m_ypos)));
2250 double startDistance = (double)(sqrt((x - startX)*(x - startX) + (y - startY)*(y - startY)));
2251 double endDistance = (double)(sqrt((x - endX)*(x - endX) + (y - endY)*(y - endY)));
2252
2253 if (centreDistance < startDistance && centreDistance < endDistance)
2254 return ARROW_POSITION_MIDDLE;
2255 else if (startDistance < endDistance)
2256 return ARROW_POSITION_START;
2257 else
2258 return ARROW_POSITION_END;
2259}
2260
2261// Set alignment flags
2262void wxLineShape::SetAlignmentOrientation(bool isEnd, bool isHoriz)
2263{
2264 if (isEnd)
2265 {
2266 if (isHoriz && ((m_alignmentEnd & LINE_ALIGNMENT_HORIZ) != LINE_ALIGNMENT_HORIZ))
2267 m_alignmentEnd |= LINE_ALIGNMENT_HORIZ;
2268 else if (!isHoriz && ((m_alignmentEnd & LINE_ALIGNMENT_HORIZ) == LINE_ALIGNMENT_HORIZ))
2269 m_alignmentEnd -= LINE_ALIGNMENT_HORIZ;
2270 }
2271 else
2272 {
2273 if (isHoriz && ((m_alignmentStart & LINE_ALIGNMENT_HORIZ) != LINE_ALIGNMENT_HORIZ))
2274 m_alignmentStart |= LINE_ALIGNMENT_HORIZ;
2275 else if (!isHoriz && ((m_alignmentStart & LINE_ALIGNMENT_HORIZ) == LINE_ALIGNMENT_HORIZ))
2276 m_alignmentStart -= LINE_ALIGNMENT_HORIZ;
2277 }
2278}
2279
2280void wxLineShape::SetAlignmentType(bool isEnd, int alignType)
2281{
2282 if (isEnd)
2283 {
2284 if (alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE)
2285 {
2286 if ((m_alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE) != LINE_ALIGNMENT_TO_NEXT_HANDLE)
2287 m_alignmentEnd |= LINE_ALIGNMENT_TO_NEXT_HANDLE;
2288 }
2289 else if ((m_alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE) == LINE_ALIGNMENT_TO_NEXT_HANDLE)
2290 m_alignmentEnd -= LINE_ALIGNMENT_TO_NEXT_HANDLE;
2291 }
2292 else
2293 {
2294 if (alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE)
2295 {
2296 if ((m_alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE) != LINE_ALIGNMENT_TO_NEXT_HANDLE)
2297 m_alignmentStart |= LINE_ALIGNMENT_TO_NEXT_HANDLE;
2298 }
2299 else if ((m_alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE) == LINE_ALIGNMENT_TO_NEXT_HANDLE)
2300 m_alignmentStart -= LINE_ALIGNMENT_TO_NEXT_HANDLE;
2301 }
2302}
2303
2304bool wxLineShape::GetAlignmentOrientation(bool isEnd)
2305{
2306 if (isEnd)
2307 return ((m_alignmentEnd & LINE_ALIGNMENT_HORIZ) == LINE_ALIGNMENT_HORIZ);
2308 else
2309 return ((m_alignmentStart & LINE_ALIGNMENT_HORIZ) == LINE_ALIGNMENT_HORIZ);
2310}
2311
2312int wxLineShape::GetAlignmentType(bool isEnd)
2313{
2314 if (isEnd)
2315 return (m_alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE);
2316 else
2317 return (m_alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE);
2318}
2319
2320wxRealPoint *wxLineShape::GetNextControlPoint(wxShape *nodeObject)
2321{
b9ac87bc 2322 int n = m_lineControlPoints->GetCount();
1fc25a89
JS
2323 int nn = 0;
2324 if (m_to == nodeObject)
2325 {
2326 // Must be END of line, so we want (n - 1)th control point.
2327 // But indexing ends at n-1, so subtract 2.
2328 nn = n - 2;
2329 }
2330 else nn = 1;
b9ac87bc 2331 wxNode *node = m_lineControlPoints->Item(nn);
1fc25a89
JS
2332 if (node)
2333 {
b9ac87bc 2334 return (wxRealPoint *)node->GetData();
1fc25a89
JS
2335 }
2336 else
2337 return FALSE;
2338}
2339
2340/*
2341 * Arrowhead
2342 *
2343 */
2344
2345IMPLEMENT_DYNAMIC_CLASS(wxArrowHead, wxObject)
2346
2347wxArrowHead::wxArrowHead(WXTYPE type, int end, double size, double dist, const wxString& name,
2348 wxPseudoMetaFile *mf, long arrowId)
2349{
2350 m_arrowType = type; m_arrowEnd = end; m_arrowSize = size;
2351 m_xOffset = dist;
2352 m_yOffset = 0.0;
2353 m_spacing = 5.0;
2354
2355 m_arrowName = name;
2356 m_metaFile = mf;
2357 m_id = arrowId;
2358 if (m_id == -1)
2359 m_id = wxNewId();
2360}
2361
2362wxArrowHead::wxArrowHead(wxArrowHead& toCopy)
2363{
2364 m_arrowType = toCopy.m_arrowType; m_arrowEnd = toCopy.GetArrowEnd();
2365 m_arrowSize = toCopy.m_arrowSize;
2366 m_xOffset = toCopy.m_xOffset;
2367 m_yOffset = toCopy.m_yOffset;
2368 m_spacing = toCopy.m_spacing;
2369 m_arrowName = toCopy.m_arrowName ;
2370 if (toCopy.m_metaFile)
2371 m_metaFile = new wxPseudoMetaFile(*(toCopy.m_metaFile));
2372 else
2373 m_metaFile = NULL;
2374 m_id = wxNewId();
2375}
2376
2377wxArrowHead::~wxArrowHead()
2378{
2379 if (m_metaFile) delete m_metaFile;
2380}
2381
2382void wxArrowHead::SetSize(double size)
2383{
2384 m_arrowSize = size;
2385 if ((m_arrowType == ARROW_METAFILE) && m_metaFile)
2386 {
2387 double oldWidth = m_metaFile->m_width;
2388 if (oldWidth == 0.0)
2389 return;
2390
2391 double scale = (double)(size/oldWidth);
2392 if (scale != 1.0)
2393 m_metaFile->Scale(scale, scale);
2394 }
2395}
2396
2397// Can override this to create a different class of label shape
2398wxLabelShape* wxLineShape::OnCreateLabelShape(wxLineShape *parent, wxShapeRegion *region, double w, double h)
2399{
2400 return new wxLabelShape(parent, region, w, h);
2401}
2402
2403/*
2404 * Label object
2405 *
2406 */
2407
2408IMPLEMENT_DYNAMIC_CLASS(wxLabelShape, wxRectangleShape)
2409
2410wxLabelShape::wxLabelShape(wxLineShape *parent, wxShapeRegion *region, double w, double h):wxRectangleShape(w, h)
2411{
2412 m_lineShape = parent;
2413 m_shapeRegion = region;
2414 SetPen(wxThePenList->FindOrCreatePen(wxColour(0, 0, 0), 1, wxDOT));
2415}
2416
2417wxLabelShape::~wxLabelShape()
2418{
2419}
2420
2421void wxLabelShape::OnDraw(wxDC& dc)
2422{
2423 if (m_lineShape && !m_lineShape->GetDrawHandles())
2424 return;
2425
2426 double x1 = (double)(m_xpos - m_width/2.0);
2427 double y1 = (double)(m_ypos - m_height/2.0);
2428
2429 if (m_pen)
2430 {
2431 if (m_pen->GetWidth() == 0)
2432 dc.SetPen(* g_oglTransparentPen);
2433 else
2434 dc.SetPen(* m_pen);
2435 }
2436 dc.SetBrush(* wxTRANSPARENT_BRUSH);
2437
2438 if (m_cornerRadius > 0.0)
2439 dc.DrawRoundedRectangle(WXROUND(x1), WXROUND(y1), WXROUND(m_width), WXROUND(m_height), m_cornerRadius);
2440 else
2441 dc.DrawRectangle(WXROUND(x1), WXROUND(y1), WXROUND(m_width), WXROUND(m_height));
2442}
2443
2444void wxLabelShape::OnDrawContents(wxDC& dc)
2445{
2446}
2447
2448void wxLabelShape::OnDragLeft(bool draw, double x, double y, int keys, int attachment)
2449{
2450 wxRectangleShape::OnDragLeft(draw, x, y, keys, attachment);
2451}
2452
2453void wxLabelShape::OnBeginDragLeft(double x, double y, int keys, int attachment)
2454{
2455 wxRectangleShape::OnBeginDragLeft(x, y, keys, attachment);
2456}
2457
2458void wxLabelShape::OnEndDragLeft(double x, double y, int keys, int attachment)
2459{
2460 wxRectangleShape::OnEndDragLeft(x, y, keys, attachment);
2461}
2462
2463bool wxLabelShape::OnMovePre(wxDC& dc, double x, double y, double old_x, double old_y, bool display)
2464{
2465 return m_lineShape->OnLabelMovePre(dc, this, x, y, old_x, old_y, display);
2466}
2467
2468bool wxLineShape::OnLabelMovePre(wxDC& dc, wxLabelShape* labelShape, double x, double y, double old_x, double old_y, bool display)
2469{
2470 labelShape->m_shapeRegion->SetSize(labelShape->GetWidth(), labelShape->GetHeight());
2471
2472 // Find position in line's region list
2473 int i = 0;
b9ac87bc 2474 wxNode *node = GetRegions().GetFirst();
1fc25a89
JS
2475 while (node)
2476 {
b9ac87bc 2477 if (labelShape->m_shapeRegion == (wxShapeRegion *)node->GetData())
1fc25a89
JS
2478 node = NULL;
2479 else
2480 {
b9ac87bc 2481 node = node->GetNext();
1fc25a89
JS
2482 i ++;
2483 }
2484 }
2485 double xx, yy;
2486 GetLabelPosition(i, &xx, &yy);
2487 // Set the region's offset, relative to the default position for
2488 // each region.
2489 labelShape->m_shapeRegion->SetPosition((double)(x - xx), (double)(y - yy));
2490
2491 labelShape->SetX(x);
2492 labelShape->SetY(y);
2493
2494 // Need to reformat to fit region.
2495 if (labelShape->m_shapeRegion->GetText())
2496 {
2497
2498 wxString s(labelShape->m_shapeRegion->GetText());
2499 labelShape->FormatText(dc, s, i);
2500 DrawRegion(dc, labelShape->m_shapeRegion, xx, yy);
2501 }
2502 return TRUE;
2503}
2504
2505// Divert left and right clicks to line object
2506void wxLabelShape::OnLeftClick(double x, double y, int keys, int attachment)
2507{
2508 m_lineShape->GetEventHandler()->OnLeftClick(x, y, keys, attachment);
2509}
2510
2511void wxLabelShape::OnRightClick(double x, double y, int keys, int attachment)
2512{
2513 m_lineShape->GetEventHandler()->OnRightClick(x, y, keys, attachment);
2514}
2515