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