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_addstroketext.h"
24
25 #include "../boardeditor.h"
26
27 #include <librepcb/common/geometry/cmd/cmdstroketextedit.h>
28 #include <librepcb/common/geometry/stroketext.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/positivelengthedit.h>
33 #include <librepcb/project/boards/board.h>
34 #include <librepcb/project/boards/boardlayerstack.h>
35 #include <librepcb/project/boards/cmd/cmdboardstroketextadd.h>
36 #include <librepcb/project/boards/items/bi_stroketext.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_AddStrokeText(const Context & context)51 BoardEditorState_AddStrokeText::BoardEditorState_AddStrokeText(
52 const Context& context) noexcept
53 : BoardEditorState(context),
54 mIsUndoCmdActive(false),
55 mLastStrokeTextProperties(
56 Uuid::createRandom(), // UUID is not relevant here
57 GraphicsLayerName(GraphicsLayer::sBoardDocumentation), // Layer
58 "{{PROJECT}}", // Text
59 Point(), // Position is not relevant here
60 Angle::deg0(), // Rotation
61 PositiveLength(1500000), // Height
62 UnsignedLength(200000), // Line width
63 StrokeTextSpacing(), // Letter spacing
64 StrokeTextSpacing(), // Line spacing
65 Alignment(HAlign::left(), VAlign::bottom()), // Alignment
66 false, // Mirror
67 true // Auto rotate
68 ),
69 mCurrentTextToPlace(nullptr) {
70 }
71
~BoardEditorState_AddStrokeText()72 BoardEditorState_AddStrokeText::~BoardEditorState_AddStrokeText() noexcept {
73 }
74
75 /*******************************************************************************
76 * General Methods
77 ******************************************************************************/
78
entry()79 bool BoardEditorState_AddStrokeText::entry() noexcept {
80 Q_ASSERT(mIsUndoCmdActive == false);
81
82 Board* board = getActiveBoard();
83 if (!board) return false;
84
85 // Clear board selection because selection does not make sense in this state
86 board->clearSelection();
87 makeLayerVisible();
88
89 // Add a new stroke text
90 Point pos = mContext.editorGraphicsView.mapGlobalPosToScenePos(QCursor::pos(),
91 true, true);
92 if (!addText(*board, pos)) return false;
93
94 // Add the "Layer:" label to the toolbar
95 mLayerLabel.reset(new QLabel(tr("Layer:")));
96 mLayerLabel->setIndent(10);
97 mContext.editorUi.commandToolbar->addWidget(mLayerLabel.data());
98
99 // Add the layers combobox to the toolbar
100 mLayerComboBox.reset(new GraphicsLayerComboBox());
101 mLayerComboBox->setLayers(getAllowedGeometryLayers(*board));
102 mLayerComboBox->setCurrentLayer(mLastStrokeTextProperties.getLayerName());
103 mContext.editorUi.commandToolbar->addWidget(mLayerComboBox.data());
104 connect(mLayerComboBox.data(), &GraphicsLayerComboBox::currentLayerChanged,
105 this, &BoardEditorState_AddStrokeText::layerComboBoxLayerChanged);
106
107 // Add the "Text:" label to the toolbar
108 mTextLabel.reset(new QLabel(tr("Text:")));
109 mTextLabel->setIndent(10);
110 mContext.editorUi.commandToolbar->addWidget(mTextLabel.data());
111
112 // Add the text combobox to the toolbar
113 mTextComboBox.reset(new QComboBox());
114 mTextComboBox->setEditable(true);
115 mTextComboBox->setMinimumContentsLength(20);
116 mTextComboBox->addItem("{{BOARD}}");
117 mTextComboBox->addItem("{{PROJECT}}");
118 mTextComboBox->addItem("{{AUTHOR}}");
119 mTextComboBox->addItem("{{VERSION}}");
120 mTextComboBox->setCurrentIndex(
121 mTextComboBox->findText(mLastStrokeTextProperties.getText()));
122 mTextComboBox->setCurrentText(mLastStrokeTextProperties.getText());
123 connect(mTextComboBox.data(), &QComboBox::currentTextChanged, this,
124 &BoardEditorState_AddStrokeText::textComboBoxValueChanged);
125 mContext.editorUi.commandToolbar->addWidget(mTextComboBox.data());
126
127 // Add the "Height:" label to the toolbar
128 mHeightLabel.reset(new QLabel(tr("Height:")));
129 mHeightLabel->setIndent(10);
130 mContext.editorUi.commandToolbar->addWidget(mHeightLabel.data());
131
132 // Add the height spinbox to the toolbar
133 mHeightEdit.reset(new PositiveLengthEdit());
134 mHeightEdit->setValue(mLastStrokeTextProperties.getHeight());
135 connect(mHeightEdit.data(), &PositiveLengthEdit::valueChanged, this,
136 &BoardEditorState_AddStrokeText::heightEditValueChanged);
137 mContext.editorUi.commandToolbar->addWidget(mHeightEdit.data());
138
139 // Add the "Mirror:" label to the toolbar
140 mMirrorLabel.reset(new QLabel(tr("Mirror:")));
141 mMirrorLabel->setIndent(10);
142 mContext.editorUi.commandToolbar->addWidget(mMirrorLabel.data());
143
144 // Add the mirror checkbox to the toolbar
145 mMirrorCheckBox.reset(new QCheckBox());
146 mMirrorCheckBox->setChecked(mLastStrokeTextProperties.getMirrored());
147 connect(mMirrorCheckBox.data(), &QCheckBox::toggled, this,
148 &BoardEditorState_AddStrokeText::mirrorCheckBoxToggled);
149 mContext.editorUi.commandToolbar->addWidget(mMirrorCheckBox.data());
150
151 // Change the cursor
152 mContext.editorGraphicsView.setCursor(Qt::CrossCursor);
153
154 return true;
155 }
156
exit()157 bool BoardEditorState_AddStrokeText::exit() noexcept {
158 // Abort the currently active command
159 if (!abortCommand(true)) return false;
160
161 // Remove actions / widgets from the "command" toolbar
162 mMirrorCheckBox.reset();
163 mMirrorLabel.reset();
164 mHeightEdit.reset();
165 mHeightLabel.reset();
166 mTextComboBox.reset();
167 mTextLabel.reset();
168 mLayerComboBox.reset();
169 mLayerLabel.reset();
170
171 // Reset the cursor
172 mContext.editorGraphicsView.setCursor(Qt::ArrowCursor);
173
174 return true;
175 }
176
177 /*******************************************************************************
178 * Event Handlers
179 ******************************************************************************/
180
processRotateCw()181 bool BoardEditorState_AddStrokeText::processRotateCw() noexcept {
182 return rotateText(-Angle::deg90());
183 }
184
processRotateCcw()185 bool BoardEditorState_AddStrokeText::processRotateCcw() noexcept {
186 return rotateText(Angle::deg90());
187 }
188
processFlipHorizontal()189 bool BoardEditorState_AddStrokeText::processFlipHorizontal() noexcept {
190 return flipText(Qt::Horizontal);
191 }
192
processFlipVertical()193 bool BoardEditorState_AddStrokeText::processFlipVertical() noexcept {
194 return flipText(Qt::Vertical);
195 }
196
processGraphicsSceneMouseMoved(QGraphicsSceneMouseEvent & e)197 bool BoardEditorState_AddStrokeText::processGraphicsSceneMouseMoved(
198 QGraphicsSceneMouseEvent& e) noexcept {
199 Point pos = Point::fromPx(e.scenePos()).mappedToGrid(getGridInterval());
200 return updatePosition(pos);
201 }
202
processGraphicsSceneLeftMouseButtonPressed(QGraphicsSceneMouseEvent & e)203 bool BoardEditorState_AddStrokeText::processGraphicsSceneLeftMouseButtonPressed(
204 QGraphicsSceneMouseEvent& e) noexcept {
205 Board* board = getActiveBoard();
206 if (!board) return false;
207
208 Point pos = Point::fromPx(e.scenePos()).mappedToGrid(getGridInterval());
209 fixPosition(pos);
210 addText(*board, pos);
211 return true;
212 }
213
214 bool BoardEditorState_AddStrokeText::
processGraphicsSceneLeftMouseButtonDoubleClicked(QGraphicsSceneMouseEvent & e)215 processGraphicsSceneLeftMouseButtonDoubleClicked(
216 QGraphicsSceneMouseEvent& e) noexcept {
217 return processGraphicsSceneLeftMouseButtonPressed(e);
218 }
219
220 bool BoardEditorState_AddStrokeText::
processGraphicsSceneRightMouseButtonReleased(QGraphicsSceneMouseEvent & e)221 processGraphicsSceneRightMouseButtonReleased(
222 QGraphicsSceneMouseEvent& e) noexcept {
223 // Only rotate if cursor was not moved during click
224 if (e.screenPos() == e.buttonDownScreenPos(Qt::RightButton)) {
225 rotateText(Angle::deg90());
226 }
227
228 // Always accept the event if we are placing a text! When ignoring the
229 // event, the state machine will abort the tool by a right click!
230 return mIsUndoCmdActive;
231 }
232
233 /*******************************************************************************
234 * Private Methods
235 ******************************************************************************/
236
addText(Board & board,const Point & pos)237 bool BoardEditorState_AddStrokeText::addText(Board& board,
238 const Point& pos) noexcept {
239 Q_ASSERT(mIsUndoCmdActive == false);
240
241 try {
242 mContext.undoStack.beginCmdGroup(tr("Add text to board"));
243 mIsUndoCmdActive = true;
244 mLastStrokeTextProperties.setPosition(pos);
245 mCurrentTextToPlace = new BI_StrokeText(
246 board, StrokeText(Uuid::createRandom(), mLastStrokeTextProperties));
247 QScopedPointer<CmdBoardStrokeTextAdd> cmdAdd(
248 new CmdBoardStrokeTextAdd(*mCurrentTextToPlace));
249 mContext.undoStack.appendToCmdGroup(cmdAdd.take());
250 mCurrentTextEditCmd.reset(
251 new CmdStrokeTextEdit(mCurrentTextToPlace->getText()));
252 return true;
253 } catch (const Exception& e) {
254 QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
255 abortCommand(false);
256 return false;
257 }
258 }
259
rotateText(const Angle & angle)260 bool BoardEditorState_AddStrokeText::rotateText(const Angle& angle) noexcept {
261 if ((!mCurrentTextEditCmd) || (!mCurrentTextToPlace)) return false;
262
263 mCurrentTextEditCmd->rotate(angle, mCurrentTextToPlace->getPosition(), true);
264 mLastStrokeTextProperties = mCurrentTextToPlace->getText();
265
266 return true; // Event handled
267 }
268
flipText(Qt::Orientation orientation)269 bool BoardEditorState_AddStrokeText::flipText(
270 Qt::Orientation orientation) noexcept {
271 if ((!mCurrentTextEditCmd) || (!mCurrentTextToPlace)) return false;
272
273 mCurrentTextEditCmd->mirrorGeometry(orientation,
274 mCurrentTextToPlace->getPosition(), true);
275 mCurrentTextEditCmd->mirrorLayer(true);
276 mLastStrokeTextProperties = mCurrentTextToPlace->getText();
277
278 // Update toolbar widgets
279 mLayerComboBox->setCurrentLayer(mLastStrokeTextProperties.getLayerName());
280 mMirrorCheckBox->setChecked(mLastStrokeTextProperties.getMirrored());
281
282 return true; // Event handled
283 }
284
updatePosition(const Point & pos)285 bool BoardEditorState_AddStrokeText::updatePosition(const Point& pos) noexcept {
286 if (mCurrentTextEditCmd) {
287 mCurrentTextEditCmd->setPosition(pos, true);
288 return true; // Event handled
289 } else {
290 return false;
291 }
292 }
293
fixPosition(const Point & pos)294 bool BoardEditorState_AddStrokeText::fixPosition(const Point& pos) noexcept {
295 Q_ASSERT(mIsUndoCmdActive == true);
296
297 try {
298 if (mCurrentTextEditCmd) {
299 mCurrentTextEditCmd->setPosition(pos, false);
300 mContext.undoStack.appendToCmdGroup(mCurrentTextEditCmd.take());
301 }
302 mContext.undoStack.commitCmdGroup();
303 mIsUndoCmdActive = false;
304 mCurrentTextToPlace = nullptr;
305 return true;
306 } catch (const Exception& e) {
307 QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
308 abortCommand(false);
309 return false;
310 }
311 }
312
abortCommand(bool showErrMsgBox)313 bool BoardEditorState_AddStrokeText::abortCommand(bool showErrMsgBox) noexcept {
314 try {
315 // Delete the current edit command
316 mCurrentTextEditCmd.reset();
317
318 // Abort the undo command
319 if (mIsUndoCmdActive) {
320 mContext.undoStack.abortCmdGroup();
321 mIsUndoCmdActive = false;
322 }
323
324 // Reset attributes, go back to idle state
325 mCurrentTextToPlace = nullptr;
326 return true;
327 } catch (const Exception& e) {
328 if (showErrMsgBox) {
329 QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
330 }
331 return false;
332 }
333 }
334
layerComboBoxLayerChanged(const GraphicsLayerName & layerName)335 void BoardEditorState_AddStrokeText::layerComboBoxLayerChanged(
336 const GraphicsLayerName& layerName) noexcept {
337 mLastStrokeTextProperties.setLayerName(layerName);
338 if (mCurrentTextEditCmd) {
339 mCurrentTextEditCmd->setLayerName(mLastStrokeTextProperties.getLayerName(),
340 true);
341 makeLayerVisible();
342 }
343 }
344
textComboBoxValueChanged(const QString & value)345 void BoardEditorState_AddStrokeText::textComboBoxValueChanged(
346 const QString& value) noexcept {
347 mLastStrokeTextProperties.setText(value.trimmed());
348 if (mCurrentTextEditCmd) {
349 mCurrentTextEditCmd->setText(mLastStrokeTextProperties.getText(), true);
350 }
351 }
352
heightEditValueChanged(const PositiveLength & value)353 void BoardEditorState_AddStrokeText::heightEditValueChanged(
354 const PositiveLength& value) noexcept {
355 mLastStrokeTextProperties.setHeight(value);
356 if (mCurrentTextEditCmd) {
357 mCurrentTextEditCmd->setHeight(mLastStrokeTextProperties.getHeight(), true);
358 }
359 }
360
mirrorCheckBoxToggled(bool checked)361 void BoardEditorState_AddStrokeText::mirrorCheckBoxToggled(
362 bool checked) noexcept {
363 mLastStrokeTextProperties.setMirrored(checked);
364 if (mCurrentTextEditCmd) {
365 mCurrentTextEditCmd->setMirrored(mLastStrokeTextProperties.getMirrored(),
366 true);
367 }
368 }
369
makeLayerVisible()370 void BoardEditorState_AddStrokeText::makeLayerVisible() noexcept {
371 if (Board* board = getActiveBoard()) {
372 GraphicsLayer* layer = board->getLayerStack().getLayer(
373 *mLastStrokeTextProperties.getLayerName());
374 if (layer && layer->isEnabled()) layer->setVisible(true);
375 }
376 }
377
378 /*******************************************************************************
379 * End of File
380 ******************************************************************************/
381
382 } // namespace editor
383 } // namespace project
384 } // namespace librepcb
385