kivio

kiviopolylineconnector.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2004 Peter Simonsson <psn@linux.se>,
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18*/
19
20#include "kiviopolylineconnector.h"
21
22#include <kdebug.h>
23
24#include <KoZoomHandler.h>
25#include <KoSize.h>
26
27#include "kivio_line_style.h"
28#include "kivio_painter.h"
29#include "kivio_intra_stencil_data.h"
30#include "kivio_connector_point.h"
31#include "kivio_custom_drag_data.h"
32#include "tkmath.h"
33#include "kivio_page.h"
34#include "kivio_layer.h"
35#include "kivio_common.h"
36
37
38namespace Kivio {
39 PolyLineConnector::PolyLineConnector()
40 : Kivio1DStencil()
41 {
42 m_startArrow = new KivioArrowHead();
43 m_endArrow = new KivioArrowHead();
44
45 m_needsWidth = false;
46 m_needsText = false; // FIXME add text support
47
48 m_pCanProtect->clearBit(kpAspect);
49 m_pCanProtect->clearBit(kpWidth);
50 m_pCanProtect->clearBit(kpHeight);
51 m_pCanProtect->clearBit(kpX);
52 m_pCanProtect->clearBit(kpY);
53
54 // This is a stencil of type connector
55 setType(kstConnector);
56 }
57
58 PolyLineConnector::~PolyLineConnector()
59 {
60 delete m_startArrow;
61 delete m_endArrow;
62 }
63
64 KivioStencil* PolyLineConnector::duplicate()
65 {
66 PolyLineConnector* connector = new PolyLineConnector();
67 copyBasicInto(connector);
68 connector->m_points = m_points;
69
70 // Copy the arrow head information
71 connector->setStartAHType( m_startArrow->type() );
72 connector->setStartAHWidth( m_startArrow->width() );
73 connector->setStartAHLength( m_startArrow->length() );
74
75 connector->setEndAHType( m_endArrow->type() );
76 connector->setEndAHWidth( m_endArrow->width() );
77 connector->setEndAHLength( m_endArrow->length() );
78
79 return connector;
80 }
81
82 bool PolyLineConnector::loadCustom(const TQDomElement& e)
83 {
84 TQDomNode node = e.firstChild();
85 TQString nodeName;
86 m_points.clear();
87
88 while(!node.isNull()) {
89 nodeName = node.nodeName();
90
91 if(nodeName == "KivioArrowHeads") {
92 loadArrowHeads(node.toElement());
93 } else if(nodeName == "KivioGeometryPoints") {
94 TQDomNode pointsNode = node.firstChild();
95 TQDomElement el;
96
97 while(!pointsNode.isNull()) {
98 if(pointsNode.nodeName() == "KivioPoint") {
99 el = pointsNode.toElement();
100 KoPoint p;
101 p.setX(XmlReadFloat(el, "x", 1.0f));
102 p.setY(XmlReadFloat(el, "y", 1.0f));
103 addPoint(p);
104 }
105
106 pointsNode = pointsNode.nextSibling();
107 }
108 }
109
110 node = node.nextSibling();
111 }
112
113 return true;
114 }
115
116 bool PolyLineConnector::saveCustom(TQDomElement& e, TQDomDocument& doc)
117 {
118 e.appendChild(saveArrowHeads(doc));
119 TQDomElement pointsElement = doc.createElement("KivioGeometryPoints");
120
121 for(TQValueList<KoPoint>::iterator it = m_points.begin(); it != m_points.end(); ++it) {
122 KoPoint p = (*it);
123 TQDomElement el = doc.createElement("KivioPoint");
124 XmlWriteFloat(el, "x", p.x());
125 XmlWriteFloat(el, "y", p.y());
126 pointsElement.appendChild(el);
127 }
128
129 e.appendChild(pointsElement);
130
131 return true;
132 }
133
134 KivioCollisionType PolyLineConnector::checkForCollision(KoPoint* p, double threshold)
135 {
136 unsigned int i = 0;
137 KoPoint point;
138 double px = p->x();
139 double py = p->y();
140 uint count = m_points.count();
141
142 while(i < count) {
143 point = m_points[i];
144
145 if(px >= point.x() - threshold && px <= point.x() + threshold &&
146 py >= point.y() - threshold && py <= point.y() + threshold)
147 {
148 return static_cast<KivioCollisionType>(i + kctCustom + 1);
149 }
150
151 i++;
152 }
153
154 i = 0;
155 count--; // As we need current + 1;
156
157 while(i < count) {
158 point = m_points[i];
159
160 if(collisionLine(point.x(), point.y(),
161 m_points[i + 1].x(), m_points[i + 1].y(), px, py, threshold))
162 {
163 return kctBody;
164 }
165
166 i++;
167 }
168
169 return kctNone;
170 }
171
172 void PolyLineConnector::paint(KivioIntraStencilData* data)
173 {
174 if(m_points.count() < 2) {
175 kdDebug(43000) << "ARGH! We're missing points in this connector!" << endl;
176 return;
177 }
178
179 KoZoomHandler* zoom = data->zoomHandler;
180 KivioPainter* painter = data->painter;
181
182 painter->setLineStyle(m_pLineStyle);
183 painter->setLineWidth(zoom->zoomItY(m_pLineStyle->width()));
184
185 TQPointArray pa(m_points.count());
186 TQValueList<KoPoint>::iterator it;
187 int i = 0;
188
189 for(it = m_points.begin(); it != m_points.end(); ++it) {
190 pa.setPoint(i, zoom->zoomPoint(*it));
191 i++;
192 }
193
194 KoPoint startVec = m_points[1] - m_points[0];
195 KoPoint endVec = m_points.last() - m_points[m_points.count() - 2];
196 double startLen = startVec.manhattanLength();
197 double endLen = endVec.manhattanLength();
198
199 if(startLen) {
200 startVec.setX(startVec.x() / startLen);
201 startVec.setY(startVec.y() / startLen);
202 TQPoint p = pa[0];
203 p.setX(p.x() + tqRound(startVec.x() * zoom->zoomItX(m_startArrow->cut())));
204 p.setY(p.y() + tqRound(startVec.y() * zoom->zoomItY(m_startArrow->cut())));
205 }
206
207 if(endLen) {
208 endVec.setX(endVec.x() / endLen);
209 endVec.setY(endVec.y() / endLen);
210 TQPoint p = pa[m_points.count() - 1];
211 p.setX(p.x() + tqRound(endVec.x() * zoom->zoomItX(m_endArrow->cut())));
212 p.setY(p.y() + tqRound(endVec.y() * zoom->zoomItY(m_endArrow->cut())));
213 }
214
215 painter->drawPolyline(pa);
216 painter->setBGColor(m_pFillStyle->color());
217
218 if(startLen) {
219 m_startArrow->paint(painter, m_points[0].x(), m_points[0].y(), -startVec.x(), -startVec.y(), zoom);
220 }
221
222 if(endLen) {
223 m_endArrow->paint(painter, m_points.last().x(), m_points.last().y(), endVec.x(), endVec.y(), zoom);
224 }
225 }
226
227 void PolyLineConnector::paintOutline(KivioIntraStencilData* data)
228 {
229 paint(data);
230 }
231
232 void PolyLineConnector::paintSelectionHandles( KivioIntraStencilData* data )
233 {
234 KivioPainter* painter = data->painter;
235 KoZoomHandler* zoomHandler = data->zoomHandler;
236 TQValueList<KoPoint>::Iterator it;
237 int x, y, flag;
238 x = y = flag = 0;
239
240 for(it = m_points.begin(); it != m_points.end(); ++it) {
241 x = zoomHandler->zoomItX((*it).x());
242 y = zoomHandler->zoomItY((*it).y());
243
244 if((*it) == m_pEnd->position()) {
245 flag = ((m_pEnd->target()) ? KivioPainter::cpfConnected : 0) | KivioPainter::cpfEnd;
246 } else if((*it) == m_pStart->position()) {
247 flag = ((m_pStart->target()) ? KivioPainter::cpfConnected : 0) | KivioPainter::cpfStart;
248 } else {
249 flag = 0;
250 }
251
252 painter->drawHandle(x, y, flag);
253 }
254 }
255
256 void PolyLineConnector::addPoint(const KoPoint& p)
257 {
258 if(m_points.count() == 0) {
259 m_pStart->setPosition(p.x(), p.y(), false);
260 m_pStart->disconnect();
261 } else {
262 m_pEnd->setPosition(p.x(), p.y(), false);
263 m_pEnd->disconnect();
264 }
265
266 m_points.append(p);
267 }
268
269 void PolyLineConnector::movePoint(unsigned int index, double xOffset, double yOffset)
270 {
271 if(index >= m_points.count()) {
272 return;
273 }
274
275 KoPoint p(xOffset, yOffset);
276 m_points[index] += p;
277
278 if(index == (m_points.count() - 1)) {
279 m_pEnd->setPosition(m_points[index].x(), m_points[index].y(), false);
280 m_pEnd->disconnect();
281 } else if(index == 0) {
282 m_pStart->setPosition(m_points[index].x(), m_points[index].y(), false);
283 m_pStart->disconnect();
284 }
285 }
286
287 void PolyLineConnector::movePointTo(unsigned int index, const KoPoint& p)
288 {
289 m_points[index] = p;
290
291 if(index == (m_points.count() - 1)) {
292 m_pEnd->setPosition(p.x(), p.y(), false);
293 m_pEnd->disconnect();
294 } else if(index == 0) {
295 m_pStart->setPosition(p.x(), p.y(), false);
296 m_pStart->disconnect();
297 }
298 }
299
300 void PolyLineConnector::moveLastPointTo(const KoPoint& p)
301 {
302 movePointTo(m_points.count() - 1, p);
303 }
304
305 void PolyLineConnector::customDrag(KivioCustomDragData* data)
306 {
307 KoPoint pos(data->x, data->y);
308 setCustomIDPoint(data->id, pos, data->page);
309 }
310
311 void PolyLineConnector::move(double xOffset, double yOffset)
312 {
313 for(unsigned int i = 0; i < m_points.count(); i++) {
314 movePoint(i, xOffset, yOffset);
315 }
316 }
317
318 double PolyLineConnector::x()
319 {
320 if(m_points.count() == 0) {
321 return 0;
322 }
323
324 return rect().x();
325 }
326
327 void PolyLineConnector::setX(double newX)
328 {
329 double dx = newX - x();
330 move(dx, 0);
331 }
332
333 double PolyLineConnector::y()
334 {
335 if(m_points.count() == 0) {
336 return 0;
337 }
338
339 return rect().y();
340 }
341
342 void PolyLineConnector::setY(double newY)
343 {
344 double dy = newY - y();
345 move(0, dy);
346 }
347
348 void PolyLineConnector::checkForConnection(KivioConnectorPoint* cp, KivioPage* page)
349 {
350 if(cp->connectable()) {
351 KivioLayer* currentLayer = page->curLayer();
352 KivioLayer* layer = page->firstLayer();
353 bool found = false;
354
355 while(layer && !found) {
356 if((layer != currentLayer) && (!layer->connectable() || !layer->visible())) {
357 layer = page->nextLayer();
358 continue;
359 }
360
361 if(layer->connectPointToTarget(cp, 8.0f)) {
362 found = true;
363 }
364
365 layer = page->nextLayer();
366 }
367
368 if(!found) {
369 cp->disconnect();
370 }
371 }
372 }
373
374 void PolyLineConnector::updateConnectorPoints(KivioConnectorPoint* cp, double /*oldX*/, double /*oldY*/)
375 {
376 if(cp == m_pStart) {
377 m_points[0] = m_pStart->position();
378 } else if(cp == m_pEnd) {
379 m_points[m_points.count() - 1] = m_pEnd->position();
380 }
381 }
382
383 KoRect PolyLineConnector::rect()
384 {
385 KoPoint p = m_points.first();
386 KoPoint topLeft(p.x(), p.y()), bottomRight;
387 TQValueList<KoPoint>::iterator it;
388 TQValueList<KoPoint>::iterator itEnd = m_points.end();
389
390 for(it = m_points.begin(); it != itEnd; ++it) {
391 p = (*it);
392 topLeft.setX(TQMIN(p.x(), topLeft.x()));
393 topLeft.setY(TQMIN(p.y(), topLeft.y()));
394 bottomRight.setX(TQMAX(p.x(), bottomRight.x()));
395 bottomRight.setY(TQMAX(p.y(), bottomRight.y()));
396 }
397
398 KoRect rect;
399 rect.moveTopLeft(topLeft);
400 rect.setWidth(bottomRight.x() - topLeft.x());
401 rect.setHeight(bottomRight.y() - topLeft.y());
402 return rect;
403 }
404
405 bool PolyLineConnector::isInRect(const KoRect& rect)
406 {
407 TQValueList<KoPoint>::Iterator it;
408 bool retVal = true;
409
410 for(it = m_points.begin(); it != m_points.end(); ++it) {
411 retVal = retVal && rect.contains((*it));
412 }
413
414 return retVal;
415 }
416
417 bool PolyLineConnector::loadArrowHeads(const TQDomElement& e)
418 {
419 bool first = true;
420 TQDomNode node = e.firstChild();
421
422 while(!node.isNull()) {
423 if(node.nodeName() == "KivioArrowHead") {
424 if(first) {
425 m_startArrow->loadXML(node.toElement());
426 first = false;
427 } else {
428 m_endArrow->loadXML(node.toElement());
429 }
430 }
431
432 node = node.nextSibling();
433 }
434
435 return true;
436 }
437
438 TQDomElement PolyLineConnector::saveArrowHeads(TQDomDocument& doc)
439 {
440 TQDomElement e = doc.createElement("KivioArrowHeads");
441
442 e.appendChild( m_startArrow->saveXML(doc) );
443 e.appendChild( m_endArrow->saveXML(doc) );
444
445 return e;
446 }
447
448 void PolyLineConnector::removePoint(unsigned int index)
449 {
450 if(index >= m_points.count()) {
451 return;
452 }
453
454 m_points.remove(m_points.at(index));
455 }
456
457 void PolyLineConnector::removeLastPoint()
458 {
459 removePoint(m_points.count() - 1);
460 }
461
462 void PolyLineConnector::setCustomIDPoint(int customID, const KoPoint& point, KivioPage* page)
463 {
464 int index = customID - (kctCustom + 1);
465
466 if((index < 0) || index >= (int)m_points.count()) {
467 kdDebug(43000) << "PolyLineConnector::setCustomIDPoint: Index out of range! Index = " << index << endl;
468 return;
469 }
470
471 movePointTo(index, point);
472 KivioConnectorPoint* cp;
473
474 if(index == 0) {
475 cp = m_pStart;
476 } else if(index == ((int)m_points.count() - 1)) {
477 cp = m_pEnd;
478 } else {
479 return;
480 }
481
482 checkForConnection(cp, page);
483 }
484
485 KoPoint PolyLineConnector::customIDPoint(int customID)
486 {
487 int index = customID - (kctCustom + 1);
488
489 if((index < 0) || index >= (int)m_points.count()) {
490 kdDebug(43000) << "PolyLineConnector::customIDPoint: Index out of range! Index = " << index << endl;
491 return KoPoint();
492 }
493
494 return m_points[index];
495 }
496
497}