1 /*******************************************************************
2
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Fritzing. If not, see <http://www.gnu.org/licenses/>.
18
19 ********************************************************************
20
21 $Revision: 6912 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-03-09 08:18:59 +0100 (Sa, 09. Mrz 2013) $
24
25 ********************************************************************/
26
27 #include "clipablewire.h"
28 #include "../connectors/connectoritem.h"
29 #include "../model/modelpart.h"
30 #include "../utils/graphicsutils.h"
31
32 #include <qmath.h>
33
34 static double connectorRectClipInset = 0.5;
35
36 /////////////////////////////////////////////////////////
37
38 #define CONVEX
39
40 /* ======= Crossings algorithm ============================================ */
41
42 // from: http://tog.acm.org/GraphicsGems//gemsiv/ptpoly_haines/ptinpoly.c
43
44 #define XCOORD 0
45 #define YCOORD 1
46
47 /* Shoot a test ray along +X axis. The strategy, from MacMartin, is to
48 * compare vertex Y values to the testing point's Y and quickly discard
49 * edges which are entirely to one side of the test ray.
50 *
51 * Input 2D polygon _pgon_ with _numverts_ number of vertices and test point
52 * _point_, returns 1 if inside, 0 if outside. WINDING and CONVEX can be
53 * defined for this test.
54 */
CrossingsTest(double pgon[][2],int numverts,double point[2])55 int CrossingsTest( double pgon[][2], int numverts, double point[2] )
56 {
57 #ifdef WINDING
58 register int crossings ;
59 #endif
60 register int j, yflag0, yflag1, inside_flag, xflag0 ;
61 register double ty, tx, *vtx0, *vtx1 ;
62 #ifdef CONVEX
63 register int line_flag ;
64 #endif
65
66 tx = point[XCOORD] ;
67 ty = point[YCOORD] ;
68
69 vtx0 = pgon[numverts-1] ;
70 /* get test bit for above/below X axis */
71 yflag0 = ( vtx0[YCOORD] >= ty ) ;
72 vtx1 = pgon[0] ;
73
74 #ifdef WINDING
75 crossings = 0 ;
76 #else
77 inside_flag = 0 ;
78 #endif
79 #ifdef CONVEX
80 line_flag = 0 ;
81 #endif
82 for ( j = numverts+1 ; --j ; ) {
83
84 yflag1 = ( vtx1[YCOORD] >= ty ) ;
85 /* check if endpoints straddle (are on opposite sides) of X axis
86 * (i.e. the Y's differ); if so, +X ray could intersect this edge.
87 */
88 if ( yflag0 != yflag1 ) {
89 xflag0 = ( vtx0[XCOORD] >= tx ) ;
90 /* check if endpoints are on same side of the Y axis (i.e. X's
91 * are the same); if so, it's easy to test if edge hits or misses.
92 */
93 if ( xflag0 == ( vtx1[XCOORD] >= tx ) ) {
94
95 /* if edge's X values both right of the point, must hit */
96 #ifdef WINDING
97 if ( xflag0 ) crossings += ( yflag0 ? -1 : 1 ) ;
98 #else
99 if ( xflag0 ) inside_flag = !inside_flag ;
100 #endif
101 } else {
102 /* compute intersection of pgon segment with +X ray, note
103 * if >= point's X; if so, the ray hits it.
104 */
105 if ( (vtx1[XCOORD] - (vtx1[YCOORD]-ty)*
106 ( vtx0[XCOORD]-vtx1[XCOORD])/(vtx0[YCOORD]-vtx1[YCOORD])) >= tx ) {
107 #ifdef WINDING
108 crossings += ( yflag0 ? -1 : 1 ) ;
109 #else
110 inside_flag = !inside_flag ;
111 #endif
112 }
113 }
114 #ifdef CONVEX
115 /* if this is second edge hit, then done testing */
116 if ( line_flag ) goto Exit ;
117
118 /* note that one edge has been hit by the ray's line */
119 line_flag = true;
120 #endif
121 }
122
123 /* move to next pair of vertices, retaining info as possible */
124 yflag0 = yflag1 ;
125 vtx0 = vtx1 ;
126 vtx1 += 2 ;
127 }
128 #ifdef CONVEX
129 Exit: ;
130 #endif
131 #ifdef WINDING
132 /* test if crossings is not zero */
133 inside_flag = (crossings != 0) ;
134 #endif
135
136 return( inside_flag ) ;
137 }
138
139
140 /////////////////////////////////////////////////////////
141
ClipableWire(ModelPart * modelPart,ViewLayer::ViewID viewID,const ViewGeometry & viewGeometry,long id,QMenu * itemMenu,bool initLabel)142 ClipableWire::ClipableWire( ModelPart * modelPart, ViewLayer::ViewID viewID, const ViewGeometry & viewGeometry, long id, QMenu * itemMenu, bool initLabel )
143 : Wire(modelPart, viewID, viewGeometry, id, itemMenu, initLabel)
144 {
145 m_clipEnds = false;
146 m_trackHoverItem = NULL;
147 m_justFilteredEvent = NULL;
148 m_cachedOriginalLine.setPoints(QPointF(-99999,-99999), QPointF(-99999,-99999));
149 }
150
getPaintLine()151 const QLineF & ClipableWire::getPaintLine() {
152 if (!m_clipEnds) {
153 return Wire::getPaintLine();
154 }
155
156 QLineF originalLine = this->line();
157 if (m_cachedOriginalLine == originalLine) {
158 return m_cachedLine;
159 }
160
161 int t0c = 0;
162 ConnectorItem* to0 = NULL;
163 foreach (ConnectorItem * toConnectorItem, m_connector0->connectedToItems()) {
164 if (toConnectorItem->attachedToItemType() != ModelPart::Wire) {
165 to0 = toConnectorItem;
166 break;
167 }
168 t0c++;
169 }
170
171 int t1c = 0;
172 ConnectorItem* to1 = NULL;
173 foreach (ConnectorItem * toConnectorItem, m_connector1->connectedToItems()) {
174 if (toConnectorItem->attachedToItemType() != ModelPart::Wire) {
175 to1 = toConnectorItem;
176 break;
177 }
178 t1c++;
179 }
180
181 if ((to0 == NULL && to1 == NULL) || // no need to clip an unconnected wire
182 (to0 == NULL && t0c == 0) || // dragging out a wire, no need to clip
183 (to1 == NULL && t1c == 0) || // dragging out a wire, no need to clip
184 m_dragEnd) // dragging out a wire, no need to clip
185 {
186 return Wire::getPaintLine();
187 }
188
189 QPointF p1 = originalLine.p1();
190 QPointF p2 = originalLine.p2();
191 // does to0 always go with p1?
192 calcClip(p1, p2, to0, to1);
193 m_cachedOriginalLine = originalLine;
194 m_cachedLine.setPoints(p1, p2);
195 return m_cachedLine;
196
197 }
198
setClipEnds(bool clipEnds)199 void ClipableWire::setClipEnds(bool clipEnds ) {
200 if (m_clipEnds != clipEnds) {
201 prepareGeometryChange();
202 m_clipEnds = clipEnds;
203 }
204 }
205
calcClip(QPointF & p1,QPointF & p2,ConnectorItem * c1,ConnectorItem * c2)206 void ClipableWire::calcClip(QPointF & p1, QPointF & p2, ConnectorItem * c1, ConnectorItem * c2) {
207
208 if (c1 != NULL && c2 != NULL && c1->isEffectivelyCircular() && c2->isEffectivelyCircular()) {
209 //c1->debugInfo("c1");
210 //c2->debugInfo("c2");
211 GraphicsUtils::shortenLine(p1, p2, c1->calcClipRadius() + (m_pen.width() / 2.0), c2->calcClipRadius() + (m_pen.width() / 2.0));
212 return;
213 }
214
215 if (c1 != NULL && c1->isEffectivelyCircular()) {
216 GraphicsUtils::shortenLine(p1, p2, c1->calcClipRadius() + (m_pen.width() / 2.0), 0);
217 p2 = findIntersection(c2, p2);
218 return;
219 }
220
221 if (c2 != NULL && c2->isEffectivelyCircular()) {
222 GraphicsUtils::shortenLine(p1, p2, 0, c2->calcClipRadius() + (m_pen.width() / 2.0));
223 p1 = findIntersection(c1, p1);
224 return;
225 }
226
227 p1 = findIntersection(c1, p1);
228 p2 = findIntersection(c2, p2);
229 }
230
findIntersection(ConnectorItem * connectorItem,const QPointF & p)231 QPointF ClipableWire::findIntersection(ConnectorItem * connectorItem, const QPointF & p)
232 {
233 if (connectorItem == NULL) return p;
234
235 QRectF r = connectorItem->rect();
236 r.adjust(connectorRectClipInset, connectorRectClipInset, -connectorRectClipInset, -connectorRectClipInset); // inset it a little bit so the wire touches
237 QPolygonF poly = this->mapFromScene(connectorItem->mapToScene(r));
238 QLineF l1 = this->line();
239 int count = poly.count();
240 for (int i = 0; i < count; i++) {
241 QLineF l2(poly[i], poly[(i + 1) % count]);
242 QPointF intersectingPoint;
243 if (l1.intersect(l2, &intersectingPoint) == QLineF::BoundedIntersection) {
244 return intersectingPoint;
245 }
246 }
247
248 return this->mapFromScene(connectorItem->mapToScene(r.center()));
249 }
250
filterMousePressConnectorEvent(ConnectorItem * connectorItem,QGraphicsSceneMouseEvent * event)251 bool ClipableWire::filterMousePressConnectorEvent(ConnectorItem * connectorItem, QGraphicsSceneMouseEvent * event) {
252 m_justFilteredEvent = NULL;
253
254 if (!m_clipEnds) return false;
255 if (m_viewID != ViewLayer::PCBView) return false;
256
257 ConnectorItem * to = NULL;
258 foreach (ConnectorItem * toConnectorItem, connectorItem->connectedToItems()) {
259 if (toConnectorItem->attachedToItemType() != ModelPart::Wire) {
260 to = toConnectorItem;
261 break;
262 }
263 }
264 if (to == NULL) return false;
265
266 if (insideInnerCircle(to, event->scenePos()) || !insideSpoke(this, event->scenePos())) {
267 m_justFilteredEvent = event;
268 event->ignore();
269 return true;
270 }
271
272 return false;
273 }
274
mousePressEvent(QGraphicsSceneMouseEvent * event)275 void ClipableWire::mousePressEvent(QGraphicsSceneMouseEvent *event)
276 {
277 if ((long) event == (long) m_justFilteredEvent) {
278 event->ignore();
279 return;
280 }
281
282 Wire::mousePressEvent(event);
283 }
284
hoverEnterConnectorItem(QGraphicsSceneHoverEvent * event,ConnectorItem * item)285 void ClipableWire::hoverEnterConnectorItem(QGraphicsSceneHoverEvent * event , ConnectorItem * item) {
286
287 //Wire::hoverEnterConnectorItem(event, item);
288
289 // track mouse move events for hover redirecting
290
291 ConnectorItem * to = NULL;
292 foreach (ConnectorItem * toConnectorItem, item->connectedToItems()) {
293 if (toConnectorItem->attachedToItemType() != ModelPart::Wire) {
294 to = toConnectorItem;
295 break;
296 }
297 }
298
299 if (to) {
300 m_trackHoverItem = to;
301 m_trackHoverLastWireItem = NULL;
302 m_trackHoverLastItem = NULL;
303 dispatchHover(event->scenePos());
304 }
305 }
306
hoverLeaveConnectorItem(QGraphicsSceneHoverEvent * event,ConnectorItem * item)307 void ClipableWire::hoverLeaveConnectorItem(QGraphicsSceneHoverEvent * event, ConnectorItem * item) {
308 Q_UNUSED(item);
309 Q_UNUSED(event);
310 dispatchHoverAux(false, NULL);
311 m_trackHoverItem = NULL;
312 //Wire::hoverLeaveConnectorItem(event, item);
313 }
314
hoverMoveConnectorItem(QGraphicsSceneHoverEvent * event,ConnectorItem * item)315 void ClipableWire::hoverMoveConnectorItem(QGraphicsSceneHoverEvent * event, ConnectorItem * item) {
316 if (m_trackHoverItem) {
317 dispatchHover(event->scenePos());
318 }
319
320 Wire::hoverMoveConnectorItem(event, item);
321 }
322
dispatchHover(QPointF scenePos)323 void ClipableWire::dispatchHover(QPointF scenePos) {
324 bool inInner = false;
325 ClipableWire * inWire = NULL;
326 if (insideInnerCircle(m_trackHoverItem, scenePos)) {
327 //DebugDialog::debug("got inner circle");
328 inInner = true;
329 }
330 else {
331 foreach (ConnectorItem * toConnectorItem, m_trackHoverItem->connectedToItems()) {
332 if (toConnectorItem->attachedToItemType() != ModelPart::Wire) continue;
333
334 ClipableWire * w = dynamic_cast<ClipableWire *>(toConnectorItem->attachedTo());
335 if (w == NULL) continue;
336 if (w->getRatsnest()) continue; // is there a better way to check this?
337
338 if (insideSpoke(w, scenePos)) {
339 //DebugDialog::debug("got inside spoke");
340 inWire = w;
341 break;
342 }
343 }
344 }
345
346 dispatchHoverAux(inInner, inWire);
347 }
348
dispatchHoverAux(bool inInner,Wire * inWire)349 void ClipableWire::dispatchHoverAux(bool inInner, Wire * inWire)
350 {
351 if (m_trackHoverItem == NULL) return;
352
353 if (inInner) {
354 if (m_trackHoverLastItem == m_trackHoverItem) {
355 // no change
356 return;
357 }
358 if (m_trackHoverLastWireItem) {
359 ((ItemBase *) m_trackHoverLastWireItem)->hoverLeaveConnectorItem();
360 m_trackHoverLastWireItem = NULL;
361 }
362
363 m_trackHoverLastItem = m_trackHoverItem;
364 m_trackHoverLastItem->setHoverColor();
365 m_trackHoverLastItem->attachedTo()->hoverEnterConnectorItem();
366 }
367 else if (inWire) {
368 if (m_trackHoverLastWireItem == inWire) {
369 // no change
370 return;
371 }
372 if (m_trackHoverLastItem) {
373 QList<ConnectorItem *> visited;
374 m_trackHoverLastItem->restoreColor(visited);
375 m_trackHoverLastItem->attachedTo()->hoverLeaveConnectorItem();
376 m_trackHoverLastItem = NULL;
377 }
378 if (m_trackHoverLastWireItem) {
379 ((ItemBase *) m_trackHoverLastWireItem)->hoverLeaveConnectorItem();
380 }
381 m_trackHoverLastWireItem = inWire;
382 ((ItemBase *) m_trackHoverLastWireItem)->hoverEnterConnectorItem();
383 }
384 else {
385 //DebugDialog::debug("got none");
386 if (m_trackHoverLastItem != NULL) {
387 QList<ConnectorItem *> visited;
388 m_trackHoverLastItem->restoreColor(visited);
389 m_trackHoverLastItem->attachedTo()->hoverLeaveConnectorItem();
390 m_trackHoverLastItem = NULL;
391 }
392 if (m_trackHoverLastWireItem != NULL) {
393 ((ItemBase *) m_trackHoverLastWireItem)->hoverLeaveConnectorItem();
394 m_trackHoverLastWireItem = NULL;
395 }
396 }
397 }
398
insideInnerCircle(ConnectorItem * connectorItem,QPointF scenePos)399 bool ClipableWire::insideInnerCircle(ConnectorItem * connectorItem, QPointF scenePos)
400 {
401 QPointF localPos = connectorItem->mapFromScene(scenePos);
402 double rad = connectorItem->radius();
403 if (rad <= 0) return false;
404
405 rad -= ((connectorItem->strokeWidth() / 2.0)); // shrink it a little bit
406
407 QRectF r = connectorItem->rect();
408 QPointF c = r.center();
409 if ( (localPos.x() - c.x()) * (localPos.x() - c.x()) + (localPos.y() - c.y()) * (localPos.y() - c.y()) < rad * rad ) {
410 // inside the inner circle
411 return true;
412 }
413
414 return false;
415 }
416
insideSpoke(ClipableWire * wire,QPointF scenePos)417 bool ClipableWire::insideSpoke(ClipableWire * wire, QPointF scenePos)
418 {
419 QLineF l = wire->line();
420 QLineF normal = l.normalVector();
421 normal.setLength(wire->width() / 2.0);
422 double parallelogram[4][2];
423 parallelogram[0][XCOORD] = normal.p2().x();
424 parallelogram[0][YCOORD] = normal.p2().y();
425 parallelogram[1][XCOORD] = normal.p1().x() - normal.dx();
426 parallelogram[1][YCOORD] = normal.p1().y() - normal.dy();
427 parallelogram[2][XCOORD] = parallelogram[1][XCOORD] + l.dx();
428 parallelogram[2][YCOORD] = parallelogram[1][YCOORD] + l.dy();
429 parallelogram[3][XCOORD] = parallelogram[0][XCOORD] + l.dx();
430 parallelogram[3][YCOORD] = parallelogram[0][YCOORD] + l.dy();
431 QPointF mp = wire->mapFromScene(scenePos);
432 double point[2];
433 point[XCOORD] = mp.x();
434 point[YCOORD] = mp.y();
435 if (CrossingsTest(parallelogram, 4, point)) {
436 return true;
437 }
438
439 return false;
440 }
441