1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program 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  * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*******************************************************************************
21  *  Includes
22  ******************************************************************************/
23 #include "bgi_plane.h"
24 
25 #include "../../project.h"
26 #include "../board.h"
27 #include "../boardlayerstack.h"
28 #include "../items/bi_plane.h"
29 
30 #include <librepcb/common/geometry/polygon.h>
31 #include <librepcb/common/graphics/primitivepathgraphicsitem.h>
32 #include <librepcb/common/toolbox.h>
33 
34 #include <QPrinter>
35 #include <QtCore>
36 #include <QtWidgets>
37 
38 /*******************************************************************************
39  *  Namespace
40  ******************************************************************************/
41 namespace librepcb {
42 namespace project {
43 
44 /*******************************************************************************
45  *  Constructors / Destructor
46  ******************************************************************************/
47 
BGI_Plane(BI_Plane & plane)48 BGI_Plane::BGI_Plane(BI_Plane& plane) noexcept
49   : BGI_Base(),
50     mPlane(plane),
51     mLayer(nullptr),
52     mLineWidthPx(0),
53     mVertexRadiusPx(0) {
54   updateCacheAndRepaint();
55 }
56 
~BGI_Plane()57 BGI_Plane::~BGI_Plane() noexcept {
58 }
59 
60 /*******************************************************************************
61  *  Getters
62  ******************************************************************************/
63 
isSelectable() const64 bool BGI_Plane::isSelectable() const noexcept {
65   return mLayer && mLayer->isVisible();
66 }
67 
getLineIndexAtPosition(const Point & pos) const68 int BGI_Plane::getLineIndexAtPosition(const Point& pos) const noexcept {
69   // We build temporary PrimitivePathGraphicsItem objects for each segment of
70   // the plane and check if the specified position is located within the shape
71   // of one of these graphics items. This is quite ugly, but was easy to
72   // implement and seems to work nicely... ;-)
73   for (int i = 1; i < mPlane.getOutline().getVertices().count(); ++i) {
74     Path path;
75     path.addVertex(mPlane.getOutline().getVertices()[i - 1]);
76     path.addVertex(mPlane.getOutline().getVertices()[i]);
77 
78     PrimitivePathGraphicsItem item(const_cast<BGI_Plane*>(this));
79     item.setPath(path.toQPainterPathPx());
80     item.setLineWidth(UnsignedLength(Length::fromPx(mLineWidthPx)));
81     item.setLineLayer(mLayer);
82 
83     if (item.shape().contains(item.mapFromScene(pos.toPxQPointF()))) {
84       return i;
85     }
86   }
87 
88   return -1;
89 }
90 
getVertexIndicesAtPosition(const Point & pos) const91 QVector<int> BGI_Plane::getVertexIndicesAtPosition(const Point& pos) const
92     noexcept {
93   QVector<int> indices;
94   for (int i = 0; i < mPlane.getOutline().getVertices().count(); ++i) {
95     Point diff = (mPlane.getOutline().getVertices()[i].getPos() - pos);
96     if (diff.getLength()->toPx() < mVertexRadiusPx) {
97       indices.append(i);
98     }
99   }
100   return indices;
101 }
102 
103 /*******************************************************************************
104  *  General Methods
105  ******************************************************************************/
106 
updateCacheAndRepaint()107 void BGI_Plane::updateCacheAndRepaint() noexcept {
108   prepareGeometryChange();
109 
110   setZValue(getZValueOfCopperLayer(*mPlane.getLayerName()));
111 
112   mLayer = getLayer(*mPlane.getLayerName());
113 
114   // set shape and bounding rect
115   mOutline = mPlane.getOutline().toClosedPath().toQPainterPathPx();
116   mShape = mShape = Toolbox::shapeFromPath(
117       mOutline, QPen(Length::fromMm(0.3).toPx()), QBrush());
118   mBoundingRect = mShape.boundingRect();
119 
120   // get areas
121   mAreas.clear();
122   for (const Path& r : mPlane.getFragments()) {
123     mAreas.append(r.toQPainterPathPx());
124     mBoundingRect = mBoundingRect.united(mAreas.last().boundingRect());
125   }
126 
127   update();
128 }
129 
130 /*******************************************************************************
131  *  Inherited from QGraphicsItem
132  ******************************************************************************/
133 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)134 void BGI_Plane::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
135                       QWidget* widget) {
136   Q_UNUSED(widget);
137 
138   const bool selected = mPlane.isSelected();
139   const bool deviceIsPrinter =
140       (dynamic_cast<QPrinter*>(painter->device()) != nullptr);
141   const qreal lod =
142       option->levelOfDetailFromTransform(painter->worldTransform());
143 
144   if (mLayer && mLayer->isVisible()) {
145     // draw outline only on screen, not for print or PDF export
146     if (!deviceIsPrinter) {
147       mLineWidthPx = 3 / lod;
148       painter->setPen(QPen(mLayer->getColor(selected), mLineWidthPx,
149                            Qt::DashLine, Qt::RoundCap));
150       painter->setBrush(Qt::NoBrush);
151       painter->drawPath(mOutline);
152 
153       // if the plane is selected, draw vertex handles
154       if (selected) {
155         mVertexRadiusPx = (mLineWidthPx / 2) + Length::fromMm(0.2).toPx();
156         painter->setPen(
157             QPen(mLayer->getColor(selected), 0, Qt::SolidLine, Qt::RoundCap));
158         foreach (const Vertex& vertex, mPlane.getOutline().getVertices()) {
159           painter->drawEllipse(vertex.getPos().toPxQPointF(), mVertexRadiusPx,
160                                mVertexRadiusPx);
161         }
162       }
163     }
164 
165     // draw plane only if plane should be visible
166     if (mPlane.isVisible()) {
167       painter->setPen(Qt::NoPen);
168       painter->setBrush(mLayer->getColor(selected));
169       foreach (const QPainterPath& area, mAreas) { painter->drawPath(area); }
170     }
171   }
172 
173 #ifdef QT_DEBUG
174   // draw bounding rect
175   const GraphicsLayer* layer = mPlane.getBoard().getLayerStack().getLayer(
176       GraphicsLayer::sDebugGraphicsItemsBoundingRects);
177   if (layer) {
178     if (layer->isVisible()) {
179       painter->setPen(QPen(layer->getColor(selected), 0));
180       painter->setBrush(Qt::NoBrush);
181       painter->drawRect(mBoundingRect);
182     }
183   }
184 #endif
185 }
186 
187 /*******************************************************************************
188  *  Private Methods
189  ******************************************************************************/
190 
getLayer(QString name) const191 GraphicsLayer* BGI_Plane::getLayer(QString name) const noexcept {
192   if (mPlane.getIsMirrored()) name = GraphicsLayer::getMirroredLayerName(name);
193   return mPlane.getBoard().getLayerStack().getLayer(name);
194 }
195 
196 /*******************************************************************************
197  *  End of File
198  ******************************************************************************/
199 
200 }  // namespace project
201 }  // namespace librepcb
202