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 "boardeditorstate_drawpolygon.h"
24
25 #include "../boardeditor.h"
26
27 #include <librepcb/common/geometry/cmd/cmdpolygonedit.h>
28 #include <librepcb/common/geometry/polygon.h>
29 #include <librepcb/common/graphics/graphicsview.h>
30 #include <librepcb/common/undostack.h>
31 #include <librepcb/common/widgets/graphicslayercombobox.h>
32 #include <librepcb/common/widgets/unsignedlengthedit.h>
33 #include <librepcb/project/boards/board.h>
34 #include <librepcb/project/boards/boardlayerstack.h>
35 #include <librepcb/project/boards/cmd/cmdboardpolygonadd.h>
36 #include <librepcb/project/boards/items/bi_polygon.h>
37
38 #include <QtCore>
39
40 /*******************************************************************************
41 * Namespace
42 ******************************************************************************/
43 namespace librepcb {
44 namespace project {
45 namespace editor {
46
47 /*******************************************************************************
48 * Constructors / Destructor
49 ******************************************************************************/
50
BoardEditorState_DrawPolygon(const Context & context)51 BoardEditorState_DrawPolygon::BoardEditorState_DrawPolygon(
52 const Context& context) noexcept
53 : BoardEditorState(context),
54 mIsUndoCmdActive(false),
55 mLastPolygonProperties(
56 Uuid::createRandom(), // UUID is not relevant here
57 GraphicsLayerName(GraphicsLayer::sBoardOutlines), // Layer
58 UnsignedLength(0), // Line width
59 false, // Is filled
60 false, // Is grab area
61 Path() // Path is not relevant here
62 ),
63 mLastSegmentPos(),
64 mCurrentPolygon(nullptr),
65 mCurrentPolygonEditCmd(nullptr) {
66 }
67
~BoardEditorState_DrawPolygon()68 BoardEditorState_DrawPolygon::~BoardEditorState_DrawPolygon() noexcept {
69 }
70
71 /*******************************************************************************
72 * General Methods
73 ******************************************************************************/
74
entry()75 bool BoardEditorState_DrawPolygon::entry() noexcept {
76 Q_ASSERT(mIsUndoCmdActive == false);
77
78 Board* board = getActiveBoard();
79 if (!board) return false;
80
81 // Clear board selection because selection does not make sense in this state
82 board->clearSelection();
83
84 // Add the "Layer:" label to the toolbar
85 mLayerLabel.reset(new QLabel(tr("Layer:")));
86 mLayerLabel->setIndent(10);
87 mContext.editorUi.commandToolbar->addWidget(mLayerLabel.data());
88
89 // Add the layers combobox to the toolbar
90 mLayerComboBox.reset(new GraphicsLayerComboBox());
91 mLayerComboBox->setLayers(getAllowedGeometryLayers(*board));
92 mLayerComboBox->setCurrentLayer(mLastPolygonProperties.getLayerName());
93 mContext.editorUi.commandToolbar->addWidget(mLayerComboBox.data());
94 connect(mLayerComboBox.data(), &GraphicsLayerComboBox::currentLayerChanged,
95 this, &BoardEditorState_DrawPolygon::layerComboBoxLayerChanged);
96
97 // Add the "Width:" label to the toolbar
98 mWidthLabel.reset(new QLabel(tr("Width:")));
99 mWidthLabel->setIndent(10);
100 mContext.editorUi.commandToolbar->addWidget(mWidthLabel.data());
101
102 // Add the widths combobox to the toolbar
103 mWidthEdit.reset(new UnsignedLengthEdit());
104 mWidthEdit->setValue(mLastPolygonProperties.getLineWidth());
105 mContext.editorUi.commandToolbar->addWidget(mWidthEdit.data());
106 connect(mWidthEdit.data(), &UnsignedLengthEdit::valueChanged, this,
107 &BoardEditorState_DrawPolygon::widthEditValueChanged);
108
109 // Add the "Filled:" label to the toolbar
110 mFillLabel.reset(new QLabel(tr("Filled:")));
111 mFillLabel->setIndent(10);
112 mContext.editorUi.commandToolbar->addWidget(mFillLabel.data());
113
114 // Add the filled checkbox to the toolbar
115 mFillCheckBox.reset(new QCheckBox());
116 mFillCheckBox->setChecked(mLastPolygonProperties.isFilled());
117 mContext.editorUi.commandToolbar->addWidget(mFillCheckBox.data());
118 connect(mFillCheckBox.data(), &QCheckBox::toggled, this,
119 &BoardEditorState_DrawPolygon::filledCheckBoxCheckedChanged);
120
121 // Change the cursor
122 mContext.editorGraphicsView.setCursor(Qt::CrossCursor);
123
124 return true;
125 }
126
exit()127 bool BoardEditorState_DrawPolygon::exit() noexcept {
128 // Abort the currently active command
129 if (!abortCommand(true)) return false;
130
131 // Remove actions / widgets from the "command" toolbar
132 mFillCheckBox.reset();
133 mFillLabel.reset();
134 mWidthEdit.reset();
135 mWidthLabel.reset();
136 mLayerComboBox.reset();
137 mLayerLabel.reset();
138 qDeleteAll(mActionSeparators);
139 mActionSeparators.clear();
140
141 // Reset the cursor
142 mContext.editorGraphicsView.setCursor(Qt::ArrowCursor);
143
144 return true;
145 }
146
147 /*******************************************************************************
148 * Event Handlers
149 ******************************************************************************/
150
processAbortCommand()151 bool BoardEditorState_DrawPolygon::processAbortCommand() noexcept {
152 if (mIsUndoCmdActive) {
153 // Just finish the current polygon, not exiting the whole tool.
154 return abortCommand(true);
155 } else {
156 // Allow leaving the tool.
157 return false;
158 }
159 }
160
processGraphicsSceneMouseMoved(QGraphicsSceneMouseEvent & e)161 bool BoardEditorState_DrawPolygon::processGraphicsSceneMouseMoved(
162 QGraphicsSceneMouseEvent& e) noexcept {
163 Point pos = Point::fromPx(e.scenePos()).mappedToGrid(getGridInterval());
164 return updateLastVertexPosition(pos);
165 }
166
processGraphicsSceneLeftMouseButtonPressed(QGraphicsSceneMouseEvent & e)167 bool BoardEditorState_DrawPolygon::processGraphicsSceneLeftMouseButtonPressed(
168 QGraphicsSceneMouseEvent& e) noexcept {
169 Board* board = getActiveBoard();
170 if (!board) return false;
171
172 Point pos = Point::fromPx(e.scenePos()).mappedToGrid(getGridInterval());
173 if (mIsUndoCmdActive) {
174 addSegment(pos);
175 } else {
176 startAddPolygon(*board, pos);
177 }
178 return true;
179 }
180
181 bool BoardEditorState_DrawPolygon::
processGraphicsSceneLeftMouseButtonDoubleClicked(QGraphicsSceneMouseEvent & e)182 processGraphicsSceneLeftMouseButtonDoubleClicked(
183 QGraphicsSceneMouseEvent& e) noexcept {
184 return processGraphicsSceneLeftMouseButtonPressed(e);
185 }
186
processGraphicsSceneRightMouseButtonReleased(QGraphicsSceneMouseEvent & e)187 bool BoardEditorState_DrawPolygon::processGraphicsSceneRightMouseButtonReleased(
188 QGraphicsSceneMouseEvent& e) noexcept {
189 Q_UNUSED(e);
190 return processAbortCommand();
191 }
192
processSwitchToBoard(int index)193 bool BoardEditorState_DrawPolygon::processSwitchToBoard(int index) noexcept {
194 // Allow switching to an existing board if no command is active.
195 return (!mIsUndoCmdActive) && (index >= 0);
196 }
197
198 /*******************************************************************************
199 * Private Methods
200 ******************************************************************************/
201
startAddPolygon(Board & board,const Point & pos)202 bool BoardEditorState_DrawPolygon::startAddPolygon(Board& board,
203 const Point& pos) noexcept {
204 Q_ASSERT(mIsUndoCmdActive == false);
205
206 try {
207 // Start a new undo command
208 mContext.undoStack.beginCmdGroup(tr("Draw board polygon"));
209 mIsUndoCmdActive = true;
210
211 // Add polygon with two vertices
212 mLastPolygonProperties.setPath(Path({Vertex(pos), Vertex(pos)}));
213 mCurrentPolygon = new BI_Polygon(
214 board, Polygon(Uuid::createRandom(), mLastPolygonProperties));
215 mContext.undoStack.appendToCmdGroup(
216 new CmdBoardPolygonAdd(*mCurrentPolygon));
217
218 // Start undo command
219 mCurrentPolygonEditCmd.reset(
220 new CmdPolygonEdit(mCurrentPolygon->getPolygon()));
221 mLastSegmentPos = pos;
222 makeSelectedLayerVisible();
223 return true;
224 } catch (const Exception& e) {
225 QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
226 abortCommand(false);
227 return false;
228 }
229 }
230
addSegment(const Point & pos)231 bool BoardEditorState_DrawPolygon::addSegment(const Point& pos) noexcept {
232 Q_ASSERT(mIsUndoCmdActive == true);
233
234 // Abort if no segment drawn
235 if (pos == mLastSegmentPos) {
236 abortCommand(true);
237 return false;
238 }
239
240 try {
241 // Finish undo command to allow reverting segment by segment
242 if (mCurrentPolygonEditCmd) {
243 mContext.undoStack.appendToCmdGroup(mCurrentPolygonEditCmd.take());
244 }
245 mContext.undoStack.commitCmdGroup();
246 mIsUndoCmdActive = false;
247
248 // If the polygon is now closed, abort now
249 if (mCurrentPolygon->getPolygon().getPath().isClosed()) {
250 abortCommand(true);
251 return true;
252 }
253
254 // Start a new undo command
255 mContext.undoStack.beginCmdGroup(tr("Draw board polygon"));
256 mIsUndoCmdActive = true;
257 mCurrentPolygonEditCmd.reset(
258 new CmdPolygonEdit(mCurrentPolygon->getPolygon()));
259
260 // Add new vertex
261 Path newPath = mCurrentPolygon->getPolygon().getPath();
262 newPath.addVertex(pos, Angle::deg0());
263 mCurrentPolygonEditCmd->setPath(newPath, true);
264 mLastSegmentPos = pos;
265 return true;
266 } catch (const Exception& e) {
267 QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
268 abortCommand(false);
269 return false;
270 }
271 }
272
updateLastVertexPosition(const Point & pos)273 bool BoardEditorState_DrawPolygon::updateLastVertexPosition(
274 const Point& pos) noexcept {
275 if (mCurrentPolygonEditCmd) {
276 Path newPath = mCurrentPolygon->getPolygon().getPath();
277 newPath.getVertices().last().setPos(pos);
278 mCurrentPolygonEditCmd->setPath(newPath, true);
279 return true;
280 } else {
281 return false;
282 }
283 }
284
abortCommand(bool showErrMsgBox)285 bool BoardEditorState_DrawPolygon::abortCommand(bool showErrMsgBox) noexcept {
286 try {
287 // Delete the current edit command
288 mCurrentPolygonEditCmd.reset();
289
290 // Abort the undo command
291 if (mIsUndoCmdActive) {
292 mContext.undoStack.abortCmdGroup();
293 mIsUndoCmdActive = false;
294 }
295
296 // Reset attributes, go back to idle state
297 mCurrentPolygon = nullptr;
298 return true;
299 } catch (const Exception& e) {
300 if (showErrMsgBox) {
301 QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
302 }
303 return false;
304 }
305 }
306
layerComboBoxLayerChanged(const GraphicsLayerName & layerName)307 void BoardEditorState_DrawPolygon::layerComboBoxLayerChanged(
308 const GraphicsLayerName& layerName) noexcept {
309 mLastPolygonProperties.setLayerName(layerName);
310 if (mCurrentPolygonEditCmd) {
311 mCurrentPolygonEditCmd->setLayerName(mLastPolygonProperties.getLayerName(),
312 true);
313 makeSelectedLayerVisible();
314 }
315 }
316
widthEditValueChanged(const UnsignedLength & value)317 void BoardEditorState_DrawPolygon::widthEditValueChanged(
318 const UnsignedLength& value) noexcept {
319 mLastPolygonProperties.setLineWidth(value);
320 if (mCurrentPolygonEditCmd) {
321 mCurrentPolygonEditCmd->setLineWidth(mLastPolygonProperties.getLineWidth(),
322 true);
323 }
324 }
325
filledCheckBoxCheckedChanged(bool checked)326 void BoardEditorState_DrawPolygon::filledCheckBoxCheckedChanged(
327 bool checked) noexcept {
328 mLastPolygonProperties.setIsFilled(checked);
329 if (mCurrentPolygonEditCmd) {
330 mCurrentPolygonEditCmd->setIsFilled(mLastPolygonProperties.isFilled(),
331 true);
332 mCurrentPolygonEditCmd->setIsGrabArea(mLastPolygonProperties.isFilled(),
333 true);
334 }
335 }
336
makeSelectedLayerVisible()337 void BoardEditorState_DrawPolygon::makeSelectedLayerVisible() noexcept {
338 if (mCurrentPolygon) {
339 Board& board = mCurrentPolygon->getBoard();
340 GraphicsLayer* layer =
341 board.getLayerStack().getLayer(*mLastPolygonProperties.getLayerName());
342 if (layer && layer->isEnabled()) layer->setVisible(true);
343 }
344 }
345
346 /*******************************************************************************
347 * End of File
348 ******************************************************************************/
349
350 } // namespace editor
351 } // namespace project
352 } // namespace librepcb
353